Test churn from adding make_uuid() into CPL.
[libdcp.git] / src / certificate_chain.cc
1 /*
2     Copyright (C) 2013-2021 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
35 /** @file  src/certificate_chain.cc
36  *  @brief CertificateChain class
37  */
38
39
40 #include "certificate_chain.h"
41 #include "compose.hpp"
42 #include "dcp_assert.h"
43 #include "exceptions.h"
44 #include "util.h"
45 #include "warnings.h"
46 #include <asdcp/KM_util.h>
47 #include <libcxml/cxml.h>
48 LIBDCP_DISABLE_WARNINGS
49 #include <libxml++/libxml++.h>
50 LIBDCP_ENABLE_WARNINGS
51 #include <xmlsec/xmldsig.h>
52 #include <xmlsec/dl.h>
53 #include <xmlsec/app.h>
54 #include <xmlsec/crypto.h>
55 #include <openssl/sha.h>
56 #include <openssl/bio.h>
57 #include <openssl/evp.h>
58 #include <openssl/pem.h>
59 #include <openssl/rsa.h>
60 #include <boost/filesystem.hpp>
61 #include <boost/algorithm/string.hpp>
62 #include <fstream>
63 #include <iostream>
64
65
66 using std::string;
67 using std::ofstream;
68 using std::ifstream;
69 using std::runtime_error;
70 using namespace dcp;
71
72
73 /** Run a shell command.
74  *  @param cmd Command to run (UTF8-encoded).
75  */
76 static void
77 command (string cmd)
78 {
79 #ifdef LIBDCP_WINDOWS
80         /* We need to use CreateProcessW on Windows so that the UTF-8/16 mess
81            is handled correctly.
82         */
83         int const wn = MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, 0, 0);
84         auto buffer = new wchar_t[wn];
85         if (MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, buffer, wn) == 0) {
86                 delete[] buffer;
87                 return;
88         }
89
90         int code = 1;
91
92         STARTUPINFOW startup_info;
93         memset (&startup_info, 0, sizeof (startup_info));
94         startup_info.cb = sizeof (startup_info);
95         PROCESS_INFORMATION process_info;
96
97         /* XXX: this doesn't actually seem to work; failing commands end up with
98            a return code of 0
99         */
100         if (CreateProcessW (0, buffer, 0, 0, FALSE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
101                 WaitForSingleObject (process_info.hProcess, INFINITE);
102                 DWORD c;
103                 if (GetExitCodeProcess (process_info.hProcess, &c)) {
104                         code = c;
105                 }
106                 CloseHandle (process_info.hProcess);
107                 CloseHandle (process_info.hThread);
108         }
109
110         delete[] buffer;
111 #else
112         cmd += " 2> /dev/null";
113         int const r = system (cmd.c_str ());
114         int const code = WEXITSTATUS (r);
115 #endif
116         if (code) {
117                 throw dcp::MiscError (String::compose ("error %1 in %2 within %3", code, cmd, boost::filesystem::current_path().string()));
118         }
119 }
120
121
122 /** Extract a public key from a private key and create a SHA1 digest of it.
123  *  @param private_key Private key
124  *  @param openssl openssl binary name (or full path if openssl is not on the system path).
125  *  @return SHA1 digest of corresponding public key, with escaped / characters.
126  */
127 static string
128 public_key_digest (boost::filesystem::path private_key, boost::filesystem::path openssl)
129 {
130         boost::filesystem::path public_name = private_key.string() + ".public";
131
132         /* Create the public key from the private key */
133         command (String::compose("\"%1\" rsa -outform PEM -pubout -in %2 -out %3", openssl.string(), private_key.string(), public_name.string()));
134
135         /* Read in the public key from the file */
136
137         string pub;
138         ifstream f (public_name.string().c_str());
139         if (!f.good ()) {
140                 throw dcp::MiscError ("public key not found");
141         }
142
143         bool read = false;
144         while (f.good ()) {
145                 string line;
146                 getline (f, line);
147                 if (line.length() >= 10 && line.substr(0, 10) == "-----BEGIN") {
148                         read = true;
149                 } else if (line.length() >= 8 && line.substr(0, 8) == "-----END") {
150                         break;
151                 } else if (read) {
152                         pub += line;
153                 }
154         }
155
156         /* Decode the base64 of the public key */
157
158         unsigned char buffer[512];
159         int const N = dcp::base64_decode (pub, buffer, 1024);
160
161         /* Hash it with SHA1 (without the first 24 bytes, for reasons that are not entirely clear) */
162
163         SHA_CTX context;
164         if (!SHA1_Init (&context)) {
165                 throw dcp::MiscError ("could not init SHA1 context");
166         }
167
168         if (!SHA1_Update (&context, buffer + 24, N - 24)) {
169                 throw dcp::MiscError ("could not update SHA1 digest");
170         }
171
172         unsigned char digest[SHA_DIGEST_LENGTH];
173         if (!SHA1_Final (digest, &context)) {
174                 throw dcp::MiscError ("could not finish SHA1 digest");
175         }
176
177         char digest_base64[64];
178         string dig = Kumu::base64encode (digest, SHA_DIGEST_LENGTH, digest_base64, 64);
179 #ifdef LIBDCP_WINDOWS
180         boost::replace_all (dig, "/", "\\/");
181 #else
182         boost::replace_all (dig, "/", "\\\\/");
183 #endif
184         return dig;
185 }
186
187
188 CertificateChain::CertificateChain (
189         boost::filesystem::path openssl,
190         string organisation,
191         string organisational_unit,
192         string root_common_name,
193         string intermediate_common_name,
194         string leaf_common_name
195         )
196 {
197         /* Valid for 40 years */
198         int const days = 365 * 40;
199
200         auto directory = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
201         boost::filesystem::create_directories (directory);
202
203         auto const cwd = boost::filesystem::current_path ();
204         boost::filesystem::current_path (directory);
205
206         string quoted_openssl = "\"" + openssl.string() + "\"";
207
208         command (quoted_openssl + " genrsa -out ca.key 2048");
209
210         {
211                 ofstream f ("ca.cnf");
212                 f << "[ req ]\n"
213                   << "distinguished_name = req_distinguished_name\n"
214                   << "x509_extensions   = v3_ca\n"
215                   << "string_mask = nombstr\n"
216                   << "[ v3_ca ]\n"
217                   << "basicConstraints = critical,CA:true,pathlen:3\n"
218                   << "keyUsage = keyCertSign,cRLSign\n"
219                   << "subjectKeyIdentifier = hash\n"
220                   << "authorityKeyIdentifier = keyid:always,issuer:always\n"
221                   << "[ req_distinguished_name ]\n"
222                   << "O = Unique organization name\n"
223                   << "OU = Organization unit\n"
224                   << "CN = Entity and dnQualifier\n";
225         }
226
227         string const ca_subject = "/O=" + organisation +
228                 "/OU=" + organisational_unit +
229                 "/CN=" + root_common_name +
230                 "/dnQualifier=" + public_key_digest ("ca.key", openssl);
231
232         {
233                 command (
234                         String::compose (
235                                 "%1 req -new -x509 -sha256 -config ca.cnf -days %2 -set_serial 5"
236                                 " -subj \"%3\" -key ca.key -outform PEM -out ca.self-signed.pem",
237                                 quoted_openssl, days, ca_subject
238                                 )
239                         );
240         }
241
242         command (quoted_openssl + " genrsa -out intermediate.key 2048");
243
244         {
245                 ofstream f ("intermediate.cnf");
246                 f << "[ default ]\n"
247                   << "distinguished_name = req_distinguished_name\n"
248                   << "x509_extensions = v3_ca\n"
249                   << "string_mask = nombstr\n"
250                   << "[ v3_ca ]\n"
251                   << "basicConstraints = critical,CA:true,pathlen:2\n"
252                   << "keyUsage = keyCertSign,cRLSign\n"
253                   << "subjectKeyIdentifier = hash\n"
254                   << "authorityKeyIdentifier = keyid:always,issuer:always\n"
255                   << "[ req_distinguished_name ]\n"
256                   << "O = Unique organization name\n"
257                   << "OU = Organization unit\n"
258                   << "CN = Entity and dnQualifier\n";
259         }
260
261         string const inter_subject = "/O=" + organisation +
262                 "/OU=" + organisational_unit +
263                 "/CN=" + intermediate_common_name +
264                 "/dnQualifier=" + public_key_digest ("intermediate.key", openssl);
265
266         {
267                 command (
268                         String::compose (
269                                 "%1 req -new -config intermediate.cnf -days %2 -subj \"%3\" -key intermediate.key -out intermediate.csr",
270                                 quoted_openssl, days - 1, inter_subject
271                                 )
272                         );
273         }
274
275         command (
276                 String::compose (
277                         "%1 x509 -req -sha256 -days %2 -CA ca.self-signed.pem -CAkey ca.key -set_serial 6"
278                         " -in intermediate.csr -extfile intermediate.cnf -extensions v3_ca -out intermediate.signed.pem",
279                         quoted_openssl, days - 1
280                         )
281                 );
282
283         command (quoted_openssl + " genrsa -out leaf.key 2048");
284
285         {
286                 ofstream f ("leaf.cnf");
287                 f << "[ default ]\n"
288                   << "distinguished_name = req_distinguished_name\n"
289                   << "x509_extensions   = v3_ca\n"
290                   << "string_mask = nombstr\n"
291                   << "[ v3_ca ]\n"
292                   << "basicConstraints = critical,CA:false\n"
293                   << "keyUsage = digitalSignature,keyEncipherment\n"
294                   << "subjectKeyIdentifier = hash\n"
295                   << "authorityKeyIdentifier = keyid,issuer:always\n"
296                   << "[ req_distinguished_name ]\n"
297                   << "O = Unique organization name\n"
298                   << "OU = Organization unit\n"
299                   << "CN = Entity and dnQualifier\n";
300         }
301
302         string const leaf_subject = "/O=" + organisation +
303                 "/OU=" + organisational_unit +
304                 "/CN=" + leaf_common_name +
305                 "/dnQualifier=" + public_key_digest ("leaf.key", openssl);
306
307         {
308                 command (
309                         String::compose (
310                                 "%1 req -new -config leaf.cnf -days %2 -subj \"%3\" -key leaf.key -outform PEM -out leaf.csr",
311                                 quoted_openssl, days - 2, leaf_subject
312                                 )
313                         );
314         }
315
316         command (
317                 String::compose (
318                         "%1 x509 -req -sha256 -days %2 -CA intermediate.signed.pem -CAkey intermediate.key"
319                         " -set_serial 7 -in leaf.csr -extfile leaf.cnf -extensions v3_ca -out leaf.signed.pem",
320                         quoted_openssl, days - 2
321                         )
322                 );
323
324         boost::filesystem::current_path (cwd);
325
326         _certificates.push_back (dcp::Certificate(dcp::file_to_string(directory / "ca.self-signed.pem")));
327         _certificates.push_back (dcp::Certificate(dcp::file_to_string(directory / "intermediate.signed.pem")));
328         _certificates.push_back (dcp::Certificate(dcp::file_to_string(directory / "leaf.signed.pem")));
329
330         _key = dcp::file_to_string (directory / "leaf.key");
331
332         boost::filesystem::remove_all (directory);
333 }
334
335
336 CertificateChain::CertificateChain (string s)
337 {
338         while (true) {
339                 try {
340                         Certificate c;
341                         s = c.read_string (s);
342                         _certificates.push_back (c);
343                 } catch (MiscError& e) {
344                         /* Failed to read a certificate, just stop */
345                         break;
346                 }
347         }
348
349         /* This will throw an exception if the chain cannot be ordered */
350         leaf_to_root ();
351 }
352
353
354 Certificate
355 CertificateChain::root () const
356 {
357         DCP_ASSERT (!_certificates.empty());
358         return root_to_leaf().front();
359 }
360
361
362 Certificate
363 CertificateChain::leaf () const
364 {
365         DCP_ASSERT (!_certificates.empty());
366         return root_to_leaf().back();
367 }
368
369
370 CertificateChain::List
371 CertificateChain::leaf_to_root () const
372 {
373         auto l = root_to_leaf ();
374         std::reverse (l.begin(), l.end());
375         return l;
376 }
377
378
379 CertificateChain::List
380 CertificateChain::unordered () const
381 {
382         return _certificates;
383 }
384
385
386 void
387 CertificateChain::add (Certificate c)
388 {
389         _certificates.push_back (c);
390 }
391
392
393 void
394 CertificateChain::remove (Certificate c)
395 {
396         auto i = std::find(_certificates.begin(), _certificates.end(), c);
397         if (i != _certificates.end()) {
398                 _certificates.erase (i);
399         }
400 }
401
402
403 void
404 CertificateChain::remove (int i)
405 {
406         auto j = _certificates.begin ();
407         while (j != _certificates.end () && i > 0) {
408                 --i;
409                 ++j;
410         }
411
412         if (j != _certificates.end ()) {
413                 _certificates.erase (j);
414         }
415 }
416
417
418 bool
419 CertificateChain::chain_valid () const
420 {
421         return chain_valid (_certificates);
422 }
423
424
425 bool
426 CertificateChain::chain_valid (List const & chain) const
427 {
428         /* Here I am taking a chain of certificates A/B/C/D and checking validity of B wrt A,
429            C wrt B and D wrt C.  It also appears necessary to check the issuer of B/C/D matches
430            the subject of A/B/C; I don't understand why.  I'm sure there's a better way of doing
431            this with OpenSSL but the documentation does not appear not likely to reveal it
432            any time soon.
433         */
434
435         auto store = X509_STORE_new ();
436         if (!store) {
437                 throw MiscError ("could not create X509 store");
438         }
439
440         /* Put all the certificates into the store */
441         for (auto const& i: chain) {
442                 if (!X509_STORE_add_cert(store, i.x509())) {
443                         X509_STORE_free(store);
444                         return false;
445                 }
446         }
447
448         /* Verify each one */
449         for (auto i = chain.begin(); i != chain.end(); ++i) {
450
451                 auto j = i;
452                 ++j;
453                 if (j == chain.end ()) {
454                         break;
455                 }
456
457                 auto ctx = X509_STORE_CTX_new ();
458                 if (!ctx) {
459                         X509_STORE_free (store);
460                         throw MiscError ("could not create X509 store context");
461                 }
462
463                 X509_STORE_set_flags (store, 0);
464                 if (!X509_STORE_CTX_init (ctx, store, j->x509(), 0)) {
465                         X509_STORE_CTX_free (ctx);
466                         X509_STORE_free (store);
467                         throw MiscError ("could not initialise X509 store context");
468                 }
469
470                 int const v = X509_verify_cert (ctx);
471                 X509_STORE_CTX_free (ctx);
472
473                 if (v != 1) {
474                         X509_STORE_free (store);
475                         return false;
476                 }
477
478                 /* I don't know why OpenSSL doesn't check this stuff
479                    in verify_cert, but without these checks the
480                    certificates_validation8 test fails.
481                 */
482                 if (j->issuer() != i->subject() || j->subject() == i->subject()) {
483                         X509_STORE_free (store);
484                         return false;
485                 }
486
487         }
488
489         X509_STORE_free (store);
490
491         return true;
492 }
493
494
495 bool
496 CertificateChain::private_key_valid () const
497 {
498         if (_certificates.empty ()) {
499                 return true;
500         }
501
502         if (!_key) {
503                 return false;
504         }
505
506         auto bio = BIO_new_mem_buf (const_cast<char *> (_key->c_str ()), -1);
507         if (!bio) {
508                 throw MiscError ("could not create memory BIO");
509         }
510
511         auto private_key = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
512         if (!private_key) {
513                 return false;
514         }
515
516         auto public_key = leaf().public_key ();
517
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) {
524                 return false;
525         }
526         bool const valid = !BN_cmp (private_key_n, public_key_n);
527 #else
528         bool const valid = !BN_cmp (private_key->n, public_key->n);
529 #endif
530         BIO_free (bio);
531
532         return valid;
533 }
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
559 CertificateChain::List
560 CertificateChain::root_to_leaf () const
561 {
562         auto 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
574 void
575 CertificateChain::sign (xmlpp::Element* parent, Standard standard) const
576 {
577         /* <Signer> */
578
579         parent->add_child_text("  ");
580         auto signer = parent->add_child("Signer");
581         signer->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
582         auto data = signer->add_child("X509Data", "dsig");
583         auto serial_element = data->add_child("X509IssuerSerial", "dsig");
584         serial_element->add_child("X509IssuerName", "dsig")->add_child_text (leaf().issuer());
585         serial_element->add_child("X509SerialNumber", "dsig")->add_child_text (leaf().serial());
586         data->add_child("X509SubjectName", "dsig")->add_child_text (leaf().subject());
587
588         indent (signer, 2);
589
590         /* <Signature> */
591
592         parent->add_child_text("\n  ");
593         auto signature = parent->add_child("Signature");
594         signature->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
595         signature->set_namespace ("dsig");
596         parent->add_child_text("\n");
597
598         auto signed_info = signature->add_child ("SignedInfo", "dsig");
599         signed_info->add_child("CanonicalizationMethod", "dsig")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
600
601         if (standard == Standard::INTEROP) {
602                 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
603         } else {
604                 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
605         }
606
607         auto reference = signed_info->add_child("Reference", "dsig");
608         reference->set_attribute ("URI", "");
609
610         auto transforms = reference->add_child("Transforms", "dsig");
611         transforms->add_child("Transform", "dsig")->set_attribute (
612                 "Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
613                 );
614
615         reference->add_child("DigestMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
616         /* This will be filled in by the signing later */
617         reference->add_child("DigestValue", "dsig");
618
619         signature->add_child("SignatureValue", "dsig");
620         signature->add_child("KeyInfo", "dsig");
621         add_signature_value (signature, "dsig", true);
622 }
623
624
625 void
626 CertificateChain::add_signature_value (xmlpp::Element* parent, string ns, bool add_indentation) const
627 {
628         cxml::Node cp (parent);
629         auto key_info = cp.node_child("KeyInfo")->node();
630
631         /* Add the certificate chain to the KeyInfo child node of parent */
632         for (auto const& i: leaf_to_root()) {
633                 auto data = key_info->add_child("X509Data", ns);
634
635                 {
636                         auto serial = data->add_child("X509IssuerSerial", ns);
637                         serial->add_child("X509IssuerName", ns)->add_child_text (i.issuer ());
638                         serial->add_child("X509SerialNumber", ns)->add_child_text (i.serial ());
639                 }
640
641                 data->add_child("X509Certificate", ns)->add_child_text (i.certificate());
642         }
643
644         auto signature_context = xmlSecDSigCtxCreate (0);
645         if (signature_context == 0) {
646                 throw MiscError ("could not create signature context");
647         }
648
649         signature_context->signKey = xmlSecCryptoAppKeyLoadMemory (
650                 reinterpret_cast<const unsigned char *> (_key->c_str()), _key->size(), xmlSecKeyDataFormatPem, 0, 0, 0
651                 );
652
653         if (signature_context->signKey == 0) {
654                 throw runtime_error ("could not read private key");
655         }
656
657         if (add_indentation) {
658                 indent (parent, 2);
659         }
660         int const r = xmlSecDSigCtxSign (signature_context, parent->cobj ());
661         if (r < 0) {
662                 throw MiscError (String::compose ("could not sign (%1)", r));
663         }
664
665         xmlSecDSigCtxDestroy (signature_context);
666 }
667
668
669 string
670 CertificateChain::chain () const
671 {
672         string o;
673         for (auto const& i: root_to_leaf()) {
674                 o += i.certificate(true);
675         }
676
677         return o;
678 }