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>
50 using boost::shared_ptr;
51 using boost::optional;
56 /** Namespace for classes used to hold our data; they are internal to this .cc file */
64 explicit Signer (shared_ptr<const cxml::Node> node)
65 : x509_issuer_name (node->string_child ("X509IssuerName"))
66 , x509_serial_number (node->string_child ("X509SerialNumber"))
71 void as_xml (xmlpp::Element* node) const
73 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
74 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
77 string x509_issuer_name;
78 string x509_serial_number;
86 explicit X509Data (boost::shared_ptr<const cxml::Node> node)
87 : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial")))
88 , x509_certificate (node->string_child ("X509Certificate"))
93 void as_xml (xmlpp::Element* node) const
95 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds"));
96 node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate);
99 Signer x509_issuer_serial;
100 std::string x509_certificate;
108 explicit Reference (string u)
112 explicit Reference (shared_ptr<const cxml::Node> node)
113 : uri (node->string_attribute ("URI"))
114 , digest_value (node->string_child ("DigestValue"))
119 void as_xml (xmlpp::Element* node) const
121 node->set_attribute ("URI", uri);
122 node->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
123 node->add_child("DigestValue", "ds")->add_child_text (digest_value);
134 : authenticated_public ("#ID_AuthenticatedPublic")
135 , authenticated_private ("#ID_AuthenticatedPrivate")
138 explicit SignedInfo (shared_ptr<const cxml::Node> node)
140 list<shared_ptr<cxml::Node> > references = node->node_children ("Reference");
141 for (list<shared_ptr<cxml::Node> >::const_iterator i = references.begin(); i != references.end(); ++i) {
142 if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPublic") {
143 authenticated_public = Reference (*i);
144 } else if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPrivate") {
145 authenticated_private = Reference (*i);
148 /* XXX: do something if we don't recognise the node */
152 void as_xml (xmlpp::Element* node) const
154 node->add_child ("CanonicalizationMethod", "ds")->set_attribute (
155 "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
158 node->add_child ("SignatureMethod", "ds")->set_attribute (
159 "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
162 authenticated_public.as_xml (node->add_child ("Reference", "ds"));
163 authenticated_private.as_xml (node->add_child ("Reference", "ds"));
167 Reference authenticated_public;
168 Reference authenticated_private;
176 explicit Signature (shared_ptr<const cxml::Node> node)
177 : signed_info (node->node_child ("SignedInfo"))
178 , signature_value (node->string_child ("SignatureValue"))
180 list<shared_ptr<cxml::Node> > x509_data_nodes = node->node_child("KeyInfo")->node_children ("X509Data");
181 for (list<shared_ptr<cxml::Node> >::const_iterator i = x509_data_nodes.begin(); i != x509_data_nodes.end(); ++i) {
182 x509_data.push_back (X509Data (*i));
186 void as_xml (xmlpp::Node* node) const
188 signed_info.as_xml (node->add_child ("SignedInfo", "ds"));
189 node->add_child("SignatureValue", "ds")->add_child_text (signature_value);
191 xmlpp::Element* key_info_node = node->add_child ("KeyInfo", "ds");
192 for (std::list<X509Data>::const_iterator i = x509_data.begin(); i != x509_data.end(); ++i) {
193 i->as_xml (key_info_node->add_child ("X509Data", "ds"));
197 SignedInfo signed_info;
198 string signature_value;
199 list<X509Data> x509_data;
202 class AuthenticatedPrivate
205 AuthenticatedPrivate () {}
207 explicit AuthenticatedPrivate (shared_ptr<const cxml::Node> node)
209 list<shared_ptr<cxml::Node> > encrypted_key_nodes = node->node_children ("EncryptedKey");
210 for (list<shared_ptr<cxml::Node> >::const_iterator i = encrypted_key_nodes.begin(); i != encrypted_key_nodes.end(); ++i) {
211 encrypted_key.push_back ((*i)->node_child("CipherData")->string_child ("CipherValue"));
215 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
217 references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate");
219 for (list<string>::const_iterator i = encrypted_key.begin(); i != encrypted_key.end(); ++i) {
220 xmlpp::Element* encrypted_key = node->add_child ("EncryptedKey", "enc");
221 /* XXX: hack for testing with Dolby */
222 encrypted_key->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
223 xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc");
224 encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
225 xmlpp::Element* digest_method = encryption_method->add_child ("DigestMethod", "ds");
226 /* XXX: hack for testing with Dolby */
227 digest_method->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
228 digest_method->set_attribute ("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
229 xmlpp::Element* cipher_data = encrypted_key->add_child ("CipherData", "enc");
230 cipher_data->add_child("CipherValue", "enc")->add_child_text (*i);
234 list<string> encrypted_key;
242 explicit TypedKeyId (shared_ptr<const cxml::Node> node)
243 : key_type (node->string_child ("KeyType"))
244 , key_id (remove_urn_uuid (node->string_child ("KeyId")))
249 TypedKeyId (string type, string id)
254 void as_xml (xmlpp::Element* node) const
256 xmlpp::Element* type = node->add_child("KeyType");
257 type->add_child_text (key_type);
258 node->add_child("KeyId")->add_child_text ("urn:uuid:" + key_id);
259 /* XXX: this feels like a bit of a hack */
260 if (key_type == "MDEK") {
261 type->set_attribute ("scope", "http://www.dolby.com/cp850/2012/KDM#kdm-key-type");
263 type->set_attribute ("scope", "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
276 explicit KeyIdList (shared_ptr<const cxml::Node> node)
278 list<shared_ptr<cxml::Node> > typed_key_id_nodes = node->node_children ("TypedKeyId");
279 for (list<shared_ptr<cxml::Node> >::const_iterator i = typed_key_id_nodes.begin(); i != typed_key_id_nodes.end(); ++i) {
280 typed_key_id.push_back (TypedKeyId (*i));
284 void as_xml (xmlpp::Element* node) const
286 for (list<TypedKeyId>::const_iterator i = typed_key_id.begin(); i != typed_key_id.end(); ++i) {
287 i->as_xml (node->add_child("TypedKeyId"));
291 list<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 BOOST_FOREACH (cxml::ConstNodePtr 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 xmlpp::Element* device_list = node->add_child ("DeviceList");
315 BOOST_FOREACH (string 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::list<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"))
387 void as_xml (xmlpp::Element* node) const
389 node->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM");
391 recipient.as_xml (node->add_child ("Recipient"));
392 node->add_child("CompositionPlaylistId")->add_child_text ("urn:uuid:" + composition_playlist_id);
393 node->add_child("ContentTitleText")->add_child_text (content_title_text);
394 if (content_authenticator) {
395 node->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ());
397 node->add_child("ContentKeysNotValidBefore")->add_child_text (not_valid_before.as_string ());
398 node->add_child("ContentKeysNotValidAfter")->add_child_text (not_valid_after.as_string ());
399 if (authorized_device_info) {
400 authorized_device_info->as_xml (node->add_child ("AuthorizedDeviceInfo"));
402 key_id_list.as_xml (node->add_child ("KeyIdList"));
404 xmlpp::Element* forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList");
405 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
406 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
410 string composition_playlist_id;
411 boost::optional<string> content_authenticator;
412 string content_title_text;
413 LocalTime not_valid_before;
414 LocalTime not_valid_after;
415 boost::optional<AuthorizedDeviceInfo> authorized_device_info;
416 KeyIdList key_id_list;
419 class RequiredExtensions
422 RequiredExtensions () {}
424 explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
425 : kdm_required_extensions (node->node_child ("KDMRequiredExtensions"))
430 void as_xml (xmlpp::Element* node) const
432 kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions"));
435 KDMRequiredExtensions kdm_required_extensions;
438 class AuthenticatedPublic
441 AuthenticatedPublic ()
442 : message_id (make_uuid ())
443 /* XXX: hack for Dolby to see if there must be a not-empty annotation text */
444 , annotation_text ("none")
445 , issue_date (LocalTime().as_string ())
448 explicit AuthenticatedPublic (shared_ptr<const cxml::Node> node)
449 : message_id (remove_urn_uuid (node->string_child ("MessageId")))
450 , annotation_text (node->optional_string_child ("AnnotationText"))
451 , issue_date (node->string_child ("IssueDate"))
452 , signer (node->node_child ("Signer"))
453 , required_extensions (node->node_child ("RequiredExtensions"))
458 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
460 references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
462 node->add_child("MessageId")->add_child_text ("urn:uuid:" + message_id);
463 node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
464 if (annotation_text) {
465 node->add_child("AnnotationText")->add_child_text (annotation_text.get ());
467 node->add_child("IssueDate")->add_child_text (issue_date);
469 signer.as_xml (node->add_child ("Signer"));
470 required_extensions.as_xml (node->add_child ("RequiredExtensions"));
472 node->add_child ("NonCriticalExtensions");
476 optional<string> annotation_text;
479 RequiredExtensions required_extensions;
482 /** Class to describe our data. We use a class hierarchy as it's a bit nicer
483 * for XML data than a flat description.
485 class EncryptedKDMData
493 explicit EncryptedKDMData (shared_ptr<const cxml::Node> node)
494 : authenticated_public (node->node_child ("AuthenticatedPublic"))
495 , authenticated_private (node->node_child ("AuthenticatedPrivate"))
496 , signature (node->node_child ("Signature"))
501 shared_ptr<xmlpp::Document> as_xml () const
503 shared_ptr<xmlpp::Document> document (new xmlpp::Document ());
504 xmlpp::Element* root = document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM");
505 root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
506 root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
507 map<string, xmlpp::Attribute *> references;
508 authenticated_public.as_xml (root->add_child ("AuthenticatedPublic"), references);
509 authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references);
510 signature.as_xml (root->add_child ("Signature", "ds"));
512 for (map<string, xmlpp::Attribute*>::const_iterator i = references.begin(); i != references.end(); ++i) {
513 xmlAddID (0, document->cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ());
519 AuthenticatedPublic authenticated_public;
520 AuthenticatedPrivate authenticated_private;
527 EncryptedKDM::EncryptedKDM (string s)
530 shared_ptr<cxml::Document> doc (new cxml::Document ("DCinemaSecurityMessage"));
531 doc->read_string (s);
532 _data = new data::EncryptedKDMData (doc);
533 } catch (xmlpp::parse_error& e) {
534 throw KDMFormatError (e.what ());
538 EncryptedKDM::EncryptedKDM (
539 shared_ptr<const CertificateChain> signer,
540 Certificate recipient,
541 vector<Certificate> trusted_devices,
543 string content_title_text,
544 optional<string> annotation_text,
545 LocalTime not_valid_before,
546 LocalTime not_valid_after,
547 Formulation formulation,
548 list<pair<string, string> > key_ids,
551 : _data (new data::EncryptedKDMData)
553 /* Fill our XML-ish description in with the juicy bits that the caller has given */
555 /* Our ideas, based on http://isdcf.com/papers/ISDCF-Doc5-kdm-certs.pdf, about the KDM types are:
557 * Type Trusted-device thumb ContentAuthenticator
558 * MODIFIED_TRANSITIONAL_1 assume-trust No
559 * MULTIPLE_MODIFIED_TRANSITIONAL_1 as specified No
560 * DCI_ANY assume-trust Yes
561 * DCI_SPECIFIC as specified Yes
564 data::AuthenticatedPublic& aup = _data->authenticated_public;
565 aup.signer.x509_issuer_name = signer->leaf().issuer ();
566 aup.signer.x509_serial_number = signer->leaf().serial ();
567 aup.annotation_text = annotation_text;
569 data::KDMRequiredExtensions& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
570 kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.issuer ();
571 kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
572 kre.recipient.x509_subject_name = recipient.subject ();
573 kre.composition_playlist_id = cpl_id;
574 if (formulation == DCI_ANY || formulation == DCI_SPECIFIC) {
575 kre.content_authenticator = signer->leaf().thumbprint ();
577 kre.content_title_text = content_title_text;
578 kre.not_valid_before = not_valid_before;
579 kre.not_valid_after = not_valid_after;
581 if (formulation != MODIFIED_TRANSITIONAL_TEST) {
582 kre.authorized_device_info = data::AuthorizedDeviceInfo ();
583 kre.authorized_device_info->device_list_identifier = make_uuid ();
584 string n = recipient.subject_common_name ();
585 if (n.find (".") != string::npos) {
586 n = n.substr (n.find (".") + 1);
588 kre.authorized_device_info->device_list_description = n;
590 if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) {
591 /* Use the "assume trust" thumbprint */
592 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
593 } else if (formulation == MULTIPLE_MODIFIED_TRANSITIONAL_1 || formulation == DCI_SPECIFIC) {
594 if (trusted_devices.empty ()) {
595 /* Fall back on the "assume trust" thumbprint so we
596 can generate "modified-transitional-1" KDMs
597 together with "multiple-modified-transitional-1"
598 KDMs in one go, and similarly for "dci-any" etc.
600 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
602 /* As I read the standard we should use the
603 recipient /and/ other trusted device thumbprints
604 here. MJD reports that this doesn't work with
605 his setup; a working KDM does not include the
606 recipient's thumbprint (recipient.thumbprint()).
607 Waimea uses only the trusted devices here, too.
609 BOOST_FOREACH (Certificate const & i, trusted_devices) {
610 kre.authorized_device_info->certificate_thumbprints.push_back (i.thumbprint ());
616 for (list<pair<string, string> >::const_iterator i = key_ids.begin(); i != key_ids.end(); ++i) {
617 kre.key_id_list.typed_key_id.push_back (data::TypedKeyId (i->first, i->second));
620 _data->authenticated_private.encrypted_key = keys;
622 /* Read the XML so far and sign it */
623 shared_ptr<xmlpp::Document> doc = _data->as_xml ();
624 xmlpp::Node::NodeList children = doc->get_root_node()->get_children ();
625 for (xmlpp::Node::NodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
626 if ((*i)->get_name() == "Signature") {
627 signer->add_signature_value (*i, "ds");
631 /* Read the bits that add_signature_value did back into our variables */
632 shared_ptr<cxml::Node> signed_doc (new cxml::Node (doc->get_root_node ()));
633 _data->signature = data::Signature (signed_doc->node_child ("Signature"));
636 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
637 : _data (new data::EncryptedKDMData (*other._data))
643 EncryptedKDM::operator= (EncryptedKDM const & other)
645 if (this == &other) {
650 _data = new data::EncryptedKDMData (*other._data);
654 EncryptedKDM::~EncryptedKDM ()
660 EncryptedKDM::as_xml (boost::filesystem::path path) const
662 FILE* f = fopen_boost (path, "w");
663 string const x = as_xml ();
664 fwrite (x.c_str(), 1, x.length(), f);
669 EncryptedKDM::as_xml () const
671 return _data->as_xml()->write_to_string ("UTF-8");
675 EncryptedKDM::keys () const
677 return _data->authenticated_private.encrypted_key;
681 EncryptedKDM::annotation_text () const
683 return _data->authenticated_public.annotation_text;
687 EncryptedKDM::content_title_text () const
689 return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
693 EncryptedKDM::cpl_id () const
695 return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
699 EncryptedKDM::issue_date () const
701 return _data->authenticated_public.issue_date;
705 EncryptedKDM::not_valid_before () const
707 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
711 EncryptedKDM::not_valid_after () const
713 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
717 EncryptedKDM::recipient_x509_subject_name () const
719 return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name;
723 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
725 /* Not exactly efficient... */
726 return a.as_xml() == b.as_xml();