asdcp headers moved into subdirectory.
[libdcp.git] / src / certificate_chain.cc
1 /*
2     Copyright (C) 2013-2015 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 <boost/filesystem.hpp>
55 #include <boost/algorithm/string.hpp>
56 #include <boost/foreach.hpp>
57 #include <fstream>
58 #include <sstream>
59
60 using std::string;
61 using std::ofstream;
62 using std::ifstream;
63 using std::runtime_error;
64 using std::stringstream;
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                 stringstream s;
112                 s << "error " << code << " in " << cmd << " within " << boost::filesystem::current_path();
113                 throw dcp::MiscError (s.str());
114         }
115 }
116
117 /** Extract a public key from a private key and create a SHA1 digest of it.
118  *  @param private_key Private key
119  *  @param openssl openssl binary name (or full path if openssl is not on the system path).
120  *  @return SHA1 digest of corresponding public key, with escaped / characters.
121  */
122 static string
123 public_key_digest (boost::filesystem::path private_key, boost::filesystem::path openssl)
124 {
125         boost::filesystem::path public_name = private_key.string() + ".public";
126
127         /* Create the public key from the private key */
128         stringstream s;
129         s << "\"" << openssl.string() << "\" rsa -outform PEM -pubout -in " << private_key.string() << " -out " << public_name.string ();
130         command (s.str().c_str ());
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 CertificateChain::CertificateChain (
185         boost::filesystem::path openssl,
186         string organisation,
187         string organisational_unit,
188         string root_common_name,
189         string intermediate_common_name,
190         string leaf_common_name
191         )
192 {
193         boost::filesystem::path directory = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
194         boost::filesystem::create_directories (directory);
195
196         boost::filesystem::path const cwd = boost::filesystem::current_path ();
197         boost::filesystem::current_path (directory);
198
199         string quoted_openssl = "\"" + openssl.string() + "\"";
200
201         command (quoted_openssl + " genrsa -out ca.key 2048");
202
203         {
204                 ofstream f ("ca.cnf");
205                 f << "[ req ]\n"
206                   << "distinguished_name = req_distinguished_name\n"
207                   << "x509_extensions   = v3_ca\n"
208                   << "[ v3_ca ]\n"
209                   << "basicConstraints = critical,CA:true,pathlen:3\n"
210                   << "keyUsage = keyCertSign,cRLSign\n"
211                   << "subjectKeyIdentifier = hash\n"
212                   << "authorityKeyIdentifier = keyid:always,issuer:always\n"
213                   << "[ req_distinguished_name ]\n"
214                   << "O = Unique organization name\n"
215                   << "OU = Organization unit\n"
216                   << "CN = Entity and dnQualifier\n";
217         }
218
219         string const ca_subject = "/O=" + organisation +
220                 "/OU=" + organisational_unit +
221                 "/CN=" + root_common_name +
222                 "/dnQualifier=" + public_key_digest ("ca.key", openssl);
223
224         {
225                 stringstream c;
226                 c << quoted_openssl
227                   << " req -new -x509 -sha256 -config ca.cnf -days 3650 -set_serial 5"
228                   << " -subj \"" << ca_subject << "\" -key ca.key -outform PEM -out ca.self-signed.pem";
229                 command (c.str().c_str());
230         }
231
232         command (quoted_openssl + " genrsa -out intermediate.key 2048");
233
234         {
235                 ofstream f ("intermediate.cnf");
236                 f << "[ default ]\n"
237                   << "distinguished_name = req_distinguished_name\n"
238                   << "x509_extensions = v3_ca\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                 stringstream s;
257                 s << quoted_openssl
258                   << " req -new -config intermediate.cnf -days 3649 -subj \"" << inter_subject << "\" -key intermediate.key -out intermediate.csr";
259                 command (s.str().c_str());
260         }
261
262
263         command (
264                 quoted_openssl +
265                 " x509 -req -sha256 -days 3649 -CA ca.self-signed.pem -CAkey ca.key -set_serial 6"
266                 " -in intermediate.csr -extfile intermediate.cnf -extensions v3_ca -out intermediate.signed.pem"
267                 );
268
269         command (quoted_openssl + " genrsa -out leaf.key 2048");
270
271         {
272                 ofstream f ("leaf.cnf");
273                 f << "[ default ]\n"
274                   << "distinguished_name = req_distinguished_name\n"
275                   << "x509_extensions   = v3_ca\n"
276                   << "[ v3_ca ]\n"
277                   << "basicConstraints = critical,CA:false\n"
278                   << "keyUsage = digitalSignature,keyEncipherment\n"
279                   << "subjectKeyIdentifier = hash\n"
280                   << "authorityKeyIdentifier = keyid,issuer:always\n"
281                   << "[ req_distinguished_name ]\n"
282                   << "O = Unique organization name\n"
283                   << "OU = Organization unit\n"
284                   << "CN = Entity and dnQualifier\n";
285         }
286
287         string const leaf_subject = "/O=" + organisation +
288                 "/OU=" + organisational_unit +
289                 "/CN=" + leaf_common_name +
290                 "/dnQualifier=" + public_key_digest ("leaf.key", openssl);
291
292         {
293                 stringstream s;
294                 s << quoted_openssl << " req -new -config leaf.cnf -days 3648 -subj \"" << leaf_subject << "\" -key leaf.key -outform PEM -out leaf.csr";
295                 command (s.str().c_str());
296         }
297
298         command (
299                 quoted_openssl +
300                 " x509 -req -sha256 -days 3648 -CA intermediate.signed.pem -CAkey intermediate.key"
301                 " -set_serial 7 -in leaf.csr -extfile leaf.cnf -extensions v3_ca -out leaf.signed.pem"
302                 );
303
304         boost::filesystem::current_path (cwd);
305
306         _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "ca.self-signed.pem")));
307         _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "intermediate.signed.pem")));
308         _certificates.push_back (dcp::Certificate (dcp::file_to_string (directory / "leaf.signed.pem")));
309
310         _key = dcp::file_to_string (directory / "leaf.key");
311
312         boost::filesystem::remove_all (directory);
313 }
314
315 /** @return Root certificate */
316 Certificate
317 CertificateChain::root () const
318 {
319         DCP_ASSERT (!_certificates.empty());
320         return _certificates.front ();
321 }
322
323 /** @return Leaf certificate */
324 Certificate
325 CertificateChain::leaf () const
326 {
327         DCP_ASSERT (_certificates.size() >= 2);
328         return _certificates.back ();
329 }
330
331 /** @return Certificates in order from root to leaf */
332 CertificateChain::List
333 CertificateChain::root_to_leaf () const
334 {
335         return _certificates;
336 }
337
338 /** @return Certificates in order from leaf to root */
339 CertificateChain::List
340 CertificateChain::leaf_to_root () const
341 {
342         List c = _certificates;
343         c.reverse ();
344         return c;
345 }
346
347 /** Add a certificate to the end of the chain.
348  *  @param c Certificate to add.
349  */
350 void
351 CertificateChain::add (Certificate c)
352 {
353         _certificates.push_back (c);
354 }
355
356 /** Remove a certificate from the chain.
357  *  @param c Certificate to remove.
358  */
359 void
360 CertificateChain::remove (Certificate c)
361 {
362         _certificates.remove (c);
363 }
364
365 /** Remove the i'th certificate in the list, as listed
366  *  from root to leaf.
367  */
368 void
369 CertificateChain::remove (int i)
370 {
371         List::iterator j = _certificates.begin ();
372         while (j != _certificates.end () && i > 0) {
373                 --i;
374                 ++j;
375         }
376
377         if (j != _certificates.end ()) {
378                 _certificates.erase (j);
379         }
380 }
381
382 /** Check to see if the chain is valid (i.e. root signs the intermediate, intermediate
383  *  signs the leaf and so on) and that the private key (if there is one) matches the
384  *  leaf certificate.
385  *  @return true if it's ok, false if not.
386  */
387 bool
388 CertificateChain::valid () const
389 {
390         /* Check the certificate chain */
391
392         X509_STORE* store = X509_STORE_new ();
393         if (!store) {
394                 return false;
395         }
396
397         for (List::const_iterator i = _certificates.begin(); i != _certificates.end(); ++i) {
398
399                 List::const_iterator j = i;
400                 ++j;
401                 if (j ==  _certificates.end ()) {
402                         break;
403                 }
404
405                 if (!X509_STORE_add_cert (store, i->x509 ())) {
406                         X509_STORE_free (store);
407                         return false;
408                 }
409
410                 X509_STORE_CTX* ctx = X509_STORE_CTX_new ();
411                 if (!ctx) {
412                         X509_STORE_free (store);
413                         return false;
414                 }
415
416                 X509_STORE_set_flags (store, 0);
417                 if (!X509_STORE_CTX_init (ctx, store, j->x509 (), 0)) {
418                         X509_STORE_CTX_free (ctx);
419                         X509_STORE_free (store);
420                         return false;
421                 }
422
423                 int v = X509_verify_cert (ctx);
424                 X509_STORE_CTX_free (ctx);
425
426                 if (v == 0) {
427                         X509_STORE_free (store);
428                         return false;
429                 }
430         }
431
432         X509_STORE_free (store);
433
434         /* Check that the leaf certificate matches the private key, if there is one */
435
436         if (!_key) {
437                 return true;
438         }
439
440         BIO* bio = BIO_new_mem_buf (const_cast<char *> (_key->c_str ()), -1);
441         if (!bio) {
442                 throw MiscError ("could not create memory BIO");
443         }
444
445         RSA* private_key = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
446         RSA* public_key = leaf().public_key ();
447         bool const valid = !BN_cmp (private_key->n, public_key->n);
448         BIO_free (bio);
449
450         return valid;
451 }
452
453 /** @return true if the chain is now in order from root to leaf,
454  *  false if no correct order was found.
455  */
456 bool
457 CertificateChain::attempt_reorder ()
458 {
459         List original = _certificates;
460         _certificates.sort ();
461         do {
462                 if (valid ()) {
463                         return true;
464                 }
465         } while (std::next_permutation (_certificates.begin(), _certificates.end ()));
466
467         _certificates = original;
468         return false;
469 }
470
471 /** Add a &lt;Signer&gt; and &lt;ds:Signature&gt; nodes to an XML node.
472  *  @param parent XML node to add to.
473  *  @param standard INTEROP or SMPTE.
474  */
475 void
476 CertificateChain::sign (xmlpp::Element* parent, Standard standard) const
477 {
478         /* <Signer> */
479
480         xmlpp::Element* signer = parent->add_child("Signer");
481         xmlpp::Element* data = signer->add_child("X509Data", "dsig");
482         xmlpp::Element* serial_element = data->add_child("X509IssuerSerial", "dsig");
483         serial_element->add_child("X509IssuerName", "dsig")->add_child_text (leaf().issuer());
484         serial_element->add_child("X509SerialNumber", "dsig")->add_child_text (leaf().serial());
485         data->add_child("X509SubjectName", "dsig")->add_child_text (leaf().subject());
486
487         /* <Signature> */
488
489         xmlpp::Element* signature = parent->add_child("Signature", "dsig");
490
491         xmlpp::Element* signed_info = signature->add_child ("SignedInfo", "dsig");
492         signed_info->add_child("CanonicalizationMethod", "dsig")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
493
494         if (standard == INTEROP) {
495                 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
496         } else {
497                 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
498         }
499
500         xmlpp::Element* reference = signed_info->add_child("Reference", "dsig");
501         reference->set_attribute ("URI", "");
502
503         xmlpp::Element* transforms = reference->add_child("Transforms", "dsig");
504         transforms->add_child("Transform", "dsig")->set_attribute (
505                 "Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
506                 );
507
508         reference->add_child("DigestMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
509         /* This will be filled in by the signing later */
510         reference->add_child("DigestValue", "dsig");
511
512         signature->add_child("SignatureValue", "dsig");
513         signature->add_child("KeyInfo", "dsig");
514         add_signature_value (signature, "dsig");
515 }
516
517
518 /** Sign an XML node.
519  *
520  *  @param parent Node to sign.
521  *  @param ns Namespace to use for the signature XML nodes.
522  */
523 void
524 CertificateChain::add_signature_value (xmlpp::Node* parent, string ns) const
525 {
526         cxml::Node cp (parent);
527         xmlpp::Node* key_info = cp.node_child("KeyInfo")->node ();
528
529         /* Add the certificate chain to the KeyInfo child node of parent */
530         BOOST_FOREACH (Certificate const & i, leaf_to_root ()) {
531                 xmlpp::Element* data = key_info->add_child("X509Data", ns);
532
533                 {
534                         xmlpp::Element* serial = data->add_child("X509IssuerSerial", ns);
535                         serial->add_child("X509IssuerName", ns)->add_child_text (i.issuer ());
536                         serial->add_child("X509SerialNumber", ns)->add_child_text (i.serial ());
537                 }
538
539                 data->add_child("X509Certificate", ns)->add_child_text (i.certificate());
540         }
541
542         xmlSecDSigCtxPtr signature_context = xmlSecDSigCtxCreate (0);
543         if (signature_context == 0) {
544                 throw MiscError ("could not create signature context");
545         }
546
547         signature_context->signKey = xmlSecCryptoAppKeyLoadMemory (
548                 reinterpret_cast<const unsigned char *> (_key->c_str()), _key->size(), xmlSecKeyDataFormatPem, 0, 0, 0
549                 );
550
551         if (signature_context->signKey == 0) {
552                 throw runtime_error ("could not read private key");
553         }
554
555         /* XXX: set key name to the PEM string: this can't be right! */
556         if (xmlSecKeySetName (signature_context->signKey, reinterpret_cast<const xmlChar *> (_key->c_str())) < 0) {
557                 throw MiscError ("could not set key name");
558         }
559
560         int const r = xmlSecDSigCtxSign (signature_context, parent->cobj ());
561         if (r < 0) {
562                 throw MiscError (String::compose ("could not sign (%1)", r));
563         }
564
565         xmlSecDSigCtxDestroy (signature_context);
566 }