X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=src%2Fencrypted_kdm.cc;h=dec2efcd844bcfdd410dc102f09e22970f4292e7;hb=4bf244a0823e7ae59071f1566495b4210166d4a5;hp=7286f2d7c2090d0b8324678f243952e6c1893bd1;hpb=730ba2273b136ad5a3bfc1a185d69e6cc50a65af;p=libdcp.git diff --git a/src/encrypted_kdm.cc b/src/encrypted_kdm.cc index 7286f2d7..dec2efcd 100644 --- a/src/encrypted_kdm.cc +++ b/src/encrypted_kdm.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2013-2017 Carl Hetherington + Copyright (C) 2013-2021 Carl Hetherington This file is part of libdcp. @@ -31,31 +31,45 @@ files in the program, then also delete it here. */ + +/** @file src/encrypted_kdm.cc + * @brief EncryptedKDM class + */ + + #include "encrypted_kdm.h" #include "util.h" #include "certificate_chain.h" #include "exceptions.h" +#include "compose.hpp" #include #include #include #include +#include #include -#include +#include + using std::list; using std::vector; using std::string; +using std::make_shared; using std::map; using std::pair; -using boost::shared_ptr; +using std::shared_ptr; using boost::optional; +using boost::starts_with; using namespace dcp; + namespace dcp { + /** Namespace for classes used to hold our data; they are internal to this .cc file */ namespace data { + class Signer { public: @@ -78,12 +92,13 @@ public: string x509_serial_number; }; + class X509Data { public: X509Data () {} - explicit X509Data (boost::shared_ptr node) + explicit X509Data (std::shared_ptr node) : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial"))) , x509_certificate (node->string_child ("X509Certificate")) { @@ -100,6 +115,7 @@ public: std::string x509_certificate; }; + class Reference { public: @@ -127,6 +143,7 @@ public: string digest_value; }; + class SignedInfo { public: @@ -137,12 +154,11 @@ public: explicit SignedInfo (shared_ptr node) { - list > references = node->node_children ("Reference"); - for (list >::const_iterator i = references.begin(); i != references.end(); ++i) { - if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPublic") { - authenticated_public = Reference (*i); - } else if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPrivate") { - authenticated_private = Reference (*i); + for (auto i: node->node_children ("Reference")) { + if (i->string_attribute("URI") == "#ID_AuthenticatedPublic") { + authenticated_public = Reference(i); + } else if (i->string_attribute("URI") == "#ID_AuthenticatedPrivate") { + authenticated_private = Reference(i); } /* XXX: do something if we don't recognise the node */ @@ -168,6 +184,7 @@ private: Reference authenticated_private; }; + class Signature { public: @@ -177,9 +194,8 @@ public: : signed_info (node->node_child ("SignedInfo")) , signature_value (node->string_child ("SignatureValue")) { - list > x509_data_nodes = node->node_child("KeyInfo")->node_children ("X509Data"); - for (list >::const_iterator i = x509_data_nodes.begin(); i != x509_data_nodes.end(); ++i) { - x509_data.push_back (X509Data (*i)); + for (auto i: node->node_child("KeyInfo")->node_children ("X509Data")) { + x509_data.push_back(X509Data(i)); } } @@ -188,17 +204,18 @@ public: signed_info.as_xml (node->add_child ("SignedInfo", "ds")); node->add_child("SignatureValue", "ds")->add_child_text (signature_value); - xmlpp::Element* key_info_node = node->add_child ("KeyInfo", "ds"); - for (std::list::const_iterator i = x509_data.begin(); i != x509_data.end(); ++i) { - i->as_xml (key_info_node->add_child ("X509Data", "ds")); + auto key_info_node = node->add_child("KeyInfo", "ds"); + for (auto i: x509_data) { + i.as_xml (key_info_node->add_child("X509Data", "ds")); } } SignedInfo signed_info; string signature_value; - list x509_data; + vector x509_data; }; + class AuthenticatedPrivate { public: @@ -206,9 +223,8 @@ public: explicit AuthenticatedPrivate (shared_ptr node) { - list > encrypted_key_nodes = node->node_children ("EncryptedKey"); - for (list >::const_iterator i = encrypted_key_nodes.begin(); i != encrypted_key_nodes.end(); ++i) { - encrypted_key.push_back ((*i)->node_child("CipherData")->string_child ("CipherValue")); + for (auto i: node->node_children ("EncryptedKey")) { + encrypted_key.push_back (i->node_child("CipherData")->string_child("CipherValue")); } } @@ -216,24 +232,25 @@ public: { references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate"); - for (list::const_iterator i = encrypted_key.begin(); i != encrypted_key.end(); ++i) { - xmlpp::Element* encrypted_key = node->add_child ("EncryptedKey", "enc"); + for (auto i: encrypted_key) { + auto encrypted_key = node->add_child ("EncryptedKey", "enc"); /* XXX: hack for testing with Dolby */ encrypted_key->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc"); - xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc"); + auto encryption_method = encrypted_key->add_child("EncryptionMethod", "enc"); encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"); - xmlpp::Element* digest_method = encryption_method->add_child ("DigestMethod", "ds"); + auto digest_method = encryption_method->add_child ("DigestMethod", "ds"); /* XXX: hack for testing with Dolby */ digest_method->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds"); digest_method->set_attribute ("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1"); - xmlpp::Element* cipher_data = encrypted_key->add_child ("CipherData", "enc"); - cipher_data->add_child("CipherValue", "enc")->add_child_text (*i); + auto cipher_data = encrypted_key->add_child("CipherData", "enc"); + cipher_data->add_child("CipherValue", "enc")->add_child_text (i); } } - list encrypted_key; + vector encrypted_key; }; + class TypedKeyId { public: @@ -253,7 +270,7 @@ public: void as_xml (xmlpp::Element* node) const { - xmlpp::Element* type = node->add_child("KeyType"); + auto type = node->add_child("KeyType"); type->add_child_text (key_type); node->add_child("KeyId")->add_child_text ("urn:uuid:" + key_id); /* XXX: this feels like a bit of a hack */ @@ -268,6 +285,7 @@ public: string key_id; }; + class KeyIdList { public: @@ -275,22 +293,22 @@ public: explicit KeyIdList (shared_ptr node) { - list > typed_key_id_nodes = node->node_children ("TypedKeyId"); - for (list >::const_iterator i = typed_key_id_nodes.begin(); i != typed_key_id_nodes.end(); ++i) { - typed_key_id.push_back (TypedKeyId (*i)); + for (auto i: node->node_children ("TypedKeyId")) { + typed_key_id.push_back(TypedKeyId(i)); } } void as_xml (xmlpp::Element* node) const { - for (list::const_iterator i = typed_key_id.begin(); i != typed_key_id.end(); ++i) { - i->as_xml (node->add_child("TypedKeyId")); + for (auto const& i: typed_key_id) { + i.as_xml (node->add_child("TypedKeyId")); } } - list typed_key_id; + vector typed_key_id; }; + class AuthorizedDeviceInfo { public: @@ -300,7 +318,7 @@ public: : device_list_identifier (remove_urn_uuid (node->string_child ("DeviceListIdentifier"))) , device_list_description (node->optional_string_child ("DeviceListDescription")) { - BOOST_FOREACH (cxml::ConstNodePtr i, node->node_child("DeviceList")->node_children("CertificateThumbprint")) { + for (auto i: node->node_child("DeviceList")->node_children("CertificateThumbprint")) { certificate_thumbprints.push_back (i->content ()); } } @@ -311,8 +329,8 @@ public: if (device_list_description) { node->add_child ("DeviceListDescription")->add_child_text (device_list_description.get()); } - xmlpp::Element* device_list = node->add_child ("DeviceList"); - BOOST_FOREACH (string i, certificate_thumbprints) { + auto device_list = node->add_child ("DeviceList"); + for (auto i: certificate_thumbprints) { device_list->add_child("CertificateThumbprint")->add_child_text (i); } } @@ -320,9 +338,10 @@ public: /** DeviceListIdentifier without the urn:uuid: prefix */ string device_list_identifier; boost::optional device_list_description; - std::list certificate_thumbprints; + std::vector certificate_thumbprints; }; + class X509IssuerSerial { public: @@ -345,6 +364,7 @@ public: string x509_serial_number; }; + class Recipient { public: @@ -367,6 +387,7 @@ public: string x509_subject_name; }; + class KDMRequiredExtensions { public: @@ -381,7 +402,25 @@ public: , authorized_device_info (node->node_child ("AuthorizedDeviceInfo")) , key_id_list (node->node_child ("KeyIdList")) { - + disable_forensic_marking_picture = false; + disable_forensic_marking_audio = optional(); + if (node->optional_node_child("ForensicMarkFlagList")) { + for (auto i: node->node_child("ForensicMarkFlagList")->node_children("ForensicMarkFlag")) { + if (i->content() == picture_disable) { + disable_forensic_marking_picture = true; + } else if (starts_with(i->content(), audio_disable)) { + disable_forensic_marking_audio = 0; + string const above = audio_disable + "-above-channel-"; + if (starts_with(i->content(), above)) { + auto above_number = i->content().substr(above.length()); + if (above_number == "") { + throw KDMFormatError("Badly-formatted ForensicMarkFlag"); + } + disable_forensic_marking_audio = atoi(above_number.c_str()); + } + } + } + } } void as_xml (xmlpp::Element* node) const @@ -401,9 +440,19 @@ public: } key_id_list.as_xml (node->add_child ("KeyIdList")); - xmlpp::Element* forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList"); - forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable"); - forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable"); + if (disable_forensic_marking_picture || disable_forensic_marking_audio) { + auto forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList"); + if (disable_forensic_marking_picture) { + forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text(picture_disable); + } + if (disable_forensic_marking_audio) { + auto mrkflg = audio_disable; + if (*disable_forensic_marking_audio > 0) { + mrkflg += String::compose ("-above-channel-%1", *disable_forensic_marking_audio); + } + forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text (mrkflg); + } + } } Recipient recipient; @@ -412,10 +461,21 @@ public: string content_title_text; LocalTime not_valid_before; LocalTime not_valid_after; + bool disable_forensic_marking_picture; + optional disable_forensic_marking_audio; boost::optional authorized_device_info; KeyIdList key_id_list; + +private: + static const string picture_disable; + static const string audio_disable; }; + +const string KDMRequiredExtensions::picture_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable"; +const string KDMRequiredExtensions::audio_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable"; + + class RequiredExtensions { public: @@ -435,6 +495,7 @@ public: KDMRequiredExtensions kdm_required_extensions; }; + class AuthenticatedPublic { public: @@ -479,6 +540,7 @@ public: RequiredExtensions required_extensions; }; + /** Class to describe our data. We use a class hierarchy as it's a bit nicer * for XML data than a flat description. */ @@ -509,10 +571,11 @@ public: authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references); signature.as_xml (root->add_child ("Signature", "ds")); - for (map::const_iterator i = references.begin(); i != references.end(); ++i) { - xmlAddID (0, document->cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ()); + for (auto i: references) { + xmlAddID (0, document->cobj(), (const xmlChar *) i.first.c_str(), i.second->cobj()); } + indent (document->get_root_node(), 0); return document; } @@ -521,13 +584,15 @@ public: Signature signature; }; + } } + EncryptedKDM::EncryptedKDM (string s) { try { - shared_ptr doc (new cxml::Document ("DCinemaSecurityMessage")); + auto doc = make_shared("DCinemaSecurityMessage"); doc->read_string (s); _data = new data::EncryptedKDMData (doc); } catch (xmlpp::parse_error& e) { @@ -535,18 +600,21 @@ EncryptedKDM::EncryptedKDM (string s) } } + EncryptedKDM::EncryptedKDM ( shared_ptr signer, Certificate recipient, - vector trusted_devices, + vector trusted_devices, string cpl_id, string content_title_text, optional annotation_text, LocalTime not_valid_before, LocalTime not_valid_after, Formulation formulation, - list > key_ids, - list keys + bool disable_forensic_marking_picture, + optional disable_forensic_marking_audio, + vector> key_ids, + vector keys ) : _data (new data::EncryptedKDMData) { @@ -561,84 +629,87 @@ EncryptedKDM::EncryptedKDM ( * DCI_SPECIFIC as specified Yes */ - data::AuthenticatedPublic& aup = _data->authenticated_public; + auto& aup = _data->authenticated_public; aup.signer.x509_issuer_name = signer->leaf().issuer (); aup.signer.x509_serial_number = signer->leaf().serial (); aup.annotation_text = annotation_text; - data::KDMRequiredExtensions& kre = _data->authenticated_public.required_extensions.kdm_required_extensions; + auto& kre = _data->authenticated_public.required_extensions.kdm_required_extensions; kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.issuer (); kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial (); kre.recipient.x509_subject_name = recipient.subject (); kre.composition_playlist_id = cpl_id; - if (formulation == DCI_ANY || formulation == DCI_SPECIFIC) { + if (formulation == Formulation::DCI_ANY || formulation == Formulation::DCI_SPECIFIC) { kre.content_authenticator = signer->leaf().thumbprint (); } kre.content_title_text = content_title_text; kre.not_valid_before = not_valid_before; kre.not_valid_after = not_valid_after; + kre.disable_forensic_marking_picture = disable_forensic_marking_picture; + kre.disable_forensic_marking_audio = disable_forensic_marking_audio; - if (formulation != MODIFIED_TRANSITIONAL_TEST) { + if (formulation != Formulation::MODIFIED_TRANSITIONAL_TEST) { kre.authorized_device_info = data::AuthorizedDeviceInfo (); kre.authorized_device_info->device_list_identifier = make_uuid (); - string n = recipient.subject_common_name (); + auto n = recipient.subject_common_name (); if (n.find (".") != string::npos) { n = n.substr (n.find (".") + 1); } kre.authorized_device_info->device_list_description = n; - if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) { + if (formulation == Formulation::MODIFIED_TRANSITIONAL_1 || formulation == Formulation::DCI_ANY) { /* Use the "assume trust" thumbprint */ kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk="); - } else if (formulation == MULTIPLE_MODIFIED_TRANSITIONAL_1 || formulation == DCI_SPECIFIC) { + } else if (formulation == Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1 || formulation == Formulation::DCI_SPECIFIC) { if (trusted_devices.empty ()) { /* Fall back on the "assume trust" thumbprint so we - can generate "modified-transitional-1" KDMs - together with "multiple-modified-transitional-1" - KDMs in one go, and similarly for "dci-any" etc. + can generate "modified-transitional-1" KDMs + together with "multiple-modified-transitional-1" + KDMs in one go, and similarly for "dci-any" etc. */ kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk="); } else { /* As I read the standard we should use the - recipient /and/ other trusted device thumbprints - here. MJD reports that this doesn't work with - his setup; a working KDM does not include the - recipient's thumbprint (recipient.thumbprint()). - Waimea uses only the trusted devices here, too. + recipient /and/ other trusted device thumbprints + here. MJD reports that this doesn't work with + his setup; a working KDM does not include the + recipient's thumbprint (recipient.thumbprint()). + Waimea uses only the trusted devices here, too. */ - BOOST_FOREACH (Certificate const & i, trusted_devices) { - kre.authorized_device_info->certificate_thumbprints.push_back (i.thumbprint ()); + for (auto i: trusted_devices) { + kre.authorized_device_info->certificate_thumbprints.push_back(i); } } } } - for (list >::const_iterator i = key_ids.begin(); i != key_ids.end(); ++i) { - kre.key_id_list.typed_key_id.push_back (data::TypedKeyId (i->first, i->second)); + for (auto i: key_ids) { + kre.key_id_list.typed_key_id.push_back(data::TypedKeyId(i.first, i.second)); } _data->authenticated_private.encrypted_key = keys; /* Read the XML so far and sign it */ - shared_ptr doc = _data->as_xml (); - xmlpp::Node::NodeList children = doc->get_root_node()->get_children (); - for (xmlpp::Node::NodeList::const_iterator i = children.begin(); i != children.end(); ++i) { - if ((*i)->get_name() == "Signature") { - signer->add_signature_value (*i, "ds"); + auto doc = _data->as_xml (); + for (auto i: doc->get_root_node()->get_children()) { + if (i->get_name() == "Signature") { + signer->add_signature_value(dynamic_cast(i), "ds", false); } } /* Read the bits that add_signature_value did back into our variables */ - shared_ptr signed_doc (new cxml::Node (doc->get_root_node ())); + auto signed_doc = make_shared(doc->get_root_node()); _data->signature = data::Signature (signed_doc->node_child ("Signature")); } + EncryptedKDM::EncryptedKDM (EncryptedKDM const & other) : _data (new data::EncryptedKDMData (*other._data)) { } + EncryptedKDM & EncryptedKDM::operator= (EncryptedKDM const & other) { @@ -651,80 +722,111 @@ EncryptedKDM::operator= (EncryptedKDM const & other) return *this; } + EncryptedKDM::~EncryptedKDM () { delete _data; } + void EncryptedKDM::as_xml (boost::filesystem::path path) const { - FILE* f = fopen_boost (path, "w"); - string const x = as_xml (); - fwrite (x.c_str(), 1, x.length(), f); + auto f = fopen_boost (path, "w"); + if (!f) { + throw FileError ("Could not open KDM file for writing", path, errno); + } + auto const x = as_xml (); + size_t const written = fwrite (x.c_str(), 1, x.length(), f); fclose (f); + if (written != x.length()) { + throw FileError ("Could not write to KDM file", path, errno); + } } + string EncryptedKDM::as_xml () const { return _data->as_xml()->write_to_string ("UTF-8"); } -list + +vector EncryptedKDM::keys () const { return _data->authenticated_private.encrypted_key; } + string EncryptedKDM::id () const { return _data->authenticated_public.message_id; } + optional EncryptedKDM::annotation_text () const { return _data->authenticated_public.annotation_text; } + string EncryptedKDM::content_title_text () const { return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text; } + string EncryptedKDM::cpl_id () const { return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id; } + string EncryptedKDM::issue_date () const { return _data->authenticated_public.issue_date; } + LocalTime EncryptedKDM::not_valid_before () const { return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before; } + LocalTime EncryptedKDM::not_valid_after () const { return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after; } + string EncryptedKDM::recipient_x509_subject_name () const { return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name; } + +CertificateChain +EncryptedKDM::signer_certificate_chain () const +{ + CertificateChain chain; + for (auto const& i: _data->signature.x509_data) { + string s = "-----BEGIN CERTIFICATE-----\n" + i.x509_certificate + "\n-----END CERTIFICATE-----"; + chain.add (Certificate(s)); + } + return chain; +} + + bool dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b) {