X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fcertificate_chain.cc;h=3ea6db60ee5fffaca1d70add4d369f39dc4c3bbf;hb=697a8c5a86b013ff4dd78b6c3b79b09522bd9e46;hp=557c8d3752d0d4c62bd7ab05275bb2e72bd25fbb;hpb=a641fdc912a3f0749015decdf9e23ff15186ef78;p=libdcp.git diff --git a/src/certificate_chain.cc b/src/certificate_chain.cc index 557c8d37..3ea6db60 100644 --- a/src/certificate_chain.cc +++ b/src/certificate_chain.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2015 Carl Hetherington + Copyright (C) 2013-2016 Carl Hetherington This file is part of libdcp. @@ -15,6 +15,20 @@ You should have received a copy of the GNU General Public License along with libdcp. If not, see . + + In addition, as a special exception, the copyright holders give + permission to link the code of portions of this program with the + OpenSSL library under certain conditions as described in each + individual source file, and distribute linked combinations + including the two. + + You must obey the GNU General Public License in all respects + for all of the code used other than OpenSSL. If you modify + file(s) with this exception, you may extend this exception to your + version of the file(s), but you are not obligated to do so. If you + do not wish to do so, delete this exception statement from your + version. If you delete this exception statement from all source + files in the program, then also delete it here. */ /** @file src/signer_chain.cc @@ -25,8 +39,8 @@ #include "exceptions.h" #include "util.h" #include "dcp_assert.h" -#include "KM_util.h" #include "compose.hpp" +#include #include #include #include @@ -37,17 +51,17 @@ #include #include #include +#include #include #include #include #include -#include +#include using std::string; using std::ofstream; using std::ifstream; using std::runtime_error; -using std::stringstream; using namespace dcp; /** Run a shell command. @@ -94,9 +108,7 @@ command (string cmd) int const code = WEXITSTATUS (r); #endif if (code) { - stringstream s; - s << "error " << code << " in " << cmd << " within " << boost::filesystem::current_path(); - throw dcp::MiscError (s.str()); + throw dcp::MiscError (String::compose ("error %1 in %2 within %3", code, cmd, boost::filesystem::current_path().string())); } } @@ -111,9 +123,7 @@ public_key_digest (boost::filesystem::path private_key, boost::filesystem::path boost::filesystem::path public_name = private_key.string() + ".public"; /* Create the public key from the private key */ - stringstream s; - s << "\"" << openssl.string() << "\" rsa -outform PEM -pubout -in " << private_key.string() << " -out " << public_name.string (); - command (s.str().c_str ()); + command (String::compose("\"%1\" rsa -outform PEM -pubout -in %2 -out %3", openssl.string(), private_key.string(), public_name.string ())); /* Read in the public key from the file */ @@ -208,11 +218,13 @@ CertificateChain::CertificateChain ( "/dnQualifier=" + public_key_digest ("ca.key", openssl); { - stringstream c; - c << quoted_openssl - << " req -new -x509 -sha256 -config ca.cnf -days 3650 -set_serial 5" - << " -subj \"" << ca_subject << "\" -key ca.key -outform PEM -out ca.self-signed.pem"; - command (c.str().c_str()); + command ( + String::compose ( + "%1 req -new -x509 -sha256 -config ca.cnf -days 3650 -set_serial 5" + " -subj \"%2\" -key ca.key -outform PEM -out ca.self-signed.pem", + quoted_openssl, ca_subject + ) + ); } command (quoted_openssl + " genrsa -out intermediate.key 2048"); @@ -239,13 +251,14 @@ CertificateChain::CertificateChain ( "/dnQualifier=" + public_key_digest ("intermediate.key", openssl); { - stringstream s; - s << quoted_openssl - << " req -new -config intermediate.cnf -days 3649 -subj \"" << inter_subject << "\" -key intermediate.key -out intermediate.csr"; - command (s.str().c_str()); + command ( + String::compose ( + "%1 req -new -config intermediate.cnf -days 3649 -subj \"%2\" -key intermediate.key -out intermediate.csr", + quoted_openssl, inter_subject + ) + ); } - command ( quoted_openssl + " x509 -req -sha256 -days 3649 -CA ca.self-signed.pem -CAkey ca.key -set_serial 6" @@ -276,9 +289,12 @@ CertificateChain::CertificateChain ( "/dnQualifier=" + public_key_digest ("leaf.key", openssl); { - stringstream s; - s << quoted_openssl << " req -new -config leaf.cnf -days 3648 -subj \"" << leaf_subject << "\" -key leaf.key -outform PEM -out leaf.csr"; - command (s.str().c_str()); + command ( + String::compose ( + "%1 req -new -config leaf.cnf -days 3648 -subj \"%2\" -key leaf.key -outform PEM -out leaf.csr", + quoted_openssl, leaf_subject + ) + ); } command ( @@ -298,39 +314,55 @@ CertificateChain::CertificateChain ( boost::filesystem::remove_all (directory); } +CertificateChain::CertificateChain (string s) +{ + while (true) { + try { + Certificate c; + s = c.read_string (s); + _certificates.push_back (c); + } catch (MiscError& e) { + /* Failed to read a certificate, just stop */ + break; + } + } + + /* This will throw an exception if the chain cannot be ordered */ + leaf_to_root (); +} + /** @return Root certificate */ Certificate CertificateChain::root () const { DCP_ASSERT (!_certificates.empty()); - return _certificates.front (); + return root_to_leaf().front (); } /** @return Leaf certificate */ Certificate CertificateChain::leaf () const { - DCP_ASSERT (_certificates.size() >= 2); - return _certificates.back (); + DCP_ASSERT (!_certificates.empty()); + return root_to_leaf().back (); } -/** @return Certificates in order from root to leaf */ +/** @return Certificates in order from leaf to root */ CertificateChain::List -CertificateChain::root_to_leaf () const +CertificateChain::leaf_to_root () const { - return _certificates; + List l = root_to_leaf (); + l.reverse (); + return l; } -/** @return Certificates in order from leaf to root */ CertificateChain::List -CertificateChain::leaf_to_root () const +CertificateChain::unordered () const { - List c = _certificates; - c.reverse (); - return c; + return _certificates; } -/** Add a certificate to the end of the chain. +/** Add a certificate to the chain. * @param c Certificate to add. */ void @@ -365,64 +397,99 @@ CertificateChain::remove (int i) } } -/** Check to see if the chain is valid (i.e. root signs the intermediate, intermediate +bool +CertificateChain::chain_valid () const +{ + return chain_valid (_certificates); +} + +/** Check to see if a chain is valid (i.e. root signs the intermediate, intermediate * signs the leaf and so on) and that the private key (if there is one) matches the * leaf certificate. * @return true if it's ok, false if not. */ bool -CertificateChain::valid () const +CertificateChain::chain_valid (List const & chain) const { - /* Check the certificate chain */ + /* Here I am taking a chain of certificates A/B/C/D and checking validity of B wrt A, + C wrt B and D wrt C. It also appears necessary to check the issuer of B/C/D matches + the subject of A/B/C; I don't understand why. I'm sure there's a better way of doing + this with OpenSSL but the documentation does not appear not likely to reveal it + any time soon. + */ X509_STORE* store = X509_STORE_new (); if (!store) { - return false; + throw MiscError ("could not create X509 store"); + } + + /* Put all the certificates into the store */ + for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) { + if (!X509_STORE_add_cert (store, i->x509 ())) { + X509_STORE_free (store); + return false; + } } - for (List::const_iterator i = _certificates.begin(); i != _certificates.end(); ++i) { + /* Verify each one */ + for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) { List::const_iterator j = i; ++j; - if (j == _certificates.end ()) { + if (j == chain.end ()) { break; } - if (!X509_STORE_add_cert (store, i->x509 ())) { - X509_STORE_free (store); - return false; - } - X509_STORE_CTX* ctx = X509_STORE_CTX_new (); if (!ctx) { X509_STORE_free (store); - return false; + throw MiscError ("could not create X509 store context"); } X509_STORE_set_flags (store, 0); - if (!X509_STORE_CTX_init (ctx, store, j->x509 (), 0)) { + if (!X509_STORE_CTX_init (ctx, store, j->x509(), 0)) { X509_STORE_CTX_free (ctx); X509_STORE_free (store); - return false; + throw MiscError ("could not initialise X509 store context"); } - int v = X509_verify_cert (ctx); + int const v = X509_verify_cert (ctx); X509_STORE_CTX_free (ctx); - if (v == 0) { + if (v != 1) { X509_STORE_free (store); return false; } + + /* I don't know why OpenSSL doesn't check this in verify_cert, but without this check + the certificates_validation8 test fails. + */ + if (j->issuer() != i->subject()) { + X509_STORE_free (store); + return false; + } + } X509_STORE_free (store); - /* Check that the leaf certificate matches the private key, if there is one */ + return true; +} - if (!_key) { +/** Check that there is a valid private key for the leaf certificate. + * Will return true if there are no certificates. + */ +bool +CertificateChain::private_key_valid () const +{ + if (_certificates.empty ()) { return true; } + if (!_key) { + return false; + } + BIO* bio = BIO_new_mem_buf (const_cast (_key->c_str ()), -1); if (!bio) { throw MiscError ("could not create memory BIO"); @@ -430,28 +497,57 @@ CertificateChain::valid () const RSA* private_key = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0); RSA* public_key = leaf().public_key (); + +#if OPENSSL_VERSION_NUMBER > 0x10100000L +#warning "Using new OpenSSL API" + BIGNUM const * private_key_n; + RSA_get0_key(private_key, &private_key_n, 0, 0); + BIGNUM const * public_key_n; + RSA_get0_key(public_key, &public_key_n, 0, 0); + bool const valid = !BN_cmp (private_key_n, public_key_n); +#else bool const valid = !BN_cmp (private_key->n, public_key->n); +#endif BIO_free (bio); return valid; } -/** @return true if the chain is now in order from root to leaf, - * false if no correct order was found. - */ bool -CertificateChain::attempt_reorder () +CertificateChain::valid (string* reason) const { - List original = _certificates; - _certificates.sort (); + try { + root_to_leaf (); + } catch (CertificateChainError& e) { + if (reason) { + *reason = "certificates do not form a chain"; + } + return false; + } + + if (!private_key_valid ()) { + if (reason) { + *reason = "private key does not exist, or does not match leaf certificate"; + } + return false; + } + + return true; +} + +/** @return Certificates in order from root to leaf */ +CertificateChain::List +CertificateChain::root_to_leaf () const +{ + List rtl = _certificates; + rtl.sort (); do { - if (valid ()) { - return true; + if (chain_valid (rtl)) { + return rtl; } - } while (std::next_permutation (_certificates.begin(), _certificates.end ())); + } while (std::next_permutation (rtl.begin(), rtl.end())); - _certificates = original; - return false; + throw CertificateChainError ("certificate chain is not consistent"); } /** Add a <Signer> and <ds:Signature> nodes to an XML node. @@ -513,7 +609,6 @@ CertificateChain::add_signature_value (xmlpp::Node* parent, string ns) const xmlpp::Node* key_info = cp.node_child("KeyInfo")->node (); /* Add the certificate chain to the KeyInfo child node of parent */ - CertificateChain::List c = leaf_to_root (); BOOST_FOREACH (Certificate const & i, leaf_to_root ()) { xmlpp::Element* data = key_info->add_child("X509Data", ns); @@ -551,3 +646,14 @@ CertificateChain::add_signature_value (xmlpp::Node* parent, string ns) const xmlSecDSigCtxDestroy (signature_context); } + +string +CertificateChain::chain () const +{ + string o; + BOOST_FOREACH (Certificate const &i, root_to_leaf ()) { + o += i.certificate(true); + } + + return o; +}