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/algorithm/string.hpp>
43 #include <boost/date_time/posix_time/posix_time.hpp>
44 #include <boost/foreach.hpp>
45 #include <boost/format.hpp>
52 using boost::shared_ptr;
53 using boost::optional;
54 using boost::starts_with;
59 /** Namespace for classes used to hold our data; they are internal to this .cc file */
67 explicit Signer (shared_ptr<const cxml::Node> node)
68 : x509_issuer_name (node->string_child ("X509IssuerName"))
69 , x509_serial_number (node->string_child ("X509SerialNumber"))
74 void as_xml (xmlpp::Element* node) const
76 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
77 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
80 string x509_issuer_name;
81 string x509_serial_number;
89 explicit X509Data (boost::shared_ptr<const cxml::Node> node)
90 : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial")))
91 , x509_certificate (node->string_child ("X509Certificate"))
96 void as_xml (xmlpp::Element* node) const
98 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds"));
99 node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate);
102 Signer x509_issuer_serial;
103 std::string x509_certificate;
111 explicit Reference (string u)
115 explicit Reference (shared_ptr<const cxml::Node> node)
116 : uri (node->string_attribute ("URI"))
117 , digest_value (node->string_child ("DigestValue"))
122 void as_xml (xmlpp::Element* node) const
124 node->set_attribute ("URI", uri);
125 node->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
126 node->add_child("DigestValue", "ds")->add_child_text (digest_value);
137 : authenticated_public ("#ID_AuthenticatedPublic")
138 , authenticated_private ("#ID_AuthenticatedPrivate")
141 explicit SignedInfo (shared_ptr<const cxml::Node> node)
143 list<shared_ptr<cxml::Node> > references = node->node_children ("Reference");
144 for (list<shared_ptr<cxml::Node> >::const_iterator i = references.begin(); i != references.end(); ++i) {
145 if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPublic") {
146 authenticated_public = Reference (*i);
147 } else if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPrivate") {
148 authenticated_private = Reference (*i);
151 /* XXX: do something if we don't recognise the node */
155 void as_xml (xmlpp::Element* node) const
157 node->add_child ("CanonicalizationMethod", "ds")->set_attribute (
158 "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
161 node->add_child ("SignatureMethod", "ds")->set_attribute (
162 "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
165 authenticated_public.as_xml (node->add_child ("Reference", "ds"));
166 authenticated_private.as_xml (node->add_child ("Reference", "ds"));
170 Reference authenticated_public;
171 Reference authenticated_private;
179 explicit Signature (shared_ptr<const cxml::Node> node)
180 : signed_info (node->node_child ("SignedInfo"))
181 , signature_value (node->string_child ("SignatureValue"))
183 list<shared_ptr<cxml::Node> > x509_data_nodes = node->node_child("KeyInfo")->node_children ("X509Data");
184 for (list<shared_ptr<cxml::Node> >::const_iterator i = x509_data_nodes.begin(); i != x509_data_nodes.end(); ++i) {
185 x509_data.push_back (X509Data (*i));
189 void as_xml (xmlpp::Node* node) const
191 signed_info.as_xml (node->add_child ("SignedInfo", "ds"));
192 node->add_child("SignatureValue", "ds")->add_child_text (signature_value);
194 xmlpp::Element* key_info_node = node->add_child ("KeyInfo", "ds");
195 for (std::list<X509Data>::const_iterator i = x509_data.begin(); i != x509_data.end(); ++i) {
196 i->as_xml (key_info_node->add_child ("X509Data", "ds"));
200 SignedInfo signed_info;
201 string signature_value;
202 list<X509Data> x509_data;
205 class AuthenticatedPrivate
208 AuthenticatedPrivate () {}
210 explicit AuthenticatedPrivate (shared_ptr<const cxml::Node> node)
212 list<shared_ptr<cxml::Node> > encrypted_key_nodes = node->node_children ("EncryptedKey");
213 for (list<shared_ptr<cxml::Node> >::const_iterator i = encrypted_key_nodes.begin(); i != encrypted_key_nodes.end(); ++i) {
214 encrypted_key.push_back ((*i)->node_child("CipherData")->string_child ("CipherValue"));
218 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
220 references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate");
222 for (list<string>::const_iterator i = encrypted_key.begin(); i != encrypted_key.end(); ++i) {
223 xmlpp::Element* encrypted_key = node->add_child ("EncryptedKey", "enc");
224 /* XXX: hack for testing with Dolby */
225 encrypted_key->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
226 xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc");
227 encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
228 xmlpp::Element* digest_method = encryption_method->add_child ("DigestMethod", "ds");
229 /* XXX: hack for testing with Dolby */
230 digest_method->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
231 digest_method->set_attribute ("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
232 xmlpp::Element* cipher_data = encrypted_key->add_child ("CipherData", "enc");
233 cipher_data->add_child("CipherValue", "enc")->add_child_text (*i);
237 list<string> encrypted_key;
245 explicit TypedKeyId (shared_ptr<const cxml::Node> node)
246 : key_type (node->string_child ("KeyType"))
247 , key_id (remove_urn_uuid (node->string_child ("KeyId")))
252 TypedKeyId (string type, string id)
257 void as_xml (xmlpp::Element* node) const
259 xmlpp::Element* type = node->add_child("KeyType");
260 type->add_child_text (key_type);
261 node->add_child("KeyId")->add_child_text ("urn:uuid:" + key_id);
262 /* XXX: this feels like a bit of a hack */
263 if (key_type == "MDEK") {
264 type->set_attribute ("scope", "http://www.dolby.com/cp850/2012/KDM#kdm-key-type");
266 type->set_attribute ("scope", "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
279 explicit KeyIdList (shared_ptr<const cxml::Node> node)
281 list<shared_ptr<cxml::Node> > typed_key_id_nodes = node->node_children ("TypedKeyId");
282 for (list<shared_ptr<cxml::Node> >::const_iterator i = typed_key_id_nodes.begin(); i != typed_key_id_nodes.end(); ++i) {
283 typed_key_id.push_back (TypedKeyId (*i));
287 void as_xml (xmlpp::Element* node) const
289 for (list<TypedKeyId>::const_iterator i = typed_key_id.begin(); i != typed_key_id.end(); ++i) {
290 i->as_xml (node->add_child("TypedKeyId"));
294 list<TypedKeyId> typed_key_id;
297 class AuthorizedDeviceInfo
300 AuthorizedDeviceInfo () {}
302 explicit AuthorizedDeviceInfo (shared_ptr<const cxml::Node> node)
303 : device_list_identifier (remove_urn_uuid (node->string_child ("DeviceListIdentifier")))
304 , device_list_description (node->optional_string_child ("DeviceListDescription"))
306 BOOST_FOREACH (cxml::ConstNodePtr i, node->node_child("DeviceList")->node_children("CertificateThumbprint")) {
307 certificate_thumbprints.push_back (i->content ());
311 void as_xml (xmlpp::Element* node) const
313 node->add_child ("DeviceListIdentifier")->add_child_text ("urn:uuid:" + device_list_identifier);
314 if (device_list_description) {
315 node->add_child ("DeviceListDescription")->add_child_text (device_list_description.get());
317 xmlpp::Element* device_list = node->add_child ("DeviceList");
318 BOOST_FOREACH (string i, certificate_thumbprints) {
319 device_list->add_child("CertificateThumbprint")->add_child_text (i);
323 /** DeviceListIdentifier without the urn:uuid: prefix */
324 string device_list_identifier;
325 boost::optional<string> device_list_description;
326 std::list<string> certificate_thumbprints;
329 class X509IssuerSerial
332 X509IssuerSerial () {}
334 explicit X509IssuerSerial (shared_ptr<const cxml::Node> node)
335 : x509_issuer_name (node->string_child ("X509IssuerName"))
336 , x509_serial_number (node->string_child ("X509SerialNumber"))
341 void as_xml (xmlpp::Element* node) const
343 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
344 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
347 string x509_issuer_name;
348 string x509_serial_number;
356 explicit Recipient (shared_ptr<const cxml::Node> node)
357 : x509_issuer_serial (node->node_child ("X509IssuerSerial"))
358 , x509_subject_name (node->string_child ("X509SubjectName"))
363 void as_xml (xmlpp::Element* node) const
365 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial"));
366 node->add_child("X509SubjectName")->add_child_text (x509_subject_name);
369 X509IssuerSerial x509_issuer_serial;
370 string x509_subject_name;
373 class KDMRequiredExtensions
376 KDMRequiredExtensions () {}
378 explicit KDMRequiredExtensions (shared_ptr<const cxml::Node> node)
379 : recipient (node->node_child ("Recipient"))
380 , composition_playlist_id (remove_urn_uuid (node->string_child ("CompositionPlaylistId")))
381 , content_title_text (node->string_child ("ContentTitleText"))
382 , not_valid_before (node->string_child ("ContentKeysNotValidBefore"))
383 , not_valid_after (node->string_child ("ContentKeysNotValidAfter"))
384 , authorized_device_info (node->node_child ("AuthorizedDeviceInfo"))
385 , key_id_list (node->node_child ("KeyIdList"))
387 disable_forensic_marking_picture = 0;
388 disable_forensic_marking_audio = 0;
389 if (node->optional_node_child("ForensicMarkFlagList")) {
390 BOOST_FOREACH (cxml::ConstNodePtr i, node->node_child("ForensicMarkFlagList")->node_children("ForensicMarkFlag")) {
391 if (i->content() == picture_disable) {
392 disable_forensic_marking_picture = -1;
393 } else if (starts_with(i->content(), audio_disable)) {
394 disable_forensic_marking_audio = -1;
395 string const above = audio_disable + "-above-channel-";
396 if (starts_with(i->content(), above)) {
397 string above_number = i->content().substr(above.length());
398 if (above_number == "") {
399 throw KDMFormatError("Badly-formatted ForensicMarkFlag");
401 disable_forensic_marking_audio = atoi(above_number.c_str());
408 void as_xml (xmlpp::Element* node) const
410 node->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM");
412 recipient.as_xml (node->add_child ("Recipient"));
413 node->add_child("CompositionPlaylistId")->add_child_text ("urn:uuid:" + composition_playlist_id);
414 node->add_child("ContentTitleText")->add_child_text (content_title_text);
415 if (content_authenticator) {
416 node->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ());
418 node->add_child("ContentKeysNotValidBefore")->add_child_text (not_valid_before.as_string ());
419 node->add_child("ContentKeysNotValidAfter")->add_child_text (not_valid_after.as_string ());
420 if (authorized_device_info) {
421 authorized_device_info->as_xml (node->add_child ("AuthorizedDeviceInfo"));
423 key_id_list.as_xml (node->add_child ("KeyIdList"));
425 if (disable_forensic_marking_picture || disable_forensic_marking_audio) {
426 xmlpp::Element* forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList");
427 if (disable_forensic_marking_picture) {
428 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text(picture_disable);
430 if (disable_forensic_marking_audio) {
431 string mrkflg = audio_disable;
432 if (disable_forensic_marking_audio != -1) {
433 mrkflg = str (boost::format (mrkflg + "-above-channel-%u") % disable_forensic_marking_audio);
435 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text (mrkflg);
441 string composition_playlist_id;
442 boost::optional<string> content_authenticator;
443 string content_title_text;
444 LocalTime not_valid_before;
445 LocalTime not_valid_after;
446 int disable_forensic_marking_picture;
447 int disable_forensic_marking_audio;
448 boost::optional<AuthorizedDeviceInfo> authorized_device_info;
449 KeyIdList key_id_list;
452 static const string picture_disable;
453 static const string audio_disable;
456 const string KDMRequiredExtensions::picture_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable";
457 const string KDMRequiredExtensions::audio_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable";
459 class RequiredExtensions
462 RequiredExtensions () {}
464 explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
465 : kdm_required_extensions (node->node_child ("KDMRequiredExtensions"))
470 void as_xml (xmlpp::Element* node) const
472 kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions"));
475 KDMRequiredExtensions kdm_required_extensions;
478 class AuthenticatedPublic
481 AuthenticatedPublic ()
482 : message_id (make_uuid ())
483 /* XXX: hack for Dolby to see if there must be a not-empty annotation text */
484 , annotation_text ("none")
485 , issue_date (LocalTime().as_string ())
488 explicit AuthenticatedPublic (shared_ptr<const cxml::Node> node)
489 : message_id (remove_urn_uuid (node->string_child ("MessageId")))
490 , annotation_text (node->optional_string_child ("AnnotationText"))
491 , issue_date (node->string_child ("IssueDate"))
492 , signer (node->node_child ("Signer"))
493 , required_extensions (node->node_child ("RequiredExtensions"))
498 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
500 references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
502 node->add_child("MessageId")->add_child_text ("urn:uuid:" + message_id);
503 node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
504 if (annotation_text) {
505 node->add_child("AnnotationText")->add_child_text (annotation_text.get ());
507 node->add_child("IssueDate")->add_child_text (issue_date);
509 signer.as_xml (node->add_child ("Signer"));
510 required_extensions.as_xml (node->add_child ("RequiredExtensions"));
512 node->add_child ("NonCriticalExtensions");
516 optional<string> annotation_text;
519 RequiredExtensions required_extensions;
522 /** Class to describe our data. We use a class hierarchy as it's a bit nicer
523 * for XML data than a flat description.
525 class EncryptedKDMData
533 explicit EncryptedKDMData (shared_ptr<const cxml::Node> node)
534 : authenticated_public (node->node_child ("AuthenticatedPublic"))
535 , authenticated_private (node->node_child ("AuthenticatedPrivate"))
536 , signature (node->node_child ("Signature"))
541 shared_ptr<xmlpp::Document> as_xml () const
543 shared_ptr<xmlpp::Document> document (new xmlpp::Document ());
544 xmlpp::Element* root = document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM");
545 root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
546 root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
547 map<string, xmlpp::Attribute *> references;
548 authenticated_public.as_xml (root->add_child ("AuthenticatedPublic"), references);
549 authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references);
550 signature.as_xml (root->add_child ("Signature", "ds"));
552 for (map<string, xmlpp::Attribute*>::const_iterator i = references.begin(); i != references.end(); ++i) {
553 xmlAddID (0, document->cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ());
559 AuthenticatedPublic authenticated_public;
560 AuthenticatedPrivate authenticated_private;
567 EncryptedKDM::EncryptedKDM (string s)
570 shared_ptr<cxml::Document> doc (new cxml::Document ("DCinemaSecurityMessage"));
571 doc->read_string (s);
572 _data = new data::EncryptedKDMData (doc);
573 } catch (xmlpp::parse_error& e) {
574 throw KDMFormatError (e.what ());
578 EncryptedKDM::EncryptedKDM (
579 shared_ptr<const CertificateChain> signer,
580 Certificate recipient,
581 vector<Certificate> trusted_devices,
583 string content_title_text,
584 optional<string> annotation_text,
585 LocalTime not_valid_before,
586 LocalTime not_valid_after,
587 Formulation formulation,
588 int disable_forensic_marking_picture,
589 int disable_forensic_marking_audio,
590 list<pair<string, string> > key_ids,
593 : _data (new data::EncryptedKDMData)
595 /* Fill our XML-ish description in with the juicy bits that the caller has given */
597 /* Our ideas, based on http://isdcf.com/papers/ISDCF-Doc5-kdm-certs.pdf, about the KDM types are:
599 * Type Trusted-device thumb ContentAuthenticator
600 * MODIFIED_TRANSITIONAL_1 assume-trust No
601 * MULTIPLE_MODIFIED_TRANSITIONAL_1 as specified No
602 * DCI_ANY assume-trust Yes
603 * DCI_SPECIFIC as specified Yes
606 data::AuthenticatedPublic& aup = _data->authenticated_public;
607 aup.signer.x509_issuer_name = signer->leaf().issuer ();
608 aup.signer.x509_serial_number = signer->leaf().serial ();
609 aup.annotation_text = annotation_text;
611 data::KDMRequiredExtensions& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
612 kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.issuer ();
613 kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
614 kre.recipient.x509_subject_name = recipient.subject ();
615 kre.composition_playlist_id = cpl_id;
616 if (formulation == DCI_ANY || formulation == DCI_SPECIFIC) {
617 kre.content_authenticator = signer->leaf().thumbprint ();
619 kre.content_title_text = content_title_text;
620 kre.not_valid_before = not_valid_before;
621 kre.not_valid_after = not_valid_after;
622 kre.disable_forensic_marking_picture = disable_forensic_marking_picture;
623 kre.disable_forensic_marking_audio = disable_forensic_marking_audio;
625 if (formulation != MODIFIED_TRANSITIONAL_TEST) {
626 kre.authorized_device_info = data::AuthorizedDeviceInfo ();
627 kre.authorized_device_info->device_list_identifier = make_uuid ();
628 string n = recipient.subject_common_name ();
629 if (n.find (".") != string::npos) {
630 n = n.substr (n.find (".") + 1);
632 kre.authorized_device_info->device_list_description = n;
634 if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) {
635 /* Use the "assume trust" thumbprint */
636 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
637 } else if (formulation == MULTIPLE_MODIFIED_TRANSITIONAL_1 || formulation == DCI_SPECIFIC) {
638 if (trusted_devices.empty ()) {
639 /* Fall back on the "assume trust" thumbprint so we
640 can generate "modified-transitional-1" KDMs
641 together with "multiple-modified-transitional-1"
642 KDMs in one go, and similarly for "dci-any" etc.
644 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
646 /* As I read the standard we should use the
647 recipient /and/ other trusted device thumbprints
648 here. MJD reports that this doesn't work with
649 his setup; a working KDM does not include the
650 recipient's thumbprint (recipient.thumbprint()).
651 Waimea uses only the trusted devices here, too.
653 BOOST_FOREACH (Certificate const & i, trusted_devices) {
654 kre.authorized_device_info->certificate_thumbprints.push_back (i.thumbprint ());
660 for (list<pair<string, string> >::const_iterator i = key_ids.begin(); i != key_ids.end(); ++i) {
661 kre.key_id_list.typed_key_id.push_back (data::TypedKeyId (i->first, i->second));
664 _data->authenticated_private.encrypted_key = keys;
666 /* Read the XML so far and sign it */
667 shared_ptr<xmlpp::Document> doc = _data->as_xml ();
668 xmlpp::Node::NodeList children = doc->get_root_node()->get_children ();
669 for (xmlpp::Node::NodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
670 if ((*i)->get_name() == "Signature") {
671 signer->add_signature_value (*i, "ds");
675 /* Read the bits that add_signature_value did back into our variables */
676 shared_ptr<cxml::Node> signed_doc (new cxml::Node (doc->get_root_node ()));
677 _data->signature = data::Signature (signed_doc->node_child ("Signature"));
680 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
681 : _data (new data::EncryptedKDMData (*other._data))
687 EncryptedKDM::operator= (EncryptedKDM const & other)
689 if (this == &other) {
694 _data = new data::EncryptedKDMData (*other._data);
698 EncryptedKDM::~EncryptedKDM ()
704 EncryptedKDM::as_xml (boost::filesystem::path path) const
706 FILE* f = fopen_boost (path, "w");
707 string const x = as_xml ();
708 fwrite (x.c_str(), 1, x.length(), f);
713 EncryptedKDM::as_xml () const
715 return _data->as_xml()->write_to_string ("UTF-8");
719 EncryptedKDM::keys () const
721 return _data->authenticated_private.encrypted_key;
725 EncryptedKDM::id () const
727 return _data->authenticated_public.message_id;
731 EncryptedKDM::annotation_text () const
733 return _data->authenticated_public.annotation_text;
737 EncryptedKDM::content_title_text () const
739 return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
743 EncryptedKDM::cpl_id () const
745 return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
749 EncryptedKDM::issue_date () const
751 return _data->authenticated_public.issue_date;
755 EncryptedKDM::not_valid_before () const
757 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
761 EncryptedKDM::not_valid_after () const
763 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
767 EncryptedKDM::recipient_x509_subject_name () const
769 return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name;
773 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
775 /* Not exactly efficient... */
776 return a.as_xml() == b.as_xml();