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