41 #include "compose.hpp"
46 #include <libcxml/cxml.h>
47 #include <libxml++/attributenode.h>
48 #include <libxml++/document.h>
49 #include <libxml++/nodes/element.h>
50 #include <libxml/parser.h>
51 #include <boost/algorithm/string.hpp>
52 #include <boost/date_time/posix_time/posix_time.hpp>
53 #include <boost/format.hpp>
59 using std::make_shared;
62 using std::shared_ptr;
63 using boost::optional;
64 using boost::starts_with;
80 explicit Signer (shared_ptr<const cxml::Node> node)
81 : x509_issuer_name (node->string_child (
"X509IssuerName"))
82 , x509_serial_number (node->string_child (
"X509SerialNumber"))
87 void as_xml (xmlpp::Element* node)
const
89 cxml::add_child(node,
"X509IssuerName",
string(
"ds"))->add_child_text(x509_issuer_name);
90 cxml::add_child(node,
"X509SerialNumber",
string(
"ds"))->add_child_text(x509_serial_number);
93 string x509_issuer_name;
94 string x509_serial_number;
103 explicit X509Data (std::shared_ptr<const cxml::Node> node)
104 : x509_issuer_serial (Signer (node->node_child (
"X509IssuerSerial")))
105 , x509_certificate (node->string_child (
"X509Certificate"))
110 void as_xml (xmlpp::Element* node)
const
112 x509_issuer_serial.as_xml(cxml::add_child(node,
"X509IssuerSerial",
string(
"ds")));
113 cxml::add_child(node,
"X509Certificate",
string(
"ds"))->add_child_text(x509_certificate);
116 Signer x509_issuer_serial;
117 std::string x509_certificate;
126 explicit Reference (
string u)
130 explicit Reference (shared_ptr<const cxml::Node> node)
131 : uri (node->string_attribute (
"URI"))
132 , digest_value (node->string_child (
"DigestValue"))
137 void as_xml (xmlpp::Element* node)
const
139 node->set_attribute (
"URI", uri);
140 cxml::add_child(node,
"DigestMethod",
string(
"ds"))->set_attribute(
"Algorithm",
"http://www.w3.org/2001/04/xmlenc#sha256");
141 cxml::add_child(node,
"DigestValue",
string(
"ds"))->add_child_text(digest_value);
153 : authenticated_public (
"#ID_AuthenticatedPublic")
154 , authenticated_private (
"#ID_AuthenticatedPrivate")
157 explicit SignedInfo (shared_ptr<const cxml::Node> node)
159 for (
auto i: node->node_children (
"Reference")) {
160 if (i->string_attribute(
"URI") ==
"#ID_AuthenticatedPublic") {
161 authenticated_public = Reference(i);
162 }
else if (i->string_attribute(
"URI") ==
"#ID_AuthenticatedPrivate") {
163 authenticated_private = Reference(i);
170 void as_xml (xmlpp::Element* node)
const
172 cxml::add_child(node,
"CanonicalizationMethod",
string(
"ds"))->set_attribute(
173 "Algorithm",
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
176 cxml::add_child(node,
"SignatureMethod",
string(
"ds"))->set_attribute(
177 "Algorithm",
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
180 authenticated_public.as_xml(cxml::add_child(node,
"Reference",
string(
"ds")));
181 authenticated_private.as_xml(cxml::add_child(node,
"Reference",
string(
"ds")));
185 Reference authenticated_public;
186 Reference authenticated_private;
195 explicit Signature (shared_ptr<const cxml::Node> node)
196 : signed_info (node->node_child (
"SignedInfo"))
197 , signature_value (node->string_child (
"SignatureValue"))
199 for (
auto i: node->node_child(
"KeyInfo")->node_children (
"X509Data")) {
200 x509_data.push_back(X509Data(i));
204 void as_xml(xmlpp::Element* element)
const
206 signed_info.as_xml(cxml::add_child(element,
"SignedInfo",
string(
"ds")));
207 cxml::add_child(element,
"SignatureValue",
string(
"ds"))->add_child_text(signature_value);
209 auto key_info_node = cxml::add_child(element,
"KeyInfo",
string(
"ds"));
210 for (
auto i: x509_data) {
211 i.as_xml(cxml::add_child(key_info_node,
"X509Data",
string(
"ds")));
215 SignedInfo signed_info;
216 string signature_value;
217 vector<X509Data> x509_data;
221 class AuthenticatedPrivate
224 AuthenticatedPrivate () {}
226 explicit AuthenticatedPrivate (shared_ptr<const cxml::Node> node)
228 for (
auto i: node->node_children (
"EncryptedKey")) {
229 encrypted_key.push_back (i->node_child(
"CipherData")->string_child(
"CipherValue"));
233 void as_xml (xmlpp::Element* node, map<string, xmlpp::AttributeNode*>& references)
const
235 references[
"ID_AuthenticatedPrivate"] =
dynamic_cast<xmlpp::AttributeNode*
>(node->set_attribute(
"Id",
"ID_AuthenticatedPrivate"));
237 for (
auto i: encrypted_key) {
238 auto encrypted_key = cxml::add_child(node,
"EncryptedKey",
string(
"enc"));
240 encrypted_key->set_namespace_declaration (
"http://www.w3.org/2001/04/xmlenc#",
"enc");
241 auto encryption_method = cxml::add_child(encrypted_key,
"EncryptionMethod",
string(
"enc"));
242 encryption_method->set_attribute (
"Algorithm",
"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
243 auto digest_method = cxml::add_child(encryption_method,
"DigestMethod",
string(
"ds"));
245 digest_method->set_namespace_declaration (
"http://www.w3.org/2000/09/xmldsig#",
"ds");
246 digest_method->set_attribute (
"Algorithm",
"http://www.w3.org/2000/09/xmldsig#sha1");
247 auto cipher_data = cxml::add_child(encrypted_key,
"CipherData",
string(
"enc"));
248 cxml::add_child(cipher_data,
"CipherValue",
string(
"enc"))->add_child_text(i);
252 vector<string> encrypted_key;
261 explicit TypedKeyId (shared_ptr<const cxml::Node> node)
262 : key_type (node->string_child (
"KeyType"))
263 , key_id (remove_urn_uuid (node->string_child (
"KeyId")))
268 TypedKeyId (
string type,
string id)
273 void as_xml (xmlpp::Element* node)
const
275 auto type = cxml::add_child(node,
"KeyType");
276 type->add_child_text (key_type);
277 cxml::add_text_child(node,
"KeyId",
"urn:uuid:" + key_id);
279 if (key_type ==
"MDEK") {
280 type->set_attribute (
"scope",
"http://www.dolby.com/cp850/2012/KDM#kdm-key-type");
282 type->set_attribute (
"scope",
"http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
296 explicit KeyIdList (shared_ptr<const cxml::Node> node)
298 for (
auto i: node->node_children (
"TypedKeyId")) {
299 typed_key_id.push_back(TypedKeyId(i));
303 void as_xml (xmlpp::Element* node)
const
305 for (
auto const& i: typed_key_id) {
306 i.as_xml(cxml::add_child(node, (
"TypedKeyId")));
310 vector<TypedKeyId> typed_key_id;
314 class AuthorizedDeviceInfo
317 AuthorizedDeviceInfo () {}
319 explicit AuthorizedDeviceInfo (shared_ptr<const cxml::Node> node)
320 : device_list_identifier (remove_urn_uuid (node->string_child (
"DeviceListIdentifier")))
321 , device_list_description (node->optional_string_child (
"DeviceListDescription"))
323 for (
auto i: node->node_child(
"DeviceList")->node_children(
"CertificateThumbprint")) {
324 certificate_thumbprints.push_back (i->content ());
328 void as_xml (xmlpp::Element* node)
const
330 cxml::add_text_child(node,
"DeviceListIdentifier",
"urn:uuid:" + device_list_identifier);
331 if (device_list_description) {
332 cxml::add_text_child(node,
"DeviceListDescription", device_list_description.get());
334 auto device_list = cxml::add_child(node,
"DeviceList");
335 for (
auto i: certificate_thumbprints) {
336 cxml::add_text_child(device_list,
"CertificateThumbprint", i);
341 string device_list_identifier;
342 boost::optional<string> device_list_description;
343 std::vector<string> certificate_thumbprints;
347 class X509IssuerSerial
350 X509IssuerSerial () {}
352 explicit X509IssuerSerial (shared_ptr<const cxml::Node> node)
353 : x509_issuer_name (node->string_child (
"X509IssuerName"))
354 , x509_serial_number (node->string_child (
"X509SerialNumber"))
359 void as_xml (xmlpp::Element* node)
const
361 cxml::add_child(node,
"X509IssuerName",
string(
"ds"))->add_child_text(x509_issuer_name);
362 cxml::add_child(node,
"X509SerialNumber",
string(
"ds"))->add_child_text(x509_serial_number);
365 string x509_issuer_name;
366 string x509_serial_number;
375 explicit Recipient (shared_ptr<const cxml::Node> node)
376 : x509_issuer_serial (node->node_child (
"X509IssuerSerial"))
377 , x509_subject_name (node->string_child (
"X509SubjectName"))
382 void as_xml (xmlpp::Element* node)
const
384 x509_issuer_serial.as_xml(cxml::add_child(node,
"X509IssuerSerial"));
385 cxml::add_text_child(node,
"X509SubjectName", x509_subject_name);
388 X509IssuerSerial x509_issuer_serial;
389 string x509_subject_name;
393 class KDMRequiredExtensions
396 KDMRequiredExtensions () {}
398 explicit KDMRequiredExtensions (shared_ptr<const cxml::Node> node)
399 : recipient (node->node_child (
"Recipient"))
400 , composition_playlist_id (remove_urn_uuid (node->string_child (
"CompositionPlaylistId")))
401 , content_title_text (node->string_child (
"ContentTitleText"))
402 , not_valid_before (node->string_child (
"ContentKeysNotValidBefore"))
403 , not_valid_after (node->string_child (
"ContentKeysNotValidAfter"))
404 , authorized_device_info (node->node_child (
"AuthorizedDeviceInfo"))
405 , key_id_list (node->node_child (
"KeyIdList"))
407 disable_forensic_marking_picture =
false;
408 disable_forensic_marking_audio = optional<int>();
409 if (node->optional_node_child(
"ForensicMarkFlagList")) {
410 for (
auto i: node->node_child(
"ForensicMarkFlagList")->node_children(
"ForensicMarkFlag")) {
411 if (i->content() == picture_disable) {
412 disable_forensic_marking_picture =
true;
413 }
else if (starts_with(i->content(), audio_disable)) {
414 disable_forensic_marking_audio = 0;
415 string const above = audio_disable +
"-above-channel-";
416 if (starts_with(i->content(), above)) {
417 auto above_number = i->content().substr(above.length());
418 if (above_number ==
"") {
421 disable_forensic_marking_audio = atoi(above_number.c_str());
428 void as_xml (xmlpp::Element* node)
const
430 node->set_attribute (
"xmlns",
"http://www.smpte-ra.org/schemas/430-1/2006/KDM");
432 recipient.as_xml(cxml::add_child(node,
"Recipient"));
433 cxml::add_text_child(node,
"CompositionPlaylistId",
"urn:uuid:" + composition_playlist_id);
434 cxml::add_text_child(node,
"ContentTitleText", content_title_text);
435 if (content_authenticator) {
436 cxml::add_text_child(node,
"ContentAuthenticator", content_authenticator.get());
438 cxml::add_text_child(node,
"ContentKeysNotValidBefore", not_valid_before.as_string());
439 cxml::add_text_child(node,
"ContentKeysNotValidAfter", not_valid_after.as_string());
440 if (authorized_device_info) {
441 authorized_device_info->as_xml(cxml::add_child(node,
"AuthorizedDeviceInfo"));
443 key_id_list.as_xml(cxml::add_child(node,
"KeyIdList"));
445 if (disable_forensic_marking_picture || disable_forensic_marking_audio) {
446 auto forensic_mark_flag_list = cxml::add_child(node,
"ForensicMarkFlagList");
447 if (disable_forensic_marking_picture) {
448 cxml::add_text_child(forensic_mark_flag_list,
"ForensicMarkFlag", picture_disable);
450 if (disable_forensic_marking_audio) {
451 auto mrkflg = audio_disable;
452 if (*disable_forensic_marking_audio > 0) {
453 mrkflg += String::compose (
"-above-channel-%1", *disable_forensic_marking_audio);
455 cxml::add_text_child(forensic_mark_flag_list,
"ForensicMarkFlag", mrkflg);
461 string composition_playlist_id;
462 boost::optional<string> content_authenticator;
463 string content_title_text;
466 bool disable_forensic_marking_picture;
467 optional<int> disable_forensic_marking_audio;
468 boost::optional<AuthorizedDeviceInfo> authorized_device_info;
469 KeyIdList key_id_list;
472 static const string picture_disable;
473 static const string audio_disable;
477 const string KDMRequiredExtensions::picture_disable =
"http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable";
478 const string KDMRequiredExtensions::audio_disable =
"http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable";
481 class RequiredExtensions
484 RequiredExtensions () {}
486 explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
487 : kdm_required_extensions (node->node_child (
"KDMRequiredExtensions"))
492 void as_xml (xmlpp::Element* node)
const
494 kdm_required_extensions.as_xml(cxml::add_child(node,
"KDMRequiredExtensions"));
497 KDMRequiredExtensions kdm_required_extensions;
501 class AuthenticatedPublic
504 AuthenticatedPublic ()
505 : message_id (make_uuid ())
507 , annotation_text (
"none")
511 explicit AuthenticatedPublic (shared_ptr<const cxml::Node> node)
512 : message_id (remove_urn_uuid (node->string_child (
"MessageId")))
513 , annotation_text (node->optional_string_child (
"AnnotationText"))
514 , issue_date (node->string_child (
"IssueDate"))
515 , signer (node->node_child (
"Signer"))
516 , required_extensions (node->node_child (
"RequiredExtensions"))
521 void as_xml (xmlpp::Element* node, map<string, xmlpp::AttributeNode*>& references)
const
523 references[
"ID_AuthenticatedPublic"] =
dynamic_cast<xmlpp::AttributeNode*
>(node->set_attribute(
"Id",
"ID_AuthenticatedPublic"));
525 cxml::add_text_child(node,
"MessageId",
"urn:uuid:" + message_id);
526 cxml::add_text_child(node,
"MessageType",
"http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
527 if (annotation_text) {
528 cxml::add_text_child(node,
"AnnotationText", annotation_text.get());
530 cxml::add_text_child(node,
"IssueDate", issue_date);
532 signer.as_xml(cxml::add_child(node,
"Signer"));
533 required_extensions.as_xml(cxml::add_child(node,
"RequiredExtensions"));
535 cxml::add_child(node,
"NonCriticalExtensions");
539 optional<string> annotation_text;
542 RequiredExtensions required_extensions;
549 class EncryptedKDMData
557 explicit EncryptedKDMData (shared_ptr<const cxml::Node> node)
558 : authenticated_public (node->node_child (
"AuthenticatedPublic"))
559 , authenticated_private (node->node_child (
"AuthenticatedPrivate"))
560 , signature (node->node_child (
"Signature"))
565 shared_ptr<xmlpp::Document> as_xml ()
const
567 auto document = make_shared<xmlpp::Document>();
568 auto root = document->create_root_node(
"DCinemaSecurityMessage",
"http://www.smpte-ra.org/schemas/430-3/2006/ETM");
569 root->set_namespace_declaration (
"http://www.w3.org/2000/09/xmldsig#",
"ds");
570 root->set_namespace_declaration (
"http://www.w3.org/2001/04/xmlenc#",
"enc");
571 map<string, xmlpp::AttributeNode*> references;
572 authenticated_public.as_xml(cxml::add_child(root,
"AuthenticatedPublic"), references);
573 authenticated_private.as_xml(cxml::add_child(root,
"AuthenticatedPrivate"), references);
574 signature.as_xml(cxml::add_child(root,
"Signature",
string(
"ds")));
576 for (
auto i: references) {
577 xmlAddID (0, document->cobj(), (
const xmlChar *) i.first.c_str(), i.second->cobj());
580 indent (document->get_root_node(), 0);
584 AuthenticatedPublic authenticated_public;
585 AuthenticatedPrivate authenticated_private;
594 EncryptedKDM::EncryptedKDM (
string s)
597 auto doc = make_shared<cxml::Document>(
"DCinemaSecurityMessage");
598 doc->read_string (s);
599 _data =
new data::EncryptedKDMData (doc);
600 }
catch (xmlpp::parse_error& e) {
602 }
catch (xmlpp::internal_error& e) {
604 }
catch (cxml::Error& e) {
610 EncryptedKDM::EncryptedKDM (
611 shared_ptr<const CertificateChain> signer,
613 vector<string> trusted_devices,
615 string content_title_text,
616 optional<string> annotation_text,
619 Formulation formulation,
620 bool disable_forensic_marking_picture,
621 optional<int> disable_forensic_marking_audio,
622 vector<pair<string, string>> key_ids,
625 : _data (new data::EncryptedKDMData)
638 auto& aup = _data->authenticated_public;
639 aup.signer.x509_issuer_name = signer->leaf().issuer ();
640 aup.signer.x509_serial_number = signer->leaf().serial ();
641 aup.annotation_text = annotation_text;
643 auto& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
644 kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.
issuer ();
645 kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
646 kre.recipient.x509_subject_name = recipient.subject ();
647 kre.composition_playlist_id = cpl_id;
648 if (formulation == Formulation::DCI_ANY || formulation == Formulation::DCI_SPECIFIC) {
649 kre.content_authenticator = signer->leaf().thumbprint ();
651 kre.content_title_text = content_title_text;
652 kre.not_valid_before = not_valid_before;
653 kre.not_valid_after = not_valid_after;
654 kre.disable_forensic_marking_picture = disable_forensic_marking_picture;
655 kre.disable_forensic_marking_audio = disable_forensic_marking_audio;
657 kre.authorized_device_info = data::AuthorizedDeviceInfo ();
658 kre.authorized_device_info->device_list_identifier = make_uuid ();
659 auto n = recipient.subject_common_name ();
660 if (n.find (
".") != string::npos) {
661 n = n.substr (n.find (
".") + 1);
663 kre.authorized_device_info->device_list_description = n;
665 if (formulation == Formulation::MODIFIED_TRANSITIONAL_1 || formulation == Formulation::DCI_ANY) {
667 kre.authorized_device_info->certificate_thumbprints.push_back (
"2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
668 }
else if (formulation == Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1 || formulation == Formulation::DCI_SPECIFIC) {
669 if (trusted_devices.empty ()) {
675 kre.authorized_device_info->certificate_thumbprints.push_back (
"2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
684 for (
auto i: trusted_devices) {
685 kre.authorized_device_info->certificate_thumbprints.push_back(i);
690 for (
auto i: key_ids) {
691 kre.key_id_list.typed_key_id.push_back(data::TypedKeyId(i.first, i.second));
694 _data->authenticated_private.encrypted_key = keys;
697 auto doc = _data->as_xml ();
698 for (
auto i: doc->get_root_node()->get_children()) {
699 if (i->get_name() ==
"Signature") {
700 signer->add_signature_value(
dynamic_cast<xmlpp::Element*
>(i),
"ds",
false);
705 auto signed_doc = make_shared<cxml::Node>(doc->get_root_node());
706 _data->signature = data::Signature (signed_doc->node_child (
"Signature"));
711 : _data (new data::EncryptedKDMData (*other._data))
720 if (
this == &other) {
725 _data =
new data::EncryptedKDMData (*other._data);
730 EncryptedKDM::~EncryptedKDM ()
741 throw FileError (
"Could not open KDM file for writing", path, errno);
744 if (f.
write(x.c_str(), 1, x.length()) != x.length()) {
745 throw FileError (
"Could not write to KDM file", path, errno);
753 return _data->as_xml()->write_to_string (
"UTF-8");
760 return _data->authenticated_private.encrypted_key;
765 EncryptedKDM::id ()
const
767 return _data->authenticated_public.message_id;
772 EncryptedKDM::annotation_text ()
const
774 return _data->authenticated_public.annotation_text;
779 EncryptedKDM::content_title_text ()
const
781 return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
786 EncryptedKDM::cpl_id ()
const
788 return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
793 EncryptedKDM::issue_date ()
const
795 return _data->authenticated_public.issue_date;
800 EncryptedKDM::not_valid_before ()
const
802 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
807 EncryptedKDM::not_valid_after ()
const
809 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
814 EncryptedKDM::recipient_x509_subject_name ()
const
816 return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name;
821 EncryptedKDM::signer_certificate_chain ()
const
824 for (
auto const& i: _data->signature.x509_data) {
825 string s =
"-----BEGIN CERTIFICATE-----\n" + i.x509_certificate +
"\n-----END CERTIFICATE-----";
A chain of any number of certificates, from root to leaf.
A wrapper for an X509 certificate.
std::string issuer() const
std::string as_xml() const
std::vector< std::string > keys() const
void as_xml(boost::filesystem::path file) const
An exception related to a file.
size_t write(const void *ptr, size_t size, size_t nmemb)
A representation of a local time (down to the second), including its offset from GMT (equivalent to x...
Exceptions thrown by libdcp.
Namespace for everything in libdcp.
Utility methods and classes.