Make certificate chain validity a parameter of the constructor.
[libdcp.git] / src / certificate_chain.cc
1 /*
2     Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
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.
10
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.
15
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/>.
18
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
23     including the two.
24
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.
32 */
33
34 /** @file  src/signer_chain.cc
35  *  @brief Functions to make signer chains.
36  */
37
38 #include "certificate_chain.h"
39 #include "exceptions.h"
40 #include "util.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>
58 #include <fstream>
59 #include <iostream>
60
61 using std::string;
62 using std::ofstream;
63 using std::ifstream;
64 using std::runtime_error;
65 using namespace dcp;
66
67 /** Run a shell command.
68  *  @param cmd Command to run (UTF8-encoded).
69  */
70 static void
71 command (string cmd)
72 {
73 #ifdef LIBDCP_WINDOWS
74         /* We need to use CreateProcessW on Windows so that the UTF-8/16 mess
75            is handled correctly.
76         */
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) {
80                 delete[] buffer;
81                 return;
82         }
83
84         int code = 1;
85
86         STARTUPINFOW startup_info;
87         memset (&startup_info, 0, sizeof (startup_info));
88         startup_info.cb = sizeof (startup_info);
89         PROCESS_INFORMATION process_info;
90
91         /* XXX: this doesn't actually seem to work; failing commands end up with
92            a return code of 0
93         */
94         if (CreateProcessW (0, buffer, 0, 0, FALSE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
95                 WaitForSingleObject (process_info.hProcess, INFINITE);
96                 DWORD c;
97                 if (GetExitCodeProcess (process_info.hProcess, &c)) {
98                         code = c;
99                 }
100                 CloseHandle (process_info.hProcess);
101                 CloseHandle (process_info.hThread);
102         }
103
104         delete[] buffer;
105 #else
106         cmd += " 2> /dev/null";
107         int const r = system (cmd.c_str ());
108         int const code = WEXITSTATUS (r);
109 #endif
110         if (code) {
111                 throw dcp::MiscError (String::compose ("error %1 in %2 within %3", code, cmd, boost::filesystem::current_path().string()));
112         }
113 }
114
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.
119  */
120 static string
121 public_key_digest (boost::filesystem::path private_key, boost::filesystem::path openssl)
122 {
123         boost::filesystem::path public_name = private_key.string() + ".public";
124
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 ()));
127
128         /* Read in the public key from the file */
129
130         string pub;
131         ifstream f (public_name.string().c_str ());
132         if (!f.good ()) {
133                 throw dcp::MiscError ("public key not found");
134         }
135
136         bool read = false;
137         while (f.good ()) {
138                 string line;
139                 getline (f, line);
140                 if (line.length() >= 10 && line.substr(0, 10) == "-----BEGIN") {
141                         read = true;
142                 } else if (line.length() >= 8 && line.substr(0, 8) == "-----END") {
143                         break;
144                 } else if (read) {
145                         pub += line;
146                 }
147         }
148
149         /* Decode the base64 of the public key */
150
151         unsigned char buffer[512];
152         int const N = dcp::base64_decode (pub, buffer, 1024);
153
154         /* Hash it with SHA1 (without the first 24 bytes, for reasons that are not entirely clear) */
155
156         SHA_CTX context;
157         if (!SHA1_Init (&context)) {
158                 throw dcp::MiscError ("could not init SHA1 context");
159         }
160
161         if (!SHA1_Update (&context, buffer + 24, N - 24)) {
162                 throw dcp::MiscError ("could not update SHA1 digest");
163         }
164
165         unsigned char digest[SHA_DIGEST_LENGTH];
166         if (!SHA1_Final (digest, &context)) {
167                 throw dcp::MiscError ("could not finish SHA1 digest");
168         }
169
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, "/", "\\/");
174 #else
175         boost::replace_all (dig, "/", "\\\\/");
176 #endif
177         return dig;
178 }
179
180 CertificateChain::CertificateChain (
181         boost::filesystem::path openssl,
182         int validity_in_days,
183         string organisation,
184         string organisational_unit,
185         string root_common_name,
186         string intermediate_common_name,
187         string leaf_common_name
188         )
189 {
190         boost::filesystem::path directory = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
191         boost::filesystem::create_directories (directory);
192
193         boost::filesystem::path const cwd = boost::filesystem::current_path ();
194         boost::filesystem::current_path (directory);
195
196         string quoted_openssl = "\"" + openssl.string() + "\"";
197
198         command (quoted_openssl + " genrsa -out ca.key 2048");
199
200         {
201                 ofstream f ("ca.cnf");
202                 f << "[ req ]\n"
203                   << "distinguished_name = req_distinguished_name\n"
204                   << "x509_extensions   = v3_ca\n"
205                   << "string_mask = nombstr\n"
206                   << "[ v3_ca ]\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";
215         }
216
217         string const ca_subject = "/O=" + organisation +
218                 "/OU=" + organisational_unit +
219                 "/CN=" + root_common_name +
220                 "/dnQualifier=" + public_key_digest ("ca.key", openssl);
221
222         {
223                 command (
224                         String::compose (
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
228                                 )
229                         );
230         }
231
232         command (quoted_openssl + " genrsa -out intermediate.key 2048");
233
234         {
235                 ofstream f ("intermediate.cnf");
236                 f << "[ default ]\n"
237                   << "distinguished_name = req_distinguished_name\n"
238                   << "x509_extensions = v3_ca\n"
239                   << "string_mask = nombstr\n"
240                   << "[ v3_ca ]\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";
249         }
250
251         string const inter_subject = "/O=" + organisation +
252                 "/OU=" + organisational_unit +
253                 "/CN=" + intermediate_common_name +
254                 "/dnQualifier=" + public_key_digest ("intermediate.key", openssl);
255
256         {
257                 command (
258                         String::compose (
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
261                                 )
262                         );
263         }
264
265         command (
266                 String::compose(
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
270                         )
271                 );
272
273         command (quoted_openssl + " genrsa -out leaf.key 2048");
274
275         {
276                 ofstream f ("leaf.cnf");
277                 f << "[ default ]\n"
278                   << "distinguished_name = req_distinguished_name\n"
279                   << "x509_extensions   = v3_ca\n"
280                   << "string_mask = nombstr\n"
281                   << "[ v3_ca ]\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";
290         }
291
292         string const leaf_subject = "/O=" + organisation +
293                 "/OU=" + organisational_unit +
294                 "/CN=" + leaf_common_name +
295                 "/dnQualifier=" + public_key_digest ("leaf.key", openssl);
296
297         {
298                 command (
299                         String::compose (
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
302                                 )
303                         );
304         }
305
306         command (
307                 String::compose(
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
311                         )
312                 );
313
314         boost::filesystem::current_path (cwd);
315
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")));
319
320         _key = dcp::file_to_string (directory / "leaf.key");
321
322         boost::filesystem::remove_all (directory);
323 }
324
325 CertificateChain::CertificateChain (string s)
326 {
327         while (true) {
328                 try {
329                         Certificate c;
330                         s = c.read_string (s);
331                         _certificates.push_back (c);
332                 } catch (MiscError& e) {
333                         /* Failed to read a certificate, just stop */
334                         break;
335                 }
336         }
337
338         /* This will throw an exception if the chain cannot be ordered */
339         leaf_to_root ();
340 }
341
342 /** @return Root certificate */
343 Certificate
344 CertificateChain::root () const
345 {
346         DCP_ASSERT (!_certificates.empty());
347         return root_to_leaf().front ();
348 }
349
350 /** @return Leaf certificate */
351 Certificate
352 CertificateChain::leaf () const
353 {
354         DCP_ASSERT (!_certificates.empty());
355         return root_to_leaf().back ();
356 }
357
358 /** @return Certificates in order from leaf to root */
359 CertificateChain::List
360 CertificateChain::leaf_to_root () const
361 {
362         List l = root_to_leaf ();
363         l.reverse ();
364         return l;
365 }
366
367 CertificateChain::List
368 CertificateChain::unordered () const
369 {
370         return _certificates;
371 }
372
373 /** Add a certificate to the chain.
374  *  @param c Certificate to add.
375  */
376 void
377 CertificateChain::add (Certificate c)
378 {
379         _certificates.push_back (c);
380 }
381
382 /** Remove a certificate from the chain.
383  *  @param c Certificate to remove.
384  */
385 void
386 CertificateChain::remove (Certificate c)
387 {
388         _certificates.remove (c);
389 }
390
391 /** Remove the i'th certificate in the list, as listed
392  *  from root to leaf.
393  */
394 void
395 CertificateChain::remove (int i)
396 {
397         List::iterator j = _certificates.begin ();
398         while (j != _certificates.end () && i > 0) {
399                 --i;
400                 ++j;
401         }
402
403         if (j != _certificates.end ()) {
404                 _certificates.erase (j);
405         }
406 }
407
408 bool
409 CertificateChain::chain_valid () const
410 {
411         return chain_valid (_certificates);
412 }
413
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
416  *  leaf certificate.
417  *  @return true if it's ok, false if not.
418  */
419 bool
420 CertificateChain::chain_valid (List const & chain) const
421 {
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
426            any time soon.
427         */
428
429         X509_STORE* store = X509_STORE_new ();
430         if (!store) {
431                 throw MiscError ("could not create X509 store");
432         }
433
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);
438                         return false;
439                 }
440         }
441
442         /* Verify each one */
443         for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) {
444
445                 List::const_iterator j = i;
446                 ++j;
447                 if (j == chain.end ()) {
448                         break;
449                 }
450
451                 X509_STORE_CTX* ctx = X509_STORE_CTX_new ();
452                 if (!ctx) {
453                         X509_STORE_free (store);
454                         throw MiscError ("could not create X509 store context");
455                 }
456
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");
462                 }
463
464                 int const v = X509_verify_cert (ctx);
465                 X509_STORE_CTX_free (ctx);
466
467                 if (v != 1) {
468                         X509_STORE_free (store);
469                         return false;
470                 }
471
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.
475                 */
476                 if (j->issuer() != i->subject() || j->subject() == i->subject()) {
477                         X509_STORE_free (store);
478                         return false;
479                 }
480
481         }
482
483         X509_STORE_free (store);
484
485         return true;
486 }
487
488 /** Check that there is a valid private key for the leaf certificate.
489  *  Will return true if there are no certificates.
490  */
491 bool
492 CertificateChain::private_key_valid () const
493 {
494         if (_certificates.empty ()) {
495                 return true;
496         }
497
498         if (!_key) {
499                 return false;
500         }
501
502         BIO* bio = BIO_new_mem_buf (const_cast<char *> (_key->c_str ()), -1);
503         if (!bio) {
504                 throw MiscError ("could not create memory BIO");
505         }
506
507         RSA* private_key = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
508         if (!private_key) {
509                 return false;
510         }
511
512         RSA* public_key = leaf().public_key ();
513
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) {
520                 return false;
521         }
522         bool const valid = !BN_cmp (private_key_n, public_key_n);
523 #else
524         bool const valid = !BN_cmp (private_key->n, public_key->n);
525 #endif
526         BIO_free (bio);
527
528         return valid;
529 }
530
531 bool
532 CertificateChain::valid (string* reason) const
533 {
534         try {
535                 root_to_leaf ();
536         } catch (CertificateChainError& e) {
537                 if (reason) {
538                         *reason = "certificates do not form a chain";
539                 }
540                 return false;
541         }
542
543         if (!private_key_valid ()) {
544                 if (reason) {
545                         *reason = "private key does not exist, or does not match leaf certificate";
546                 }
547                 return false;
548         }
549
550         return true;
551 }
552
553 /** @return Certificates in order from root to leaf */
554 CertificateChain::List
555 CertificateChain::root_to_leaf () const
556 {
557         List rtl = _certificates;
558         rtl.sort ();
559         do {
560                 if (chain_valid (rtl)) {
561                         return rtl;
562                 }
563         } while (std::next_permutation (rtl.begin(), rtl.end()));
564
565         throw CertificateChainError ("certificate chain is not consistent");
566 }
567
568 /** Add a &lt;Signer&gt; and &lt;ds:Signature&gt; nodes to an XML node.
569  *  @param parent XML node to add to.
570  *  @param standard INTEROP or SMPTE.
571  */
572 void
573 CertificateChain::sign (xmlpp::Element* parent, Standard standard) const
574 {
575         /* <Signer> */
576
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());
585
586         indent (signer, 2);
587
588         /* <Signature> */
589
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");
595
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");
598
599         if (standard == INTEROP) {
600                 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
601         } else {
602                 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
603         }
604
605         xmlpp::Element* reference = signed_info->add_child("Reference", "dsig");
606         reference->set_attribute ("URI", "");
607
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"
611                 );
612
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");
616
617         signature->add_child("SignatureValue", "dsig");
618         signature->add_child("KeyInfo", "dsig");
619         add_signature_value (signature, "dsig", true);
620 }
621
622
623 /** Sign an XML node.
624  *
625  *  @param parent Node to sign.
626  *  @param ns Namespace to use for the signature XML nodes.
627  */
628 void
629 CertificateChain::add_signature_value (xmlpp::Element* parent, string ns, bool add_indentation) const
630 {
631         cxml::Node cp (parent);
632         xmlpp::Node* key_info = cp.node_child("KeyInfo")->node ();
633
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);
637
638                 {
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 ());
642                 }
643
644                 data->add_child("X509Certificate", ns)->add_child_text (i.certificate());
645         }
646
647         xmlSecDSigCtxPtr signature_context = xmlSecDSigCtxCreate (0);
648         if (signature_context == 0) {
649                 throw MiscError ("could not create signature context");
650         }
651
652         signature_context->signKey = xmlSecCryptoAppKeyLoadMemory (
653                 reinterpret_cast<const unsigned char *> (_key->c_str()), _key->size(), xmlSecKeyDataFormatPem, 0, 0, 0
654                 );
655
656         if (signature_context->signKey == 0) {
657                 throw runtime_error ("could not read private key");
658         }
659
660         if (add_indentation) {
661                 indent (parent, 2);
662         }
663         int const r = xmlSecDSigCtxSign (signature_context, parent->cobj ());
664         if (r < 0) {
665                 throw MiscError (String::compose ("could not sign (%1)", r));
666         }
667
668         xmlSecDSigCtxDestroy (signature_context);
669 }
670
671 string
672 CertificateChain::chain () const
673 {
674         string o;
675         BOOST_FOREACH (Certificate const &i, root_to_leaf ()) {
676                 o += i.certificate(true);
677         }
678
679         return o;
680 }