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 if (authorized_device_info) {
397 authorized_device_info->as_xml (node->add_child ("AuthorizedDeviceInfo"));
399 key_id_list.as_xml (node->add_child ("KeyIdList"));
401 xmlpp::Element* forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList");
402 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
403 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
407 string composition_playlist_id;
408 boost::optional<string> content_authenticator;
409 string content_title_text;
410 LocalTime not_valid_before;
411 LocalTime not_valid_after;
412 boost::optional<AuthorizedDeviceInfo> authorized_device_info;
413 KeyIdList key_id_list;
416 class RequiredExtensions
419 RequiredExtensions () {}
421 explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
422 : kdm_required_extensions (node->node_child ("KDMRequiredExtensions"))
427 void as_xml (xmlpp::Element* node) const
429 kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions"));
432 KDMRequiredExtensions kdm_required_extensions;
435 class AuthenticatedPublic
438 AuthenticatedPublic ()
439 : message_id (make_uuid ())
440 /* XXX: hack for Dolby to see if there must be a not-empty annotation text */
441 , annotation_text ("none")
442 , issue_date (LocalTime().as_string ())
445 explicit AuthenticatedPublic (shared_ptr<const cxml::Node> node)
446 : message_id (remove_urn_uuid (node->string_child ("MessageId")))
447 , annotation_text (node->optional_string_child ("AnnotationText"))
448 , issue_date (node->string_child ("IssueDate"))
449 , signer (node->node_child ("Signer"))
450 , required_extensions (node->node_child ("RequiredExtensions"))
455 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
457 references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
459 node->add_child("MessageId")->add_child_text ("urn:uuid:" + message_id);
460 node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
461 if (annotation_text) {
462 node->add_child("AnnotationText")->add_child_text (annotation_text.get ());
464 node->add_child("IssueDate")->add_child_text (issue_date);
466 signer.as_xml (node->add_child ("Signer"));
467 required_extensions.as_xml (node->add_child ("RequiredExtensions"));
469 node->add_child ("NonCriticalExtensions");
473 optional<string> annotation_text;
476 RequiredExtensions required_extensions;
479 /** Class to describe our data. We use a class hierarchy as it's a bit nicer
480 * for XML data than a flat description.
482 class EncryptedKDMData
490 explicit EncryptedKDMData (shared_ptr<const cxml::Node> node)
491 : authenticated_public (node->node_child ("AuthenticatedPublic"))
492 , authenticated_private (node->node_child ("AuthenticatedPrivate"))
493 , signature (node->node_child ("Signature"))
498 shared_ptr<xmlpp::Document> as_xml () const
500 shared_ptr<xmlpp::Document> document (new xmlpp::Document ());
501 xmlpp::Element* root = document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM");
502 root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
503 root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
504 map<string, xmlpp::Attribute *> references;
505 authenticated_public.as_xml (root->add_child ("AuthenticatedPublic"), references);
506 authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references);
507 signature.as_xml (root->add_child ("Signature", "ds"));
509 for (map<string, xmlpp::Attribute*>::const_iterator i = references.begin(); i != references.end(); ++i) {
510 xmlAddID (0, document->cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ());
516 AuthenticatedPublic authenticated_public;
517 AuthenticatedPrivate authenticated_private;
524 EncryptedKDM::EncryptedKDM (string s)
526 shared_ptr<cxml::Document> doc (new cxml::Document ("DCinemaSecurityMessage"));
527 doc->read_string (s);
528 _data = new data::EncryptedKDMData (doc);
531 EncryptedKDM::EncryptedKDM (
532 shared_ptr<const CertificateChain> signer,
533 Certificate recipient,
534 vector<Certificate> trusted_devices,
536 string content_title_text,
537 optional<string> annotation_text,
538 LocalTime not_valid_before,
539 LocalTime not_valid_after,
540 Formulation formulation,
541 list<pair<string, string> > key_ids,
544 : _data (new data::EncryptedKDMData)
546 /* Fill our XML-ish description in with the juicy bits that the caller has given */
548 /* Our ideas about the KDM types are:
550 * Type Trusted-device thumb ContentAuthenticator
551 * MODIFIED_TRANSITIONAL_1 assume-trust No
552 * DCI_ANY assume-trust Yes
553 * DCI_SPECIFIC as specified Yes
556 data::AuthenticatedPublic& aup = _data->authenticated_public;
557 aup.signer.x509_issuer_name = signer->leaf().issuer ();
558 aup.signer.x509_serial_number = signer->leaf().serial ();
559 aup.annotation_text = annotation_text;
561 data::KDMRequiredExtensions& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
562 kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.issuer ();
563 kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
564 kre.recipient.x509_subject_name = recipient.subject ();
565 kre.composition_playlist_id = cpl_id;
566 if (formulation == DCI_ANY || formulation == DCI_SPECIFIC) {
567 kre.content_authenticator = signer->leaf().thumbprint ();
569 kre.content_title_text = content_title_text;
570 kre.not_valid_before = not_valid_before;
571 kre.not_valid_after = not_valid_after;
573 if (formulation != MODIFIED_TRANSITIONAL_TEST) {
574 kre.authorized_device_info = data::AuthorizedDeviceInfo ();
575 kre.authorized_device_info->device_list_identifier = make_uuid ();
576 string n = recipient.subject_common_name ();
577 if (n.find (".") != string::npos) {
578 n = n.substr (n.find (".") + 1);
580 kre.authorized_device_info->device_list_description = n;
582 if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) {
583 /* Use the "assume trust" thumbprint */
584 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
585 } else if (formulation == DCI_SPECIFIC) {
586 /* As I read the standard we should use the recipient
587 /and/ other trusted device thumbprints here. MJD
588 reports that this doesn't work with his setup;
589 a working KDM does not include the recipient's
590 thumbprint (recipient.thumbprint()).
591 Waimea uses only the trusted devices here, too.
593 BOOST_FOREACH (Certificate const & i, trusted_devices) {
594 kre.authorized_device_info->certificate_thumbprints.push_back (i.thumbprint ());
599 for (list<pair<string, string> >::const_iterator i = key_ids.begin(); i != key_ids.end(); ++i) {
600 kre.key_id_list.typed_key_id.push_back (data::TypedKeyId (i->first, i->second));
603 _data->authenticated_private.encrypted_key = keys;
605 /* Read the XML so far and sign it */
606 shared_ptr<xmlpp::Document> doc = _data->as_xml ();
607 xmlpp::Node::NodeList children = doc->get_root_node()->get_children ();
608 for (xmlpp::Node::NodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
609 if ((*i)->get_name() == "Signature") {
610 signer->add_signature_value (*i, "ds");
614 /* Read the bits that add_signature_value did back into our variables */
615 shared_ptr<cxml::Node> signed_doc (new cxml::Node (doc->get_root_node ()));
616 _data->signature = data::Signature (signed_doc->node_child ("Signature"));
619 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
620 : _data (new data::EncryptedKDMData (*other._data))
626 EncryptedKDM::operator= (EncryptedKDM const & other)
628 if (this == &other) {
633 _data = new data::EncryptedKDMData (*other._data);
637 EncryptedKDM::~EncryptedKDM ()
643 EncryptedKDM::as_xml (boost::filesystem::path path) const
645 FILE* f = fopen_boost (path, "w");
646 string const x = as_xml ();
647 fwrite (x.c_str(), 1, x.length(), f);
652 EncryptedKDM::as_xml () const
654 return _data->as_xml()->write_to_string ("UTF-8");
658 EncryptedKDM::keys () const
660 return _data->authenticated_private.encrypted_key;
664 EncryptedKDM::annotation_text () const
666 return _data->authenticated_public.annotation_text;
670 EncryptedKDM::content_title_text () const
672 return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
676 EncryptedKDM::cpl_id () const
678 return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
682 EncryptedKDM::issue_date () const
684 return _data->authenticated_public.issue_date;
688 EncryptedKDM::not_valid_before () const
690 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
694 EncryptedKDM::not_valid_after () const
696 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
700 EncryptedKDM::recipient_x509_subject_name () const
702 return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name;
706 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
708 /* Not exactly efficient... */
709 return a.as_xml() == b.as_xml();