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 data::AuthenticatedPublic& aup = _data->authenticated_public;
549 aup.signer.x509_issuer_name = signer->leaf().issuer ();
550 aup.signer.x509_serial_number = signer->leaf().serial ();
551 aup.annotation_text = annotation_text;
553 data::KDMRequiredExtensions& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
554 kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.issuer ();
555 kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
556 kre.recipient.x509_subject_name = recipient.subject ();
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;
565 if (formulation != MODIFIED_TRANSITIONAL_TEST) {
566 kre.authorized_device_info = data::AuthorizedDeviceInfo ();
567 kre.authorized_device_info->device_list_identifier = make_uuid ();
568 string n = recipient.subject_common_name ();
569 if (n.find (".") != string::npos) {
570 n = n.substr (n.find (".") + 1);
572 kre.authorized_device_info->device_list_description = n;
574 if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) {
575 /* Use the "assume trust" thumbprint */
576 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
577 } else if (formulation == DCI_SPECIFIC) {
578 /* As I read the standard we should use the recipient
579 /and/ other trusted device thumbprints here. MJD
580 reports that this doesn't work with his setup;
581 a working KDM does not include the recipient's
582 thumbprint (recipient.thumbprint()).
583 Waimea uses only the trusted devices here, too.
585 BOOST_FOREACH (Certificate const & i, trusted_devices) {
586 kre.authorized_device_info->certificate_thumbprints.push_back (i.thumbprint ());
591 for (list<pair<string, string> >::const_iterator i = key_ids.begin(); i != key_ids.end(); ++i) {
592 kre.key_id_list.typed_key_id.push_back (data::TypedKeyId (i->first, i->second));
595 _data->authenticated_private.encrypted_key = keys;
597 /* Read the XML so far and sign it */
598 shared_ptr<xmlpp::Document> doc = _data->as_xml ();
599 xmlpp::Node::NodeList children = doc->get_root_node()->get_children ();
600 for (xmlpp::Node::NodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
601 if ((*i)->get_name() == "Signature") {
602 signer->add_signature_value (*i, "ds");
606 /* Read the bits that add_signature_value did back into our variables */
607 shared_ptr<cxml::Node> signed_doc (new cxml::Node (doc->get_root_node ()));
608 _data->signature = data::Signature (signed_doc->node_child ("Signature"));
611 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
612 : _data (new data::EncryptedKDMData (*other._data))
618 EncryptedKDM::operator= (EncryptedKDM const & other)
620 if (this == &other) {
625 _data = new data::EncryptedKDMData (*other._data);
629 EncryptedKDM::~EncryptedKDM ()
635 EncryptedKDM::as_xml (boost::filesystem::path path) const
637 FILE* f = fopen_boost (path, "w");
638 string const x = as_xml ();
639 fwrite (x.c_str(), 1, x.length(), f);
644 EncryptedKDM::as_xml () const
646 return _data->as_xml()->write_to_string ("UTF-8");
650 EncryptedKDM::keys () const
652 return _data->authenticated_private.encrypted_key;
656 EncryptedKDM::annotation_text () const
658 return _data->authenticated_public.annotation_text;
662 EncryptedKDM::content_title_text () const
664 return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
668 EncryptedKDM::cpl_id () const
670 return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
674 EncryptedKDM::issue_date () const
676 return _data->authenticated_public.issue_date;
680 EncryptedKDM::not_valid_before () const
682 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
686 EncryptedKDM::not_valid_after () const
688 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
692 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
694 /* Not exactly efficient... */
695 return a.as_xml() == b.as_xml();