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