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");
273 explicit KeyIdList (shared_ptr<const cxml::Node> node)
275 list<shared_ptr<cxml::Node> > typed_key_id_nodes = node->node_children ("TypedKeyId");
276 for (list<shared_ptr<cxml::Node> >::const_iterator i = typed_key_id_nodes.begin(); i != typed_key_id_nodes.end(); ++i) {
277 typed_key_id.push_back (TypedKeyId (*i));
281 void as_xml (xmlpp::Element* node) const
283 for (list<TypedKeyId>::const_iterator i = typed_key_id.begin(); i != typed_key_id.end(); ++i) {
284 i->as_xml (node->add_child("TypedKeyId"));
288 list<TypedKeyId> typed_key_id;
291 class AuthorizedDeviceInfo
294 AuthorizedDeviceInfo () {}
296 explicit AuthorizedDeviceInfo (shared_ptr<const cxml::Node> node)
297 : device_list_identifier (remove_urn_uuid (node->string_child ("DeviceListIdentifier")))
298 , device_list_description (node->optional_string_child ("DeviceListDescription"))
300 BOOST_FOREACH (cxml::ConstNodePtr i, node->node_child("DeviceList")->node_children("CertificateThumbprint")) {
301 certificate_thumbprints.push_back (i->content ());
305 void as_xml (xmlpp::Element* node) const
307 node->add_child ("DeviceListIdentifier")->add_child_text ("urn:uuid:" + device_list_identifier);
308 if (device_list_description) {
309 node->add_child ("DeviceListDescription")->add_child_text (device_list_description.get());
311 xmlpp::Element* device_list = node->add_child ("DeviceList");
312 BOOST_FOREACH (string i, certificate_thumbprints) {
313 device_list->add_child("CertificateThumbprint")->add_child_text (i);
317 /** DeviceListIdentifier without the urn:uuid: prefix */
318 string device_list_identifier;
319 boost::optional<string> device_list_description;
320 std::list<string> certificate_thumbprints;
323 class X509IssuerSerial
326 X509IssuerSerial () {}
328 explicit X509IssuerSerial (shared_ptr<const cxml::Node> node)
329 : x509_issuer_name (node->string_child ("X509IssuerName"))
330 , x509_serial_number (node->string_child ("X509SerialNumber"))
335 void as_xml (xmlpp::Element* node) const
337 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
338 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
341 string x509_issuer_name;
342 string x509_serial_number;
350 explicit Recipient (shared_ptr<const cxml::Node> node)
351 : x509_issuer_serial (node->node_child ("X509IssuerSerial"))
352 , x509_subject_name (node->string_child ("X509SubjectName"))
357 void as_xml (xmlpp::Element* node) const
359 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial"));
360 node->add_child("X509SubjectName")->add_child_text (x509_subject_name);
363 X509IssuerSerial x509_issuer_serial;
364 string x509_subject_name;
367 class KDMRequiredExtensions
370 KDMRequiredExtensions () {}
372 explicit KDMRequiredExtensions (shared_ptr<const cxml::Node> node)
373 : recipient (node->node_child ("Recipient"))
374 , composition_playlist_id (remove_urn_uuid (node->string_child ("CompositionPlaylistId")))
375 , content_title_text (node->string_child ("ContentTitleText"))
376 , not_valid_before (node->string_child ("ContentKeysNotValidBefore"))
377 , not_valid_after (node->string_child ("ContentKeysNotValidAfter"))
378 , authorized_device_info (node->node_child ("AuthorizedDeviceInfo"))
379 , key_id_list (node->node_child ("KeyIdList"))
384 void as_xml (xmlpp::Element* node) const
386 node->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM");
388 recipient.as_xml (node->add_child ("Recipient"));
389 node->add_child("CompositionPlaylistId")->add_child_text ("urn:uuid:" + composition_playlist_id);
390 node->add_child("ContentTitleText")->add_child_text (content_title_text);
391 if (content_authenticator) {
392 node->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ());
394 node->add_child("ContentKeysNotValidBefore")->add_child_text (not_valid_before.as_string ());
395 node->add_child("ContentKeysNotValidAfter")->add_child_text (not_valid_after.as_string ());
396 authorized_device_info.as_xml (node->add_child ("AuthorizedDeviceInfo"));
397 key_id_list.as_xml (node->add_child ("KeyIdList"));
399 xmlpp::Element* forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList");
400 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
401 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
405 string composition_playlist_id;
406 boost::optional<string> content_authenticator;
407 string content_title_text;
408 LocalTime not_valid_before;
409 LocalTime not_valid_after;
410 AuthorizedDeviceInfo authorized_device_info;
411 KeyIdList key_id_list;
414 class RequiredExtensions
417 RequiredExtensions () {}
419 explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
420 : kdm_required_extensions (node->node_child ("KDMRequiredExtensions"))
425 void as_xml (xmlpp::Element* node) const
427 kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions"));
430 KDMRequiredExtensions kdm_required_extensions;
433 class AuthenticatedPublic
436 AuthenticatedPublic ()
437 : message_id (make_uuid ())
438 /* XXX: hack for Dolby to see if there must be a not-empty annotation text */
439 , annotation_text ("none")
440 , issue_date (LocalTime().as_string ())
443 explicit AuthenticatedPublic (shared_ptr<const cxml::Node> node)
444 : message_id (remove_urn_uuid (node->string_child ("MessageId")))
445 , annotation_text (node->optional_string_child ("AnnotationText"))
446 , issue_date (node->string_child ("IssueDate"))
447 , signer (node->node_child ("Signer"))
448 , required_extensions (node->node_child ("RequiredExtensions"))
453 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
455 references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
457 node->add_child("MessageId")->add_child_text ("urn:uuid:" + message_id);
458 node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
459 if (annotation_text) {
460 node->add_child("AnnotationText")->add_child_text (annotation_text.get ());
462 node->add_child("IssueDate")->add_child_text (issue_date);
464 signer.as_xml (node->add_child ("Signer"));
465 required_extensions.as_xml (node->add_child ("RequiredExtensions"));
467 node->add_child ("NonCriticalExtensions");
471 optional<string> annotation_text;
474 RequiredExtensions required_extensions;
477 /** Class to describe our data. We use a class hierarchy as it's a bit nicer
478 * for XML data than a flat description.
480 class EncryptedKDMData
488 explicit EncryptedKDMData (shared_ptr<const cxml::Node> node)
489 : authenticated_public (node->node_child ("AuthenticatedPublic"))
490 , authenticated_private (node->node_child ("AuthenticatedPrivate"))
491 , signature (node->node_child ("Signature"))
496 shared_ptr<xmlpp::Document> as_xml () const
498 shared_ptr<xmlpp::Document> document (new xmlpp::Document ());
499 xmlpp::Element* root = document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM");
500 root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
501 root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
502 map<string, xmlpp::Attribute *> references;
503 authenticated_public.as_xml (root->add_child ("AuthenticatedPublic"), references);
504 authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references);
505 signature.as_xml (root->add_child ("Signature", "ds"));
507 for (map<string, xmlpp::Attribute*>::const_iterator i = references.begin(); i != references.end(); ++i) {
508 xmlAddID (0, document->cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ());
514 AuthenticatedPublic authenticated_public;
515 AuthenticatedPrivate authenticated_private;
522 EncryptedKDM::EncryptedKDM (string s)
524 shared_ptr<cxml::Document> doc (new cxml::Document ("DCinemaSecurityMessage"));
525 doc->read_string (s);
526 _data = new data::EncryptedKDMData (doc);
529 EncryptedKDM::EncryptedKDM (
530 shared_ptr<const CertificateChain> signer,
531 Certificate recipient,
532 vector<Certificate> trusted_devices,
533 string device_list_description,
535 string content_title_text,
536 optional<string> annotation_text,
537 LocalTime not_valid_before,
538 LocalTime not_valid_after,
539 Formulation formulation,
540 list<pair<string, string> > key_ids,
543 : _data (new data::EncryptedKDMData)
545 /* Fill our XML-ish description in with the juicy bits that the caller has given */
547 data::AuthenticatedPublic& aup = _data->authenticated_public;
548 aup.signer.x509_issuer_name = signer->leaf().issuer ();
549 aup.signer.x509_serial_number = signer->leaf().serial ();
550 aup.annotation_text = annotation_text;
552 data::KDMRequiredExtensions& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
553 kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.issuer ();
554 kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
555 kre.recipient.x509_subject_name = recipient.subject ();
556 kre.authorized_device_info.device_list_description = device_list_description;
557 kre.composition_playlist_id = cpl_id;
558 if (formulation == DCI_ANY || formulation == DCI_SPECIFIC) {
559 kre.content_authenticator = signer->leaf().thumbprint ();
561 kre.content_title_text = content_title_text;
562 kre.not_valid_before = not_valid_before;
563 kre.not_valid_after = not_valid_after;
564 kre.authorized_device_info.device_list_identifier = make_uuid ();
565 string n = recipient.subject_common_name ();
566 if (n.find (".") != string::npos) {
567 n = n.substr (n.find (".") + 1);
569 kre.authorized_device_info.device_list_description = n;
571 if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) {
572 /* Use the "assume trust" thumbprint */
573 kre.authorized_device_info.certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
574 } else if (formulation == DCI_SPECIFIC) {
575 /* As I read the standard we should use the recipient
576 /and/ other trusted device thumbprints here. MJD
577 reports that this doesn't work with his setup;
578 a working KDM does not include the recipient's
579 thumbprint (recipient.thumbprint()).
581 BOOST_FOREACH (Certificate const & i, trusted_devices) {
582 kre.authorized_device_info.certificate_thumbprints.push_back (i.thumbprint ());
586 for (list<pair<string, string> >::const_iterator i = key_ids.begin(); i != key_ids.end(); ++i) {
587 kre.key_id_list.typed_key_id.push_back (data::TypedKeyId (i->first, i->second));
590 _data->authenticated_private.encrypted_key = keys;
592 /* Read the XML so far and sign it */
593 shared_ptr<xmlpp::Document> doc = _data->as_xml ();
594 xmlpp::Node::NodeList children = doc->get_root_node()->get_children ();
595 for (xmlpp::Node::NodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
596 if ((*i)->get_name() == "Signature") {
597 signer->add_signature_value (*i, "ds");
601 /* Read the bits that add_signature_value did back into our variables */
602 shared_ptr<cxml::Node> signed_doc (new cxml::Node (doc->get_root_node ()));
603 _data->signature = data::Signature (signed_doc->node_child ("Signature"));
606 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
607 : _data (new data::EncryptedKDMData (*other._data))
613 EncryptedKDM::operator= (EncryptedKDM const & other)
615 if (this == &other) {
620 _data = new data::EncryptedKDMData (*other._data);
624 EncryptedKDM::~EncryptedKDM ()
630 EncryptedKDM::as_xml (boost::filesystem::path path) const
632 FILE* f = fopen_boost (path, "w");
633 string const x = as_xml ();
634 fwrite (x.c_str(), 1, x.length(), f);
639 EncryptedKDM::as_xml () const
641 return _data->as_xml()->write_to_string ("UTF-8");
645 EncryptedKDM::keys () const
647 return _data->authenticated_private.encrypted_key;
651 EncryptedKDM::annotation_text () const
653 return _data->authenticated_public.annotation_text;
657 EncryptedKDM::content_title_text () const
659 return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
663 EncryptedKDM::cpl_id () const
665 return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
669 EncryptedKDM::issue_date () const
671 return _data->authenticated_public.issue_date;
675 EncryptedKDM::not_valid_before () const
677 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
681 EncryptedKDM::not_valid_after () const
683 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
687 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
689 /* Not exactly efficient... */
690 return a.as_xml() == b.as_xml();