/*
- Copyright (C) 2012-2015 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
This file is part of libdcp.
files in the program, then also delete it here.
*/
+
/** @file src/certificate.cc
- * @brief Certificate class.
+ * @brief Certificate class
*/
-#include "KM_util.h"
+
#include "certificate.h"
#include "compose.hpp"
#include "exceptions.h"
#include "util.h"
#include "dcp_assert.h"
+#include <asdcp/KM_util.h>
#include <libxml++/nodes/element.h>
#include <openssl/x509.h>
#include <openssl/ssl.h>
#include <iostream>
#include <algorithm>
+
using std::list;
using std::string;
using std::ostream;
using std::min;
-using std::stringstream;
using namespace dcp;
+
static string const begin_certificate = "-----BEGIN CERTIFICATE-----";
static string const end_certificate = "-----END CERTIFICATE-----";
-/** @param c X509 certificate, which this object will take ownership of */
+
Certificate::Certificate (X509* c)
: _certificate (c)
- , _public_key (0)
- , _extra_data (false)
{
}
-/** Load an X509 certificate from a string.
- * @param cert String to read from.
- */
+
Certificate::Certificate (string cert)
- : _certificate (0)
- , _public_key (0)
{
- _extra_data = read_string (cert);
+ auto const s = read_string (cert);
+ if (!s.empty()) {
+ throw MiscError ("unexpected data after certificate");
+ }
}
-/** Copy constructor.
- * @param other Certificate to copy.
- */
+
Certificate::Certificate (Certificate const & other)
- : _certificate (0)
- , _public_key (0)
- , _extra_data (other._extra_data)
{
if (other._certificate) {
read_string (other.certificate (true));
}
}
-/** Read a certificate from a string.
- * @param cert String to read.
- * @return true if there is extra stuff after the end of the certificate, false if not.
- */
-bool
+
+string
Certificate::read_string (string cert)
{
/* Reformat cert so that it has line breaks every 64 characters.
See http://comments.gmane.org/gmane.comp.encryption.openssl.user/55593
*/
- stringstream s (cert);
+ list<string> lines;
string line;
- /* BEGIN */
- do {
- getline (s, line);
+ for (size_t i = 0; i < cert.length(); ++i) {
+ line += cert[i];
+ if (cert[i] == '\r' || cert[i] == '\n') {
+ boost::algorithm::trim (line);
+ lines.push_back (line);
+ line = "";
+ }
+ }
+
+ if (!line.empty()) {
boost::algorithm::trim (line);
- } while (s.good() && line != begin_certificate);
+ lines.push_back (line);
+ }
+
+ auto i = lines.begin ();
- if (line != begin_certificate) {
+ /* BEGIN */
+ while (i != lines.end() && *i != begin_certificate) {
+ ++i;
+ }
+
+ if (i == lines.end()) {
throw MiscError ("missing BEGIN line in certificate");
}
+ /* Skip over the BEGIN line */
+ ++i;
+
/* The base64 data */
bool got_end = false;
string base64 = "";
- while (getline (s, line)) {
- boost::algorithm::trim (line);
- if (line == end_certificate) {
+ while (i != lines.end()) {
+ if (*i == end_certificate) {
got_end = true;
break;
}
- base64 += line;
+ base64 += *i;
+ ++i;
}
if (!got_end) {
throw MiscError ("missing END line in certificate");
}
+ /* Skip over the END line */
+ ++i;
+
/* Make up the fixed version */
string fixed = begin_certificate + "\n";
fixed += end_certificate;
- BIO* bio = BIO_new_mem_buf (const_cast<char *> (fixed.c_str ()), -1);
+ auto bio = BIO_new_mem_buf (const_cast<char *> (fixed.c_str ()), -1);
if (!bio) {
throw MiscError ("could not create memory BIO");
}
BIO_free (bio);
- /* See if there are any non-blank lines after the certificate that we read */
- line.clear ();
- while (s.good() && line.empty()) {
- getline (s, line);
+ string extra;
+
+ while (i != lines.end()) {
+ if (!i->empty()) {
+ extra += *i + "\n";
+ }
+ ++i;
}
- return (s.good() && !line.empty());
+
+ return extra;
}
-/** Destructor */
+
Certificate::~Certificate ()
{
X509_free (_certificate);
RSA_free (_public_key);
}
-/** operator= for Certificate.
- * @param other Certificate to read from.
- */
+
Certificate &
Certificate::operator= (Certificate const & other)
{
_certificate = 0;
RSA_free (_public_key);
_public_key = 0;
- _extra_data = other._extra_data;
- read_string (other.certificate (true));
+ read_string (other.certificate(true));
return *this;
}
-/** Return the certificate as a string.
- * @param with_begin_end true to include the -----BEGIN CERTIFICATE--- / -----END CERTIFICATE----- markers.
- * @return Certificate string.
- */
+
string
Certificate::certificate (bool with_begin_end) const
{
DCP_ASSERT (_certificate);
- BIO* bio = BIO_new (BIO_s_mem ());
+ auto bio = BIO_new (BIO_s_mem());
if (!bio) {
throw MiscError ("could not create memory BIO");
}
return s;
}
-/** @return Certificate's issuer, in the form
- * dnqualifier=<dnQualififer>,CN=<commonName>,OU=<organizationalUnitName>,O=<organizationName>
- * and with + signs escaped to \+
- */
+
string
Certificate::issuer () const
{
DCP_ASSERT (_certificate);
- return name_for_xml (X509_get_issuer_name (_certificate));
+ return name_for_xml (X509_get_issuer_name(_certificate));
}
+
string
Certificate::asn_to_utf8 (ASN1_STRING* s)
{
return u;
}
+
string
Certificate::get_name_part (X509_NAME* n, int nid)
{
if (p == -1) {
return "";
}
- return asn_to_utf8 (X509_NAME_ENTRY_get_data (X509_NAME_get_entry (n, p)));
+ return asn_to_utf8 (X509_NAME_ENTRY_get_data(X509_NAME_get_entry(n, p)));
}
+
string
Certificate::name_for_xml (X509_NAME* name)
{
assert (name);
- BIO* bio = BIO_new (BIO_s_mem ());
+ auto bio = BIO_new (BIO_s_mem ());
if (!bio) {
throw MiscError ("could not create memory BIO");
}
{
DCP_ASSERT (_certificate);
- return name_for_xml (X509_get_subject_name (_certificate));
+ return name_for_xml (X509_get_subject_name(_certificate));
}
+
string
Certificate::subject_common_name () const
{
DCP_ASSERT (_certificate);
- return get_name_part (X509_get_subject_name (_certificate), NID_commonName);
+ return get_name_part (X509_get_subject_name(_certificate), NID_commonName);
}
+
string
Certificate::subject_organization_name () const
{
DCP_ASSERT (_certificate);
- return get_name_part (X509_get_subject_name (_certificate), NID_organizationName);
+ return get_name_part (X509_get_subject_name(_certificate), NID_organizationName);
}
+
string
Certificate::subject_organizational_unit_name () const
{
DCP_ASSERT (_certificate);
- return get_name_part (X509_get_subject_name (_certificate), NID_organizationalUnitName);
+ return get_name_part (X509_get_subject_name(_certificate), NID_organizationalUnitName);
+}
+
+
+static
+struct tm
+convert_time (ASN1_TIME const * time)
+{
+ struct tm t;
+ char const * s = (char const *) time->data;
+
+ if (time->type == V_ASN1_UTCTIME) {
+ sscanf(s, "%2d%2d%2d%2d%2d%2d", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec);
+ if (t.tm_year < 70) {
+ t.tm_year += 100;
+ }
+ } else if (time->type == V_ASN1_GENERALIZEDTIME) {
+ sscanf(s, "%4d%2d%2d%2d%2d%2d", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec);
+ t.tm_year -= 1900;
+ }
+
+ t.tm_mon--;
+
+ return t;
+}
+
+
+struct tm
+Certificate::not_before () const
+{
+ DCP_ASSERT (_certificate);
+#if OPENSSL_VERSION_NUMBER > 0x10100000L
+ return convert_time(X509_get0_notBefore(_certificate));
+#else
+ return convert_time(X509_get_notBefore(_certificate));
+#endif
}
+
+struct tm
+Certificate::not_after () const
+{
+ DCP_ASSERT (_certificate);
+#if OPENSSL_VERSION_NUMBER > 0x10100000L
+ return convert_time(X509_get0_notAfter(_certificate));
+#else
+ return convert_time(X509_get_notAfter(_certificate));
+#endif
+}
+
+
string
Certificate::serial () const
{
DCP_ASSERT (_certificate);
- ASN1_INTEGER* s = X509_get_serialNumber (_certificate);
+ auto s = X509_get_serialNumber (_certificate);
DCP_ASSERT (s);
- BIGNUM* b = ASN1_INTEGER_to_BN (s, 0);
+ auto b = ASN1_INTEGER_to_BN (s, 0);
char* c = BN_bn2dec (b);
BN_free (b);
return st;
}
+
string
Certificate::thumbprint () const
{
uint8_t buffer[8192];
uint8_t* p = buffer;
+
+#if OPENSSL_VERSION_NUMBER > 0x10100000L
+ i2d_re_X509_tbs(_certificate, &p);
+#else
i2d_X509_CINF (_certificate->cert_info, &p);
+#endif
unsigned int const length = p - buffer;
if (length > sizeof (buffer)) {
throw MiscError ("buffer too small to generate thumbprint");
return Kumu::base64encode (digest, 20, digest_base64, 64);
}
-/** @return RSA public key from this Certificate. Caller must not free the returned value. */
+
RSA *
Certificate::public_key () const
{
return _public_key;
}
- EVP_PKEY* key = X509_get_pubkey (_certificate);
+ auto key = X509_get_pubkey (_certificate);
if (!key) {
throw MiscError ("could not get public key from certificate");
}
return _public_key;
}
+
+static bool string_is_utf8 (X509_NAME* n, int nid)
+{
+ int p = -1;
+ p = X509_NAME_get_index_by_NID (n, nid, p);
+ return p != -1 && X509_NAME_ENTRY_get_data(X509_NAME_get_entry(n, p))->type == V_ASN1_UTF8STRING;
+}
+
+
+bool
+Certificate::has_utf8_strings () const
+{
+ auto n = X509_get_subject_name (_certificate);
+ return string_is_utf8(n, NID_commonName) ||
+ string_is_utf8(n, NID_organizationName) ||
+ string_is_utf8(n, NID_organizationalUnitName);
+}
+
+
bool
dcp::operator== (Certificate const & a, Certificate const & b)
{
return a.certificate() == b.certificate();
}
+
bool
dcp::operator< (Certificate const & a, Certificate const & b)
{
return a.certificate() < b.certificate();
}
+
ostream&
dcp::operator<< (ostream& s, Certificate const & c)
{