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.
35 /** @file src/encrypted_kdm.cc
36 * @brief EncryptedKDM class
40 #include "certificate_chain.h"
41 #include "compose.hpp"
42 #include "encrypted_kdm.h"
43 #include "exceptions.h"
46 #include <libcxml/cxml.h>
47 #include <libxml++/document.h>
48 #include <libxml++/nodes/element.h>
49 #include <libxml/parser.h>
50 #include <boost/algorithm/string.hpp>
51 #include <boost/date_time/posix_time/posix_time.hpp>
52 #include <boost/format.hpp>
58 using std::make_shared;
61 using std::shared_ptr;
62 using boost::optional;
63 using boost::starts_with;
70 /** Namespace for classes used to hold our data; they are internal to this .cc file */
79 explicit Signer (shared_ptr<const cxml::Node> node)
80 : x509_issuer_name (node->string_child ("X509IssuerName"))
81 , x509_serial_number (node->string_child ("X509SerialNumber"))
86 void as_xml (xmlpp::Element* node) const
88 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
89 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
92 string x509_issuer_name;
93 string x509_serial_number;
102 explicit X509Data (std::shared_ptr<const cxml::Node> node)
103 : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial")))
104 , x509_certificate (node->string_child ("X509Certificate"))
109 void as_xml (xmlpp::Element* node) const
111 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds"));
112 node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate);
115 Signer x509_issuer_serial;
116 std::string x509_certificate;
125 explicit Reference (string u)
129 explicit Reference (shared_ptr<const cxml::Node> node)
130 : uri (node->string_attribute ("URI"))
131 , digest_value (node->string_child ("DigestValue"))
136 void as_xml (xmlpp::Element* node) const
138 node->set_attribute ("URI", uri);
139 node->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
140 node->add_child("DigestValue", "ds")->add_child_text (digest_value);
152 : authenticated_public ("#ID_AuthenticatedPublic")
153 , authenticated_private ("#ID_AuthenticatedPrivate")
156 explicit SignedInfo (shared_ptr<const cxml::Node> node)
158 for (auto i: node->node_children ("Reference")) {
159 if (i->string_attribute("URI") == "#ID_AuthenticatedPublic") {
160 authenticated_public = Reference(i);
161 } else if (i->string_attribute("URI") == "#ID_AuthenticatedPrivate") {
162 authenticated_private = Reference(i);
165 /* XXX: do something if we don't recognise the node */
169 void as_xml (xmlpp::Element* node) const
171 node->add_child ("CanonicalizationMethod", "ds")->set_attribute (
172 "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
175 node->add_child ("SignatureMethod", "ds")->set_attribute (
176 "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
179 authenticated_public.as_xml (node->add_child ("Reference", "ds"));
180 authenticated_private.as_xml (node->add_child ("Reference", "ds"));
184 Reference authenticated_public;
185 Reference authenticated_private;
194 explicit Signature (shared_ptr<const cxml::Node> node)
195 : signed_info (node->node_child ("SignedInfo"))
196 , signature_value (node->string_child ("SignatureValue"))
198 for (auto i: node->node_child("KeyInfo")->node_children ("X509Data")) {
199 x509_data.push_back(X509Data(i));
203 void as_xml (xmlpp::Node* node) const
205 signed_info.as_xml (node->add_child ("SignedInfo", "ds"));
206 node->add_child("SignatureValue", "ds")->add_child_text (signature_value);
208 auto key_info_node = node->add_child("KeyInfo", "ds");
209 for (auto i: x509_data) {
210 i.as_xml (key_info_node->add_child("X509Data", "ds"));
214 SignedInfo signed_info;
215 string signature_value;
216 vector<X509Data> x509_data;
220 class AuthenticatedPrivate
223 AuthenticatedPrivate () {}
225 explicit AuthenticatedPrivate (shared_ptr<const cxml::Node> node)
227 for (auto i: node->node_children ("EncryptedKey")) {
228 encrypted_key.push_back (i->node_child("CipherData")->string_child("CipherValue"));
232 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
234 references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate");
236 for (auto i: encrypted_key) {
237 auto encrypted_key = node->add_child ("EncryptedKey", "enc");
238 /* XXX: hack for testing with Dolby */
239 encrypted_key->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
240 auto encryption_method = encrypted_key->add_child("EncryptionMethod", "enc");
241 encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
242 auto digest_method = encryption_method->add_child ("DigestMethod", "ds");
243 /* XXX: hack for testing with Dolby */
244 digest_method->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
245 digest_method->set_attribute ("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
246 auto cipher_data = encrypted_key->add_child("CipherData", "enc");
247 cipher_data->add_child("CipherValue", "enc")->add_child_text (i);
251 vector<string> encrypted_key;
260 explicit TypedKeyId (shared_ptr<const cxml::Node> node)
261 : key_type (node->string_child ("KeyType"))
262 , key_id (remove_urn_uuid (node->string_child ("KeyId")))
267 TypedKeyId (string type, string id)
272 void as_xml (xmlpp::Element* node) const
274 auto type = node->add_child("KeyType");
275 type->add_child_text (key_type);
276 node->add_child("KeyId")->add_child_text ("urn:uuid:" + key_id);
277 /* XXX: this feels like a bit of a hack */
278 if (key_type == "MDEK") {
279 type->set_attribute ("scope", "http://www.dolby.com/cp850/2012/KDM#kdm-key-type");
281 type->set_attribute ("scope", "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
295 explicit KeyIdList (shared_ptr<const cxml::Node> node)
297 for (auto i: node->node_children ("TypedKeyId")) {
298 typed_key_id.push_back(TypedKeyId(i));
302 void as_xml (xmlpp::Element* node) const
304 for (auto const& i: typed_key_id) {
305 i.as_xml (node->add_child("TypedKeyId"));
309 vector<TypedKeyId> typed_key_id;
313 class AuthorizedDeviceInfo
316 AuthorizedDeviceInfo () {}
318 explicit AuthorizedDeviceInfo (shared_ptr<const cxml::Node> node)
319 : device_list_identifier (remove_urn_uuid (node->string_child ("DeviceListIdentifier")))
320 , device_list_description (node->optional_string_child ("DeviceListDescription"))
322 for (auto i: node->node_child("DeviceList")->node_children("CertificateThumbprint")) {
323 certificate_thumbprints.push_back (i->content ());
327 void as_xml (xmlpp::Element* node) const
329 node->add_child ("DeviceListIdentifier")->add_child_text ("urn:uuid:" + device_list_identifier);
330 if (device_list_description) {
331 node->add_child ("DeviceListDescription")->add_child_text (device_list_description.get());
333 auto device_list = node->add_child ("DeviceList");
334 for (auto i: certificate_thumbprints) {
335 device_list->add_child("CertificateThumbprint")->add_child_text (i);
339 /** DeviceListIdentifier without the urn:uuid: prefix */
340 string device_list_identifier;
341 boost::optional<string> device_list_description;
342 std::vector<string> certificate_thumbprints;
346 class X509IssuerSerial
349 X509IssuerSerial () {}
351 explicit X509IssuerSerial (shared_ptr<const cxml::Node> node)
352 : x509_issuer_name (node->string_child ("X509IssuerName"))
353 , x509_serial_number (node->string_child ("X509SerialNumber"))
358 void as_xml (xmlpp::Element* node) const
360 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
361 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
364 string x509_issuer_name;
365 string x509_serial_number;
374 explicit Recipient (shared_ptr<const cxml::Node> node)
375 : x509_issuer_serial (node->node_child ("X509IssuerSerial"))
376 , x509_subject_name (node->string_child ("X509SubjectName"))
381 void as_xml (xmlpp::Element* node) const
383 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial"));
384 node->add_child("X509SubjectName")->add_child_text (x509_subject_name);
387 X509IssuerSerial x509_issuer_serial;
388 string x509_subject_name;
392 class KDMRequiredExtensions
395 KDMRequiredExtensions () {}
397 explicit KDMRequiredExtensions (shared_ptr<const cxml::Node> node)
398 : recipient (node->node_child ("Recipient"))
399 , composition_playlist_id (remove_urn_uuid (node->string_child ("CompositionPlaylistId")))
400 , content_title_text (node->string_child ("ContentTitleText"))
401 , not_valid_before (node->string_child ("ContentKeysNotValidBefore"))
402 , not_valid_after (node->string_child ("ContentKeysNotValidAfter"))
403 , authorized_device_info (node->node_child ("AuthorizedDeviceInfo"))
404 , key_id_list (node->node_child ("KeyIdList"))
406 disable_forensic_marking_picture = false;
407 disable_forensic_marking_audio = optional<int>();
408 if (node->optional_node_child("ForensicMarkFlagList")) {
409 for (auto i: node->node_child("ForensicMarkFlagList")->node_children("ForensicMarkFlag")) {
410 if (i->content() == picture_disable) {
411 disable_forensic_marking_picture = true;
412 } else if (starts_with(i->content(), audio_disable)) {
413 disable_forensic_marking_audio = 0;
414 string const above = audio_disable + "-above-channel-";
415 if (starts_with(i->content(), above)) {
416 auto above_number = i->content().substr(above.length());
417 if (above_number == "") {
418 throw KDMFormatError("Badly-formatted ForensicMarkFlag");
420 disable_forensic_marking_audio = atoi(above_number.c_str());
427 void as_xml (xmlpp::Element* node) const
429 node->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM");
431 recipient.as_xml (node->add_child ("Recipient"));
432 node->add_child("CompositionPlaylistId")->add_child_text ("urn:uuid:" + composition_playlist_id);
433 node->add_child("ContentTitleText")->add_child_text (content_title_text);
434 if (content_authenticator) {
435 node->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ());
437 node->add_child("ContentKeysNotValidBefore")->add_child_text (not_valid_before.as_string ());
438 node->add_child("ContentKeysNotValidAfter")->add_child_text (not_valid_after.as_string ());
439 if (authorized_device_info) {
440 authorized_device_info->as_xml (node->add_child ("AuthorizedDeviceInfo"));
442 key_id_list.as_xml (node->add_child ("KeyIdList"));
444 if (disable_forensic_marking_picture || disable_forensic_marking_audio) {
445 auto forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList");
446 if (disable_forensic_marking_picture) {
447 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text(picture_disable);
449 if (disable_forensic_marking_audio) {
450 auto mrkflg = audio_disable;
451 if (*disable_forensic_marking_audio > 0) {
452 mrkflg += String::compose ("-above-channel-%1", *disable_forensic_marking_audio);
454 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text (mrkflg);
460 string composition_playlist_id;
461 boost::optional<string> content_authenticator;
462 string content_title_text;
463 LocalTime not_valid_before;
464 LocalTime not_valid_after;
465 bool disable_forensic_marking_picture;
466 optional<int> disable_forensic_marking_audio;
467 boost::optional<AuthorizedDeviceInfo> authorized_device_info;
468 KeyIdList key_id_list;
471 static const string picture_disable;
472 static const string audio_disable;
476 const string KDMRequiredExtensions::picture_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable";
477 const string KDMRequiredExtensions::audio_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable";
480 class RequiredExtensions
483 RequiredExtensions () {}
485 explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
486 : kdm_required_extensions (node->node_child ("KDMRequiredExtensions"))
491 void as_xml (xmlpp::Element* node) const
493 kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions"));
496 KDMRequiredExtensions kdm_required_extensions;
500 class AuthenticatedPublic
503 AuthenticatedPublic ()
504 : message_id (make_uuid ())
505 /* XXX: hack for Dolby to see if there must be a not-empty annotation text */
506 , annotation_text ("none")
507 , issue_date (LocalTime().as_string ())
510 explicit AuthenticatedPublic (shared_ptr<const cxml::Node> node)
511 : message_id (remove_urn_uuid (node->string_child ("MessageId")))
512 , annotation_text (node->optional_string_child ("AnnotationText"))
513 , issue_date (node->string_child ("IssueDate"))
514 , signer (node->node_child ("Signer"))
515 , required_extensions (node->node_child ("RequiredExtensions"))
520 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
522 references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
524 node->add_child("MessageId")->add_child_text ("urn:uuid:" + message_id);
525 node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
526 if (annotation_text) {
527 node->add_child("AnnotationText")->add_child_text (annotation_text.get ());
529 node->add_child("IssueDate")->add_child_text (issue_date);
531 signer.as_xml (node->add_child ("Signer"));
532 required_extensions.as_xml (node->add_child ("RequiredExtensions"));
534 node->add_child ("NonCriticalExtensions");
538 optional<string> annotation_text;
541 RequiredExtensions required_extensions;
545 /** Class to describe our data. We use a class hierarchy as it's a bit nicer
546 * for XML data than a flat description.
548 class EncryptedKDMData
556 explicit EncryptedKDMData (shared_ptr<const cxml::Node> node)
557 : authenticated_public (node->node_child ("AuthenticatedPublic"))
558 , authenticated_private (node->node_child ("AuthenticatedPrivate"))
559 , signature (node->node_child ("Signature"))
564 shared_ptr<xmlpp::Document> as_xml () const
566 shared_ptr<xmlpp::Document> document (new xmlpp::Document ());
567 xmlpp::Element* root = document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM");
568 root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
569 root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
570 map<string, xmlpp::Attribute *> references;
571 authenticated_public.as_xml (root->add_child ("AuthenticatedPublic"), references);
572 authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references);
573 signature.as_xml (root->add_child ("Signature", "ds"));
575 for (auto i: references) {
576 xmlAddID (0, document->cobj(), (const xmlChar *) i.first.c_str(), i.second->cobj());
579 indent (document->get_root_node(), 0);
583 AuthenticatedPublic authenticated_public;
584 AuthenticatedPrivate authenticated_private;
593 EncryptedKDM::EncryptedKDM (string s)
596 auto doc = make_shared<cxml::Document>("DCinemaSecurityMessage");
597 doc->read_string (s);
598 _data = new data::EncryptedKDMData (doc);
599 } catch (xmlpp::parse_error& e) {
600 throw KDMFormatError (e.what ());
601 } catch (xmlpp::internal_error& e) {
602 throw KDMFormatError(e.what());
603 } catch (cxml::Error& e) {
604 throw KDMFormatError(e.what());
609 EncryptedKDM::EncryptedKDM (
610 shared_ptr<const CertificateChain> signer,
611 Certificate recipient,
612 vector<string> trusted_devices,
614 string content_title_text,
615 optional<string> annotation_text,
616 LocalTime not_valid_before,
617 LocalTime not_valid_after,
618 Formulation formulation,
619 bool disable_forensic_marking_picture,
620 optional<int> disable_forensic_marking_audio,
621 vector<pair<string, string>> key_ids,
624 : _data (new data::EncryptedKDMData)
626 /* Fill our XML-ish description in with the juicy bits that the caller has given */
628 /* Our ideas, based on http://isdcf.com/papers/ISDCF-Doc5-kdm-certs.pdf, about the KDM types are:
630 * Type Trusted-device thumb ContentAuthenticator
631 * MODIFIED_TRANSITIONAL_1 assume-trust No
632 * MULTIPLE_MODIFIED_TRANSITIONAL_1 as specified No
633 * DCI_ANY assume-trust Yes
634 * DCI_SPECIFIC as specified Yes
637 auto& aup = _data->authenticated_public;
638 aup.signer.x509_issuer_name = signer->leaf().issuer ();
639 aup.signer.x509_serial_number = signer->leaf().serial ();
640 aup.annotation_text = annotation_text;
642 auto& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
643 kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.issuer ();
644 kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
645 kre.recipient.x509_subject_name = recipient.subject ();
646 kre.composition_playlist_id = cpl_id;
647 if (formulation == Formulation::DCI_ANY || formulation == Formulation::DCI_SPECIFIC) {
648 kre.content_authenticator = signer->leaf().thumbprint ();
650 kre.content_title_text = content_title_text;
651 kre.not_valid_before = not_valid_before;
652 kre.not_valid_after = not_valid_after;
653 kre.disable_forensic_marking_picture = disable_forensic_marking_picture;
654 kre.disable_forensic_marking_audio = disable_forensic_marking_audio;
656 kre.authorized_device_info = data::AuthorizedDeviceInfo ();
657 kre.authorized_device_info->device_list_identifier = make_uuid ();
658 auto n = recipient.subject_common_name ();
659 if (n.find (".") != string::npos) {
660 n = n.substr (n.find (".") + 1);
662 kre.authorized_device_info->device_list_description = n;
664 if (formulation == Formulation::MODIFIED_TRANSITIONAL_1 || formulation == Formulation::DCI_ANY) {
665 /* Use the "assume trust" thumbprint */
666 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
667 } else if (formulation == Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1 || formulation == Formulation::DCI_SPECIFIC) {
668 if (trusted_devices.empty ()) {
669 /* Fall back on the "assume trust" thumbprint so we
670 can generate "modified-transitional-1" KDMs
671 together with "multiple-modified-transitional-1"
672 KDMs in one go, and similarly for "dci-any" etc.
674 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
676 /* As I read the standard we should use the
677 recipient /and/ other trusted device thumbprints
678 here. MJD reports that this doesn't work with
679 his setup; a working KDM does not include the
680 recipient's thumbprint (recipient.thumbprint()).
681 Waimea uses only the trusted devices here, too.
683 for (auto i: trusted_devices) {
684 kre.authorized_device_info->certificate_thumbprints.push_back(i);
689 for (auto i: key_ids) {
690 kre.key_id_list.typed_key_id.push_back(data::TypedKeyId(i.first, i.second));
693 _data->authenticated_private.encrypted_key = keys;
695 /* Read the XML so far and sign it */
696 auto doc = _data->as_xml ();
697 for (auto i: doc->get_root_node()->get_children()) {
698 if (i->get_name() == "Signature") {
699 signer->add_signature_value(dynamic_cast<xmlpp::Element*>(i), "ds", false);
703 /* Read the bits that add_signature_value did back into our variables */
704 auto signed_doc = make_shared<cxml::Node>(doc->get_root_node());
705 _data->signature = data::Signature (signed_doc->node_child ("Signature"));
709 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
710 : _data (new data::EncryptedKDMData (*other._data))
717 EncryptedKDM::operator= (EncryptedKDM const & other)
719 if (this == &other) {
724 _data = new data::EncryptedKDMData (*other._data);
729 EncryptedKDM::~EncryptedKDM ()
736 EncryptedKDM::as_xml (boost::filesystem::path path) const
740 throw FileError ("Could not open KDM file for writing", path, errno);
742 auto const x = as_xml ();
743 if (f.write(x.c_str(), 1, x.length()) != x.length()) {
744 throw FileError ("Could not write to KDM file", path, errno);
750 EncryptedKDM::as_xml () const
752 return _data->as_xml()->write_to_string ("UTF-8");
757 EncryptedKDM::keys () const
759 return _data->authenticated_private.encrypted_key;
764 EncryptedKDM::id () const
766 return _data->authenticated_public.message_id;
771 EncryptedKDM::annotation_text () const
773 return _data->authenticated_public.annotation_text;
778 EncryptedKDM::content_title_text () const
780 return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
785 EncryptedKDM::cpl_id () const
787 return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
792 EncryptedKDM::issue_date () const
794 return _data->authenticated_public.issue_date;
799 EncryptedKDM::not_valid_before () const
801 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
806 EncryptedKDM::not_valid_after () const
808 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
813 EncryptedKDM::recipient_x509_subject_name () const
815 return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name;
820 EncryptedKDM::signer_certificate_chain () const
822 CertificateChain chain;
823 for (auto const& i: _data->signature.x509_data) {
824 string s = "-----BEGIN CERTIFICATE-----\n" + i.x509_certificate + "\n-----END CERTIFICATE-----";
825 chain.add (Certificate(s));
832 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
834 /* Not exactly efficient... */
835 return a.as_xml() == b.as_xml();