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>
63 using std::runtime_error;
66 /** Run a shell command.
67 * @param cmd Command to run (UTF8-encoded).
73 /* We need to use CreateProcessW on Windows so that the UTF-8/16 mess
76 int const wn = MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, 0, 0);
77 wchar_t* buffer = new wchar_t[wn];
78 if (MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, buffer, wn) == 0) {
85 STARTUPINFOW startup_info;
86 memset (&startup_info, 0, sizeof (startup_info));
87 startup_info.cb = sizeof (startup_info);
88 PROCESS_INFORMATION process_info;
90 /* XXX: this doesn't actually seem to work; failing commands end up with
93 if (CreateProcessW (0, buffer, 0, 0, FALSE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
94 WaitForSingleObject (process_info.hProcess, INFINITE);
96 if (GetExitCodeProcess (process_info.hProcess, &c)) {
99 CloseHandle (process_info.hProcess);
100 CloseHandle (process_info.hThread);
105 cmd += " 2> /dev/null";
106 int const r = system (cmd.c_str ());
107 int const code = WEXITSTATUS (r);
110 throw dcp::MiscError (String::compose ("error %1 in %2 within %3", code, cmd, boost::filesystem::current_path().string()));
114 /** Extract a public key from a private key and create a SHA1 digest of it.
115 * @param private_key Private key
116 * @param openssl openssl binary name (or full path if openssl is not on the system path).
117 * @return SHA1 digest of corresponding public key, with escaped / characters.
120 public_key_digest (boost::filesystem::path private_key, boost::filesystem::path openssl)
122 boost::filesystem::path public_name = private_key.string() + ".public";
124 /* Create the public key from the private key */
125 command (String::compose("\"%1\" rsa -outform PEM -pubout -in %2 -out %3", openssl.string(), private_key.string(), public_name.string ()));
127 /* Read in the public key from the file */
130 ifstream f (public_name.string().c_str ());
132 throw dcp::MiscError ("public key not found");
139 if (line.length() >= 10 && line.substr(0, 10) == "-----BEGIN") {
141 } else if (line.length() >= 8 && line.substr(0, 8) == "-----END") {
148 /* Decode the base64 of the public key */
150 unsigned char buffer[512];
151 int const N = dcp::base64_decode (pub, buffer, 1024);
153 /* Hash it with SHA1 (without the first 24 bytes, for reasons that are not entirely clear) */
156 if (!SHA1_Init (&context)) {
157 throw dcp::MiscError ("could not init SHA1 context");
160 if (!SHA1_Update (&context, buffer + 24, N - 24)) {
161 throw dcp::MiscError ("could not update SHA1 digest");
164 unsigned char digest[SHA_DIGEST_LENGTH];
165 if (!SHA1_Final (digest, &context)) {
166 throw dcp::MiscError ("could not finish SHA1 digest");
169 char digest_base64[64];
170 string dig = Kumu::base64encode (digest, SHA_DIGEST_LENGTH, digest_base64, 64);
171 #ifdef LIBDCP_WINDOWS
172 boost::replace_all (dig, "/", "\\/");
174 boost::replace_all (dig, "/", "\\\\/");
179 CertificateChain::CertificateChain (
180 boost::filesystem::path openssl,
182 string organisational_unit,
183 string root_common_name,
184 string intermediate_common_name,
185 string leaf_common_name
188 /* Valid for 40 years */
189 int const days = 365 * 40;
191 boost::filesystem::path directory = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
192 boost::filesystem::create_directories (directory);
194 boost::filesystem::path const cwd = boost::filesystem::current_path ();
195 boost::filesystem::current_path (directory);
197 string quoted_openssl = "\"" + openssl.string() + "\"";
199 command (quoted_openssl + " genrsa -out ca.key 2048");
202 ofstream f ("ca.cnf");
204 << "distinguished_name = req_distinguished_name\n"
205 << "x509_extensions = v3_ca\n"
206 << "string_mask = nombstr\n"
208 << "basicConstraints = critical,CA:true,pathlen:3\n"
209 << "keyUsage = keyCertSign,cRLSign\n"
210 << "subjectKeyIdentifier = hash\n"
211 << "authorityKeyIdentifier = keyid:always,issuer:always\n"
212 << "[ req_distinguished_name ]\n"
213 << "O = Unique organization name\n"
214 << "OU = Organization unit\n"
215 << "CN = Entity and dnQualifier\n";
218 string const ca_subject = "/O=" + organisation +
219 "/OU=" + organisational_unit +
220 "/CN=" + root_common_name +
221 "/dnQualifier=" + public_key_digest ("ca.key", openssl);
226 "%1 req -new -x509 -sha256 -config ca.cnf -days %2 -set_serial 5"
227 " -subj \"%3\" -key ca.key -outform PEM -out ca.self-signed.pem",
228 quoted_openssl, days, ca_subject
233 command (quoted_openssl + " genrsa -out intermediate.key 2048");
236 ofstream f ("intermediate.cnf");
238 << "distinguished_name = req_distinguished_name\n"
239 << "x509_extensions = v3_ca\n"
240 << "string_mask = nombstr\n"
242 << "basicConstraints = critical,CA:true,pathlen:2\n"
243 << "keyUsage = keyCertSign,cRLSign\n"
244 << "subjectKeyIdentifier = hash\n"
245 << "authorityKeyIdentifier = keyid:always,issuer:always\n"
246 << "[ req_distinguished_name ]\n"
247 << "O = Unique organization name\n"
248 << "OU = Organization unit\n"
249 << "CN = Entity and dnQualifier\n";
252 string const inter_subject = "/O=" + organisation +
253 "/OU=" + organisational_unit +
254 "/CN=" + intermediate_common_name +
255 "/dnQualifier=" + public_key_digest ("intermediate.key", openssl);
260 "%1 req -new -config intermediate.cnf -days %2 -subj \"%3\" -key intermediate.key -out intermediate.csr",
261 quoted_openssl, days - 1, inter_subject
268 "%1 x509 -req -sha256 -days %2 -CA ca.self-signed.pem -CAkey ca.key -set_serial 6"
269 " -in intermediate.csr -extfile intermediate.cnf -extensions v3_ca -out intermediate.signed.pem",
270 quoted_openssl, days - 1
274 command (quoted_openssl + " genrsa -out leaf.key 2048");
277 ofstream f ("leaf.cnf");
279 << "distinguished_name = req_distinguished_name\n"
280 << "x509_extensions = v3_ca\n"
281 << "string_mask = nombstr\n"
283 << "basicConstraints = critical,CA:false\n"
284 << "keyUsage = digitalSignature,keyEncipherment\n"
285 << "subjectKeyIdentifier = hash\n"
286 << "authorityKeyIdentifier = keyid,issuer:always\n"
287 << "[ req_distinguished_name ]\n"
288 << "O = Unique organization name\n"
289 << "OU = Organization unit\n"
290 << "CN = Entity and dnQualifier\n";
293 string const leaf_subject = "/O=" + organisation +
294 "/OU=" + organisational_unit +
295 "/CN=" + leaf_common_name +
296 "/dnQualifier=" + public_key_digest ("leaf.key", openssl);
301 "%1 req -new -config leaf.cnf -days %2 -subj \"%3\" -key leaf.key -outform PEM -out leaf.csr",
302 quoted_openssl, days - 2, leaf_subject
309 "%1 x509 -req -sha256 -days %2 -CA intermediate.signed.pem -CAkey intermediate.key"
310 " -set_serial 7 -in leaf.csr -extfile leaf.cnf -extensions v3_ca -out leaf.signed.pem",
311 quoted_openssl, days - 2
315 boost::filesystem::current_path (cwd);
317 _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "ca.self-signed.pem")));
318 _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "intermediate.signed.pem")));
319 _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "leaf.signed.pem")));
321 _key = dcp::file_to_string (directory / "leaf.key");
323 boost::filesystem::remove_all (directory);
326 CertificateChain::CertificateChain (string s)
331 s = c.read_string (s);
332 _certificates.push_back (c);
333 } catch (MiscError& e) {
334 /* Failed to read a certificate, just stop */
339 /* This will throw an exception if the chain cannot be ordered */
343 /** @return Root certificate */
345 CertificateChain::root () const
347 DCP_ASSERT (!_certificates.empty());
348 return root_to_leaf().front ();
351 /** @return Leaf certificate */
353 CertificateChain::leaf () const
355 DCP_ASSERT (!_certificates.empty());
356 return root_to_leaf().back ();
359 /** @return Certificates in order from leaf to root */
360 CertificateChain::List
361 CertificateChain::leaf_to_root () const
363 List l = root_to_leaf ();
364 std::reverse (l.begin(), l.end());
368 CertificateChain::List
369 CertificateChain::unordered () const
371 return _certificates;
374 /** Add a certificate to the chain.
375 * @param c Certificate to add.
378 CertificateChain::add (Certificate c)
380 _certificates.push_back (c);
383 /** Remove a certificate from the chain.
384 * @param c Certificate to remove.
387 CertificateChain::remove (Certificate c)
389 auto i = std::find(_certificates.begin(), _certificates.end(), c);
390 if (i != _certificates.end()) {
391 _certificates.erase (i);
395 /** Remove the i'th certificate in the list, as listed
399 CertificateChain::remove (int i)
401 List::iterator j = _certificates.begin ();
402 while (j != _certificates.end () && i > 0) {
407 if (j != _certificates.end ()) {
408 _certificates.erase (j);
413 CertificateChain::chain_valid () const
415 return chain_valid (_certificates);
418 /** Check to see if a chain is valid (i.e. root signs the intermediate, intermediate
419 * signs the leaf and so on) and that the private key (if there is one) matches the
421 * @return true if it's ok, false if not.
424 CertificateChain::chain_valid (List const & chain) const
426 /* Here I am taking a chain of certificates A/B/C/D and checking validity of B wrt A,
427 C wrt B and D wrt C. It also appears necessary to check the issuer of B/C/D matches
428 the subject of A/B/C; I don't understand why. I'm sure there's a better way of doing
429 this with OpenSSL but the documentation does not appear not likely to reveal it
433 X509_STORE* store = X509_STORE_new ();
435 throw MiscError ("could not create X509 store");
438 /* Put all the certificates into the store */
439 for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) {
440 if (!X509_STORE_add_cert (store, i->x509 ())) {
441 X509_STORE_free (store);
446 /* Verify each one */
447 for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) {
449 List::const_iterator j = i;
451 if (j == chain.end ()) {
455 X509_STORE_CTX* ctx = X509_STORE_CTX_new ();
457 X509_STORE_free (store);
458 throw MiscError ("could not create X509 store context");
461 X509_STORE_set_flags (store, 0);
462 if (!X509_STORE_CTX_init (ctx, store, j->x509(), 0)) {
463 X509_STORE_CTX_free (ctx);
464 X509_STORE_free (store);
465 throw MiscError ("could not initialise X509 store context");
468 int const v = X509_verify_cert (ctx);
469 X509_STORE_CTX_free (ctx);
472 X509_STORE_free (store);
476 /* I don't know why OpenSSL doesn't check this stuff
477 in verify_cert, but without these checks the
478 certificates_validation8 test fails.
480 if (j->issuer() != i->subject() || j->subject() == i->subject()) {
481 X509_STORE_free (store);
487 X509_STORE_free (store);
492 /** Check that there is a valid private key for the leaf certificate.
493 * Will return true if there are no certificates.
496 CertificateChain::private_key_valid () const
498 if (_certificates.empty ()) {
506 BIO* bio = BIO_new_mem_buf (const_cast<char *> (_key->c_str ()), -1);
508 throw MiscError ("could not create memory BIO");
511 RSA* private_key = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
516 RSA* public_key = leaf().public_key ();
518 #if OPENSSL_VERSION_NUMBER > 0x10100000L
519 BIGNUM const * private_key_n;
520 RSA_get0_key(private_key, &private_key_n, 0, 0);
521 BIGNUM const * public_key_n;
522 RSA_get0_key(public_key, &public_key_n, 0, 0);
523 if (!private_key_n || !public_key_n) {
526 bool const valid = !BN_cmp (private_key_n, public_key_n);
528 bool const valid = !BN_cmp (private_key->n, public_key->n);
536 CertificateChain::valid (string* reason) const
540 } catch (CertificateChainError& e) {
542 *reason = "certificates do not form a chain";
547 if (!private_key_valid ()) {
549 *reason = "private key does not exist, or does not match leaf certificate";
557 /** @return Certificates in order from root to leaf */
558 CertificateChain::List
559 CertificateChain::root_to_leaf () const
561 List rtl = _certificates;
562 std::sort (rtl.begin(), rtl.end());
564 if (chain_valid (rtl)) {
567 } while (std::next_permutation (rtl.begin(), rtl.end()));
569 throw CertificateChainError ("certificate chain is not consistent");
572 /** Add a <Signer> and <ds:Signature> nodes to an XML node.
573 * @param parent XML node to add to.
574 * @param standard INTEROP or SMPTE.
577 CertificateChain::sign (xmlpp::Element* parent, Standard standard) const
581 parent->add_child_text(" ");
582 xmlpp::Element* signer = parent->add_child("Signer");
583 signer->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
584 xmlpp::Element* data = signer->add_child("X509Data", "dsig");
585 xmlpp::Element* serial_element = data->add_child("X509IssuerSerial", "dsig");
586 serial_element->add_child("X509IssuerName", "dsig")->add_child_text (leaf().issuer());
587 serial_element->add_child("X509SerialNumber", "dsig")->add_child_text (leaf().serial());
588 data->add_child("X509SubjectName", "dsig")->add_child_text (leaf().subject());
594 parent->add_child_text("\n ");
595 xmlpp::Element* signature = parent->add_child("Signature");
596 signature->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
597 signature->set_namespace ("dsig");
598 parent->add_child_text("\n");
600 xmlpp::Element* signed_info = signature->add_child ("SignedInfo", "dsig");
601 signed_info->add_child("CanonicalizationMethod", "dsig")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
603 if (standard == Standard::INTEROP) {
604 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
606 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
609 xmlpp::Element* reference = signed_info->add_child("Reference", "dsig");
610 reference->set_attribute ("URI", "");
612 xmlpp::Element* transforms = reference->add_child("Transforms", "dsig");
613 transforms->add_child("Transform", "dsig")->set_attribute (
614 "Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
617 reference->add_child("DigestMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
618 /* This will be filled in by the signing later */
619 reference->add_child("DigestValue", "dsig");
621 signature->add_child("SignatureValue", "dsig");
622 signature->add_child("KeyInfo", "dsig");
623 add_signature_value (signature, "dsig", true);
627 /** Sign an XML node.
629 * @param parent Node to sign.
630 * @param ns Namespace to use for the signature XML nodes.
633 CertificateChain::add_signature_value (xmlpp::Element* parent, string ns, bool add_indentation) const
635 cxml::Node cp (parent);
636 auto key_info = cp.node_child("KeyInfo")->node();
638 /* Add the certificate chain to the KeyInfo child node of parent */
639 for (auto const& i: leaf_to_root()) {
640 auto data = key_info->add_child("X509Data", ns);
643 auto serial = data->add_child("X509IssuerSerial", ns);
644 serial->add_child("X509IssuerName", ns)->add_child_text (i.issuer ());
645 serial->add_child("X509SerialNumber", ns)->add_child_text (i.serial ());
648 data->add_child("X509Certificate", ns)->add_child_text (i.certificate());
651 auto signature_context = xmlSecDSigCtxCreate (0);
652 if (signature_context == 0) {
653 throw MiscError ("could not create signature context");
656 signature_context->signKey = xmlSecCryptoAppKeyLoadMemory (
657 reinterpret_cast<const unsigned char *> (_key->c_str()), _key->size(), xmlSecKeyDataFormatPem, 0, 0, 0
660 if (signature_context->signKey == 0) {
661 throw runtime_error ("could not read private key");
664 if (add_indentation) {
667 int const r = xmlSecDSigCtxSign (signature_context, parent->cobj ());
669 throw MiscError (String::compose ("could not sign (%1)", r));
672 xmlSecDSigCtxDestroy (signature_context);
676 CertificateChain::chain () const
679 for (auto const& i: root_to_leaf()) {
680 o += i.certificate(true);