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 "encrypted_kdm.h"
42 #include "certificate_chain.h"
43 #include "exceptions.h"
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;
69 /** Namespace for classes used to hold our data; they are internal to this .cc file */
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);
164 /* XXX: do something if we don't recognise the node */
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");
237 /* XXX: hack for testing with Dolby */
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");
242 /* XXX: hack for testing with Dolby */
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);
276 /* XXX: this feels like a bit of a hack */
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);
338 /** DeviceListIdentifier without the urn:uuid: prefix */
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 == "") {
417 throw KDMFormatError("Badly-formatted ForensicMarkFlag");
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;
462 LocalTime not_valid_before;
463 LocalTime not_valid_after;
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 ())
504 /* XXX: hack for Dolby to see if there must be a not-empty annotation text */
505 , annotation_text ("none")
506 , issue_date (LocalTime().as_string ())
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;
544 /** Class to describe our data. We use a class hierarchy as it's a bit nicer
545 * for XML data than a flat description.
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) {
599 throw KDMFormatError (e.what ());
604 EncryptedKDM::EncryptedKDM (
605 shared_ptr<const CertificateChain> signer,
606 Certificate recipient,
607 vector<string> trusted_devices,
609 string content_title_text,
610 optional<string> annotation_text,
611 LocalTime not_valid_before,
612 LocalTime not_valid_after,
613 Formulation formulation,
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)
621 /* Fill our XML-ish description in with the juicy bits that the caller has given */
623 /* Our ideas, based on http://isdcf.com/papers/ISDCF-Doc5-kdm-certs.pdf, about the KDM types are:
625 * Type Trusted-device thumb ContentAuthenticator
626 * MODIFIED_TRANSITIONAL_1 assume-trust No
627 * MULTIPLE_MODIFIED_TRANSITIONAL_1 as specified No
628 * DCI_ANY assume-trust Yes
629 * DCI_SPECIFIC as specified Yes
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;
651 if (formulation != Formulation::MODIFIED_TRANSITIONAL_TEST) {
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) {
661 /* Use the "assume trust" thumbprint */
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 ()) {
665 /* Fall back on the "assume trust" thumbprint so we
666 can generate "modified-transitional-1" KDMs
667 together with "multiple-modified-transitional-1"
668 KDMs in one go, and similarly for "dci-any" etc.
670 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
672 /* As I read the standard we should use the
673 recipient /and/ other trusted device thumbprints
674 here. MJD reports that this doesn't work with
675 his setup; a working KDM does not include the
676 recipient's thumbprint (recipient.thumbprint()).
677 Waimea uses only the trusted devices here, too.
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;
692 /* Read the XML so far and sign it */
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);
700 /* Read the bits that add_signature_value did back into our variables */
701 auto signed_doc = make_shared<cxml::Node>(doc->get_root_node());
702 _data->signature = data::Signature (signed_doc->node_child ("Signature"));
706 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
707 : _data (new data::EncryptedKDMData (*other._data))
714 EncryptedKDM::operator= (EncryptedKDM const & other)
716 if (this == &other) {
721 _data = new data::EncryptedKDMData (*other._data);
726 EncryptedKDM::~EncryptedKDM ()
733 EncryptedKDM::as_xml (boost::filesystem::path path) const
735 auto f = fopen_boost (path, "w");
737 throw FileError ("Could not open KDM file for writing", path, errno);
739 auto const x = as_xml ();
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);
749 EncryptedKDM::as_xml () const
751 return _data->as_xml()->write_to_string ("UTF-8");
756 EncryptedKDM::keys () const
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
821 CertificateChain chain;
822 for (auto const& i: _data->signature.x509_data) {
823 string s = "-----BEGIN CERTIFICATE-----\n" + i.x509_certificate + "\n-----END CERTIFICATE-----";
824 chain.add (Certificate(s));
831 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
833 /* Not exactly efficient... */
834 return a.as_xml() == b.as_xml();