2 Copyright (C) 2013-2017 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 <libcxml/cxml.h>
39 #include <libxml++/document.h>
40 #include <libxml++/nodes/element.h>
41 #include <libxml/parser.h>
42 #include <boost/date_time/posix_time/posix_time.hpp>
43 #include <boost/foreach.hpp>
44 #include <boost/format.hpp>
51 using boost::shared_ptr;
52 using boost::optional;
57 /** Namespace for classes used to hold our data; they are internal to this .cc file */
65 explicit Signer (shared_ptr<const cxml::Node> node)
66 : x509_issuer_name (node->string_child ("X509IssuerName"))
67 , x509_serial_number (node->string_child ("X509SerialNumber"))
72 void as_xml (xmlpp::Element* node) const
74 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
75 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
78 string x509_issuer_name;
79 string x509_serial_number;
87 explicit X509Data (boost::shared_ptr<const cxml::Node> node)
88 : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial")))
89 , x509_certificate (node->string_child ("X509Certificate"))
94 void as_xml (xmlpp::Element* node) const
96 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds"));
97 node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate);
100 Signer x509_issuer_serial;
101 std::string x509_certificate;
109 explicit Reference (string u)
113 explicit Reference (shared_ptr<const cxml::Node> node)
114 : uri (node->string_attribute ("URI"))
115 , digest_value (node->string_child ("DigestValue"))
120 void as_xml (xmlpp::Element* node) const
122 node->set_attribute ("URI", uri);
123 node->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
124 node->add_child("DigestValue", "ds")->add_child_text (digest_value);
135 : authenticated_public ("#ID_AuthenticatedPublic")
136 , authenticated_private ("#ID_AuthenticatedPrivate")
139 explicit SignedInfo (shared_ptr<const cxml::Node> node)
141 list<shared_ptr<cxml::Node> > references = node->node_children ("Reference");
142 for (list<shared_ptr<cxml::Node> >::const_iterator i = references.begin(); i != references.end(); ++i) {
143 if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPublic") {
144 authenticated_public = Reference (*i);
145 } else if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPrivate") {
146 authenticated_private = Reference (*i);
149 /* XXX: do something if we don't recognise the node */
153 void as_xml (xmlpp::Element* node) const
155 node->add_child ("CanonicalizationMethod", "ds")->set_attribute (
156 "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
159 node->add_child ("SignatureMethod", "ds")->set_attribute (
160 "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
163 authenticated_public.as_xml (node->add_child ("Reference", "ds"));
164 authenticated_private.as_xml (node->add_child ("Reference", "ds"));
168 Reference authenticated_public;
169 Reference authenticated_private;
177 explicit Signature (shared_ptr<const cxml::Node> node)
178 : signed_info (node->node_child ("SignedInfo"))
179 , signature_value (node->string_child ("SignatureValue"))
181 list<shared_ptr<cxml::Node> > x509_data_nodes = node->node_child("KeyInfo")->node_children ("X509Data");
182 for (list<shared_ptr<cxml::Node> >::const_iterator i = x509_data_nodes.begin(); i != x509_data_nodes.end(); ++i) {
183 x509_data.push_back (X509Data (*i));
187 void as_xml (xmlpp::Node* node) const
189 signed_info.as_xml (node->add_child ("SignedInfo", "ds"));
190 node->add_child("SignatureValue", "ds")->add_child_text (signature_value);
192 xmlpp::Element* key_info_node = node->add_child ("KeyInfo", "ds");
193 for (std::list<X509Data>::const_iterator i = x509_data.begin(); i != x509_data.end(); ++i) {
194 i->as_xml (key_info_node->add_child ("X509Data", "ds"));
198 SignedInfo signed_info;
199 string signature_value;
200 list<X509Data> x509_data;
203 class AuthenticatedPrivate
206 AuthenticatedPrivate () {}
208 explicit AuthenticatedPrivate (shared_ptr<const cxml::Node> node)
210 list<shared_ptr<cxml::Node> > encrypted_key_nodes = node->node_children ("EncryptedKey");
211 for (list<shared_ptr<cxml::Node> >::const_iterator i = encrypted_key_nodes.begin(); i != encrypted_key_nodes.end(); ++i) {
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 (list<string>::const_iterator i = encrypted_key.begin(); i != encrypted_key.end(); ++i) {
221 xmlpp::Element* 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 xmlpp::Element* 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 xmlpp::Element* 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 xmlpp::Element* cipher_data = encrypted_key->add_child ("CipherData", "enc");
231 cipher_data->add_child("CipherValue", "enc")->add_child_text (*i);
235 list<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 list<shared_ptr<cxml::Node> > typed_key_id_nodes = node->node_children ("TypedKeyId");
280 for (list<shared_ptr<cxml::Node> >::const_iterator i = typed_key_id_nodes.begin(); i != typed_key_id_nodes.end(); ++i) {
281 typed_key_id.push_back (TypedKeyId (*i));
285 void as_xml (xmlpp::Element* node) const
287 for (list<TypedKeyId>::const_iterator i = typed_key_id.begin(); i != typed_key_id.end(); ++i) {
288 i->as_xml (node->add_child("TypedKeyId"));
292 list<TypedKeyId> typed_key_id;
295 class AuthorizedDeviceInfo
298 AuthorizedDeviceInfo () {}
300 explicit AuthorizedDeviceInfo (shared_ptr<const cxml::Node> node)
301 : device_list_identifier (remove_urn_uuid (node->string_child ("DeviceListIdentifier")))
302 , device_list_description (node->optional_string_child ("DeviceListDescription"))
304 BOOST_FOREACH (cxml::ConstNodePtr i, node->node_child("DeviceList")->node_children("CertificateThumbprint")) {
305 certificate_thumbprints.push_back (i->content ());
309 void as_xml (xmlpp::Element* node) const
311 node->add_child ("DeviceListIdentifier")->add_child_text ("urn:uuid:" + device_list_identifier);
312 if (device_list_description) {
313 node->add_child ("DeviceListDescription")->add_child_text (device_list_description.get());
315 xmlpp::Element* device_list = node->add_child ("DeviceList");
316 BOOST_FOREACH (string i, certificate_thumbprints) {
317 device_list->add_child("CertificateThumbprint")->add_child_text (i);
321 /** DeviceListIdentifier without the urn:uuid: prefix */
322 string device_list_identifier;
323 boost::optional<string> device_list_description;
324 std::list<string> certificate_thumbprints;
327 class X509IssuerSerial
330 X509IssuerSerial () {}
332 explicit X509IssuerSerial (shared_ptr<const cxml::Node> node)
333 : x509_issuer_name (node->string_child ("X509IssuerName"))
334 , x509_serial_number (node->string_child ("X509SerialNumber"))
339 void as_xml (xmlpp::Element* node) const
341 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
342 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
345 string x509_issuer_name;
346 string x509_serial_number;
354 explicit Recipient (shared_ptr<const cxml::Node> node)
355 : x509_issuer_serial (node->node_child ("X509IssuerSerial"))
356 , x509_subject_name (node->string_child ("X509SubjectName"))
361 void as_xml (xmlpp::Element* node) const
363 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial"));
364 node->add_child("X509SubjectName")->add_child_text (x509_subject_name);
367 X509IssuerSerial x509_issuer_serial;
368 string x509_subject_name;
371 class KDMRequiredExtensions
374 KDMRequiredExtensions () {}
376 explicit KDMRequiredExtensions (shared_ptr<const cxml::Node> node)
377 : recipient (node->node_child ("Recipient"))
378 , composition_playlist_id (remove_urn_uuid (node->string_child ("CompositionPlaylistId")))
379 , content_title_text (node->string_child ("ContentTitleText"))
380 , not_valid_before (node->string_child ("ContentKeysNotValidBefore"))
381 , not_valid_after (node->string_child ("ContentKeysNotValidAfter"))
382 , authorized_device_info (node->node_child ("AuthorizedDeviceInfo"))
383 , key_id_list (node->node_child ("KeyIdList"))
388 void as_xml (xmlpp::Element* node) const
390 node->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM");
392 recipient.as_xml (node->add_child ("Recipient"));
393 node->add_child("CompositionPlaylistId")->add_child_text ("urn:uuid:" + composition_playlist_id);
394 node->add_child("ContentTitleText")->add_child_text (content_title_text);
395 if (content_authenticator) {
396 node->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ());
398 node->add_child("ContentKeysNotValidBefore")->add_child_text (not_valid_before.as_string ());
399 node->add_child("ContentKeysNotValidAfter")->add_child_text (not_valid_after.as_string ());
400 if (authorized_device_info) {
401 authorized_device_info->as_xml (node->add_child ("AuthorizedDeviceInfo"));
403 key_id_list.as_xml (node->add_child ("KeyIdList"));
405 if (disable_forensic_marking_picture || disable_forensic_marking_audio) {
406 xmlpp::Element* forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList");
407 if (disable_forensic_marking_picture) {
408 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
410 if (disable_forensic_marking_audio) {
411 string mrkflg = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable";
412 if (disable_forensic_marking_audio != -1) {
413 mrkflg = str (boost::format (mrkflg + "-above-channel-%u") % disable_forensic_marking_audio);
415 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text (mrkflg);
421 string composition_playlist_id;
422 boost::optional<string> content_authenticator;
423 string content_title_text;
424 LocalTime not_valid_before;
425 LocalTime not_valid_after;
426 int disable_forensic_marking_picture;
427 int disable_forensic_marking_audio;
428 boost::optional<AuthorizedDeviceInfo> authorized_device_info;
429 KeyIdList key_id_list;
432 class RequiredExtensions
435 RequiredExtensions () {}
437 explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
438 : kdm_required_extensions (node->node_child ("KDMRequiredExtensions"))
443 void as_xml (xmlpp::Element* node) const
445 kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions"));
448 KDMRequiredExtensions kdm_required_extensions;
451 class AuthenticatedPublic
454 AuthenticatedPublic ()
455 : message_id (make_uuid ())
456 /* XXX: hack for Dolby to see if there must be a not-empty annotation text */
457 , annotation_text ("none")
458 , issue_date (LocalTime().as_string ())
461 explicit AuthenticatedPublic (shared_ptr<const cxml::Node> node)
462 : message_id (remove_urn_uuid (node->string_child ("MessageId")))
463 , annotation_text (node->optional_string_child ("AnnotationText"))
464 , issue_date (node->string_child ("IssueDate"))
465 , signer (node->node_child ("Signer"))
466 , required_extensions (node->node_child ("RequiredExtensions"))
471 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
473 references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
475 node->add_child("MessageId")->add_child_text ("urn:uuid:" + message_id);
476 node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
477 if (annotation_text) {
478 node->add_child("AnnotationText")->add_child_text (annotation_text.get ());
480 node->add_child("IssueDate")->add_child_text (issue_date);
482 signer.as_xml (node->add_child ("Signer"));
483 required_extensions.as_xml (node->add_child ("RequiredExtensions"));
485 node->add_child ("NonCriticalExtensions");
489 optional<string> annotation_text;
492 RequiredExtensions required_extensions;
495 /** Class to describe our data. We use a class hierarchy as it's a bit nicer
496 * for XML data than a flat description.
498 class EncryptedKDMData
506 explicit EncryptedKDMData (shared_ptr<const cxml::Node> node)
507 : authenticated_public (node->node_child ("AuthenticatedPublic"))
508 , authenticated_private (node->node_child ("AuthenticatedPrivate"))
509 , signature (node->node_child ("Signature"))
514 shared_ptr<xmlpp::Document> as_xml () const
516 shared_ptr<xmlpp::Document> document (new xmlpp::Document ());
517 xmlpp::Element* root = document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM");
518 root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
519 root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
520 map<string, xmlpp::Attribute *> references;
521 authenticated_public.as_xml (root->add_child ("AuthenticatedPublic"), references);
522 authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references);
523 signature.as_xml (root->add_child ("Signature", "ds"));
525 for (map<string, xmlpp::Attribute*>::const_iterator i = references.begin(); i != references.end(); ++i) {
526 xmlAddID (0, document->cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ());
532 AuthenticatedPublic authenticated_public;
533 AuthenticatedPrivate authenticated_private;
540 EncryptedKDM::EncryptedKDM (string s)
543 shared_ptr<cxml::Document> doc (new cxml::Document ("DCinemaSecurityMessage"));
544 doc->read_string (s);
545 _data = new data::EncryptedKDMData (doc);
546 } catch (xmlpp::parse_error& e) {
547 throw KDMFormatError (e.what ());
551 EncryptedKDM::EncryptedKDM (
552 shared_ptr<const CertificateChain> signer,
553 Certificate recipient,
554 vector<Certificate> trusted_devices,
556 string content_title_text,
557 optional<string> annotation_text,
558 LocalTime not_valid_before,
559 LocalTime not_valid_after,
560 Formulation formulation,
561 int disable_forensic_marking_picture,
562 int disable_forensic_marking_audio,
563 list<pair<string, string> > key_ids,
566 : _data (new data::EncryptedKDMData)
568 /* Fill our XML-ish description in with the juicy bits that the caller has given */
570 /* Our ideas, based on http://isdcf.com/papers/ISDCF-Doc5-kdm-certs.pdf, about the KDM types are:
572 * Type Trusted-device thumb ContentAuthenticator
573 * MODIFIED_TRANSITIONAL_1 assume-trust No
574 * MULTIPLE_MODIFIED_TRANSITIONAL_1 as specified No
575 * DCI_ANY assume-trust Yes
576 * DCI_SPECIFIC as specified Yes
579 data::AuthenticatedPublic& aup = _data->authenticated_public;
580 aup.signer.x509_issuer_name = signer->leaf().issuer ();
581 aup.signer.x509_serial_number = signer->leaf().serial ();
582 aup.annotation_text = annotation_text;
584 data::KDMRequiredExtensions& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
585 kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.issuer ();
586 kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
587 kre.recipient.x509_subject_name = recipient.subject ();
588 kre.composition_playlist_id = cpl_id;
589 if (formulation == DCI_ANY || formulation == DCI_SPECIFIC) {
590 kre.content_authenticator = signer->leaf().thumbprint ();
592 kre.content_title_text = content_title_text;
593 kre.not_valid_before = not_valid_before;
594 kre.not_valid_after = not_valid_after;
595 kre.disable_forensic_marking_picture = disable_forensic_marking_picture;
596 kre.disable_forensic_marking_audio = disable_forensic_marking_audio;
598 if (formulation != MODIFIED_TRANSITIONAL_TEST) {
599 kre.authorized_device_info = data::AuthorizedDeviceInfo ();
600 kre.authorized_device_info->device_list_identifier = make_uuid ();
601 string n = recipient.subject_common_name ();
602 if (n.find (".") != string::npos) {
603 n = n.substr (n.find (".") + 1);
605 kre.authorized_device_info->device_list_description = n;
607 if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) {
608 /* Use the "assume trust" thumbprint */
609 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
610 } else if (formulation == MULTIPLE_MODIFIED_TRANSITIONAL_1 || formulation == DCI_SPECIFIC) {
611 if (trusted_devices.empty ()) {
612 /* Fall back on the "assume trust" thumbprint so we
613 can generate "modified-transitional-1" KDMs
614 together with "multiple-modified-transitional-1"
615 KDMs in one go, and similarly for "dci-any" etc.
617 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
619 /* As I read the standard we should use the
620 recipient /and/ other trusted device thumbprints
621 here. MJD reports that this doesn't work with
622 his setup; a working KDM does not include the
623 recipient's thumbprint (recipient.thumbprint()).
624 Waimea uses only the trusted devices here, too.
626 BOOST_FOREACH (Certificate const & i, trusted_devices) {
627 kre.authorized_device_info->certificate_thumbprints.push_back (i.thumbprint ());
633 for (list<pair<string, string> >::const_iterator i = key_ids.begin(); i != key_ids.end(); ++i) {
634 kre.key_id_list.typed_key_id.push_back (data::TypedKeyId (i->first, i->second));
637 _data->authenticated_private.encrypted_key = keys;
639 /* Read the XML so far and sign it */
640 shared_ptr<xmlpp::Document> doc = _data->as_xml ();
641 xmlpp::Node::NodeList children = doc->get_root_node()->get_children ();
642 for (xmlpp::Node::NodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
643 if ((*i)->get_name() == "Signature") {
644 signer->add_signature_value (*i, "ds");
648 /* Read the bits that add_signature_value did back into our variables */
649 shared_ptr<cxml::Node> signed_doc (new cxml::Node (doc->get_root_node ()));
650 _data->signature = data::Signature (signed_doc->node_child ("Signature"));
653 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
654 : _data (new data::EncryptedKDMData (*other._data))
660 EncryptedKDM::operator= (EncryptedKDM const & other)
662 if (this == &other) {
667 _data = new data::EncryptedKDMData (*other._data);
671 EncryptedKDM::~EncryptedKDM ()
677 EncryptedKDM::as_xml (boost::filesystem::path path) const
679 FILE* f = fopen_boost (path, "w");
680 string const x = as_xml ();
681 fwrite (x.c_str(), 1, x.length(), f);
686 EncryptedKDM::as_xml () const
688 return _data->as_xml()->write_to_string ("UTF-8");
692 EncryptedKDM::keys () const
694 return _data->authenticated_private.encrypted_key;
698 EncryptedKDM::id () const
700 return _data->authenticated_public.message_id;
704 EncryptedKDM::annotation_text () const
706 return _data->authenticated_public.annotation_text;
710 EncryptedKDM::content_title_text () const
712 return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
716 EncryptedKDM::cpl_id () const
718 return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
722 EncryptedKDM::issue_date () const
724 return _data->authenticated_public.issue_date;
728 EncryptedKDM::not_valid_before () const
730 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
734 EncryptedKDM::not_valid_after () const
736 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
740 EncryptedKDM::recipient_x509_subject_name () const
742 return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name;
746 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
748 /* Not exactly efficient... */
749 return a.as_xml() == b.as_xml();