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