41 #include "compose.hpp"
44 #include "filesystem.h"
45 #include "scope_guard.h"
48 #include <asdcp/KM_util.h>
49 #include <libcxml/cxml.h>
50 LIBDCP_DISABLE_WARNINGS
51 #include <libxml++/libxml++.h>
52 LIBDCP_ENABLE_WARNINGS
53 #include <xmlsec/xmldsig.h>
54 #include <xmlsec/dl.h>
55 #include <xmlsec/app.h>
56 #include <xmlsec/crypto.h>
57 #include <openssl/sha.h>
58 #include <openssl/bio.h>
59 #include <openssl/evp.h>
60 #include <openssl/pem.h>
61 #include <openssl/rsa.h>
62 #include <openssl/x509.h>
63 #include <boost/algorithm/string.hpp>
71 using std::runtime_error;
85 int const wn = MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, 0, 0);
86 auto buffer =
new wchar_t[wn];
87 if (MultiByteToWideChar (CP_UTF8, 0, cmd.c_str(), -1, buffer, wn) == 0) {
94 STARTUPINFOW startup_info;
95 memset (&startup_info, 0,
sizeof (startup_info));
96 startup_info.cb =
sizeof (startup_info);
97 PROCESS_INFORMATION process_info;
102 if (CreateProcessW (0, buffer, 0, 0, FALSE, CREATE_NO_WINDOW, 0, 0, &startup_info, &process_info)) {
103 WaitForSingleObject (process_info.hProcess, INFINITE);
105 if (GetExitCodeProcess (process_info.hProcess, &c)) {
108 CloseHandle (process_info.hProcess);
109 CloseHandle (process_info.hThread);
114 cmd +=
" 2> /dev/null";
115 int const r = system (cmd.c_str ());
116 int const code = WEXITSTATUS (r);
119 throw dcp::MiscError(String::compose(
"error %1 in %2 within %3", code, cmd, filesystem::current_path().
string()));
125 dcp::public_key_digest(RSA* public_key)
128 unsigned char buffer[512];
129 unsigned char* buffer_ptr = buffer;
130 auto length = i2d_RSA_PUBKEY(public_key, &buffer_ptr);
132 throw MiscError(
"Could not convert public key to DER");
138 if (!SHA1_Init (&context)) {
142 if (!SHA1_Update(&context, buffer + 24, length - 24)) {
146 unsigned char digest[SHA_DIGEST_LENGTH];
147 if (!SHA1_Final (digest, &context)) {
151 char digest_base64[64];
152 string dig = Kumu::base64encode (digest, SHA_DIGEST_LENGTH, digest_base64, 64);
153 return escape_digest(dig);
158 dcp::escape_digest(
string digest)
160 boost::replace_all(digest,
"/",
"\\/");
161 boost::replace_all(digest,
"+",
"\\+");
172 dcp::public_key_digest(boost::filesystem::path private_key_file)
174 auto private_key_string = dcp::file_to_string(private_key_file);
177 auto private_key_bio = BIO_new_mem_buf(
const_cast<char*
>(private_key_string.c_str()), -1);
178 if (!private_key_bio) {
179 throw MiscError(
"Could not create memory BIO");
181 dcp::ScopeGuard sg_private_key_bio([private_key_bio]() { BIO_free(private_key_bio); });
184 auto private_key = PEM_read_bio_PrivateKey(private_key_bio,
nullptr,
nullptr,
nullptr);
186 throw MiscError(
"Could not read private key");
188 dcp::ScopeGuard sg_private_key([private_key]() { EVP_PKEY_free(private_key); });
191 auto public_key = EVP_PKEY_get1_RSA(private_key);
193 throw MiscError(
"Could not obtain public key");
195 dcp::ScopeGuard sg_public_key([public_key]() { RSA_free(public_key); });
197 return public_key_digest(public_key);
201 CertificateChain::CertificateChain (
202 boost::filesystem::path openssl,
203 int validity_in_days,
205 string organisational_unit,
206 string root_common_name,
207 string intermediate_common_name,
208 string leaf_common_name
211 auto directory = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path ();
212 filesystem::create_directories(directory);
214 auto const cwd = boost::filesystem::current_path();
219 boost::filesystem::current_path(directory);
221 string quoted_openssl =
"\"" + openssl.string() +
"\"";
223 command (quoted_openssl +
" genrsa -out ca.key 2048");
226 ofstream f (
"ca.cnf");
228 <<
"distinguished_name = req_distinguished_name\n"
229 <<
"x509_extensions = v3_ca\n"
230 <<
"string_mask = nombstr\n"
232 <<
"basicConstraints = critical,CA:true,pathlen:3\n"
233 <<
"keyUsage = keyCertSign,cRLSign\n"
234 <<
"subjectKeyIdentifier = hash\n"
235 <<
"authorityKeyIdentifier = keyid:always,issuer:always\n"
236 <<
"[ req_distinguished_name ]\n"
237 <<
"O = Unique organization name\n"
238 <<
"OU = Organization unit\n"
239 <<
"CN = Entity and dnQualifier\n";
242 string const ca_subject =
"/O=" + organisation +
243 "/OU=" + organisational_unit +
244 "/CN=" + root_common_name +
245 "/dnQualifier=" + public_key_digest (
"ca.key");
250 "%1 req -new -x509 -sha256 -config ca.cnf -days %2 -set_serial 5"
251 " -subj \"%3\" -key ca.key -outform PEM -out ca.self-signed.pem",
252 quoted_openssl, validity_in_days, ca_subject
257 command (quoted_openssl +
" genrsa -out intermediate.key 2048");
260 ofstream f (
"intermediate.cnf");
262 <<
"distinguished_name = req_distinguished_name\n"
263 <<
"x509_extensions = v3_ca\n"
264 <<
"string_mask = nombstr\n"
266 <<
"basicConstraints = critical,CA:true,pathlen:2\n"
267 <<
"keyUsage = keyCertSign,cRLSign\n"
268 <<
"subjectKeyIdentifier = hash\n"
269 <<
"authorityKeyIdentifier = keyid:always,issuer:always\n"
270 <<
"[ req_distinguished_name ]\n"
271 <<
"O = Unique organization name\n"
272 <<
"OU = Organization unit\n"
273 <<
"CN = Entity and dnQualifier\n";
276 string const inter_subject =
"/O=" + organisation +
277 "/OU=" + organisational_unit +
278 "/CN=" + intermediate_common_name +
279 "/dnQualifier=" + public_key_digest (
"intermediate.key");
284 "%1 req -new -config intermediate.cnf -days %2 -subj \"%3\" -key intermediate.key -out intermediate.csr",
285 quoted_openssl, validity_in_days - 1, inter_subject
292 "%1 x509 -req -sha256 -days %2 -CA ca.self-signed.pem -CAkey ca.key -set_serial 6"
293 " -in intermediate.csr -extfile intermediate.cnf -extensions v3_ca -out intermediate.signed.pem",
294 quoted_openssl, validity_in_days - 1
298 command (quoted_openssl +
" genrsa -out leaf.key 2048");
301 ofstream f (
"leaf.cnf");
303 <<
"distinguished_name = req_distinguished_name\n"
304 <<
"x509_extensions = v3_ca\n"
305 <<
"string_mask = nombstr\n"
307 <<
"basicConstraints = critical,CA:false\n"
308 <<
"keyUsage = digitalSignature,keyEncipherment\n"
309 <<
"subjectKeyIdentifier = hash\n"
310 <<
"authorityKeyIdentifier = keyid,issuer:always\n"
311 <<
"[ req_distinguished_name ]\n"
312 <<
"O = Unique organization name\n"
313 <<
"OU = Organization unit\n"
314 <<
"CN = Entity and dnQualifier\n";
317 string const leaf_subject =
"/O=" + organisation +
318 "/OU=" + organisational_unit +
319 "/CN=" + leaf_common_name +
320 "/dnQualifier=" + public_key_digest (
"leaf.key");
325 "%1 req -new -config leaf.cnf -days %2 -subj \"%3\" -key leaf.key -outform PEM -out leaf.csr",
326 quoted_openssl, validity_in_days - 2, leaf_subject
333 "%1 x509 -req -sha256 -days %2 -CA intermediate.signed.pem -CAkey intermediate.key"
334 " -set_serial 7 -in leaf.csr -extfile leaf.cnf -extensions v3_ca -out leaf.signed.pem",
335 quoted_openssl, validity_in_days - 2
342 boost::filesystem::current_path(cwd);
348 _key = dcp::file_to_string (directory /
"leaf.key");
350 filesystem::remove_all(directory);
354 CertificateChain::CertificateChain (
string s)
388 CertificateChain::List
392 std::reverse (l.begin(), l.end());
397 CertificateChain::List
398 CertificateChain::unordered ()
const
458 auto store = X509_STORE_new ();
460 throw MiscError (
"could not create X509 store");
464 for (
auto const& i: chain) {
465 if (!X509_STORE_add_cert(store, i.x509())) {
466 X509_STORE_free(store);
472 for (
auto i = chain.begin(); i != chain.end(); ++i) {
476 if (j == chain.end ()) {
480 auto ctx = X509_STORE_CTX_new ();
482 X509_STORE_free (store);
483 throw MiscError (
"could not create X509 store context");
486 X509_STORE_set_flags (store, 0);
487 if (!X509_STORE_CTX_init (ctx, store, j->x509(), 0)) {
488 X509_STORE_CTX_free (ctx);
489 X509_STORE_free (store);
490 throw MiscError (
"could not initialise X509 store context");
493 int const v = X509_verify_cert (ctx);
496 X509_STORE_free (store);
498 *error = X509_verify_cert_error_string(X509_STORE_CTX_get_error(ctx));
500 X509_STORE_CTX_free(ctx);
504 X509_STORE_CTX_free(ctx);
510 if (j->issuer() != i->subject() || j->subject() == i->subject()) {
511 X509_STORE_free (store);
517 X509_STORE_free (store);
534 auto bio = BIO_new_mem_buf (
const_cast<char *
> (
_key->c_str ()), -1);
536 throw MiscError (
"could not create memory BIO");
539 auto private_key = PEM_read_bio_RSAPrivateKey (bio, 0, 0, 0);
546 #if OPENSSL_VERSION_NUMBER > 0x10100000L
547 BIGNUM
const * private_key_n;
548 RSA_get0_key(private_key, &private_key_n, 0, 0);
549 BIGNUM
const * public_key_n;
550 RSA_get0_key(public_key, &public_key_n, 0, 0);
551 if (!private_key_n || !public_key_n) {
554 bool const valid = !BN_cmp (private_key_n, public_key_n);
556 bool const valid = !BN_cmp (private_key->n, public_key->n);
571 *reason =
"certificates do not form a chain";
578 *reason =
"private key does not exist, or does not match leaf certificate";
587 CertificateChain::List
591 std::sort (rtl.begin(), rtl.end());
597 }
while (std::next_permutation (rtl.begin(), rtl.end()));
608 parent->add_child_text(
" ");
609 auto signer = cxml::add_child(parent,
"Signer");
610 signer->set_namespace_declaration (
"http://www.w3.org/2000/09/xmldsig#",
"dsig");
611 auto data = cxml::add_child(signer,
"X509Data",
string(
"dsig"));
612 auto serial_element = cxml::add_child(data,
"X509IssuerSerial",
string(
"dsig"));
613 cxml::add_child(serial_element,
"X509IssuerName",
string(
"dsig"))->add_child_text(
leaf().issuer());
614 cxml::add_child(serial_element,
"X509SerialNumber",
string(
"dsig"))->add_child_text(
leaf().serial());
615 cxml::add_child(data,
"X509SubjectName",
string(
"dsig"))->add_child_text(
leaf().subject());
621 parent->add_child_text(
"\n ");
622 auto signature = cxml::add_child(parent,
"Signature");
623 signature->set_namespace_declaration (
"http://www.w3.org/2000/09/xmldsig#",
"dsig");
624 signature->set_namespace (
"dsig");
625 parent->add_child_text(
"\n");
627 auto signed_info = cxml::add_child(signature,
"SignedInfo",
string(
"dsig"));
628 cxml::add_child(signed_info,
"CanonicalizationMethod",
string(
"dsig"))->set_attribute(
"Algorithm",
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
630 if (standard == Standard::INTEROP) {
631 cxml::add_child(signed_info,
"SignatureMethod",
string(
"dsig"))->set_attribute(
"Algorithm",
"http://www.w3.org/2000/09/xmldsig#rsa-sha1");
633 cxml::add_child(signed_info,
"SignatureMethod",
string(
"dsig"))->set_attribute(
"Algorithm",
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
636 auto reference = cxml::add_child(signed_info,
"Reference",
string(
"dsig"));
637 reference->set_attribute (
"URI",
"");
639 auto transforms = cxml::add_child(reference,
"Transforms",
string(
"dsig"));
640 cxml::add_child(transforms,
"Transform",
string(
"dsig"))->set_attribute(
641 "Algorithm",
"http://www.w3.org/2000/09/xmldsig#enveloped-signature"
644 cxml::add_child(reference,
"DigestMethod",
string(
"dsig"))->set_attribute(
"Algorithm",
"http://www.w3.org/2000/09/xmldsig#sha1");
646 cxml::add_child(reference,
"DigestValue",
string(
"dsig"));
648 cxml::add_child(signature,
"SignatureValue",
string(
"dsig"));
649 cxml::add_child(signature,
"KeyInfo",
string(
"dsig"));
657 cxml::Node cp (parent);
658 auto key_info =
dynamic_cast<xmlpp::Element*
>(cp.node_child(
"KeyInfo")->node());
659 DCP_ASSERT(key_info);
663 auto data = cxml::add_child(key_info,
"X509Data", ns);
666 auto serial = cxml::add_child(data,
"X509IssuerSerial", ns);
667 cxml::add_child(serial,
"X509IssuerName", ns)->add_child_text(i.issuer());
668 cxml::add_child(serial,
"X509SerialNumber", ns)->add_child_text(i.serial());
671 cxml::add_child(data,
"X509Certificate", ns)->add_child_text(i.certificate());
674 auto signature_context = xmlSecDSigCtxCreate (0);
675 if (signature_context == 0) {
676 throw MiscError (
"could not create signature context");
679 signature_context->signKey = xmlSecCryptoAppKeyLoadMemory (
680 reinterpret_cast<const unsigned char *
> (
_key->c_str()),
_key->size(), xmlSecKeyDataFormatPem, 0, 0, 0
683 if (signature_context->signKey == 0) {
684 throw runtime_error (
"could not read private key");
687 if (add_indentation) {
690 int const r = xmlSecDSigCtxSign (signature_context, parent->cobj ());
692 throw MiscError (String::compose (
"could not sign (%1)", r));
695 xmlSecDSigCtxDestroy (signature_context);
700 CertificateChain::chain ()
const
704 o += i.certificate(
true);
static void command(string cmd)
List leaf_to_root() const
List root_to_leaf() const
boost::optional< std::string > _key
bool private_key_valid() const
bool valid(std::string *reason=nullptr) 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.
Utility methods and classes.