41 #include "compose.hpp"
46 #include <asdcp/KM_util.h>
47 #include <libcxml/cxml.h>
48 LIBDCP_DISABLE_WARNINGS
49 #include <libxml++/libxml++.h>
50 LIBDCP_ENABLE_WARNINGS
51 #include <xmlsec/xmldsig.h>
52 #include <xmlsec/dl.h>
53 #include <xmlsec/app.h>
54 #include <xmlsec/crypto.h>
55 #include <openssl/sha.h>
56 #include <openssl/bio.h>
57 #include <openssl/evp.h>
58 #include <openssl/pem.h>
59 #include <openssl/rsa.h>
60 #include <boost/filesystem.hpp>
61 #include <boost/algorithm/string.hpp>
69 using std::runtime_error;
83 int const wn = MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, 0, 0);
84 auto buffer =
new wchar_t[wn];
85 if (MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, buffer, wn) == 0) {
92 STARTUPINFOW startup_info;
93 memset (&startup_info, 0,
sizeof (startup_info));
94 startup_info.cb =
sizeof (startup_info);
95 PROCESS_INFORMATION process_info;
100 if (CreateProcessW (0, buffer, 0, 0, FALSE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
101 WaitForSingleObject (process_info.hProcess, INFINITE);
103 if (GetExitCodeProcess (process_info.hProcess, &c)) {
106 CloseHandle (process_info.hProcess);
107 CloseHandle (process_info.hThread);
112 cmd +=
" 2> /dev/null";
113 int const r = system (cmd.c_str ());
114 int const code = WEXITSTATUS (r);
117 throw dcp::MiscError (String::compose (
"error %1 in %2 within %3", code, cmd, boost::filesystem::current_path().
string()));
130 boost::filesystem::path public_name = private_key.string() +
".public";
133 command (String::compose(
"\"%1\" rsa -outform PEM -pubout -in %2 -out %3", openssl.string(), private_key.string(), public_name.string()));
138 ifstream f (public_name.string().c_str());
147 if (line.length() >= 10 && line.substr(0, 10) ==
"-----BEGIN") {
149 }
else if (line.length() >= 8 && line.substr(0, 8) ==
"-----END") {
158 unsigned char buffer[512];
164 if (!SHA1_Init (&context)) {
168 if (!SHA1_Update (&context, buffer + 24, N - 24)) {
172 unsigned char digest[SHA_DIGEST_LENGTH];
173 if (!SHA1_Final (digest, &context)) {
177 char digest_base64[64];
178 string dig = Kumu::base64encode (digest, SHA_DIGEST_LENGTH, digest_base64, 64);
179 #ifdef LIBDCP_WINDOWS
180 boost::replace_all (dig,
"/",
"\\/");
182 boost::replace_all (dig,
"/",
"\\\\/");
188 CertificateChain::CertificateChain (
189 boost::filesystem::path openssl,
190 int validity_in_days,
192 string organisational_unit,
193 string root_common_name,
194 string intermediate_common_name,
195 string leaf_common_name
198 auto directory = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
199 boost::filesystem::create_directories (directory);
201 auto const cwd = boost::filesystem::current_path ();
202 boost::filesystem::current_path (directory);
204 string quoted_openssl =
"\"" + openssl.string() +
"\"";
206 command (quoted_openssl +
" genrsa -out ca.key 2048");
209 ofstream f (
"ca.cnf");
211 <<
"distinguished_name = req_distinguished_name\n"
212 <<
"x509_extensions = v3_ca\n"
213 <<
"string_mask = nombstr\n"
215 <<
"basicConstraints = critical,CA:true,pathlen:3\n"
216 <<
"keyUsage = keyCertSign,cRLSign\n"
217 <<
"subjectKeyIdentifier = hash\n"
218 <<
"authorityKeyIdentifier = keyid:always,issuer:always\n"
219 <<
"[ req_distinguished_name ]\n"
220 <<
"O = Unique organization name\n"
221 <<
"OU = Organization unit\n"
222 <<
"CN = Entity and dnQualifier\n";
225 string const ca_subject =
"/O=" + organisation +
226 "/OU=" + organisational_unit +
227 "/CN=" + root_common_name +
233 "%1 req -new -x509 -sha256 -config ca.cnf -days %2 -set_serial 5"
234 " -subj \"%3\" -key ca.key -outform PEM -out ca.self-signed.pem",
235 quoted_openssl, validity_in_days, ca_subject
240 command (quoted_openssl +
" genrsa -out intermediate.key 2048");
243 ofstream f (
"intermediate.cnf");
245 <<
"distinguished_name = req_distinguished_name\n"
246 <<
"x509_extensions = v3_ca\n"
247 <<
"string_mask = nombstr\n"
249 <<
"basicConstraints = critical,CA:true,pathlen:2\n"
250 <<
"keyUsage = keyCertSign,cRLSign\n"
251 <<
"subjectKeyIdentifier = hash\n"
252 <<
"authorityKeyIdentifier = keyid:always,issuer:always\n"
253 <<
"[ req_distinguished_name ]\n"
254 <<
"O = Unique organization name\n"
255 <<
"OU = Organization unit\n"
256 <<
"CN = Entity and dnQualifier\n";
259 string const inter_subject =
"/O=" + organisation +
260 "/OU=" + organisational_unit +
261 "/CN=" + intermediate_common_name +
267 "%1 req -new -config intermediate.cnf -days %2 -subj \"%3\" -key intermediate.key -out intermediate.csr",
268 quoted_openssl, validity_in_days - 1, inter_subject
275 "%1 x509 -req -sha256 -days %2 -CA ca.self-signed.pem -CAkey ca.key -set_serial 6"
276 " -in intermediate.csr -extfile intermediate.cnf -extensions v3_ca -out intermediate.signed.pem",
277 quoted_openssl, validity_in_days - 1
281 command (quoted_openssl +
" genrsa -out leaf.key 2048");
284 ofstream f (
"leaf.cnf");
286 <<
"distinguished_name = req_distinguished_name\n"
287 <<
"x509_extensions = v3_ca\n"
288 <<
"string_mask = nombstr\n"
290 <<
"basicConstraints = critical,CA:false\n"
291 <<
"keyUsage = digitalSignature,keyEncipherment\n"
292 <<
"subjectKeyIdentifier = hash\n"
293 <<
"authorityKeyIdentifier = keyid,issuer:always\n"
294 <<
"[ req_distinguished_name ]\n"
295 <<
"O = Unique organization name\n"
296 <<
"OU = Organization unit\n"
297 <<
"CN = Entity and dnQualifier\n";
300 string const leaf_subject =
"/O=" + organisation +
301 "/OU=" + organisational_unit +
302 "/CN=" + leaf_common_name +
308 "%1 req -new -config leaf.cnf -days %2 -subj \"%3\" -key leaf.key -outform PEM -out leaf.csr",
309 quoted_openssl, validity_in_days - 2, leaf_subject
316 "%1 x509 -req -sha256 -days %2 -CA intermediate.signed.pem -CAkey intermediate.key"
317 " -set_serial 7 -in leaf.csr -extfile leaf.cnf -extensions v3_ca -out leaf.signed.pem",
318 quoted_openssl, validity_in_days - 2
322 boost::filesystem::current_path (cwd);
328 _key = dcp::file_to_string (directory /
"leaf.key");
330 boost::filesystem::remove_all (directory);
334 CertificateChain::CertificateChain (
string s)
368 CertificateChain::List
372 std::reverse (l.begin(), l.end());
377 CertificateChain::List
378 CertificateChain::unordered ()
const
433 auto store = X509_STORE_new ();
435 throw MiscError (
"could not create X509 store");
439 for (
auto const& i: chain) {
440 if (!X509_STORE_add_cert(store, i.x509())) {
441 X509_STORE_free(store);
447 for (
auto i = chain.begin(); i != chain.end(); ++i) {
451 if (j == chain.end ()) {
455 auto ctx = X509_STORE_CTX_new ();
457 X509_STORE_free (store);
458 throw MiscError (
"could not create X509 store context");
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");
468 int const v = X509_verify_cert (ctx);
469 X509_STORE_CTX_free (ctx);
472 X509_STORE_free (store);
480 if (j->issuer() != i->subject() || j->subject() == i->subject()) {
481 X509_STORE_free (store);
487 X509_STORE_free (store);
504 auto bio = BIO_new_mem_buf (
const_cast<char *
> (
_key->c_str ()), -1);
506 throw MiscError (
"could not create memory BIO");
509 auto private_key = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
516 #if OPENSSL_VERSION_NUMBER > 0x10100000L
517 BIGNUM
const * private_key_n;
518 RSA_get0_key(private_key, &private_key_n, 0, 0);
519 BIGNUM
const * public_key_n;
520 RSA_get0_key(public_key, &public_key_n, 0, 0);
521 if (!private_key_n || !public_key_n) {
524 bool const valid = !BN_cmp (private_key_n, public_key_n);
526 bool const valid = !BN_cmp (private_key->n, public_key->n);
535 CertificateChain::valid (
string* reason)
const
541 *reason =
"certificates do not form a chain";
548 *reason =
"private key does not exist, or does not match leaf certificate";
557 CertificateChain::List
561 std::sort (rtl.begin(), rtl.end());
566 }
while (std::next_permutation (rtl.begin(), rtl.end()));
577 parent->add_child_text(
" ");
578 auto signer = parent->add_child(
"Signer");
579 signer->set_namespace_declaration (
"http://www.w3.org/2000/09/xmldsig#",
"dsig");
580 auto data = signer->add_child(
"X509Data",
"dsig");
581 auto serial_element = data->add_child(
"X509IssuerSerial",
"dsig");
582 serial_element->add_child(
"X509IssuerName",
"dsig")->add_child_text (
leaf().issuer());
583 serial_element->add_child(
"X509SerialNumber",
"dsig")->add_child_text (
leaf().serial());
584 data->add_child(
"X509SubjectName",
"dsig")->add_child_text (
leaf().subject());
590 parent->add_child_text(
"\n ");
591 auto signature = parent->add_child(
"Signature");
592 signature->set_namespace_declaration (
"http://www.w3.org/2000/09/xmldsig#",
"dsig");
593 signature->set_namespace (
"dsig");
594 parent->add_child_text(
"\n");
596 auto signed_info = signature->add_child (
"SignedInfo",
"dsig");
597 signed_info->add_child(
"CanonicalizationMethod",
"dsig")->set_attribute (
"Algorithm",
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
599 if (standard == Standard::INTEROP) {
600 signed_info->add_child(
"SignatureMethod",
"dsig")->set_attribute(
"Algorithm",
"http://www.w3.org/2000/09/xmldsig#rsa-sha1");
602 signed_info->add_child(
"SignatureMethod",
"dsig")->set_attribute(
"Algorithm",
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
605 auto reference = signed_info->add_child(
"Reference",
"dsig");
606 reference->set_attribute (
"URI",
"");
608 auto transforms = reference->add_child(
"Transforms",
"dsig");
609 transforms->add_child(
"Transform",
"dsig")->set_attribute (
610 "Algorithm",
"http://www.w3.org/2000/09/xmldsig#enveloped-signature"
613 reference->add_child(
"DigestMethod",
"dsig")->set_attribute(
"Algorithm",
"http://www.w3.org/2000/09/xmldsig#sha1");
615 reference->add_child(
"DigestValue",
"dsig");
617 signature->add_child(
"SignatureValue",
"dsig");
618 signature->add_child(
"KeyInfo",
"dsig");
626 cxml::Node cp (parent);
627 auto key_info = cp.node_child(
"KeyInfo")->node();
631 auto data = key_info->add_child(
"X509Data", ns);
634 auto serial = data->add_child(
"X509IssuerSerial", ns);
635 serial->add_child(
"X509IssuerName", ns)->add_child_text (i.issuer ());
636 serial->add_child(
"X509SerialNumber", ns)->add_child_text (i.serial ());
639 data->add_child(
"X509Certificate", ns)->add_child_text (i.certificate());
642 auto signature_context = xmlSecDSigCtxCreate (0);
643 if (signature_context == 0) {
644 throw MiscError (
"could not create signature context");
647 signature_context->signKey = xmlSecCryptoAppKeyLoadMemory (
648 reinterpret_cast<const unsigned char *
> (
_key->c_str()),
_key->size(), xmlSecKeyDataFormatPem, 0, 0, 0
651 if (signature_context->signKey == 0) {
652 throw runtime_error (
"could not read private key");
655 if (add_indentation) {
658 int const r = xmlSecDSigCtxSign (signature_context, parent->cobj ());
660 throw MiscError (String::compose (
"could not sign (%1)", r));
663 xmlSecDSigCtxDestroy (signature_context);
668 CertificateChain::chain ()
const
672 o += i.certificate(
true);
static void command(string cmd)
static string public_key_digest(boost::filesystem::path private_key, boost::filesystem::path openssl)
List leaf_to_root() const
List root_to_leaf() const
boost::optional< std::string > _key
bool private_key_valid() const
void add_signature_value(xmlpp::Element *parent, std::string ns, bool add_indentation) const
void sign(xmlpp::Element *parent, Standard standard) const
void remove(Certificate c)
A wrapper for an X509 certificate.
std::string read_string(std::string)
A miscellaneous exception.
Exceptions thrown by libdcp.
Namespace for everything in libdcp.
int base64_decode(std::string const &in, unsigned char *out, int out_length)
Utility methods and classes.