Use enum class for the things in types.h
[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         string organisation,
183         string organisational_unit,
184         string root_common_name,
185         string intermediate_common_name,
186         string leaf_common_name
187         )
188 {
189         /* Valid for 40 years */
190         int const days = 365 * 40;
191
192         boost::filesystem::path directory = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
193         boost::filesystem::create_directories (directory);
194
195         boost::filesystem::path const cwd = boost::filesystem::current_path ();
196         boost::filesystem::current_path (directory);
197
198         string quoted_openssl = "\"" + openssl.string() + "\"";
199
200         command (quoted_openssl + " genrsa -out ca.key 2048");
201
202         {
203                 ofstream f ("ca.cnf");
204                 f << "[ req ]\n"
205                   << "distinguished_name = req_distinguished_name\n"
206                   << "x509_extensions   = v3_ca\n"
207                   << "string_mask = nombstr\n"
208                   << "[ v3_ca ]\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";
217         }
218
219         string const ca_subject = "/O=" + organisation +
220                 "/OU=" + organisational_unit +
221                 "/CN=" + root_common_name +
222                 "/dnQualifier=" + public_key_digest ("ca.key", openssl);
223
224         {
225                 command (
226                         String::compose (
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
230                                 )
231                         );
232         }
233
234         command (quoted_openssl + " genrsa -out intermediate.key 2048");
235
236         {
237                 ofstream f ("intermediate.cnf");
238                 f << "[ default ]\n"
239                   << "distinguished_name = req_distinguished_name\n"
240                   << "x509_extensions = v3_ca\n"
241                   << "string_mask = nombstr\n"
242                   << "[ v3_ca ]\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";
251         }
252
253         string const inter_subject = "/O=" + organisation +
254                 "/OU=" + organisational_unit +
255                 "/CN=" + intermediate_common_name +
256                 "/dnQualifier=" + public_key_digest ("intermediate.key", openssl);
257
258         {
259                 command (
260                         String::compose (
261                                 "%1 req -new -config intermediate.cnf -days %2 -subj \"%3\" -key intermediate.key -out intermediate.csr",
262                                 quoted_openssl, days - 1, inter_subject
263                                 )
264                         );
265         }
266
267         command (
268                 String::compose (
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
272                         )
273                 );
274
275         command (quoted_openssl + " genrsa -out leaf.key 2048");
276
277         {
278                 ofstream f ("leaf.cnf");
279                 f << "[ default ]\n"
280                   << "distinguished_name = req_distinguished_name\n"
281                   << "x509_extensions   = v3_ca\n"
282                   << "string_mask = nombstr\n"
283                   << "[ v3_ca ]\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";
292         }
293
294         string const leaf_subject = "/O=" + organisation +
295                 "/OU=" + organisational_unit +
296                 "/CN=" + leaf_common_name +
297                 "/dnQualifier=" + public_key_digest ("leaf.key", openssl);
298
299         {
300                 command (
301                         String::compose (
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
304                                 )
305                         );
306         }
307
308         command (
309                 String::compose (
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
313                         )
314                 );
315
316         boost::filesystem::current_path (cwd);
317
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")));
321
322         _key = dcp::file_to_string (directory / "leaf.key");
323
324         boost::filesystem::remove_all (directory);
325 }
326
327 CertificateChain::CertificateChain (string s)
328 {
329         while (true) {
330                 try {
331                         Certificate c;
332                         s = c.read_string (s);
333                         _certificates.push_back (c);
334                 } catch (MiscError& e) {
335                         /* Failed to read a certificate, just stop */
336                         break;
337                 }
338         }
339
340         /* This will throw an exception if the chain cannot be ordered */
341         leaf_to_root ();
342 }
343
344 /** @return Root certificate */
345 Certificate
346 CertificateChain::root () const
347 {
348         DCP_ASSERT (!_certificates.empty());
349         return root_to_leaf().front ();
350 }
351
352 /** @return Leaf certificate */
353 Certificate
354 CertificateChain::leaf () const
355 {
356         DCP_ASSERT (!_certificates.empty());
357         return root_to_leaf().back ();
358 }
359
360 /** @return Certificates in order from leaf to root */
361 CertificateChain::List
362 CertificateChain::leaf_to_root () const
363 {
364         List l = root_to_leaf ();
365         std::reverse (l.begin(), l.end());
366         return l;
367 }
368
369 CertificateChain::List
370 CertificateChain::unordered () const
371 {
372         return _certificates;
373 }
374
375 /** Add a certificate to the chain.
376  *  @param c Certificate to add.
377  */
378 void
379 CertificateChain::add (Certificate c)
380 {
381         _certificates.push_back (c);
382 }
383
384 /** Remove a certificate from the chain.
385  *  @param c Certificate to remove.
386  */
387 void
388 CertificateChain::remove (Certificate c)
389 {
390         auto i = std::find(_certificates.begin(), _certificates.end(), c);
391         if (i != _certificates.end()) {
392                 _certificates.erase (i);
393         }
394 }
395
396 /** Remove the i'th certificate in the list, as listed
397  *  from root to leaf.
398  */
399 void
400 CertificateChain::remove (int i)
401 {
402         List::iterator j = _certificates.begin ();
403         while (j != _certificates.end () && i > 0) {
404                 --i;
405                 ++j;
406         }
407
408         if (j != _certificates.end ()) {
409                 _certificates.erase (j);
410         }
411 }
412
413 bool
414 CertificateChain::chain_valid () const
415 {
416         return chain_valid (_certificates);
417 }
418
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
421  *  leaf certificate.
422  *  @return true if it's ok, false if not.
423  */
424 bool
425 CertificateChain::chain_valid (List const & chain) const
426 {
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
431            any time soon.
432         */
433
434         X509_STORE* store = X509_STORE_new ();
435         if (!store) {
436                 throw MiscError ("could not create X509 store");
437         }
438
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);
443                         return false;
444                 }
445         }
446
447         /* Verify each one */
448         for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) {
449
450                 List::const_iterator j = i;
451                 ++j;
452                 if (j == chain.end ()) {
453                         break;
454                 }
455
456                 X509_STORE_CTX* ctx = X509_STORE_CTX_new ();
457                 if (!ctx) {
458                         X509_STORE_free (store);
459                         throw MiscError ("could not create X509 store context");
460                 }
461
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");
467                 }
468
469                 int const v = X509_verify_cert (ctx);
470                 X509_STORE_CTX_free (ctx);
471
472                 if (v != 1) {
473                         X509_STORE_free (store);
474                         return false;
475                 }
476
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.
480                 */
481                 if (j->issuer() != i->subject() || j->subject() == i->subject()) {
482                         X509_STORE_free (store);
483                         return false;
484                 }
485
486         }
487
488         X509_STORE_free (store);
489
490         return true;
491 }
492
493 /** Check that there is a valid private key for the leaf certificate.
494  *  Will return true if there are no certificates.
495  */
496 bool
497 CertificateChain::private_key_valid () const
498 {
499         if (_certificates.empty ()) {
500                 return true;
501         }
502
503         if (!_key) {
504                 return false;
505         }
506
507         BIO* bio = BIO_new_mem_buf (const_cast<char *> (_key->c_str ()), -1);
508         if (!bio) {
509                 throw MiscError ("could not create memory BIO");
510         }
511
512         RSA* private_key = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
513         if (!private_key) {
514                 return false;
515         }
516
517         RSA* public_key = leaf().public_key ();
518
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) {
525                 return false;
526         }
527         bool const valid = !BN_cmp (private_key_n, public_key_n);
528 #else
529         bool const valid = !BN_cmp (private_key->n, public_key->n);
530 #endif
531         BIO_free (bio);
532
533         return valid;
534 }
535
536 bool
537 CertificateChain::valid (string* reason) const
538 {
539         try {
540                 root_to_leaf ();
541         } catch (CertificateChainError& e) {
542                 if (reason) {
543                         *reason = "certificates do not form a chain";
544                 }
545                 return false;
546         }
547
548         if (!private_key_valid ()) {
549                 if (reason) {
550                         *reason = "private key does not exist, or does not match leaf certificate";
551                 }
552                 return false;
553         }
554
555         return true;
556 }
557
558 /** @return Certificates in order from root to leaf */
559 CertificateChain::List
560 CertificateChain::root_to_leaf () const
561 {
562         List rtl = _certificates;
563         std::sort (rtl.begin(), rtl.end());
564         do {
565                 if (chain_valid (rtl)) {
566                         return rtl;
567                 }
568         } while (std::next_permutation (rtl.begin(), rtl.end()));
569
570         throw CertificateChainError ("certificate chain is not consistent");
571 }
572
573 /** Add a &lt;Signer&gt; and &lt;ds:Signature&gt; nodes to an XML node.
574  *  @param parent XML node to add to.
575  *  @param standard INTEROP or SMPTE.
576  */
577 void
578 CertificateChain::sign (xmlpp::Element* parent, Standard standard) const
579 {
580         /* <Signer> */
581
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());
590
591         indent (signer, 2);
592
593         /* <Signature> */
594
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");
600
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");
603
604         if (standard == Standard::INTEROP) {
605                 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
606         } else {
607                 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
608         }
609
610         xmlpp::Element* reference = signed_info->add_child("Reference", "dsig");
611         reference->set_attribute ("URI", "");
612
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"
616                 );
617
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");
621
622         signature->add_child("SignatureValue", "dsig");
623         signature->add_child("KeyInfo", "dsig");
624         add_signature_value (signature, "dsig", true);
625 }
626
627
628 /** Sign an XML node.
629  *
630  *  @param parent Node to sign.
631  *  @param ns Namespace to use for the signature XML nodes.
632  */
633 void
634 CertificateChain::add_signature_value (xmlpp::Element* parent, string ns, bool add_indentation) const
635 {
636         cxml::Node cp (parent);
637         xmlpp::Node* key_info = cp.node_child("KeyInfo")->node ();
638
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);
642
643                 {
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 ());
647                 }
648
649                 data->add_child("X509Certificate", ns)->add_child_text (i.certificate());
650         }
651
652         xmlSecDSigCtxPtr signature_context = xmlSecDSigCtxCreate (0);
653         if (signature_context == 0) {
654                 throw MiscError ("could not create signature context");
655         }
656
657         signature_context->signKey = xmlSecCryptoAppKeyLoadMemory (
658                 reinterpret_cast<const unsigned char *> (_key->c_str()), _key->size(), xmlSecKeyDataFormatPem, 0, 0, 0
659                 );
660
661         if (signature_context->signKey == 0) {
662                 throw runtime_error ("could not read private key");
663         }
664
665         if (add_indentation) {
666                 indent (parent, 2);
667         }
668         int const r = xmlSecDSigCtxSign (signature_context, parent->cobj ());
669         if (r < 0) {
670                 throw MiscError (String::compose ("could not sign (%1)", r));
671         }
672
673         xmlSecDSigCtxDestroy (signature_context);
674 }
675
676 string
677 CertificateChain::chain () const
678 {
679         string o;
680         BOOST_FOREACH (Certificate const &i, root_to_leaf ()) {
681                 o += i.certificate(true);
682         }
683
684         return o;
685 }