2 Copyright (C) 2013-2016 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 <libcxml/cxml.h>
38 #include <libxml++/document.h>
39 #include <libxml++/nodes/element.h>
40 #include <libxml/parser.h>
41 #include <boost/date_time/posix_time/posix_time.hpp>
42 #include <boost/foreach.hpp>
49 using boost::shared_ptr;
50 using boost::optional;
55 /** Namespace for classes used to hold our data; they are internal to this .cc file */
63 explicit Signer (shared_ptr<const cxml::Node> node)
64 : x509_issuer_name (node->string_child ("X509IssuerName"))
65 , x509_serial_number (node->string_child ("X509SerialNumber"))
70 void as_xml (xmlpp::Element* node) const
72 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
73 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
76 string x509_issuer_name;
77 string x509_serial_number;
85 explicit X509Data (boost::shared_ptr<const cxml::Node> node)
86 : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial")))
87 , x509_certificate (node->string_child ("X509Certificate"))
92 void as_xml (xmlpp::Element* node) const
94 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds"));
95 node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate);
98 Signer x509_issuer_serial;
99 std::string x509_certificate;
107 explicit Reference (string u)
111 explicit Reference (shared_ptr<const cxml::Node> node)
112 : uri (node->string_attribute ("URI"))
113 , digest_value (node->string_child ("DigestValue"))
118 void as_xml (xmlpp::Element* node) const
120 node->set_attribute ("URI", uri);
121 node->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
122 node->add_child("DigestValue", "ds")->add_child_text (digest_value);
133 : authenticated_public ("#ID_AuthenticatedPublic")
134 , authenticated_private ("#ID_AuthenticatedPrivate")
137 explicit SignedInfo (shared_ptr<const cxml::Node> node)
139 list<shared_ptr<cxml::Node> > references = node->node_children ("Reference");
140 for (list<shared_ptr<cxml::Node> >::const_iterator i = references.begin(); i != references.end(); ++i) {
141 if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPublic") {
142 authenticated_public = Reference (*i);
143 } else if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPrivate") {
144 authenticated_private = Reference (*i);
147 /* XXX: do something if we don't recognise the node */
151 void as_xml (xmlpp::Element* node) const
153 node->add_child ("CanonicalizationMethod", "ds")->set_attribute (
154 "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
157 node->add_child ("SignatureMethod", "ds")->set_attribute (
158 "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
161 authenticated_public.as_xml (node->add_child ("Reference", "ds"));
162 authenticated_private.as_xml (node->add_child ("Reference", "ds"));
166 Reference authenticated_public;
167 Reference authenticated_private;
175 explicit Signature (shared_ptr<const cxml::Node> node)
176 : signed_info (node->node_child ("SignedInfo"))
177 , signature_value (node->string_child ("SignatureValue"))
179 list<shared_ptr<cxml::Node> > x509_data_nodes = node->node_child("KeyInfo")->node_children ("X509Data");
180 for (list<shared_ptr<cxml::Node> >::const_iterator i = x509_data_nodes.begin(); i != x509_data_nodes.end(); ++i) {
181 x509_data.push_back (X509Data (*i));
185 void as_xml (xmlpp::Node* node) const
187 signed_info.as_xml (node->add_child ("SignedInfo", "ds"));
188 node->add_child("SignatureValue", "ds")->add_child_text (signature_value);
190 xmlpp::Element* key_info_node = node->add_child ("KeyInfo", "ds");
191 for (std::list<X509Data>::const_iterator i = x509_data.begin(); i != x509_data.end(); ++i) {
192 i->as_xml (key_info_node->add_child ("X509Data", "ds"));
196 SignedInfo signed_info;
197 string signature_value;
198 list<X509Data> x509_data;
201 class AuthenticatedPrivate
204 AuthenticatedPrivate () {}
206 explicit AuthenticatedPrivate (shared_ptr<const cxml::Node> node)
208 list<shared_ptr<cxml::Node> > encrypted_key_nodes = node->node_children ("EncryptedKey");
209 for (list<shared_ptr<cxml::Node> >::const_iterator i = encrypted_key_nodes.begin(); i != encrypted_key_nodes.end(); ++i) {
210 encrypted_key.push_back ((*i)->node_child("CipherData")->string_child ("CipherValue"));
214 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
216 references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate");
218 for (list<string>::const_iterator i = encrypted_key.begin(); i != encrypted_key.end(); ++i) {
219 xmlpp::Element* encrypted_key = node->add_child ("EncryptedKey", "enc");
220 /* XXX: hack for testing with Dolby */
221 encrypted_key->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
222 xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc");
223 encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
224 xmlpp::Element* digest_method = encryption_method->add_child ("DigestMethod", "ds");
225 /* XXX: hack for testing with Dolby */
226 digest_method->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
227 digest_method->set_attribute ("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
228 xmlpp::Element* cipher_data = encrypted_key->add_child ("CipherData", "enc");
229 cipher_data->add_child("CipherValue", "enc")->add_child_text (*i);
233 list<string> encrypted_key;
241 explicit TypedKeyId (shared_ptr<const cxml::Node> node)
242 : key_type (node->string_child ("KeyType"))
243 , key_id (remove_urn_uuid (node->string_child ("KeyId")))
248 TypedKeyId (string type, string id)
253 void as_xml (xmlpp::Element* node) const
255 xmlpp::Element* type = node->add_child("KeyType");
256 type->add_child_text (key_type);
257 node->add_child("KeyId")->add_child_text ("urn:uuid:" + key_id);
258 /* XXX: this feels like a bit of a hack */
259 if (key_type == "MDEK") {
260 type->set_attribute ("scope", "http://www.dolby.com/cp850/2012/KDM#kdm-key-type");
262 type->set_attribute ("scope", "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
275 explicit KeyIdList (shared_ptr<const cxml::Node> node)
277 list<shared_ptr<cxml::Node> > typed_key_id_nodes = node->node_children ("TypedKeyId");
278 for (list<shared_ptr<cxml::Node> >::const_iterator i = typed_key_id_nodes.begin(); i != typed_key_id_nodes.end(); ++i) {
279 typed_key_id.push_back (TypedKeyId (*i));
283 void as_xml (xmlpp::Element* node) const
285 for (list<TypedKeyId>::const_iterator i = typed_key_id.begin(); i != typed_key_id.end(); ++i) {
286 i->as_xml (node->add_child("TypedKeyId"));
290 list<TypedKeyId> typed_key_id;
293 class AuthorizedDeviceInfo
296 AuthorizedDeviceInfo () {}
298 explicit AuthorizedDeviceInfo (shared_ptr<const cxml::Node> node)
299 : device_list_identifier (remove_urn_uuid (node->string_child ("DeviceListIdentifier")))
300 , device_list_description (node->optional_string_child ("DeviceListDescription"))
302 BOOST_FOREACH (cxml::ConstNodePtr i, node->node_child("DeviceList")->node_children("CertificateThumbprint")) {
303 certificate_thumbprints.push_back (i->content ());
307 void as_xml (xmlpp::Element* node) const
309 node->add_child ("DeviceListIdentifier")->add_child_text ("urn:uuid:" + device_list_identifier);
310 if (device_list_description) {
311 node->add_child ("DeviceListDescription")->add_child_text (device_list_description.get());
313 xmlpp::Element* device_list = node->add_child ("DeviceList");
314 BOOST_FOREACH (string i, certificate_thumbprints) {
315 device_list->add_child("CertificateThumbprint")->add_child_text (i);
319 /** DeviceListIdentifier without the urn:uuid: prefix */
320 string device_list_identifier;
321 boost::optional<string> device_list_description;
322 std::list<string> certificate_thumbprints;
325 class X509IssuerSerial
328 X509IssuerSerial () {}
330 explicit X509IssuerSerial (shared_ptr<const cxml::Node> node)
331 : x509_issuer_name (node->string_child ("X509IssuerName"))
332 , x509_serial_number (node->string_child ("X509SerialNumber"))
337 void as_xml (xmlpp::Element* node) const
339 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
340 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
343 string x509_issuer_name;
344 string x509_serial_number;
352 explicit Recipient (shared_ptr<const cxml::Node> node)
353 : x509_issuer_serial (node->node_child ("X509IssuerSerial"))
354 , x509_subject_name (node->string_child ("X509SubjectName"))
359 void as_xml (xmlpp::Element* node) const
361 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial"));
362 node->add_child("X509SubjectName")->add_child_text (x509_subject_name);
365 X509IssuerSerial x509_issuer_serial;
366 string x509_subject_name;
369 class KDMRequiredExtensions
372 KDMRequiredExtensions () {}
374 explicit KDMRequiredExtensions (shared_ptr<const cxml::Node> node)
375 : recipient (node->node_child ("Recipient"))
376 , composition_playlist_id (remove_urn_uuid (node->string_child ("CompositionPlaylistId")))
377 , content_title_text (node->string_child ("ContentTitleText"))
378 , not_valid_before (node->string_child ("ContentKeysNotValidBefore"))
379 , not_valid_after (node->string_child ("ContentKeysNotValidAfter"))
380 , authorized_device_info (node->node_child ("AuthorizedDeviceInfo"))
381 , key_id_list (node->node_child ("KeyIdList"))
386 void as_xml (xmlpp::Element* node) const
388 node->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM");
390 recipient.as_xml (node->add_child ("Recipient"));
391 node->add_child("CompositionPlaylistId")->add_child_text ("urn:uuid:" + composition_playlist_id);
392 node->add_child("ContentTitleText")->add_child_text (content_title_text);
393 if (content_authenticator) {
394 node->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ());
396 node->add_child("ContentKeysNotValidBefore")->add_child_text (not_valid_before.as_string ());
397 node->add_child("ContentKeysNotValidAfter")->add_child_text (not_valid_after.as_string ());
398 if (authorized_device_info) {
399 authorized_device_info->as_xml (node->add_child ("AuthorizedDeviceInfo"));
401 key_id_list.as_xml (node->add_child ("KeyIdList"));
403 xmlpp::Element* forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList");
404 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
405 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
409 string composition_playlist_id;
410 boost::optional<string> content_authenticator;
411 string content_title_text;
412 LocalTime not_valid_before;
413 LocalTime not_valid_after;
414 boost::optional<AuthorizedDeviceInfo> authorized_device_info;
415 KeyIdList key_id_list;
418 class RequiredExtensions
421 RequiredExtensions () {}
423 explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
424 : kdm_required_extensions (node->node_child ("KDMRequiredExtensions"))
429 void as_xml (xmlpp::Element* node) const
431 kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions"));
434 KDMRequiredExtensions kdm_required_extensions;
437 class AuthenticatedPublic
440 AuthenticatedPublic ()
441 : message_id (make_uuid ())
442 /* XXX: hack for Dolby to see if there must be a not-empty annotation text */
443 , annotation_text ("none")
444 , issue_date (LocalTime().as_string ())
447 explicit AuthenticatedPublic (shared_ptr<const cxml::Node> node)
448 : message_id (remove_urn_uuid (node->string_child ("MessageId")))
449 , annotation_text (node->optional_string_child ("AnnotationText"))
450 , issue_date (node->string_child ("IssueDate"))
451 , signer (node->node_child ("Signer"))
452 , required_extensions (node->node_child ("RequiredExtensions"))
457 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
459 references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
461 node->add_child("MessageId")->add_child_text ("urn:uuid:" + message_id);
462 node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
463 if (annotation_text) {
464 node->add_child("AnnotationText")->add_child_text (annotation_text.get ());
466 node->add_child("IssueDate")->add_child_text (issue_date);
468 signer.as_xml (node->add_child ("Signer"));
469 required_extensions.as_xml (node->add_child ("RequiredExtensions"));
471 node->add_child ("NonCriticalExtensions");
475 optional<string> annotation_text;
478 RequiredExtensions required_extensions;
481 /** Class to describe our data. We use a class hierarchy as it's a bit nicer
482 * for XML data than a flat description.
484 class EncryptedKDMData
492 explicit EncryptedKDMData (shared_ptr<const cxml::Node> node)
493 : authenticated_public (node->node_child ("AuthenticatedPublic"))
494 , authenticated_private (node->node_child ("AuthenticatedPrivate"))
495 , signature (node->node_child ("Signature"))
500 shared_ptr<xmlpp::Document> as_xml () const
502 shared_ptr<xmlpp::Document> document (new xmlpp::Document ());
503 xmlpp::Element* root = document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM");
504 root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
505 root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
506 map<string, xmlpp::Attribute *> references;
507 authenticated_public.as_xml (root->add_child ("AuthenticatedPublic"), references);
508 authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references);
509 signature.as_xml (root->add_child ("Signature", "ds"));
511 for (map<string, xmlpp::Attribute*>::const_iterator i = references.begin(); i != references.end(); ++i) {
512 xmlAddID (0, document->cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ());
518 AuthenticatedPublic authenticated_public;
519 AuthenticatedPrivate authenticated_private;
526 EncryptedKDM::EncryptedKDM (string s)
528 shared_ptr<cxml::Document> doc (new cxml::Document ("DCinemaSecurityMessage"));
529 doc->read_string (s);
530 _data = new data::EncryptedKDMData (doc);
533 EncryptedKDM::EncryptedKDM (
534 shared_ptr<const CertificateChain> signer,
535 Certificate recipient,
536 vector<Certificate> trusted_devices,
538 string content_title_text,
539 optional<string> annotation_text,
540 LocalTime not_valid_before,
541 LocalTime not_valid_after,
542 Formulation formulation,
543 list<pair<string, string> > key_ids,
546 : _data (new data::EncryptedKDMData)
548 /* Fill our XML-ish description in with the juicy bits that the caller has given */
550 /* Our ideas about the KDM types are:
552 * Type Trusted-device thumb ContentAuthenticator
553 * MODIFIED_TRANSITIONAL_1 assume-trust No
554 * DCI_ANY assume-trust Yes
555 * DCI_SPECIFIC as specified Yes
558 data::AuthenticatedPublic& aup = _data->authenticated_public;
559 aup.signer.x509_issuer_name = signer->leaf().issuer ();
560 aup.signer.x509_serial_number = signer->leaf().serial ();
561 aup.annotation_text = annotation_text;
563 data::KDMRequiredExtensions& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
564 kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.issuer ();
565 kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
566 kre.recipient.x509_subject_name = recipient.subject ();
567 kre.composition_playlist_id = cpl_id;
568 if (formulation == DCI_ANY || formulation == DCI_SPECIFIC) {
569 kre.content_authenticator = signer->leaf().thumbprint ();
571 kre.content_title_text = content_title_text;
572 kre.not_valid_before = not_valid_before;
573 kre.not_valid_after = not_valid_after;
575 if (formulation != MODIFIED_TRANSITIONAL_TEST) {
576 kre.authorized_device_info = data::AuthorizedDeviceInfo ();
577 kre.authorized_device_info->device_list_identifier = make_uuid ();
578 string n = recipient.subject_common_name ();
579 if (n.find (".") != string::npos) {
580 n = n.substr (n.find (".") + 1);
582 kre.authorized_device_info->device_list_description = n;
584 if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) {
585 /* Use the "assume trust" thumbprint */
586 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
587 } else if (formulation == DCI_SPECIFIC) {
588 /* As I read the standard we should use the recipient
589 /and/ other trusted device thumbprints here. MJD
590 reports that this doesn't work with his setup;
591 a working KDM does not include the recipient's
592 thumbprint (recipient.thumbprint()).
593 Waimea uses only the trusted devices here, too.
595 BOOST_FOREACH (Certificate const & i, trusted_devices) {
596 kre.authorized_device_info->certificate_thumbprints.push_back (i.thumbprint ());
601 for (list<pair<string, string> >::const_iterator i = key_ids.begin(); i != key_ids.end(); ++i) {
602 kre.key_id_list.typed_key_id.push_back (data::TypedKeyId (i->first, i->second));
605 _data->authenticated_private.encrypted_key = keys;
607 /* Read the XML so far and sign it */
608 shared_ptr<xmlpp::Document> doc = _data->as_xml ();
609 xmlpp::Node::NodeList children = doc->get_root_node()->get_children ();
610 for (xmlpp::Node::NodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
611 if ((*i)->get_name() == "Signature") {
612 signer->add_signature_value (*i, "ds");
616 /* Read the bits that add_signature_value did back into our variables */
617 shared_ptr<cxml::Node> signed_doc (new cxml::Node (doc->get_root_node ()));
618 _data->signature = data::Signature (signed_doc->node_child ("Signature"));
621 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
622 : _data (new data::EncryptedKDMData (*other._data))
628 EncryptedKDM::operator= (EncryptedKDM const & other)
630 if (this == &other) {
635 _data = new data::EncryptedKDMData (*other._data);
639 EncryptedKDM::~EncryptedKDM ()
645 EncryptedKDM::as_xml (boost::filesystem::path path) const
647 FILE* f = fopen_boost (path, "w");
648 string const x = as_xml ();
649 fwrite (x.c_str(), 1, x.length(), f);
654 EncryptedKDM::as_xml () const
656 return _data->as_xml()->write_to_string ("UTF-8");
660 EncryptedKDM::keys () const
662 return _data->authenticated_private.encrypted_key;
666 EncryptedKDM::annotation_text () const
668 return _data->authenticated_public.annotation_text;
672 EncryptedKDM::content_title_text () const
674 return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
678 EncryptedKDM::cpl_id () const
680 return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
684 EncryptedKDM::issue_date () const
686 return _data->authenticated_public.issue_date;
690 EncryptedKDM::not_valid_before () const
692 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
696 EncryptedKDM::not_valid_after () const
698 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
702 EncryptedKDM::recipient_x509_subject_name () const
704 return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name;
708 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
710 /* Not exactly efficient... */
711 return a.as_xml() == b.as_xml();