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