2 Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
34 /** @file src/signer_chain.cc
35 * @brief Functions to make signer chains.
38 #include "certificate_chain.h"
39 #include "exceptions.h"
41 #include "dcp_assert.h"
42 #include "compose.hpp"
43 #include <asdcp/KM_util.h>
44 #include <libcxml/cxml.h>
45 #include <libxml++/libxml++.h>
46 #include <xmlsec/xmldsig.h>
47 #include <xmlsec/dl.h>
48 #include <xmlsec/app.h>
49 #include <xmlsec/crypto.h>
50 #include <openssl/sha.h>
51 #include <openssl/bio.h>
52 #include <openssl/evp.h>
53 #include <openssl/pem.h>
54 #include <openssl/rsa.h>
55 #include <boost/filesystem.hpp>
56 #include <boost/algorithm/string.hpp>
57 #include <boost/foreach.hpp>
64 using std::runtime_error;
67 /** Run a shell command.
68 * @param cmd Command to run (UTF8-encoded).
74 /* We need to use CreateProcessW on Windows so that the UTF-8/16 mess
77 int const wn = MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, 0, 0);
78 wchar_t* buffer = new wchar_t[wn];
79 if (MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, buffer, wn) == 0) {
86 STARTUPINFOW startup_info;
87 memset (&startup_info, 0, sizeof (startup_info));
88 startup_info.cb = sizeof (startup_info);
89 PROCESS_INFORMATION process_info;
91 /* XXX: this doesn't actually seem to work; failing commands end up with
94 if (CreateProcessW (0, buffer, 0, 0, FALSE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
95 WaitForSingleObject (process_info.hProcess, INFINITE);
97 if (GetExitCodeProcess (process_info.hProcess, &c)) {
100 CloseHandle (process_info.hProcess);
101 CloseHandle (process_info.hThread);
106 cmd += " 2> /dev/null";
107 int const r = system (cmd.c_str ());
108 int const code = WEXITSTATUS (r);
111 throw dcp::MiscError (String::compose ("error %1 in %2 within %3", code, cmd, boost::filesystem::current_path().string()));
115 /** Extract a public key from a private key and create a SHA1 digest of it.
116 * @param private_key Private key
117 * @param openssl openssl binary name (or full path if openssl is not on the system path).
118 * @return SHA1 digest of corresponding public key, with escaped / characters.
121 public_key_digest (boost::filesystem::path private_key, boost::filesystem::path openssl)
123 boost::filesystem::path public_name = private_key.string() + ".public";
125 /* Create the public key from the private key */
126 command (String::compose("\"%1\" rsa -outform PEM -pubout -in %2 -out %3", openssl.string(), private_key.string(), public_name.string ()));
128 /* Read in the public key from the file */
131 ifstream f (public_name.string().c_str ());
133 throw dcp::MiscError ("public key not found");
140 if (line.length() >= 10 && line.substr(0, 10) == "-----BEGIN") {
142 } else if (line.length() >= 8 && line.substr(0, 8) == "-----END") {
149 /* Decode the base64 of the public key */
151 unsigned char buffer[512];
152 int const N = dcp::base64_decode (pub, buffer, 1024);
154 /* Hash it with SHA1 (without the first 24 bytes, for reasons that are not entirely clear) */
157 if (!SHA1_Init (&context)) {
158 throw dcp::MiscError ("could not init SHA1 context");
161 if (!SHA1_Update (&context, buffer + 24, N - 24)) {
162 throw dcp::MiscError ("could not update SHA1 digest");
165 unsigned char digest[SHA_DIGEST_LENGTH];
166 if (!SHA1_Final (digest, &context)) {
167 throw dcp::MiscError ("could not finish SHA1 digest");
170 char digest_base64[64];
171 string dig = Kumu::base64encode (digest, SHA_DIGEST_LENGTH, digest_base64, 64);
172 #ifdef LIBDCP_WINDOWS
173 boost::replace_all (dig, "/", "\\/");
175 boost::replace_all (dig, "/", "\\\\/");
180 CertificateChain::CertificateChain (
181 boost::filesystem::path openssl,
183 string organisational_unit,
184 string root_common_name,
185 string intermediate_common_name,
186 string leaf_common_name
189 boost::filesystem::path directory = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
190 boost::filesystem::create_directories (directory);
192 boost::filesystem::path const cwd = boost::filesystem::current_path ();
193 boost::filesystem::current_path (directory);
195 string quoted_openssl = "\"" + openssl.string() + "\"";
197 command (quoted_openssl + " genrsa -out ca.key 2048");
200 ofstream f ("ca.cnf");
202 << "distinguished_name = req_distinguished_name\n"
203 << "x509_extensions = v3_ca\n"
204 << "string_mask = nombstr\n"
206 << "basicConstraints = critical,CA:true,pathlen:3\n"
207 << "keyUsage = keyCertSign,cRLSign\n"
208 << "subjectKeyIdentifier = hash\n"
209 << "authorityKeyIdentifier = keyid:always,issuer:always\n"
210 << "[ req_distinguished_name ]\n"
211 << "O = Unique organization name\n"
212 << "OU = Organization unit\n"
213 << "CN = Entity and dnQualifier\n";
216 string const ca_subject = "/O=" + organisation +
217 "/OU=" + organisational_unit +
218 "/CN=" + root_common_name +
219 "/dnQualifier=" + public_key_digest ("ca.key", openssl);
224 "%1 req -new -x509 -sha256 -config ca.cnf -days 3650 -set_serial 5"
225 " -subj \"%2\" -key ca.key -outform PEM -out ca.self-signed.pem",
226 quoted_openssl, ca_subject
231 command (quoted_openssl + " genrsa -out intermediate.key 2048");
234 ofstream f ("intermediate.cnf");
236 << "distinguished_name = req_distinguished_name\n"
237 << "x509_extensions = v3_ca\n"
238 << "string_mask = nombstr\n"
240 << "basicConstraints = critical,CA:true,pathlen:2\n"
241 << "keyUsage = keyCertSign,cRLSign\n"
242 << "subjectKeyIdentifier = hash\n"
243 << "authorityKeyIdentifier = keyid:always,issuer:always\n"
244 << "[ req_distinguished_name ]\n"
245 << "O = Unique organization name\n"
246 << "OU = Organization unit\n"
247 << "CN = Entity and dnQualifier\n";
250 string const inter_subject = "/O=" + organisation +
251 "/OU=" + organisational_unit +
252 "/CN=" + intermediate_common_name +
253 "/dnQualifier=" + public_key_digest ("intermediate.key", openssl);
258 "%1 req -new -config intermediate.cnf -days 3649 -subj \"%2\" -key intermediate.key -out intermediate.csr",
259 quoted_openssl, inter_subject
266 " x509 -req -sha256 -days 3649 -CA ca.self-signed.pem -CAkey ca.key -set_serial 6"
267 " -in intermediate.csr -extfile intermediate.cnf -extensions v3_ca -out intermediate.signed.pem"
270 command (quoted_openssl + " genrsa -out leaf.key 2048");
273 ofstream f ("leaf.cnf");
275 << "distinguished_name = req_distinguished_name\n"
276 << "x509_extensions = v3_ca\n"
277 << "string_mask = nombstr\n"
279 << "basicConstraints = critical,CA:false\n"
280 << "keyUsage = digitalSignature,keyEncipherment\n"
281 << "subjectKeyIdentifier = hash\n"
282 << "authorityKeyIdentifier = keyid,issuer:always\n"
283 << "[ req_distinguished_name ]\n"
284 << "O = Unique organization name\n"
285 << "OU = Organization unit\n"
286 << "CN = Entity and dnQualifier\n";
289 string const leaf_subject = "/O=" + organisation +
290 "/OU=" + organisational_unit +
291 "/CN=" + leaf_common_name +
292 "/dnQualifier=" + public_key_digest ("leaf.key", openssl);
297 "%1 req -new -config leaf.cnf -days 3648 -subj \"%2\" -key leaf.key -outform PEM -out leaf.csr",
298 quoted_openssl, leaf_subject
305 " x509 -req -sha256 -days 3648 -CA intermediate.signed.pem -CAkey intermediate.key"
306 " -set_serial 7 -in leaf.csr -extfile leaf.cnf -extensions v3_ca -out leaf.signed.pem"
309 boost::filesystem::current_path (cwd);
311 _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "ca.self-signed.pem")));
312 _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "intermediate.signed.pem")));
313 _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "leaf.signed.pem")));
315 _key = dcp::file_to_string (directory / "leaf.key");
317 boost::filesystem::remove_all (directory);
320 CertificateChain::CertificateChain (string s)
325 s = c.read_string (s);
326 _certificates.push_back (c);
327 } catch (MiscError& e) {
328 /* Failed to read a certificate, just stop */
333 /* This will throw an exception if the chain cannot be ordered */
337 /** @return Root certificate */
339 CertificateChain::root () const
341 DCP_ASSERT (!_certificates.empty());
342 return root_to_leaf().front ();
345 /** @return Leaf certificate */
347 CertificateChain::leaf () const
349 DCP_ASSERT (!_certificates.empty());
350 return root_to_leaf().back ();
353 /** @return Certificates in order from leaf to root */
354 CertificateChain::List
355 CertificateChain::leaf_to_root () const
357 List l = root_to_leaf ();
362 CertificateChain::List
363 CertificateChain::unordered () const
365 return _certificates;
368 /** Add a certificate to the chain.
369 * @param c Certificate to add.
372 CertificateChain::add (Certificate c)
374 _certificates.push_back (c);
377 /** Remove a certificate from the chain.
378 * @param c Certificate to remove.
381 CertificateChain::remove (Certificate c)
383 _certificates.remove (c);
386 /** Remove the i'th certificate in the list, as listed
390 CertificateChain::remove (int i)
392 List::iterator j = _certificates.begin ();
393 while (j != _certificates.end () && i > 0) {
398 if (j != _certificates.end ()) {
399 _certificates.erase (j);
404 CertificateChain::chain_valid () const
406 return chain_valid (_certificates);
409 /** Check to see if a chain is valid (i.e. root signs the intermediate, intermediate
410 * signs the leaf and so on) and that the private key (if there is one) matches the
412 * @return true if it's ok, false if not.
415 CertificateChain::chain_valid (List const & chain) const
417 /* Here I am taking a chain of certificates A/B/C/D and checking validity of B wrt A,
418 C wrt B and D wrt C. It also appears necessary to check the issuer of B/C/D matches
419 the subject of A/B/C; I don't understand why. I'm sure there's a better way of doing
420 this with OpenSSL but the documentation does not appear not likely to reveal it
424 X509_STORE* store = X509_STORE_new ();
426 throw MiscError ("could not create X509 store");
429 /* Put all the certificates into the store */
430 for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) {
431 if (!X509_STORE_add_cert (store, i->x509 ())) {
432 X509_STORE_free (store);
437 /* Verify each one */
438 for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) {
440 List::const_iterator j = i;
442 if (j == chain.end ()) {
446 X509_STORE_CTX* ctx = X509_STORE_CTX_new ();
448 X509_STORE_free (store);
449 throw MiscError ("could not create X509 store context");
452 X509_STORE_set_flags (store, 0);
453 if (!X509_STORE_CTX_init (ctx, store, j->x509(), 0)) {
454 X509_STORE_CTX_free (ctx);
455 X509_STORE_free (store);
456 throw MiscError ("could not initialise X509 store context");
459 int const v = X509_verify_cert (ctx);
460 X509_STORE_CTX_free (ctx);
463 X509_STORE_free (store);
467 /* I don't know why OpenSSL doesn't check this in verify_cert, but without this check
468 the certificates_validation8 test fails.
470 if (j->issuer() != i->subject()) {
471 X509_STORE_free (store);
477 X509_STORE_free (store);
482 /** Check that there is a valid private key for the leaf certificate.
483 * Will return true if there are no certificates.
486 CertificateChain::private_key_valid () const
488 if (_certificates.empty ()) {
496 BIO* bio = BIO_new_mem_buf (const_cast<char *> (_key->c_str ()), -1);
498 throw MiscError ("could not create memory BIO");
501 RSA* private_key = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
502 RSA* public_key = leaf().public_key ();
504 #if OPENSSL_VERSION_NUMBER > 0x10100000L
505 #warning "Using new OpenSSL API"
506 BIGNUM const * private_key_n;
507 RSA_get0_key(private_key, &private_key_n, 0, 0);
508 BIGNUM const * public_key_n;
509 RSA_get0_key(public_key, &public_key_n, 0, 0);
510 bool const valid = !BN_cmp (private_key_n, public_key_n);
512 bool const valid = !BN_cmp (private_key->n, public_key->n);
520 CertificateChain::valid (string* reason) const
524 } catch (CertificateChainError& e) {
526 *reason = "certificates do not form a chain";
531 if (!private_key_valid ()) {
533 *reason = "private key does not exist, or does not match leaf certificate";
541 /** @return Certificates in order from root to leaf */
542 CertificateChain::List
543 CertificateChain::root_to_leaf () const
545 List rtl = _certificates;
548 if (chain_valid (rtl)) {
551 } while (std::next_permutation (rtl.begin(), rtl.end()));
553 throw CertificateChainError ("certificate chain is not consistent");
556 /** Add a <Signer> and <ds:Signature> nodes to an XML node.
557 * @param parent XML node to add to.
558 * @param standard INTEROP or SMPTE.
561 CertificateChain::sign (xmlpp::Element* parent, Standard standard) const
565 xmlpp::Element* signer = parent->add_child("Signer");
566 signer->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
567 xmlpp::Element* data = signer->add_child("X509Data", "dsig");
568 xmlpp::Element* serial_element = data->add_child("X509IssuerSerial", "dsig");
569 serial_element->add_child("X509IssuerName", "dsig")->add_child_text (leaf().issuer());
570 serial_element->add_child("X509SerialNumber", "dsig")->add_child_text (leaf().serial());
571 data->add_child("X509SubjectName", "dsig")->add_child_text (leaf().subject());
575 xmlpp::Element* signature = parent->add_child("Signature");
576 signature->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
577 signature->set_namespace ("dsig");
579 xmlpp::Element* signed_info = signature->add_child ("SignedInfo", "dsig");
580 signed_info->add_child("CanonicalizationMethod", "dsig")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
582 if (standard == INTEROP) {
583 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
585 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
588 xmlpp::Element* reference = signed_info->add_child("Reference", "dsig");
589 reference->set_attribute ("URI", "");
591 xmlpp::Element* transforms = reference->add_child("Transforms", "dsig");
592 transforms->add_child("Transform", "dsig")->set_attribute (
593 "Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
596 reference->add_child("DigestMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
597 /* This will be filled in by the signing later */
598 reference->add_child("DigestValue", "dsig");
600 signature->add_child("SignatureValue", "dsig");
601 signature->add_child("KeyInfo", "dsig");
602 add_signature_value (signature, "dsig");
606 /** Sign an XML node.
608 * @param parent Node to sign.
609 * @param ns Namespace to use for the signature XML nodes.
612 CertificateChain::add_signature_value (xmlpp::Node* parent, string ns) const
614 cxml::Node cp (parent);
615 xmlpp::Node* key_info = cp.node_child("KeyInfo")->node ();
617 /* Add the certificate chain to the KeyInfo child node of parent */
618 BOOST_FOREACH (Certificate const & i, leaf_to_root ()) {
619 xmlpp::Element* data = key_info->add_child("X509Data", ns);
622 xmlpp::Element* serial = data->add_child("X509IssuerSerial", ns);
623 serial->add_child("X509IssuerName", ns)->add_child_text (i.issuer ());
624 serial->add_child("X509SerialNumber", ns)->add_child_text (i.serial ());
627 data->add_child("X509Certificate", ns)->add_child_text (i.certificate());
630 xmlSecDSigCtxPtr signature_context = xmlSecDSigCtxCreate (0);
631 if (signature_context == 0) {
632 throw MiscError ("could not create signature context");
635 signature_context->signKey = xmlSecCryptoAppKeyLoadMemory (
636 reinterpret_cast<const unsigned char *> (_key->c_str()), _key->size(), xmlSecKeyDataFormatPem, 0, 0, 0
639 if (signature_context->signKey == 0) {
640 throw runtime_error ("could not read private key");
643 /* XXX: set key name to the PEM string: this can't be right! */
644 if (xmlSecKeySetName (signature_context->signKey, reinterpret_cast<const xmlChar *> (_key->c_str())) < 0) {
645 throw MiscError ("could not set key name");
648 int const r = xmlSecDSigCtxSign (signature_context, parent->cobj ());
650 throw MiscError (String::compose ("could not sign (%1)", r));
653 xmlSecDSigCtxDestroy (signature_context);
657 CertificateChain::chain () const
660 BOOST_FOREACH (Certificate const &i, root_to_leaf ()) {
661 o += i.certificate(true);