2 Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
34 #include "encrypted_kdm.h"
36 #include "certificate_chain.h"
37 #include "exceptions.h"
38 #include "compose.hpp"
39 #include <libcxml/cxml.h>
40 #include <libxml++/document.h>
41 #include <libxml++/nodes/element.h>
42 #include <libxml/parser.h>
43 #include <boost/algorithm/string.hpp>
44 #include <boost/date_time/posix_time/posix_time.hpp>
45 #include <boost/format.hpp>
50 using std::make_shared;
53 using std::shared_ptr;
54 using boost::optional;
55 using boost::starts_with;
60 /** Namespace for classes used to hold our data; they are internal to this .cc file */
68 explicit Signer (shared_ptr<const cxml::Node> node)
69 : x509_issuer_name (node->string_child ("X509IssuerName"))
70 , x509_serial_number (node->string_child ("X509SerialNumber"))
75 void as_xml (xmlpp::Element* node) const
77 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
78 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
81 string x509_issuer_name;
82 string x509_serial_number;
90 explicit X509Data (std::shared_ptr<const cxml::Node> node)
91 : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial")))
92 , x509_certificate (node->string_child ("X509Certificate"))
97 void as_xml (xmlpp::Element* node) const
99 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds"));
100 node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate);
103 Signer x509_issuer_serial;
104 std::string x509_certificate;
112 explicit Reference (string u)
116 explicit Reference (shared_ptr<const cxml::Node> node)
117 : uri (node->string_attribute ("URI"))
118 , digest_value (node->string_child ("DigestValue"))
123 void as_xml (xmlpp::Element* node) const
125 node->set_attribute ("URI", uri);
126 node->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
127 node->add_child("DigestValue", "ds")->add_child_text (digest_value);
138 : authenticated_public ("#ID_AuthenticatedPublic")
139 , authenticated_private ("#ID_AuthenticatedPrivate")
142 explicit SignedInfo (shared_ptr<const cxml::Node> node)
144 for (auto i: node->node_children ("Reference")) {
145 if (i->string_attribute("URI") == "#ID_AuthenticatedPublic") {
146 authenticated_public = Reference(i);
147 } else if (i->string_attribute("URI") == "#ID_AuthenticatedPrivate") {
148 authenticated_private = Reference(i);
151 /* XXX: do something if we don't recognise the node */
155 void as_xml (xmlpp::Element* node) const
157 node->add_child ("CanonicalizationMethod", "ds")->set_attribute (
158 "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
161 node->add_child ("SignatureMethod", "ds")->set_attribute (
162 "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
165 authenticated_public.as_xml (node->add_child ("Reference", "ds"));
166 authenticated_private.as_xml (node->add_child ("Reference", "ds"));
170 Reference authenticated_public;
171 Reference authenticated_private;
179 explicit Signature (shared_ptr<const cxml::Node> node)
180 : signed_info (node->node_child ("SignedInfo"))
181 , signature_value (node->string_child ("SignatureValue"))
183 for (auto i: node->node_child("KeyInfo")->node_children ("X509Data")) {
184 x509_data.push_back(X509Data(i));
188 void as_xml (xmlpp::Node* node) const
190 signed_info.as_xml (node->add_child ("SignedInfo", "ds"));
191 node->add_child("SignatureValue", "ds")->add_child_text (signature_value);
193 auto key_info_node = node->add_child("KeyInfo", "ds");
194 for (auto i: x509_data) {
195 i.as_xml (key_info_node->add_child("X509Data", "ds"));
199 SignedInfo signed_info;
200 string signature_value;
201 vector<X509Data> x509_data;
204 class AuthenticatedPrivate
207 AuthenticatedPrivate () {}
209 explicit AuthenticatedPrivate (shared_ptr<const cxml::Node> node)
211 for (auto i: node->node_children ("EncryptedKey")) {
212 encrypted_key.push_back (i->node_child("CipherData")->string_child("CipherValue"));
216 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
218 references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate");
220 for (auto i: encrypted_key) {
221 auto encrypted_key = node->add_child ("EncryptedKey", "enc");
222 /* XXX: hack for testing with Dolby */
223 encrypted_key->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
224 auto encryption_method = encrypted_key->add_child("EncryptionMethod", "enc");
225 encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
226 auto digest_method = encryption_method->add_child ("DigestMethod", "ds");
227 /* XXX: hack for testing with Dolby */
228 digest_method->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
229 digest_method->set_attribute ("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
230 auto cipher_data = encrypted_key->add_child("CipherData", "enc");
231 cipher_data->add_child("CipherValue", "enc")->add_child_text (i);
235 vector<string> encrypted_key;
243 explicit TypedKeyId (shared_ptr<const cxml::Node> node)
244 : key_type (node->string_child ("KeyType"))
245 , key_id (remove_urn_uuid (node->string_child ("KeyId")))
250 TypedKeyId (string type, string id)
255 void as_xml (xmlpp::Element* node) const
257 xmlpp::Element* type = node->add_child("KeyType");
258 type->add_child_text (key_type);
259 node->add_child("KeyId")->add_child_text ("urn:uuid:" + key_id);
260 /* XXX: this feels like a bit of a hack */
261 if (key_type == "MDEK") {
262 type->set_attribute ("scope", "http://www.dolby.com/cp850/2012/KDM#kdm-key-type");
264 type->set_attribute ("scope", "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
277 explicit KeyIdList (shared_ptr<const cxml::Node> node)
279 for (auto i: node->node_children ("TypedKeyId")) {
280 typed_key_id.push_back(TypedKeyId(i));
284 void as_xml (xmlpp::Element* node) const
286 for (auto const& i: typed_key_id) {
287 i.as_xml (node->add_child("TypedKeyId"));
291 vector<TypedKeyId> typed_key_id;
294 class AuthorizedDeviceInfo
297 AuthorizedDeviceInfo () {}
299 explicit AuthorizedDeviceInfo (shared_ptr<const cxml::Node> node)
300 : device_list_identifier (remove_urn_uuid (node->string_child ("DeviceListIdentifier")))
301 , device_list_description (node->optional_string_child ("DeviceListDescription"))
303 for (auto i: node->node_child("DeviceList")->node_children("CertificateThumbprint")) {
304 certificate_thumbprints.push_back (i->content ());
308 void as_xml (xmlpp::Element* node) const
310 node->add_child ("DeviceListIdentifier")->add_child_text ("urn:uuid:" + device_list_identifier);
311 if (device_list_description) {
312 node->add_child ("DeviceListDescription")->add_child_text (device_list_description.get());
314 auto device_list = node->add_child ("DeviceList");
315 for (auto i: certificate_thumbprints) {
316 device_list->add_child("CertificateThumbprint")->add_child_text (i);
320 /** DeviceListIdentifier without the urn:uuid: prefix */
321 string device_list_identifier;
322 boost::optional<string> device_list_description;
323 std::vector<string> certificate_thumbprints;
326 class X509IssuerSerial
329 X509IssuerSerial () {}
331 explicit X509IssuerSerial (shared_ptr<const cxml::Node> node)
332 : x509_issuer_name (node->string_child ("X509IssuerName"))
333 , x509_serial_number (node->string_child ("X509SerialNumber"))
338 void as_xml (xmlpp::Element* node) const
340 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
341 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
344 string x509_issuer_name;
345 string x509_serial_number;
353 explicit Recipient (shared_ptr<const cxml::Node> node)
354 : x509_issuer_serial (node->node_child ("X509IssuerSerial"))
355 , x509_subject_name (node->string_child ("X509SubjectName"))
360 void as_xml (xmlpp::Element* node) const
362 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial"));
363 node->add_child("X509SubjectName")->add_child_text (x509_subject_name);
366 X509IssuerSerial x509_issuer_serial;
367 string x509_subject_name;
370 class KDMRequiredExtensions
373 KDMRequiredExtensions () {}
375 explicit KDMRequiredExtensions (shared_ptr<const cxml::Node> node)
376 : recipient (node->node_child ("Recipient"))
377 , composition_playlist_id (remove_urn_uuid (node->string_child ("CompositionPlaylistId")))
378 , content_title_text (node->string_child ("ContentTitleText"))
379 , not_valid_before (node->string_child ("ContentKeysNotValidBefore"))
380 , not_valid_after (node->string_child ("ContentKeysNotValidAfter"))
381 , authorized_device_info (node->node_child ("AuthorizedDeviceInfo"))
382 , key_id_list (node->node_child ("KeyIdList"))
384 disable_forensic_marking_picture = false;
385 disable_forensic_marking_audio = optional<int>();
386 if (node->optional_node_child("ForensicMarkFlagList")) {
387 for (auto i: node->node_child("ForensicMarkFlagList")->node_children("ForensicMarkFlag")) {
388 if (i->content() == picture_disable) {
389 disable_forensic_marking_picture = true;
390 } else if (starts_with(i->content(), audio_disable)) {
391 disable_forensic_marking_audio = 0;
392 string const above = audio_disable + "-above-channel-";
393 if (starts_with(i->content(), above)) {
394 auto above_number = i->content().substr(above.length());
395 if (above_number == "") {
396 throw KDMFormatError("Badly-formatted ForensicMarkFlag");
398 disable_forensic_marking_audio = atoi(above_number.c_str());
405 void as_xml (xmlpp::Element* node) const
407 node->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM");
409 recipient.as_xml (node->add_child ("Recipient"));
410 node->add_child("CompositionPlaylistId")->add_child_text ("urn:uuid:" + composition_playlist_id);
411 node->add_child("ContentTitleText")->add_child_text (content_title_text);
412 if (content_authenticator) {
413 node->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ());
415 node->add_child("ContentKeysNotValidBefore")->add_child_text (not_valid_before.as_string ());
416 node->add_child("ContentKeysNotValidAfter")->add_child_text (not_valid_after.as_string ());
417 if (authorized_device_info) {
418 authorized_device_info->as_xml (node->add_child ("AuthorizedDeviceInfo"));
420 key_id_list.as_xml (node->add_child ("KeyIdList"));
422 if (disable_forensic_marking_picture || disable_forensic_marking_audio) {
423 auto forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList");
424 if (disable_forensic_marking_picture) {
425 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text(picture_disable);
427 if (disable_forensic_marking_audio) {
428 auto mrkflg = audio_disable;
429 if (*disable_forensic_marking_audio > 0) {
430 mrkflg += String::compose ("-above-channel-%1", *disable_forensic_marking_audio);
432 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text (mrkflg);
438 string composition_playlist_id;
439 boost::optional<string> content_authenticator;
440 string content_title_text;
441 LocalTime not_valid_before;
442 LocalTime not_valid_after;
443 bool disable_forensic_marking_picture;
444 optional<int> disable_forensic_marking_audio;
445 boost::optional<AuthorizedDeviceInfo> authorized_device_info;
446 KeyIdList key_id_list;
449 static const string picture_disable;
450 static const string audio_disable;
453 const string KDMRequiredExtensions::picture_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable";
454 const string KDMRequiredExtensions::audio_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable";
456 class RequiredExtensions
459 RequiredExtensions () {}
461 explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
462 : kdm_required_extensions (node->node_child ("KDMRequiredExtensions"))
467 void as_xml (xmlpp::Element* node) const
469 kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions"));
472 KDMRequiredExtensions kdm_required_extensions;
475 class AuthenticatedPublic
478 AuthenticatedPublic ()
479 : message_id (make_uuid ())
480 /* XXX: hack for Dolby to see if there must be a not-empty annotation text */
481 , annotation_text ("none")
482 , issue_date (LocalTime().as_string ())
485 explicit AuthenticatedPublic (shared_ptr<const cxml::Node> node)
486 : message_id (remove_urn_uuid (node->string_child ("MessageId")))
487 , annotation_text (node->optional_string_child ("AnnotationText"))
488 , issue_date (node->string_child ("IssueDate"))
489 , signer (node->node_child ("Signer"))
490 , required_extensions (node->node_child ("RequiredExtensions"))
495 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
497 references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
499 node->add_child("MessageId")->add_child_text ("urn:uuid:" + message_id);
500 node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
501 if (annotation_text) {
502 node->add_child("AnnotationText")->add_child_text (annotation_text.get ());
504 node->add_child("IssueDate")->add_child_text (issue_date);
506 signer.as_xml (node->add_child ("Signer"));
507 required_extensions.as_xml (node->add_child ("RequiredExtensions"));
509 node->add_child ("NonCriticalExtensions");
513 optional<string> annotation_text;
516 RequiredExtensions required_extensions;
519 /** Class to describe our data. We use a class hierarchy as it's a bit nicer
520 * for XML data than a flat description.
522 class EncryptedKDMData
530 explicit EncryptedKDMData (shared_ptr<const cxml::Node> node)
531 : authenticated_public (node->node_child ("AuthenticatedPublic"))
532 , authenticated_private (node->node_child ("AuthenticatedPrivate"))
533 , signature (node->node_child ("Signature"))
538 shared_ptr<xmlpp::Document> as_xml () const
540 shared_ptr<xmlpp::Document> document (new xmlpp::Document ());
541 xmlpp::Element* root = document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM");
542 root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
543 root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
544 map<string, xmlpp::Attribute *> references;
545 authenticated_public.as_xml (root->add_child ("AuthenticatedPublic"), references);
546 authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references);
547 signature.as_xml (root->add_child ("Signature", "ds"));
549 for (auto i: references) {
550 xmlAddID (0, document->cobj(), (const xmlChar *) i.first.c_str(), i.second->cobj());
553 indent (document->get_root_node(), 0);
557 AuthenticatedPublic authenticated_public;
558 AuthenticatedPrivate authenticated_private;
565 EncryptedKDM::EncryptedKDM (string s)
568 auto doc = make_shared<cxml::Document>("DCinemaSecurityMessage");
569 doc->read_string (s);
570 _data = new data::EncryptedKDMData (doc);
571 } catch (xmlpp::parse_error& e) {
572 throw KDMFormatError (e.what ());
576 /** @param trusted_devices Trusted device thumbprints */
577 EncryptedKDM::EncryptedKDM (
578 shared_ptr<const CertificateChain> signer,
579 Certificate recipient,
580 vector<string> trusted_devices,
582 string content_title_text,
583 optional<string> annotation_text,
584 LocalTime not_valid_before,
585 LocalTime not_valid_after,
586 Formulation formulation,
587 bool disable_forensic_marking_picture,
588 optional<int> disable_forensic_marking_audio,
589 vector<pair<string, string>> key_ids,
592 : _data (new data::EncryptedKDMData)
594 /* Fill our XML-ish description in with the juicy bits that the caller has given */
596 /* Our ideas, based on http://isdcf.com/papers/ISDCF-Doc5-kdm-certs.pdf, about the KDM types are:
598 * Type Trusted-device thumb ContentAuthenticator
599 * MODIFIED_TRANSITIONAL_1 assume-trust No
600 * MULTIPLE_MODIFIED_TRANSITIONAL_1 as specified No
601 * DCI_ANY assume-trust Yes
602 * DCI_SPECIFIC as specified Yes
605 data::AuthenticatedPublic& aup = _data->authenticated_public;
606 aup.signer.x509_issuer_name = signer->leaf().issuer ();
607 aup.signer.x509_serial_number = signer->leaf().serial ();
608 aup.annotation_text = annotation_text;
610 auto& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
611 kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.issuer ();
612 kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
613 kre.recipient.x509_subject_name = recipient.subject ();
614 kre.composition_playlist_id = cpl_id;
615 if (formulation == DCI_ANY || formulation == DCI_SPECIFIC) {
616 kre.content_authenticator = signer->leaf().thumbprint ();
618 kre.content_title_text = content_title_text;
619 kre.not_valid_before = not_valid_before;
620 kre.not_valid_after = not_valid_after;
621 kre.disable_forensic_marking_picture = disable_forensic_marking_picture;
622 kre.disable_forensic_marking_audio = disable_forensic_marking_audio;
624 if (formulation != MODIFIED_TRANSITIONAL_TEST) {
625 kre.authorized_device_info = data::AuthorizedDeviceInfo ();
626 kre.authorized_device_info->device_list_identifier = make_uuid ();
627 auto n = recipient.subject_common_name ();
628 if (n.find (".") != string::npos) {
629 n = n.substr (n.find (".") + 1);
631 kre.authorized_device_info->device_list_description = n;
633 if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) {
634 /* Use the "assume trust" thumbprint */
635 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
636 } else if (formulation == MULTIPLE_MODIFIED_TRANSITIONAL_1 || formulation == DCI_SPECIFIC) {
637 if (trusted_devices.empty ()) {
638 /* Fall back on the "assume trust" thumbprint so we
639 can generate "modified-transitional-1" KDMs
640 together with "multiple-modified-transitional-1"
641 KDMs in one go, and similarly for "dci-any" etc.
643 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
645 /* As I read the standard we should use the
646 recipient /and/ other trusted device thumbprints
647 here. MJD reports that this doesn't work with
648 his setup; a working KDM does not include the
649 recipient's thumbprint (recipient.thumbprint()).
650 Waimea uses only the trusted devices here, too.
652 for (auto i: trusted_devices) {
653 kre.authorized_device_info->certificate_thumbprints.push_back(i);
659 for (auto i: key_ids) {
660 kre.key_id_list.typed_key_id.push_back(data::TypedKeyId(i.first, i.second));
663 _data->authenticated_private.encrypted_key = keys;
665 /* Read the XML so far and sign it */
666 auto doc = _data->as_xml ();
667 for (auto i: doc->get_root_node()->get_children()) {
668 if (i->get_name() == "Signature") {
669 signer->add_signature_value(dynamic_cast<xmlpp::Element*>(i), "ds", false);
673 /* Read the bits that add_signature_value did back into our variables */
674 auto signed_doc = make_shared<cxml::Node>(doc->get_root_node());
675 _data->signature = data::Signature (signed_doc->node_child ("Signature"));
678 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
679 : _data (new data::EncryptedKDMData (*other._data))
685 EncryptedKDM::operator= (EncryptedKDM const & other)
687 if (this == &other) {
692 _data = new data::EncryptedKDMData (*other._data);
696 EncryptedKDM::~EncryptedKDM ()
702 EncryptedKDM::as_xml (boost::filesystem::path path) const
704 auto f = fopen_boost (path, "w");
706 throw FileError ("Could not open KDM file for writing", path, errno);
708 auto const x = as_xml ();
709 size_t const written = fwrite (x.c_str(), 1, x.length(), f);
711 if (written != x.length()) {
712 throw FileError ("Could not write to KDM file", path, errno);
717 EncryptedKDM::as_xml () const
719 return _data->as_xml()->write_to_string ("UTF-8");
723 EncryptedKDM::keys () const
725 return _data->authenticated_private.encrypted_key;
729 EncryptedKDM::id () const
731 return _data->authenticated_public.message_id;
735 EncryptedKDM::annotation_text () const
737 return _data->authenticated_public.annotation_text;
741 EncryptedKDM::content_title_text () const
743 return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
747 EncryptedKDM::cpl_id () const
749 return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
753 EncryptedKDM::issue_date () const
755 return _data->authenticated_public.issue_date;
759 EncryptedKDM::not_valid_before () const
761 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
765 EncryptedKDM::not_valid_after () const
767 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
771 EncryptedKDM::recipient_x509_subject_name () const
773 return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name;
777 EncryptedKDM::signer_certificate_chain () const
779 CertificateChain chain;
780 for (auto const& i: _data->signature.x509_data) {
781 string s = "-----BEGIN CERTIFICATE-----\n" + i.x509_certificate + "\n-----END CERTIFICATE-----";
782 chain.add (Certificate(s));
788 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
790 /* Not exactly efficient... */
791 return a.as_xml() == b.as_xml();