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 /* Valid for 40 years */
190 int const days = 365 * 40;
192 boost::filesystem::path directory = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
193 boost::filesystem::create_directories (directory);
195 boost::filesystem::path const cwd = boost::filesystem::current_path ();
196 boost::filesystem::current_path (directory);
198 string quoted_openssl = "\"" + openssl.string() + "\"";
200 command (quoted_openssl + " genrsa -out ca.key 2048");
203 ofstream f ("ca.cnf");
205 << "distinguished_name = req_distinguished_name\n"
206 << "x509_extensions = v3_ca\n"
207 << "string_mask = nombstr\n"
209 << "basicConstraints = critical,CA:true,pathlen:3\n"
210 << "keyUsage = keyCertSign,cRLSign\n"
211 << "subjectKeyIdentifier = hash\n"
212 << "authorityKeyIdentifier = keyid:always,issuer:always\n"
213 << "[ req_distinguished_name ]\n"
214 << "O = Unique organization name\n"
215 << "OU = Organization unit\n"
216 << "CN = Entity and dnQualifier\n";
219 string const ca_subject = "/O=" + organisation +
220 "/OU=" + organisational_unit +
221 "/CN=" + root_common_name +
222 "/dnQualifier=" + public_key_digest ("ca.key", openssl);
227 "%1 req -new -x509 -sha256 -config ca.cnf -days %2 -set_serial 5"
228 " -subj \"%3\" -key ca.key -outform PEM -out ca.self-signed.pem",
229 quoted_openssl, days, ca_subject
234 command (quoted_openssl + " genrsa -out intermediate.key 2048");
237 ofstream f ("intermediate.cnf");
239 << "distinguished_name = req_distinguished_name\n"
240 << "x509_extensions = v3_ca\n"
241 << "string_mask = nombstr\n"
243 << "basicConstraints = critical,CA:true,pathlen:2\n"
244 << "keyUsage = keyCertSign,cRLSign\n"
245 << "subjectKeyIdentifier = hash\n"
246 << "authorityKeyIdentifier = keyid:always,issuer:always\n"
247 << "[ req_distinguished_name ]\n"
248 << "O = Unique organization name\n"
249 << "OU = Organization unit\n"
250 << "CN = Entity and dnQualifier\n";
253 string const inter_subject = "/O=" + organisation +
254 "/OU=" + organisational_unit +
255 "/CN=" + intermediate_common_name +
256 "/dnQualifier=" + public_key_digest ("intermediate.key", openssl);
261 "%1 req -new -config intermediate.cnf -days %2 -subj \"%3\" -key intermediate.key -out intermediate.csr",
262 quoted_openssl, days - 1, inter_subject
269 "%1 x509 -req -sha256 -days %2 -CA ca.self-signed.pem -CAkey ca.key -set_serial 6"
270 " -in intermediate.csr -extfile intermediate.cnf -extensions v3_ca -out intermediate.signed.pem",
271 quoted_openssl, days - 1
275 command (quoted_openssl + " genrsa -out leaf.key 2048");
278 ofstream f ("leaf.cnf");
280 << "distinguished_name = req_distinguished_name\n"
281 << "x509_extensions = v3_ca\n"
282 << "string_mask = nombstr\n"
284 << "basicConstraints = critical,CA:false\n"
285 << "keyUsage = digitalSignature,keyEncipherment\n"
286 << "subjectKeyIdentifier = hash\n"
287 << "authorityKeyIdentifier = keyid,issuer:always\n"
288 << "[ req_distinguished_name ]\n"
289 << "O = Unique organization name\n"
290 << "OU = Organization unit\n"
291 << "CN = Entity and dnQualifier\n";
294 string const leaf_subject = "/O=" + organisation +
295 "/OU=" + organisational_unit +
296 "/CN=" + leaf_common_name +
297 "/dnQualifier=" + public_key_digest ("leaf.key", openssl);
302 "%1 req -new -config leaf.cnf -days %2 -subj \"%3\" -key leaf.key -outform PEM -out leaf.csr",
303 quoted_openssl, days - 2, leaf_subject
310 "%1 x509 -req -sha256 -days %2 -CA intermediate.signed.pem -CAkey intermediate.key"
311 " -set_serial 7 -in leaf.csr -extfile leaf.cnf -extensions v3_ca -out leaf.signed.pem",
312 quoted_openssl, days - 2
316 boost::filesystem::current_path (cwd);
318 _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "ca.self-signed.pem")));
319 _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "intermediate.signed.pem")));
320 _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "leaf.signed.pem")));
322 _key = dcp::file_to_string (directory / "leaf.key");
324 boost::filesystem::remove_all (directory);
327 CertificateChain::CertificateChain (string s)
332 s = c.read_string (s);
333 _certificates.push_back (c);
334 } catch (MiscError& e) {
335 /* Failed to read a certificate, just stop */
340 /* This will throw an exception if the chain cannot be ordered */
344 /** @return Root certificate */
346 CertificateChain::root () const
348 DCP_ASSERT (!_certificates.empty());
349 return root_to_leaf().front ();
352 /** @return Leaf certificate */
354 CertificateChain::leaf () const
356 DCP_ASSERT (!_certificates.empty());
357 return root_to_leaf().back ();
360 /** @return Certificates in order from leaf to root */
361 CertificateChain::List
362 CertificateChain::leaf_to_root () const
364 List l = root_to_leaf ();
365 std::reverse (l.begin(), l.end());
369 CertificateChain::List
370 CertificateChain::unordered () const
372 return _certificates;
375 /** Add a certificate to the chain.
376 * @param c Certificate to add.
379 CertificateChain::add (Certificate c)
381 _certificates.push_back (c);
384 /** Remove a certificate from the chain.
385 * @param c Certificate to remove.
388 CertificateChain::remove (Certificate c)
390 auto i = std::find(_certificates.begin(), _certificates.end(), c);
391 if (i != _certificates.end()) {
392 _certificates.erase (i);
396 /** Remove the i'th certificate in the list, as listed
400 CertificateChain::remove (int i)
402 List::iterator j = _certificates.begin ();
403 while (j != _certificates.end () && i > 0) {
408 if (j != _certificates.end ()) {
409 _certificates.erase (j);
414 CertificateChain::chain_valid () const
416 return chain_valid (_certificates);
419 /** Check to see if a chain is valid (i.e. root signs the intermediate, intermediate
420 * signs the leaf and so on) and that the private key (if there is one) matches the
422 * @return true if it's ok, false if not.
425 CertificateChain::chain_valid (List const & chain) const
427 /* Here I am taking a chain of certificates A/B/C/D and checking validity of B wrt A,
428 C wrt B and D wrt C. It also appears necessary to check the issuer of B/C/D matches
429 the subject of A/B/C; I don't understand why. I'm sure there's a better way of doing
430 this with OpenSSL but the documentation does not appear not likely to reveal it
434 X509_STORE* store = X509_STORE_new ();
436 throw MiscError ("could not create X509 store");
439 /* Put all the certificates into the store */
440 for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) {
441 if (!X509_STORE_add_cert (store, i->x509 ())) {
442 X509_STORE_free (store);
447 /* Verify each one */
448 for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) {
450 List::const_iterator j = i;
452 if (j == chain.end ()) {
456 X509_STORE_CTX* ctx = X509_STORE_CTX_new ();
458 X509_STORE_free (store);
459 throw MiscError ("could not create X509 store context");
462 X509_STORE_set_flags (store, 0);
463 if (!X509_STORE_CTX_init (ctx, store, j->x509(), 0)) {
464 X509_STORE_CTX_free (ctx);
465 X509_STORE_free (store);
466 throw MiscError ("could not initialise X509 store context");
469 int const v = X509_verify_cert (ctx);
470 X509_STORE_CTX_free (ctx);
473 X509_STORE_free (store);
477 /* I don't know why OpenSSL doesn't check this stuff
478 in verify_cert, but without these checks the
479 certificates_validation8 test fails.
481 if (j->issuer() != i->subject() || j->subject() == i->subject()) {
482 X509_STORE_free (store);
488 X509_STORE_free (store);
493 /** Check that there is a valid private key for the leaf certificate.
494 * Will return true if there are no certificates.
497 CertificateChain::private_key_valid () const
499 if (_certificates.empty ()) {
507 BIO* bio = BIO_new_mem_buf (const_cast<char *> (_key->c_str ()), -1);
509 throw MiscError ("could not create memory BIO");
512 RSA* private_key = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
517 RSA* public_key = leaf().public_key ();
519 #if OPENSSL_VERSION_NUMBER > 0x10100000L
520 BIGNUM const * private_key_n;
521 RSA_get0_key(private_key, &private_key_n, 0, 0);
522 BIGNUM const * public_key_n;
523 RSA_get0_key(public_key, &public_key_n, 0, 0);
524 if (!private_key_n || !public_key_n) {
527 bool const valid = !BN_cmp (private_key_n, public_key_n);
529 bool const valid = !BN_cmp (private_key->n, public_key->n);
537 CertificateChain::valid (string* reason) const
541 } catch (CertificateChainError& e) {
543 *reason = "certificates do not form a chain";
548 if (!private_key_valid ()) {
550 *reason = "private key does not exist, or does not match leaf certificate";
558 /** @return Certificates in order from root to leaf */
559 CertificateChain::List
560 CertificateChain::root_to_leaf () const
562 List rtl = _certificates;
563 std::sort (rtl.begin(), rtl.end());
565 if (chain_valid (rtl)) {
568 } while (std::next_permutation (rtl.begin(), rtl.end()));
570 throw CertificateChainError ("certificate chain is not consistent");
573 /** Add a <Signer> and <ds:Signature> nodes to an XML node.
574 * @param parent XML node to add to.
575 * @param standard INTEROP or SMPTE.
578 CertificateChain::sign (xmlpp::Element* parent, Standard standard) const
582 parent->add_child_text(" ");
583 xmlpp::Element* signer = parent->add_child("Signer");
584 signer->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
585 xmlpp::Element* data = signer->add_child("X509Data", "dsig");
586 xmlpp::Element* serial_element = data->add_child("X509IssuerSerial", "dsig");
587 serial_element->add_child("X509IssuerName", "dsig")->add_child_text (leaf().issuer());
588 serial_element->add_child("X509SerialNumber", "dsig")->add_child_text (leaf().serial());
589 data->add_child("X509SubjectName", "dsig")->add_child_text (leaf().subject());
595 parent->add_child_text("\n ");
596 xmlpp::Element* signature = parent->add_child("Signature");
597 signature->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
598 signature->set_namespace ("dsig");
599 parent->add_child_text("\n");
601 xmlpp::Element* signed_info = signature->add_child ("SignedInfo", "dsig");
602 signed_info->add_child("CanonicalizationMethod", "dsig")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
604 if (standard == INTEROP) {
605 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
607 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
610 xmlpp::Element* reference = signed_info->add_child("Reference", "dsig");
611 reference->set_attribute ("URI", "");
613 xmlpp::Element* transforms = reference->add_child("Transforms", "dsig");
614 transforms->add_child("Transform", "dsig")->set_attribute (
615 "Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
618 reference->add_child("DigestMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
619 /* This will be filled in by the signing later */
620 reference->add_child("DigestValue", "dsig");
622 signature->add_child("SignatureValue", "dsig");
623 signature->add_child("KeyInfo", "dsig");
624 add_signature_value (signature, "dsig", true);
628 /** Sign an XML node.
630 * @param parent Node to sign.
631 * @param ns Namespace to use for the signature XML nodes.
634 CertificateChain::add_signature_value (xmlpp::Element* parent, string ns, bool add_indentation) const
636 cxml::Node cp (parent);
637 xmlpp::Node* key_info = cp.node_child("KeyInfo")->node ();
639 /* Add the certificate chain to the KeyInfo child node of parent */
640 BOOST_FOREACH (Certificate const & i, leaf_to_root ()) {
641 xmlpp::Element* data = key_info->add_child("X509Data", ns);
644 xmlpp::Element* serial = data->add_child("X509IssuerSerial", ns);
645 serial->add_child("X509IssuerName", ns)->add_child_text (i.issuer ());
646 serial->add_child("X509SerialNumber", ns)->add_child_text (i.serial ());
649 data->add_child("X509Certificate", ns)->add_child_text (i.certificate());
652 xmlSecDSigCtxPtr signature_context = xmlSecDSigCtxCreate (0);
653 if (signature_context == 0) {
654 throw MiscError ("could not create signature context");
657 signature_context->signKey = xmlSecCryptoAppKeyLoadMemory (
658 reinterpret_cast<const unsigned char *> (_key->c_str()), _key->size(), xmlSecKeyDataFormatPem, 0, 0, 0
661 if (signature_context->signKey == 0) {
662 throw runtime_error ("could not read private key");
665 if (add_indentation) {
668 int const r = xmlSecDSigCtxSign (signature_context, parent->cobj ());
670 throw MiscError (String::compose ("could not sign (%1)", r));
673 xmlSecDSigCtxDestroy (signature_context);
677 CertificateChain::chain () const
680 BOOST_FOREACH (Certificate const &i, root_to_leaf ()) {
681 o += i.certificate(true);