44 #include "compose.hpp"
45 #include <libcxml/cxml.h>
46 #include <libxml++/document.h>
47 #include <libxml++/nodes/element.h>
48 #include <libxml/parser.h>
49 #include <boost/algorithm/string.hpp>
50 #include <boost/date_time/posix_time/posix_time.hpp>
51 #include <boost/format.hpp>
57 using std::make_shared;
60 using std::shared_ptr;
61 using boost::optional;
62 using boost::starts_with;
78 explicit Signer (shared_ptr<const cxml::Node> node)
79 : x509_issuer_name (node->string_child (
"X509IssuerName"))
80 , x509_serial_number (node->string_child (
"X509SerialNumber"))
85 void as_xml (xmlpp::Element* node)
const
87 node->add_child(
"X509IssuerName",
"ds")->add_child_text (x509_issuer_name);
88 node->add_child(
"X509SerialNumber",
"ds")->add_child_text (x509_serial_number);
91 string x509_issuer_name;
92 string x509_serial_number;
101 explicit X509Data (std::shared_ptr<const cxml::Node> node)
102 : x509_issuer_serial (Signer (node->node_child (
"X509IssuerSerial")))
103 , x509_certificate (node->string_child (
"X509Certificate"))
108 void as_xml (xmlpp::Element* node)
const
110 x509_issuer_serial.as_xml (node->add_child (
"X509IssuerSerial",
"ds"));
111 node->add_child(
"X509Certificate",
"ds")->add_child_text (x509_certificate);
114 Signer x509_issuer_serial;
115 std::string x509_certificate;
124 explicit Reference (
string u)
128 explicit Reference (shared_ptr<const cxml::Node> node)
129 : uri (node->string_attribute (
"URI"))
130 , digest_value (node->string_child (
"DigestValue"))
135 void as_xml (xmlpp::Element* node)
const
137 node->set_attribute (
"URI", uri);
138 node->add_child(
"DigestMethod",
"ds")->set_attribute (
"Algorithm",
"http://www.w3.org/2001/04/xmlenc#sha256");
139 node->add_child(
"DigestValue",
"ds")->add_child_text (digest_value);
151 : authenticated_public (
"#ID_AuthenticatedPublic")
152 , authenticated_private (
"#ID_AuthenticatedPrivate")
155 explicit SignedInfo (shared_ptr<const cxml::Node> node)
157 for (
auto i: node->node_children (
"Reference")) {
158 if (i->string_attribute(
"URI") ==
"#ID_AuthenticatedPublic") {
159 authenticated_public = Reference(i);
160 }
else if (i->string_attribute(
"URI") ==
"#ID_AuthenticatedPrivate") {
161 authenticated_private = Reference(i);
168 void as_xml (xmlpp::Element* node)
const
170 node->add_child (
"CanonicalizationMethod",
"ds")->set_attribute (
171 "Algorithm",
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
174 node->add_child (
"SignatureMethod",
"ds")->set_attribute (
175 "Algorithm",
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
178 authenticated_public.as_xml (node->add_child (
"Reference",
"ds"));
179 authenticated_private.as_xml (node->add_child (
"Reference",
"ds"));
183 Reference authenticated_public;
184 Reference authenticated_private;
193 explicit Signature (shared_ptr<const cxml::Node> node)
194 : signed_info (node->node_child (
"SignedInfo"))
195 , signature_value (node->string_child (
"SignatureValue"))
197 for (
auto i: node->node_child(
"KeyInfo")->node_children (
"X509Data")) {
198 x509_data.push_back(X509Data(i));
202 void as_xml (xmlpp::Node* node)
const
204 signed_info.as_xml (node->add_child (
"SignedInfo",
"ds"));
205 node->add_child(
"SignatureValue",
"ds")->add_child_text (signature_value);
207 auto key_info_node = node->add_child(
"KeyInfo",
"ds");
208 for (
auto i: x509_data) {
209 i.as_xml (key_info_node->add_child(
"X509Data",
"ds"));
213 SignedInfo signed_info;
214 string signature_value;
215 vector<X509Data> x509_data;
219 class AuthenticatedPrivate
222 AuthenticatedPrivate () {}
224 explicit AuthenticatedPrivate (shared_ptr<const cxml::Node> node)
226 for (
auto i: node->node_children (
"EncryptedKey")) {
227 encrypted_key.push_back (i->node_child(
"CipherData")->string_child(
"CipherValue"));
231 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references)
const
233 references[
"ID_AuthenticatedPrivate"] = node->set_attribute (
"Id",
"ID_AuthenticatedPrivate");
235 for (
auto i: encrypted_key) {
236 auto encrypted_key = node->add_child (
"EncryptedKey",
"enc");
238 encrypted_key->set_namespace_declaration (
"http://www.w3.org/2001/04/xmlenc#",
"enc");
239 auto encryption_method = encrypted_key->add_child(
"EncryptionMethod",
"enc");
240 encryption_method->set_attribute (
"Algorithm",
"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
241 auto digest_method = encryption_method->add_child (
"DigestMethod",
"ds");
243 digest_method->set_namespace_declaration (
"http://www.w3.org/2000/09/xmldsig#",
"ds");
244 digest_method->set_attribute (
"Algorithm",
"http://www.w3.org/2000/09/xmldsig#sha1");
245 auto cipher_data = encrypted_key->add_child(
"CipherData",
"enc");
246 cipher_data->add_child(
"CipherValue",
"enc")->add_child_text (i);
250 vector<string> encrypted_key;
259 explicit TypedKeyId (shared_ptr<const cxml::Node> node)
260 : key_type (node->string_child (
"KeyType"))
261 , key_id (remove_urn_uuid (node->string_child (
"KeyId")))
266 TypedKeyId (
string type,
string id)
271 void as_xml (xmlpp::Element* node)
const
273 auto type = node->add_child(
"KeyType");
274 type->add_child_text (key_type);
275 node->add_child(
"KeyId")->add_child_text (
"urn:uuid:" + key_id);
277 if (key_type ==
"MDEK") {
278 type->set_attribute (
"scope",
"http://www.dolby.com/cp850/2012/KDM#kdm-key-type");
280 type->set_attribute (
"scope",
"http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
294 explicit KeyIdList (shared_ptr<const cxml::Node> node)
296 for (
auto i: node->node_children (
"TypedKeyId")) {
297 typed_key_id.push_back(TypedKeyId(i));
301 void as_xml (xmlpp::Element* node)
const
303 for (
auto const& i: typed_key_id) {
304 i.as_xml (node->add_child(
"TypedKeyId"));
308 vector<TypedKeyId> typed_key_id;
312 class AuthorizedDeviceInfo
315 AuthorizedDeviceInfo () {}
317 explicit AuthorizedDeviceInfo (shared_ptr<const cxml::Node> node)
318 : device_list_identifier (remove_urn_uuid (node->string_child (
"DeviceListIdentifier")))
319 , device_list_description (node->optional_string_child (
"DeviceListDescription"))
321 for (
auto i: node->node_child(
"DeviceList")->node_children(
"CertificateThumbprint")) {
322 certificate_thumbprints.push_back (i->content ());
326 void as_xml (xmlpp::Element* node)
const
328 node->add_child (
"DeviceListIdentifier")->add_child_text (
"urn:uuid:" + device_list_identifier);
329 if (device_list_description) {
330 node->add_child (
"DeviceListDescription")->add_child_text (device_list_description.get());
332 auto device_list = node->add_child (
"DeviceList");
333 for (
auto i: certificate_thumbprints) {
334 device_list->add_child(
"CertificateThumbprint")->add_child_text (i);
339 string device_list_identifier;
340 boost::optional<string> device_list_description;
341 std::vector<string> certificate_thumbprints;
345 class X509IssuerSerial
348 X509IssuerSerial () {}
350 explicit X509IssuerSerial (shared_ptr<const cxml::Node> node)
351 : x509_issuer_name (node->string_child (
"X509IssuerName"))
352 , x509_serial_number (node->string_child (
"X509SerialNumber"))
357 void as_xml (xmlpp::Element* node)
const
359 node->add_child(
"X509IssuerName",
"ds")->add_child_text (x509_issuer_name);
360 node->add_child(
"X509SerialNumber",
"ds")->add_child_text (x509_serial_number);
363 string x509_issuer_name;
364 string x509_serial_number;
373 explicit Recipient (shared_ptr<const cxml::Node> node)
374 : x509_issuer_serial (node->node_child (
"X509IssuerSerial"))
375 , x509_subject_name (node->string_child (
"X509SubjectName"))
380 void as_xml (xmlpp::Element* node)
const
382 x509_issuer_serial.as_xml (node->add_child (
"X509IssuerSerial"));
383 node->add_child(
"X509SubjectName")->add_child_text (x509_subject_name);
386 X509IssuerSerial x509_issuer_serial;
387 string x509_subject_name;
391 class KDMRequiredExtensions
394 KDMRequiredExtensions () {}
396 explicit KDMRequiredExtensions (shared_ptr<const cxml::Node> node)
397 : recipient (node->node_child (
"Recipient"))
398 , composition_playlist_id (remove_urn_uuid (node->string_child (
"CompositionPlaylistId")))
399 , content_title_text (node->string_child (
"ContentTitleText"))
400 , not_valid_before (node->string_child (
"ContentKeysNotValidBefore"))
401 , not_valid_after (node->string_child (
"ContentKeysNotValidAfter"))
402 , authorized_device_info (node->node_child (
"AuthorizedDeviceInfo"))
403 , key_id_list (node->node_child (
"KeyIdList"))
405 disable_forensic_marking_picture =
false;
406 disable_forensic_marking_audio = optional<int>();
407 if (node->optional_node_child(
"ForensicMarkFlagList")) {
408 for (
auto i: node->node_child(
"ForensicMarkFlagList")->node_children(
"ForensicMarkFlag")) {
409 if (i->content() == picture_disable) {
410 disable_forensic_marking_picture =
true;
411 }
else if (starts_with(i->content(), audio_disable)) {
412 disable_forensic_marking_audio = 0;
413 string const above = audio_disable +
"-above-channel-";
414 if (starts_with(i->content(), above)) {
415 auto above_number = i->content().substr(above.length());
416 if (above_number ==
"") {
419 disable_forensic_marking_audio = atoi(above_number.c_str());
426 void as_xml (xmlpp::Element* node)
const
428 node->set_attribute (
"xmlns",
"http://www.smpte-ra.org/schemas/430-1/2006/KDM");
430 recipient.as_xml (node->add_child (
"Recipient"));
431 node->add_child(
"CompositionPlaylistId")->add_child_text (
"urn:uuid:" + composition_playlist_id);
432 node->add_child(
"ContentTitleText")->add_child_text (content_title_text);
433 if (content_authenticator) {
434 node->add_child(
"ContentAuthenticator")->add_child_text (content_authenticator.get ());
436 node->add_child(
"ContentKeysNotValidBefore")->add_child_text (not_valid_before.as_string ());
437 node->add_child(
"ContentKeysNotValidAfter")->add_child_text (not_valid_after.as_string ());
438 if (authorized_device_info) {
439 authorized_device_info->as_xml (node->add_child (
"AuthorizedDeviceInfo"));
441 key_id_list.as_xml (node->add_child (
"KeyIdList"));
443 if (disable_forensic_marking_picture || disable_forensic_marking_audio) {
444 auto forensic_mark_flag_list = node->add_child (
"ForensicMarkFlagList");
445 if (disable_forensic_marking_picture) {
446 forensic_mark_flag_list->add_child(
"ForensicMarkFlag")->add_child_text(picture_disable);
448 if (disable_forensic_marking_audio) {
449 auto mrkflg = audio_disable;
450 if (*disable_forensic_marking_audio > 0) {
451 mrkflg += String::compose (
"-above-channel-%1", *disable_forensic_marking_audio);
453 forensic_mark_flag_list->add_child(
"ForensicMarkFlag")->add_child_text (mrkflg);
459 string composition_playlist_id;
460 boost::optional<string> content_authenticator;
461 string content_title_text;
464 bool disable_forensic_marking_picture;
465 optional<int> disable_forensic_marking_audio;
466 boost::optional<AuthorizedDeviceInfo> authorized_device_info;
467 KeyIdList key_id_list;
470 static const string picture_disable;
471 static const string audio_disable;
475 const string KDMRequiredExtensions::picture_disable =
"http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable";
476 const string KDMRequiredExtensions::audio_disable =
"http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable";
479 class RequiredExtensions
482 RequiredExtensions () {}
484 explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
485 : kdm_required_extensions (node->node_child (
"KDMRequiredExtensions"))
490 void as_xml (xmlpp::Element* node)
const
492 kdm_required_extensions.as_xml (node->add_child (
"KDMRequiredExtensions"));
495 KDMRequiredExtensions kdm_required_extensions;
499 class AuthenticatedPublic
502 AuthenticatedPublic ()
503 : message_id (make_uuid ())
505 , annotation_text (
"none")
509 explicit AuthenticatedPublic (shared_ptr<const cxml::Node> node)
510 : message_id (remove_urn_uuid (node->string_child (
"MessageId")))
511 , annotation_text (node->optional_string_child (
"AnnotationText"))
512 , issue_date (node->string_child (
"IssueDate"))
513 , signer (node->node_child (
"Signer"))
514 , required_extensions (node->node_child (
"RequiredExtensions"))
519 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references)
const
521 references[
"ID_AuthenticatedPublic"] = node->set_attribute (
"Id",
"ID_AuthenticatedPublic");
523 node->add_child(
"MessageId")->add_child_text (
"urn:uuid:" + message_id);
524 node->add_child(
"MessageType")->add_child_text (
"http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
525 if (annotation_text) {
526 node->add_child(
"AnnotationText")->add_child_text (annotation_text.get ());
528 node->add_child(
"IssueDate")->add_child_text (issue_date);
530 signer.as_xml (node->add_child (
"Signer"));
531 required_extensions.as_xml (node->add_child (
"RequiredExtensions"));
533 node->add_child (
"NonCriticalExtensions");
537 optional<string> annotation_text;
540 RequiredExtensions required_extensions;
547 class EncryptedKDMData
555 explicit EncryptedKDMData (shared_ptr<const cxml::Node> node)
556 : authenticated_public (node->node_child (
"AuthenticatedPublic"))
557 , authenticated_private (node->node_child (
"AuthenticatedPrivate"))
558 , signature (node->node_child (
"Signature"))
563 shared_ptr<xmlpp::Document> as_xml ()
const
565 shared_ptr<xmlpp::Document> document (
new xmlpp::Document ());
566 xmlpp::Element* root = document->create_root_node (
"DCinemaSecurityMessage",
"http://www.smpte-ra.org/schemas/430-3/2006/ETM");
567 root->set_namespace_declaration (
"http://www.w3.org/2000/09/xmldsig#",
"ds");
568 root->set_namespace_declaration (
"http://www.w3.org/2001/04/xmlenc#",
"enc");
569 map<string, xmlpp::Attribute *> references;
570 authenticated_public.as_xml (root->add_child (
"AuthenticatedPublic"), references);
571 authenticated_private.as_xml (root->add_child (
"AuthenticatedPrivate"), references);
572 signature.as_xml (root->add_child (
"Signature",
"ds"));
574 for (
auto i: references) {
575 xmlAddID (0, document->cobj(), (
const xmlChar *) i.first.c_str(), i.second->cobj());
578 indent (document->get_root_node(), 0);
582 AuthenticatedPublic authenticated_public;
583 AuthenticatedPrivate authenticated_private;
592 EncryptedKDM::EncryptedKDM (
string s)
595 auto doc = make_shared<cxml::Document>(
"DCinemaSecurityMessage");
596 doc->read_string (s);
597 _data =
new data::EncryptedKDMData (doc);
598 }
catch (xmlpp::parse_error& e) {
604 EncryptedKDM::EncryptedKDM (
605 shared_ptr<const CertificateChain> signer,
607 vector<string> trusted_devices,
609 string content_title_text,
610 optional<string> annotation_text,
614 bool disable_forensic_marking_picture,
615 optional<int> disable_forensic_marking_audio,
616 vector<pair<string, string>> key_ids,
619 : _data (new data::EncryptedKDMData)
632 auto& aup = _data->authenticated_public;
633 aup.signer.x509_issuer_name = signer->leaf().issuer ();
634 aup.signer.x509_serial_number = signer->leaf().serial ();
635 aup.annotation_text = annotation_text;
637 auto& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
638 kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.
issuer ();
639 kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
640 kre.recipient.x509_subject_name = recipient.subject ();
641 kre.composition_playlist_id = cpl_id;
642 if (formulation == Formulation::DCI_ANY || formulation == Formulation::DCI_SPECIFIC) {
643 kre.content_authenticator = signer->leaf().thumbprint ();
645 kre.content_title_text = content_title_text;
646 kre.not_valid_before = not_valid_before;
647 kre.not_valid_after = not_valid_after;
648 kre.disable_forensic_marking_picture = disable_forensic_marking_picture;
649 kre.disable_forensic_marking_audio = disable_forensic_marking_audio;
652 kre.authorized_device_info = data::AuthorizedDeviceInfo ();
653 kre.authorized_device_info->device_list_identifier = make_uuid ();
654 auto n = recipient.subject_common_name ();
655 if (n.find (
".") != string::npos) {
656 n = n.substr (n.find (
".") + 1);
658 kre.authorized_device_info->device_list_description = n;
660 if (formulation == Formulation::MODIFIED_TRANSITIONAL_1 || formulation == Formulation::DCI_ANY) {
662 kre.authorized_device_info->certificate_thumbprints.push_back (
"2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
663 }
else if (formulation == Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1 || formulation == Formulation::DCI_SPECIFIC) {
664 if (trusted_devices.empty ()) {
670 kre.authorized_device_info->certificate_thumbprints.push_back (
"2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
679 for (
auto i: trusted_devices) {
680 kre.authorized_device_info->certificate_thumbprints.push_back(i);
686 for (
auto i: key_ids) {
687 kre.key_id_list.typed_key_id.push_back(data::TypedKeyId(i.first, i.second));
690 _data->authenticated_private.encrypted_key = keys;
693 auto doc = _data->as_xml ();
694 for (
auto i: doc->get_root_node()->get_children()) {
695 if (i->get_name() ==
"Signature") {
696 signer->add_signature_value(
dynamic_cast<xmlpp::Element*
>(i),
"ds",
false);
701 auto signed_doc = make_shared<cxml::Node>(doc->get_root_node());
702 _data->signature = data::Signature (signed_doc->node_child (
"Signature"));
707 : _data (new data::EncryptedKDMData (*other._data))
716 if (
this == &other) {
721 _data =
new data::EncryptedKDMData (*other._data);
726 EncryptedKDM::~EncryptedKDM ()
737 throw FileError (
"Could not open KDM file for writing", path, errno);
740 size_t const written = fwrite (x.c_str(), 1, x.length(), f);
742 if (written != x.length()) {
743 throw FileError (
"Could not write to KDM file", path, errno);
751 return _data->as_xml()->write_to_string (
"UTF-8");
758 return _data->authenticated_private.encrypted_key;
763 EncryptedKDM::id ()
const
765 return _data->authenticated_public.message_id;
770 EncryptedKDM::annotation_text ()
const
772 return _data->authenticated_public.annotation_text;
777 EncryptedKDM::content_title_text ()
const
779 return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
784 EncryptedKDM::cpl_id ()
const
786 return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
791 EncryptedKDM::issue_date ()
const
793 return _data->authenticated_public.issue_date;
798 EncryptedKDM::not_valid_before ()
const
800 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
805 EncryptedKDM::not_valid_after ()
const
807 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
812 EncryptedKDM::recipient_x509_subject_name ()
const
814 return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name;
819 EncryptedKDM::signer_certificate_chain ()
const
822 for (
auto const& i: _data->signature.x509_data) {
823 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.
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.
FILE * fopen_boost(boost::filesystem::path, std::string)
@ MODIFIED_TRANSITIONAL_TEST
Utility methods and classes.