Attempt to fix Sony digest validation by indenting the <Signer>
[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                   << "string_mask = nombstr\n"
205                   << "[ v3_ca ]\n"
206                   << "basicConstraints = critical,CA:true,pathlen:3\n"
207                   << "keyUsage = keyCertSign,cRLSign\n"
208                   << "subjectKeyIdentifier = hash\n"
209                   << "authorityKeyIdentifier = keyid:always,issuer:always\n"
210                   << "[ req_distinguished_name ]\n"
211                   << "O = Unique organization name\n"
212                   << "OU = Organization unit\n"
213                   << "CN = Entity and dnQualifier\n";
214         }
215
216         string const ca_subject = "/O=" + organisation +
217                 "/OU=" + organisational_unit +
218                 "/CN=" + root_common_name +
219                 "/dnQualifier=" + public_key_digest ("ca.key", openssl);
220
221         {
222                 command (
223                         String::compose (
224                                 "%1 req -new -x509 -sha256 -config ca.cnf -days 3650 -set_serial 5"
225                                 " -subj \"%2\" -key ca.key -outform PEM -out ca.self-signed.pem",
226                                 quoted_openssl, ca_subject
227                                 )
228                         );
229         }
230
231         command (quoted_openssl + " genrsa -out intermediate.key 2048");
232
233         {
234                 ofstream f ("intermediate.cnf");
235                 f << "[ default ]\n"
236                   << "distinguished_name = req_distinguished_name\n"
237                   << "x509_extensions = v3_ca\n"
238                   << "string_mask = nombstr\n"
239                   << "[ v3_ca ]\n"
240                   << "basicConstraints = critical,CA:true,pathlen:2\n"
241                   << "keyUsage = keyCertSign,cRLSign\n"
242                   << "subjectKeyIdentifier = hash\n"
243                   << "authorityKeyIdentifier = keyid:always,issuer:always\n"
244                   << "[ req_distinguished_name ]\n"
245                   << "O = Unique organization name\n"
246                   << "OU = Organization unit\n"
247                   << "CN = Entity and dnQualifier\n";
248         }
249
250         string const inter_subject = "/O=" + organisation +
251                 "/OU=" + organisational_unit +
252                 "/CN=" + intermediate_common_name +
253                 "/dnQualifier=" + public_key_digest ("intermediate.key", openssl);
254
255         {
256                 command (
257                         String::compose (
258                                 "%1 req -new -config intermediate.cnf -days 3649 -subj \"%2\" -key intermediate.key -out intermediate.csr",
259                                 quoted_openssl, inter_subject
260                                 )
261                         );
262         }
263
264         command (
265                 quoted_openssl +
266                 " x509 -req -sha256 -days 3649 -CA ca.self-signed.pem -CAkey ca.key -set_serial 6"
267                 " -in intermediate.csr -extfile intermediate.cnf -extensions v3_ca -out intermediate.signed.pem"
268                 );
269
270         command (quoted_openssl + " genrsa -out leaf.key 2048");
271
272         {
273                 ofstream f ("leaf.cnf");
274                 f << "[ default ]\n"
275                   << "distinguished_name = req_distinguished_name\n"
276                   << "x509_extensions   = v3_ca\n"
277                   << "string_mask = nombstr\n"
278                   << "[ v3_ca ]\n"
279                   << "basicConstraints = critical,CA:false\n"
280                   << "keyUsage = digitalSignature,keyEncipherment\n"
281                   << "subjectKeyIdentifier = hash\n"
282                   << "authorityKeyIdentifier = keyid,issuer:always\n"
283                   << "[ req_distinguished_name ]\n"
284                   << "O = Unique organization name\n"
285                   << "OU = Organization unit\n"
286                   << "CN = Entity and dnQualifier\n";
287         }
288
289         string const leaf_subject = "/O=" + organisation +
290                 "/OU=" + organisational_unit +
291                 "/CN=" + leaf_common_name +
292                 "/dnQualifier=" + public_key_digest ("leaf.key", openssl);
293
294         {
295                 command (
296                         String::compose (
297                                 "%1 req -new -config leaf.cnf -days 3648 -subj \"%2\" -key leaf.key -outform PEM -out leaf.csr",
298                                 quoted_openssl, leaf_subject
299                                 )
300                         );
301         }
302
303         command (
304                 quoted_openssl +
305                 " x509 -req -sha256 -days 3648 -CA intermediate.signed.pem -CAkey intermediate.key"
306                 " -set_serial 7 -in leaf.csr -extfile leaf.cnf -extensions v3_ca -out leaf.signed.pem"
307                 );
308
309         boost::filesystem::current_path (cwd);
310
311         _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "ca.self-signed.pem")));
312         _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "intermediate.signed.pem")));
313         _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "leaf.signed.pem")));
314
315         _key = dcp::file_to_string (directory / "leaf.key");
316
317         boost::filesystem::remove_all (directory);
318 }
319
320 CertificateChain::CertificateChain (string s)
321 {
322         while (true) {
323                 try {
324                         Certificate c;
325                         s = c.read_string (s);
326                         _certificates.push_back (c);
327                 } catch (MiscError& e) {
328                         /* Failed to read a certificate, just stop */
329                         break;
330                 }
331         }
332
333         /* This will throw an exception if the chain cannot be ordered */
334         leaf_to_root ();
335 }
336
337 /** @return Root certificate */
338 Certificate
339 CertificateChain::root () const
340 {
341         DCP_ASSERT (!_certificates.empty());
342         return root_to_leaf().front ();
343 }
344
345 /** @return Leaf certificate */
346 Certificate
347 CertificateChain::leaf () const
348 {
349         DCP_ASSERT (!_certificates.empty());
350         return root_to_leaf().back ();
351 }
352
353 /** @return Certificates in order from leaf to root */
354 CertificateChain::List
355 CertificateChain::leaf_to_root () const
356 {
357         List l = root_to_leaf ();
358         l.reverse ();
359         return l;
360 }
361
362 CertificateChain::List
363 CertificateChain::unordered () const
364 {
365         return _certificates;
366 }
367
368 /** Add a certificate to the chain.
369  *  @param c Certificate to add.
370  */
371 void
372 CertificateChain::add (Certificate c)
373 {
374         _certificates.push_back (c);
375 }
376
377 /** Remove a certificate from the chain.
378  *  @param c Certificate to remove.
379  */
380 void
381 CertificateChain::remove (Certificate c)
382 {
383         _certificates.remove (c);
384 }
385
386 /** Remove the i'th certificate in the list, as listed
387  *  from root to leaf.
388  */
389 void
390 CertificateChain::remove (int i)
391 {
392         List::iterator j = _certificates.begin ();
393         while (j != _certificates.end () && i > 0) {
394                 --i;
395                 ++j;
396         }
397
398         if (j != _certificates.end ()) {
399                 _certificates.erase (j);
400         }
401 }
402
403 bool
404 CertificateChain::chain_valid () const
405 {
406         return chain_valid (_certificates);
407 }
408
409 /** Check to see if a chain is valid (i.e. root signs the intermediate, intermediate
410  *  signs the leaf and so on) and that the private key (if there is one) matches the
411  *  leaf certificate.
412  *  @return true if it's ok, false if not.
413  */
414 bool
415 CertificateChain::chain_valid (List const & chain) const
416 {
417         /* Here I am taking a chain of certificates A/B/C/D and checking validity of B wrt A,
418            C wrt B and D wrt C.  It also appears necessary to check the issuer of B/C/D matches
419            the subject of A/B/C; I don't understand why.  I'm sure there's a better way of doing
420            this with OpenSSL but the documentation does not appear not likely to reveal it
421            any time soon.
422         */
423
424         X509_STORE* store = X509_STORE_new ();
425         if (!store) {
426                 throw MiscError ("could not create X509 store");
427         }
428
429         /* Put all the certificates into the store */
430         for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) {
431                 if (!X509_STORE_add_cert (store, i->x509 ())) {
432                         X509_STORE_free (store);
433                         return false;
434                 }
435         }
436
437         /* Verify each one */
438         for (List::const_iterator i = chain.begin(); i != chain.end(); ++i) {
439
440                 List::const_iterator j = i;
441                 ++j;
442                 if (j == chain.end ()) {
443                         break;
444                 }
445
446                 X509_STORE_CTX* ctx = X509_STORE_CTX_new ();
447                 if (!ctx) {
448                         X509_STORE_free (store);
449                         throw MiscError ("could not create X509 store context");
450                 }
451
452                 X509_STORE_set_flags (store, 0);
453                 if (!X509_STORE_CTX_init (ctx, store, j->x509(), 0)) {
454                         X509_STORE_CTX_free (ctx);
455                         X509_STORE_free (store);
456                         throw MiscError ("could not initialise X509 store context");
457                 }
458
459                 int const v = X509_verify_cert (ctx);
460                 X509_STORE_CTX_free (ctx);
461
462                 if (v != 1) {
463                         X509_STORE_free (store);
464                         return false;
465                 }
466
467                 /* I don't know why OpenSSL doesn't check this in verify_cert, but without this check
468                    the certificates_validation8 test fails.
469                 */
470                 if (j->issuer() != i->subject()) {
471                         X509_STORE_free (store);
472                         return false;
473                 }
474
475         }
476
477         X509_STORE_free (store);
478
479         return true;
480 }
481
482 /** Check that there is a valid private key for the leaf certificate.
483  *  Will return true if there are no certificates.
484  */
485 bool
486 CertificateChain::private_key_valid () const
487 {
488         if (_certificates.empty ()) {
489                 return true;
490         }
491
492         if (!_key) {
493                 return false;
494         }
495
496         BIO* bio = BIO_new_mem_buf (const_cast<char *> (_key->c_str ()), -1);
497         if (!bio) {
498                 throw MiscError ("could not create memory BIO");
499         }
500
501         RSA* private_key = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
502         RSA* public_key = leaf().public_key ();
503
504 #if OPENSSL_VERSION_NUMBER > 0x10100000L
505         BIGNUM const * private_key_n;
506         RSA_get0_key(private_key, &private_key_n, 0, 0);
507         BIGNUM const * public_key_n;
508         RSA_get0_key(public_key, &public_key_n, 0, 0);
509         bool const valid = !BN_cmp (private_key_n, public_key_n);
510 #else
511         bool const valid = !BN_cmp (private_key->n, public_key->n);
512 #endif
513         BIO_free (bio);
514
515         return valid;
516 }
517
518 bool
519 CertificateChain::valid (string* reason) const
520 {
521         try {
522                 root_to_leaf ();
523         } catch (CertificateChainError& e) {
524                 if (reason) {
525                         *reason = "certificates do not form a chain";
526                 }
527                 return false;
528         }
529
530         if (!private_key_valid ()) {
531                 if (reason) {
532                         *reason = "private key does not exist, or does not match leaf certificate";
533                 }
534                 return false;
535         }
536
537         return true;
538 }
539
540 /** @return Certificates in order from root to leaf */
541 CertificateChain::List
542 CertificateChain::root_to_leaf () const
543 {
544         List rtl = _certificates;
545         rtl.sort ();
546         do {
547                 if (chain_valid (rtl)) {
548                         return rtl;
549                 }
550         } while (std::next_permutation (rtl.begin(), rtl.end()));
551
552         throw CertificateChainError ("certificate chain is not consistent");
553 }
554
555 static string
556 spaces (int n)
557 {
558         string s = "";
559         for (int i = 0; i < n; ++i) {
560                 s += " ";
561         }
562         return s;
563 }
564
565 static void
566 indent (xmlpp::Element* element, int initial)
567 {
568         xmlpp::Node* last = 0;
569         BOOST_FOREACH (xmlpp::Node * n, element->get_children()) {
570                 xmlpp::Element* e = dynamic_cast<xmlpp::Element*>(n);
571                 if (e) {
572                         element->add_child_text_before (e, "\n" + spaces(initial + 2));
573                         indent (e, initial + 2);
574                         last = n;
575                 }
576         }
577         if (last) {
578                 element->add_child_text (last, "\n" + spaces(initial));
579         }
580 }
581
582 /** Add a &lt;Signer&gt; and &lt;ds:Signature&gt; nodes to an XML node.
583  *  @param parent XML node to add to.
584  *  @param standard INTEROP or SMPTE.
585  */
586 void
587 CertificateChain::sign (xmlpp::Element* parent, Standard standard) const
588 {
589         /* <Signer> */
590
591         parent->add_child_text("  ");
592         xmlpp::Element* signer = parent->add_child("Signer");
593         signer->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
594         xmlpp::Element* data = signer->add_child("X509Data", "dsig");
595         xmlpp::Element* serial_element = data->add_child("X509IssuerSerial", "dsig");
596         serial_element->add_child("X509IssuerName", "dsig")->add_child_text (leaf().issuer());
597         serial_element->add_child("X509SerialNumber", "dsig")->add_child_text (leaf().serial());
598         data->add_child("X509SubjectName", "dsig")->add_child_text (leaf().subject());
599
600         indent (signer, 2);
601
602         /* <Signature> */
603
604         parent->add_child_text("\n  ");
605         xmlpp::Element* signature = parent->add_child("Signature");
606         signature->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "dsig");
607         signature->set_namespace ("dsig");
608         parent->add_child_text("\n");
609
610         xmlpp::Element* signed_info = signature->add_child ("SignedInfo", "dsig");
611         signed_info->add_child("CanonicalizationMethod", "dsig")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
612
613         if (standard == INTEROP) {
614                 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
615         } else {
616                 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
617         }
618
619         xmlpp::Element* reference = signed_info->add_child("Reference", "dsig");
620         reference->set_attribute ("URI", "");
621
622         xmlpp::Element* transforms = reference->add_child("Transforms", "dsig");
623         transforms->add_child("Transform", "dsig")->set_attribute (
624                 "Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
625                 );
626
627         reference->add_child("DigestMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
628         /* This will be filled in by the signing later */
629         reference->add_child("DigestValue", "dsig");
630
631         signature->add_child("SignatureValue", "dsig");
632         signature->add_child("KeyInfo", "dsig");
633         add_signature_value (signature, "dsig");
634 }
635
636
637 /** Sign an XML node.
638  *
639  *  @param parent Node to sign.
640  *  @param ns Namespace to use for the signature XML nodes.
641  */
642 void
643 CertificateChain::add_signature_value (xmlpp::Element* parent, string ns) const
644 {
645         cxml::Node cp (parent);
646         xmlpp::Node* key_info = cp.node_child("KeyInfo")->node ();
647
648         /* Add the certificate chain to the KeyInfo child node of parent */
649         BOOST_FOREACH (Certificate const & i, leaf_to_root ()) {
650                 xmlpp::Element* data = key_info->add_child("X509Data", ns);
651
652                 {
653                         xmlpp::Element* serial = data->add_child("X509IssuerSerial", ns);
654                         serial->add_child("X509IssuerName", ns)->add_child_text (i.issuer ());
655                         serial->add_child("X509SerialNumber", ns)->add_child_text (i.serial ());
656                 }
657
658                 data->add_child("X509Certificate", ns)->add_child_text (i.certificate());
659         }
660
661         xmlSecDSigCtxPtr signature_context = xmlSecDSigCtxCreate (0);
662         if (signature_context == 0) {
663                 throw MiscError ("could not create signature context");
664         }
665
666         signature_context->signKey = xmlSecCryptoAppKeyLoadMemory (
667                 reinterpret_cast<const unsigned char *> (_key->c_str()), _key->size(), xmlSecKeyDataFormatPem, 0, 0, 0
668                 );
669
670         if (signature_context->signKey == 0) {
671                 throw runtime_error ("could not read private key");
672         }
673
674         indent (parent, 2);
675         int const r = xmlSecDSigCtxSign (signature_context, parent->cobj ());
676         if (r < 0) {
677                 throw MiscError (String::compose ("could not sign (%1)", r));
678         }
679
680         xmlSecDSigCtxDestroy (signature_context);
681 }
682
683 string
684 CertificateChain::chain () const
685 {
686         string o;
687         BOOST_FOREACH (Certificate const &i, root_to_leaf ()) {
688                 o += i.certificate(true);
689         }
690
691         return o;
692 }