X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fkdm.cc;h=bdfa1fec458b6f83026b8a21d08dcee829fe9679;hb=1c724e363a644abaee7efb39d6091e7b30de0fb6;hp=6936bc1e594fe750877182d4ee2d205bf26bb8db;hpb=532c59e443d110d3a04a25194bae8a13548ee599;p=libdcp.git diff --git a/src/kdm.cc b/src/kdm.cc index 6936bc1e..bdfa1fec 100644 --- a/src/kdm.cc +++ b/src/kdm.cc @@ -18,15 +18,22 @@ */ #include +#include #include #include #include #include #include +#include "AS_DCP.h" +#include "KM_util.h" #include "util.h" #include "kdm.h" #include "compose.hpp" #include "exceptions.h" +#include "signer.h" +#include "cpl.h" +#include "mxf.h" +#include "xml/kdm_smpte.h" using std::list; using std::string; @@ -36,40 +43,33 @@ using std::setw; using std::setfill; using std::cout; using boost::shared_ptr; -using namespace libdcp; +using namespace dcp; KDM::KDM (boost::filesystem::path kdm, boost::filesystem::path private_key) + : _xml_kdm (new xml::DCinemaSecurityMessage (kdm)) { /* Read the private key */ - FILE* private_key_file = fopen (private_key.string().c_str(), "r"); + FILE* private_key_file = fopen_boost (private_key, "r"); if (!private_key_file) { - throw FileError ("could not find RSA private key file", private_key.string ()); + throw FileError ("could not find RSA private key file", private_key, errno); } RSA* rsa = PEM_read_RSAPrivateKey (private_key_file, 0, 0, 0); fclose (private_key_file); if (!rsa) { - throw FileError ("could not read RSA private key file", private_key.string ()); + throw FileError ("could not read RSA private key file", private_key, errno); } - - /* Read the KDM, decrypting it */ - - cxml::File f (kdm.string (), "DCinemaSecurityMessage"); - - shared_ptr authenticated_private = f.node_child ("AuthenticatedPrivate"); - list > encrypted_keys = authenticated_private->node_children ("EncryptedKey"); + /* Use it to decrypt the keys */ - for (list >::iterator i = encrypted_keys.begin(); i != encrypted_keys.end(); ++i) { + list encrypted_keys = _xml_kdm->authenticated_private.encrypted_keys; - /* Get the base-64-encoded cipher value from the KDM */ - shared_ptr cipher_data = (*i)->node_child ("CipherData"); - shared_ptr cipher_value_base64 = cipher_data->node_child ("CipherValue"); + for (list::iterator i = encrypted_keys.begin(); i != encrypted_keys.end(); ++i) { - /* Decode it from base-64 */ + /* Decode the base-64-encoded cipher value from the KDM */ unsigned char cipher_value[256]; - int const cipher_value_len = base64_decode (cipher_value_base64->content(), cipher_value, sizeof (cipher_value)); + int const cipher_value_len = base64_decode (*i, cipher_value, sizeof (cipher_value)); /* Decrypt it */ unsigned char* decrypted = new unsigned char[RSA_size(rsa)]; @@ -79,47 +79,248 @@ KDM::KDM (boost::filesystem::path kdm, boost::filesystem::path private_key) throw MiscError (String::compose ("Could not decrypt KDM (%1)", ERR_error_string (ERR_get_error(), 0))); } - _ciphers.push_back (KDMCipher (decrypted, decrypted_len)); + _keys.push_back (KDMKey (decrypted, decrypted_len)); delete[] decrypted; } RSA_free (rsa); } +KDM::KDM ( + shared_ptr cpl, shared_ptr signer, shared_ptr recipient_cert, + boost::posix_time::ptime not_valid_before, boost::posix_time::ptime not_valid_after, + string annotation_text, string issue_date + ) + : _xml_kdm (new xml::DCinemaSecurityMessage) +{ + xml::AuthenticatedPublic& apu = _xml_kdm->authenticated_public; + + /* AuthenticatedPublic */ + + apu.message_id = "urn:uuid:" + make_uuid (); + apu.message_type = "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type"; + apu.annotation_text = annotation_text; + apu.issue_date = issue_date; + apu.signer.x509_issuer_name = signer->certificates().leaf()->issuer (); + apu.signer.x509_serial_number = signer->certificates().leaf()->serial (); + apu.recipient.x509_issuer_serial.x509_issuer_name = recipient_cert->issuer (); + apu.recipient.x509_issuer_serial.x509_serial_number = recipient_cert->serial (); + apu.recipient.x509_subject_name = recipient_cert->subject (); + apu.composition_playlist_id = "urn:uuid:" + cpl->id (); +// apu.content_authenticator = signer->certificates().leaf()->thumbprint (); + apu.content_title_text = cpl->name (); + apu.content_keys_not_valid_before = ptime_to_string (not_valid_before); + apu.content_keys_not_valid_after = ptime_to_string (not_valid_after); + apu.authorized_device_info.device_list_identifier = "urn:uuid:" + make_uuid (); + string n = recipient_cert->common_name (); + if (n.find (".") != string::npos) { + n = n.substr (n.find (".") + 1); + } + apu.authorized_device_info.device_list_description = n; +// apu.authorized_device_info.device_list.push_back (recipient_cert->thumbprint ()); + + /* Sometimes digital_cinema_tools uses this magic thumbprint instead of that from an actual + recipient certificate. KDMs delivered to City Screen appear to use the same thing. + */ + apu.authorized_device_info.device_list.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk="); + + list > assets = cpl->assets (); + for (list >::iterator i = assets.begin(); i != assets.end(); ++i) { + /* XXX: non-MXF assets? */ + shared_ptr mxf = boost::dynamic_pointer_cast (*i); + if (mxf) { + apu.key_id_list.push_back (xml::TypedKeyId (mxf->key_type(), "urn:uuid:" + mxf->key_id())); + } + } -KDMCipher::KDMCipher (unsigned char const * raw, int len) + apu.forensic_mark_flag_list.push_back ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable"); + apu.forensic_mark_flag_list.push_back ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable"); + + /* AuthenticatedPrivate */ + + for (list >::iterator i = assets.begin(); i != assets.end(); ++i) { + /* XXX: non-MXF assets? */ + shared_ptr mxf = boost::dynamic_pointer_cast (*i); + if (mxf) { + KDMKey kkey ( + signer, cpl->id (), mxf->key_type (), mxf->key_id (), + not_valid_before, not_valid_after, mxf->key().get() + ); + + _keys.push_back (kkey); + _xml_kdm->authenticated_private.encrypted_keys.push_back (kkey.encrypted_base64 (recipient_cert)); + } + } + + /* Signature */ + + shared_ptr doc = _xml_kdm->as_xml (); + shared_ptr root (new cxml::Node (doc->get_root_node ())); + xmlpp::Node* signature = root->node_child("Signature")->node(); + signer->add_signature_value (signature, "ds"); + _xml_kdm->signature = xml::Signature (shared_ptr (new cxml::Node (signature))); +} + +KDM::KDM (KDM const & other) + : _keys (other._keys) + , _xml_kdm (new xml::DCinemaSecurityMessage (*other._xml_kdm.get())) +{ + +} + +KDM & +KDM::operator= (KDM const & other) +{ + if (this == &other) { + return *this; + } + + _keys = other._keys; + _xml_kdm.reset (new xml::DCinemaSecurityMessage (*other._xml_kdm.get ())); + + return *this; +} + +void +KDM::as_xml (boost::filesystem::path path) const +{ + shared_ptr doc = _xml_kdm->as_xml (); + /* This must *not* be the _formatted version, otherwise the signature + will be wrong. + */ + doc->write_to_file (path.string(), "UTF-8"); +} + +string +KDM::as_xml () const +{ + shared_ptr doc = _xml_kdm->as_xml (); + /* This must *not* be the _formatted version, otherwise the signature + will be wrong. + */ + return doc->write_to_string ("UTF-8"); +} + +KDMKey::KDMKey ( + shared_ptr signer, string cpl_id, string key_type, string key_id, boost::posix_time::ptime from, boost::posix_time::ptime until, Key key + ) + : _cpl_id (cpl_id) + , _key_type (key_type) + , _key_id (key_id) + , _not_valid_before (ptime_to_string (from)) + , _not_valid_after (ptime_to_string (until)) + , _key (key) +{ + base64_decode (signer->certificates().leaf()->thumbprint (), _signer_thumbprint, 20); +} + +KDMKey::KDMKey (uint8_t const * raw, int len) { switch (len) { case 134: /* interop */ - _structure_id = get (&raw, 16); - _signer_thumbprint = get (&raw, 20); - _cpl_id = get_uuid (&raw, 16); - _key_id = get_uuid (&raw, 16); + /* [0-15] is structure id (fixed sequence specified by standard) */ + raw += 16; + get (_signer_thumbprint, &raw, 20); + _cpl_id = get_uuid (&raw); + _key_id = get_uuid (&raw); _not_valid_before = get (&raw, 25); _not_valid_after = get (&raw, 25); - memcpy (_key_raw, raw, 16); - _key_string = get_hex (&raw, 16); + _key = Key (raw); break; case 138: /* SMPTE */ - _structure_id = get (&raw, 16); - _signer_thumbprint = get (&raw, 20); - _cpl_id = get_uuid (&raw, 16); + /* [0-15] is structure id (fixed sequence specified by standard) */ + raw += 16; + get (_signer_thumbprint, &raw, 20); + _cpl_id = get_uuid (&raw); _key_type = get (&raw, 4); - _key_id = get_uuid (&raw, 16); + _key_id = get_uuid (&raw); _not_valid_before = get (&raw, 25); _not_valid_after = get (&raw, 25); - memcpy (_key_raw, raw, 16); - _key_string = get_hex (&raw, 16); + _key = Key (raw); break; default: assert (false); } } +KDMKey::KDMKey (KDMKey const & other) + : _cpl_id (other._cpl_id) + , _key_type (other._key_type) + , _key_id (other._key_id) + , _not_valid_before (other._not_valid_before) + , _not_valid_after (other._not_valid_after) + , _key (other._key) +{ + memcpy (_signer_thumbprint, other._signer_thumbprint, 20); +} + +KDMKey & +KDMKey::operator= (KDMKey const & other) +{ + if (&other == this) { + return *this; + } + + _cpl_id = other._cpl_id; + _key_type = other._key_type; + _key_id = other._key_id; + _not_valid_before = other._not_valid_before; + _not_valid_after = other._not_valid_after; + _key = other._key; + memcpy (_signer_thumbprint, other._signer_thumbprint, 20); + + return *this; +} + string -KDMCipher::get (unsigned char const ** p, int N) const +KDMKey::encrypted_base64 (shared_ptr recipient_cert) const +{ + assert (_key_type.length() == 4); + assert (_not_valid_before.length() == 25); + assert (_not_valid_after.length() == 25); + + /* XXX: SMPTE only */ + uint8_t block[138]; + uint8_t* p = block; + + /* Magic value specified by SMPTE S430-1-2006 */ + uint8_t structure_id[] = { 0xf1, 0xdc, 0x12, 0x44, 0x60, 0x16, 0x9a, 0x0e, 0x85, 0xbc, 0x30, 0x06, 0x42, 0xf8, 0x66, 0xab }; + put (&p, structure_id, 16); + put (&p, _signer_thumbprint, 20); + put_uuid (&p, _cpl_id); + put (&p, _key_type); + put_uuid (&p, _key_id); + put (&p, _not_valid_before); + put (&p, _not_valid_after); + put (&p, _key.value(), ASDCP::KeyLen); + + /* Encrypt using the projector's public key */ + RSA* rsa = recipient_cert->public_key (); + unsigned char encrypted[RSA_size(rsa)]; + int const encrypted_len = RSA_public_encrypt (p - block, block, encrypted, rsa, RSA_PKCS1_OAEP_PADDING); + if (encrypted_len == -1) { + throw MiscError (String::compose ("Could not encrypt KDM (%1)", ERR_error_string (ERR_get_error(), 0))); + } + + /* Lazy overallocation */ + char out[encrypted_len * 2]; + Kumu::base64encode (encrypted, encrypted_len, out, encrypted_len * 2); + int const N = strlen (out); + stringstream lines; + for (int i = 0; i < N; ++i) { + if (i > 0 && (i % 64) == 0) { + lines << "\n"; + } + lines << out[i]; + } + + return lines.str (); +} + +string +KDMKey::get (uint8_t const ** p, int N) const { string g; for (int i = 0; i < N; ++i) { @@ -130,12 +331,19 @@ KDMCipher::get (unsigned char const ** p, int N) const return g; } +void +KDMKey::get (uint8_t* o, uint8_t const ** p, int N) const +{ + memcpy (o, *p, N); + *p += N; +} + string -KDMCipher::get_uuid (unsigned char const ** p, int N) const +KDMKey::get_uuid (unsigned char const ** p) const { stringstream g; - for (int i = 0; i < N; ++i) { + for (int i = 0; i < 16; ++i) { g << setw(2) << setfill('0') << hex << static_cast (**p); (*p)++; if (i == 3 || i == 5 || i == 7 || i == 9) { @@ -146,15 +354,47 @@ KDMCipher::get_uuid (unsigned char const ** p, int N) const return g.str (); } -string -KDMCipher::get_hex (unsigned char const ** p, int N) const +void +KDMKey::put (uint8_t ** d, uint8_t const * s, int N) const { - stringstream g; - - for (int i = 0; i < N; ++i) { - g << setw(2) << setfill('0') << hex << static_cast (**p); - (*p)++; + memcpy (*d, s, N); + (*d) += N; +} + +void +KDMKey::put (uint8_t ** d, string s) const +{ + memcpy (*d, s.c_str(), s.length()); + (*d) += s.length(); +} + +void +KDMKey::put_uuid (uint8_t ** d, string id) const +{ + id.erase (std::remove (id.begin(), id.end(), '-')); + for (int i = 0; i < 32; i += 2) { + stringstream s; + s << id[i] << id[i + 1]; + int h; + s >> hex >> h; + **d = h; + (*d)++; } +} - return g.str (); +bool +dcp::operator== (dcp::KDMKey const & a, dcp::KDMKey const & b) +{ + if (memcmp (a._signer_thumbprint, b._signer_thumbprint, 20) != 0) { + return false; + } + + return ( + a._cpl_id == b._cpl_id && + a._key_type == b._key_type && + a._key_id == b._key_id && + a._not_valid_before == b._not_valid_before && + a._not_valid_after == b._not_valid_after && + a._key == b._key + ); }