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,
182 int validity_in_days,
184 string organisational_unit,
185 string root_common_name,
186 string intermediate_common_name,
187 string leaf_common_name
190 boost::filesystem::path directory = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
191 boost::filesystem::create_directories (directory);
193 boost::filesystem::path const cwd = boost::filesystem::current_path ();
194 boost::filesystem::current_path (directory);
196 string quoted_openssl = "\"" + openssl.string() + "\"";
198 command (quoted_openssl + " genrsa -out ca.key 2048");
201 ofstream f ("ca.cnf");
203 << "distinguished_name = req_distinguished_name\n"
204 << "x509_extensions = v3_ca\n"
205 << "string_mask = nombstr\n"
207 << "basicConstraints = critical,CA:true,pathlen:3\n"
208 << "keyUsage = keyCertSign,cRLSign\n"
209 << "subjectKeyIdentifier = hash\n"
210 << "authorityKeyIdentifier = keyid:always,issuer:always\n"
211 << "[ req_distinguished_name ]\n"
212 << "O = Unique organization name\n"
213 << "OU = Organization unit\n"
214 << "CN = Entity and dnQualifier\n";
217 string const ca_subject = "/O=" + organisation +
218 "/OU=" + organisational_unit +
219 "/CN=" + root_common_name +
220 "/dnQualifier=" + public_key_digest ("ca.key", openssl);
225 "%1 req -new -x509 -sha256 -config ca.cnf -days %2 -set_serial 5"
226 " -subj \"%3\" -key ca.key -outform PEM -out ca.self-signed.pem",
227 quoted_openssl, validity_in_days, ca_subject
232 command (quoted_openssl + " genrsa -out intermediate.key 2048");
235 ofstream f ("intermediate.cnf");
237 << "distinguished_name = req_distinguished_name\n"
238 << "x509_extensions = v3_ca\n"
239 << "string_mask = nombstr\n"
241 << "basicConstraints = critical,CA:true,pathlen:2\n"
242 << "keyUsage = keyCertSign,cRLSign\n"
243 << "subjectKeyIdentifier = hash\n"
244 << "authorityKeyIdentifier = keyid:always,issuer:always\n"
245 << "[ req_distinguished_name ]\n"
246 << "O = Unique organization name\n"
247 << "OU = Organization unit\n"
248 << "CN = Entity and dnQualifier\n";
251 string const inter_subject = "/O=" + organisation +
252 "/OU=" + organisational_unit +
253 "/CN=" + intermediate_common_name +
254 "/dnQualifier=" + public_key_digest ("intermediate.key", openssl);
259 "%1 req -new -config intermediate.cnf -days %2 -subj \"%3\" -key intermediate.key -out intermediate.csr",
260 quoted_openssl, validity_in_days - 1, inter_subject
267 "%1 x509 -req -sha256 -days %2 -CA ca.self-signed.pem -CAkey ca.key -set_serial 6"
268 " -in intermediate.csr -extfile intermediate.cnf -extensions v3_ca -out intermediate.signed.pem",
269 quoted_openssl, validity_in_days - 1
273 command (quoted_openssl + " genrsa -out leaf.key 2048");
276 ofstream f ("leaf.cnf");
278 << "distinguished_name = req_distinguished_name\n"
279 << "x509_extensions = v3_ca\n"
280 << "string_mask = nombstr\n"
282 << "basicConstraints = critical,CA:false\n"
283 << "keyUsage = digitalSignature,keyEncipherment\n"
284 << "subjectKeyIdentifier = hash\n"
285 << "authorityKeyIdentifier = keyid,issuer:always\n"
286 << "[ req_distinguished_name ]\n"
287 << "O = Unique organization name\n"
288 << "OU = Organization unit\n"
289 << "CN = Entity and dnQualifier\n";
292 string const leaf_subject = "/O=" + organisation +
293 "/OU=" + organisational_unit +
294 "/CN=" + leaf_common_name +
295 "/dnQualifier=" + public_key_digest ("leaf.key", openssl);
300 "%1 req -new -config leaf.cnf -days %2 -subj \"%3\" -key leaf.key -outform PEM -out leaf.csr",
301 quoted_openssl, validity_in_days - 2, leaf_subject
308 "%1 x509 -req -sha256 -days %2 -CA intermediate.signed.pem -CAkey intermediate.key"
309 " -set_serial 7 -in leaf.csr -extfile leaf.cnf -extensions v3_ca -out leaf.signed.pem",
310 quoted_openssl, validity_in_days - 2
314 boost::filesystem::current_path (cwd);
316 _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "ca.self-signed.pem")));
317 _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "intermediate.signed.pem")));
318 _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "leaf.signed.pem")));
320 _key = dcp::file_to_string (directory / "leaf.key");
322 boost::filesystem::remove_all (directory);
325 CertificateChain::CertificateChain (string s)
330 s = c.read_string (s);
331 _certificates.push_back (c);
332 } catch (MiscError& e) {
333 /* Failed to read a certificate, just stop */
338 /* This will throw an exception if the chain cannot be ordered */
342 /** @return Root certificate */
344 CertificateChain::root () const
346 DCP_ASSERT (!_certificates.empty());
347 return root_to_leaf().front ();
350 /** @return Leaf certificate */
352 CertificateChain::leaf () const
354 DCP_ASSERT (!_certificates.empty());
355 return root_to_leaf().back ();
358 /** @return Certificates in order from leaf to root */
359 CertificateChain::List
360 CertificateChain::leaf_to_root () const
362 List l = root_to_leaf ();
367 CertificateChain::List
368 CertificateChain::unordered () const
370 return _certificates;
373 /** Add a certificate to the chain.
374 * @param c Certificate to add.
377 CertificateChain::add (Certificate c)
379 _certificates.push_back (c);
382 /** Remove a certificate from the chain.
383 * @param c Certificate to remove.
386 CertificateChain::remove (Certificate c)
388 _certificates.remove (c);
391 /** Remove the i'th certificate in the list, as listed
395 CertificateChain::remove (int i)
397 List::iterator j = _certificates.begin ();
398 while (j != _certificates.end () && i > 0) {
403 if (j != _certificates.end ()) {
404 _certificates.erase (j);
409 CertificateChain::chain_valid () const
411 return chain_valid (_certificates);
414 /** Check to see if a chain is valid (i.e. root signs the intermediate, intermediate
415 * signs the leaf and so on) and that the private key (if there is one) matches the
417 * @return true if it's ok, false if not.
420 CertificateChain::chain_valid (List const & chain) const
422 /* Here I am taking a chain of certificates A/B/C/D and checking validity of B wrt A,
423 C wrt B and D wrt C. It also appears necessary to check the issuer of B/C/D matches
424 the subject of A/B/C; I don't understand why. I'm sure there's a better way of doing
425 this with OpenSSL but the documentation does not appear not likely to reveal it
429 X509_STORE* store = X509_STORE_new ();
431 throw MiscError ("could not create X509 store");
434 /* Put all the certificates into the store */
435 for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) {
436 if (!X509_STORE_add_cert (store, i->x509 ())) {
437 X509_STORE_free (store);
442 /* Verify each one */
443 for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) {
445 List::const_iterator j = i;
447 if (j == chain.end ()) {
451 X509_STORE_CTX* ctx = X509_STORE_CTX_new ();
453 X509_STORE_free (store);
454 throw MiscError ("could not create X509 store context");
457 X509_STORE_set_flags (store, 0);
458 if (!X509_STORE_CTX_init (ctx, store, j->x509(), 0)) {
459 X509_STORE_CTX_free (ctx);
460 X509_STORE_free (store);
461 throw MiscError ("could not initialise X509 store context");
464 int const v = X509_verify_cert (ctx);
465 X509_STORE_CTX_free (ctx);
468 X509_STORE_free (store);
472 /* I don't know why OpenSSL doesn't check this stuff
473 in verify_cert, but without these checks the
474 certificates_validation8 test fails.
476 if (j->issuer() != i->subject() || j->subject() == i->subject()) {
477 X509_STORE_free (store);
483 X509_STORE_free (store);
488 /** Check that there is a valid private key for the leaf certificate.
489 * Will return true if there are no certificates.
492 CertificateChain::private_key_valid () const
494 if (_certificates.empty ()) {
502 BIO* bio = BIO_new_mem_buf (const_cast<char *> (_key->c_str ()), -1);
504 throw MiscError ("could not create memory BIO");
507 RSA* private_key = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
512 RSA* public_key = leaf().public_key ();
514 #if OPENSSL_VERSION_NUMBER > 0x10100000L
515 BIGNUM const * private_key_n;
516 RSA_get0_key(private_key, &private_key_n, 0, 0);
517 BIGNUM const * public_key_n;
518 RSA_get0_key(public_key, &public_key_n, 0, 0);
519 if (!private_key_n || !public_key_n) {
522 bool const valid = !BN_cmp (private_key_n, public_key_n);
524 bool const valid = !BN_cmp (private_key->n, public_key->n);
532 CertificateChain::valid (string* reason) const
536 } catch (CertificateChainError& e) {
538 *reason = "certificates do not form a chain";
543 if (!private_key_valid ()) {
545 *reason = "private key does not exist, or does not match leaf certificate";
553 /** @return Certificates in order from root to leaf */
554 CertificateChain::List
555 CertificateChain::root_to_leaf () const
557 List rtl = _certificates;
560 if (chain_valid (rtl)) {
563 } while (std::next_permutation (rtl.begin(), rtl.end()));
565 throw CertificateChainError ("certificate chain is not consistent");
568 /** Add a <Signer> and <ds:Signature> nodes to an XML node.
569 * @param parent XML node to add to.
570 * @param standard INTEROP or SMPTE.
573 CertificateChain::sign (xmlpp::Element* parent, Standard standard) const
577 parent->add_child_text(" ");
578 xmlpp::Element* signer = parent->add_child("Signer");
579 signer->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
580 xmlpp::Element* data = signer->add_child("X509Data", "dsig");
581 xmlpp::Element* serial_element = data->add_child("X509IssuerSerial", "dsig");
582 serial_element->add_child("X509IssuerName", "dsig")->add_child_text (leaf().issuer());
583 serial_element->add_child("X509SerialNumber", "dsig")->add_child_text (leaf().serial());
584 data->add_child("X509SubjectName", "dsig")->add_child_text (leaf().subject());
590 parent->add_child_text("\n ");
591 xmlpp::Element* signature = parent->add_child("Signature");
592 signature->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
593 signature->set_namespace ("dsig");
594 parent->add_child_text("\n");
596 xmlpp::Element* signed_info = signature->add_child ("SignedInfo", "dsig");
597 signed_info->add_child("CanonicalizationMethod", "dsig")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
599 if (standard == INTEROP) {
600 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
602 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
605 xmlpp::Element* reference = signed_info->add_child("Reference", "dsig");
606 reference->set_attribute ("URI", "");
608 xmlpp::Element* transforms = reference->add_child("Transforms", "dsig");
609 transforms->add_child("Transform", "dsig")->set_attribute (
610 "Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
613 reference->add_child("DigestMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
614 /* This will be filled in by the signing later */
615 reference->add_child("DigestValue", "dsig");
617 signature->add_child("SignatureValue", "dsig");
618 signature->add_child("KeyInfo", "dsig");
619 add_signature_value (signature, "dsig", true);
623 /** Sign an XML node.
625 * @param parent Node to sign.
626 * @param ns Namespace to use for the signature XML nodes.
629 CertificateChain::add_signature_value (xmlpp::Element* parent, string ns, bool add_indentation) const
631 cxml::Node cp (parent);
632 xmlpp::Node* key_info = cp.node_child("KeyInfo")->node ();
634 /* Add the certificate chain to the KeyInfo child node of parent */
635 BOOST_FOREACH (Certificate const & i, leaf_to_root ()) {
636 xmlpp::Element* data = key_info->add_child("X509Data", ns);
639 xmlpp::Element* serial = data->add_child("X509IssuerSerial", ns);
640 serial->add_child("X509IssuerName", ns)->add_child_text (i.issuer ());
641 serial->add_child("X509SerialNumber", ns)->add_child_text (i.serial ());
644 data->add_child("X509Certificate", ns)->add_child_text (i.certificate());
647 xmlSecDSigCtxPtr signature_context = xmlSecDSigCtxCreate (0);
648 if (signature_context == 0) {
649 throw MiscError ("could not create signature context");
652 signature_context->signKey = xmlSecCryptoAppKeyLoadMemory (
653 reinterpret_cast<const unsigned char *> (_key->c_str()), _key->size(), xmlSecKeyDataFormatPem, 0, 0, 0
656 if (signature_context->signKey == 0) {
657 throw runtime_error ("could not read private key");
660 if (add_indentation) {
663 int const r = xmlSecDSigCtxSign (signature_context, parent->cobj ());
665 throw MiscError (String::compose ("could not sign (%1)", r));
668 xmlSecDSigCtxDestroy (signature_context);
672 CertificateChain::chain () const
675 BOOST_FOREACH (Certificate const &i, root_to_leaf ()) {
676 o += i.certificate(true);