2 Copyright (C) 2013-2018 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 "compose.hpp"
39 #include <libcxml/cxml.h>
40 #include <libxml++/document.h>
41 #include <libxml++/nodes/element.h>
42 #include <libxml/parser.h>
43 #include <boost/algorithm/string.hpp>
44 #include <boost/date_time/posix_time/posix_time.hpp>
45 #include <boost/foreach.hpp>
46 #include <boost/format.hpp>
53 using boost::shared_ptr;
54 using boost::optional;
55 using boost::starts_with;
60 /** Namespace for classes used to hold our data; they are internal to this .cc file */
68 explicit Signer (shared_ptr<const cxml::Node> node)
69 : x509_issuer_name (node->string_child ("X509IssuerName"))
70 , x509_serial_number (node->string_child ("X509SerialNumber"))
75 void as_xml (xmlpp::Element* node) const
77 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
78 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
81 string x509_issuer_name;
82 string x509_serial_number;
90 explicit X509Data (boost::shared_ptr<const cxml::Node> node)
91 : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial")))
92 , x509_certificate (node->string_child ("X509Certificate"))
97 void as_xml (xmlpp::Element* node) const
99 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds"));
100 node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate);
103 Signer x509_issuer_serial;
104 std::string x509_certificate;
112 explicit Reference (string u)
116 explicit Reference (shared_ptr<const cxml::Node> node)
117 : uri (node->string_attribute ("URI"))
118 , digest_value (node->string_child ("DigestValue"))
123 void as_xml (xmlpp::Element* node) const
125 node->set_attribute ("URI", uri);
126 node->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
127 node->add_child("DigestValue", "ds")->add_child_text (digest_value);
138 : authenticated_public ("#ID_AuthenticatedPublic")
139 , authenticated_private ("#ID_AuthenticatedPrivate")
142 explicit SignedInfo (shared_ptr<const cxml::Node> node)
144 list<shared_ptr<cxml::Node> > references = node->node_children ("Reference");
145 for (list<shared_ptr<cxml::Node> >::const_iterator i = references.begin(); i != references.end(); ++i) {
146 if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPublic") {
147 authenticated_public = Reference (*i);
148 } else if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPrivate") {
149 authenticated_private = Reference (*i);
152 /* XXX: do something if we don't recognise the node */
156 void as_xml (xmlpp::Element* node) const
158 node->add_child ("CanonicalizationMethod", "ds")->set_attribute (
159 "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
162 node->add_child ("SignatureMethod", "ds")->set_attribute (
163 "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
166 authenticated_public.as_xml (node->add_child ("Reference", "ds"));
167 authenticated_private.as_xml (node->add_child ("Reference", "ds"));
171 Reference authenticated_public;
172 Reference authenticated_private;
180 explicit Signature (shared_ptr<const cxml::Node> node)
181 : signed_info (node->node_child ("SignedInfo"))
182 , signature_value (node->string_child ("SignatureValue"))
184 list<shared_ptr<cxml::Node> > x509_data_nodes = node->node_child("KeyInfo")->node_children ("X509Data");
185 for (list<shared_ptr<cxml::Node> >::const_iterator i = x509_data_nodes.begin(); i != x509_data_nodes.end(); ++i) {
186 x509_data.push_back (X509Data (*i));
190 void as_xml (xmlpp::Node* node) const
192 signed_info.as_xml (node->add_child ("SignedInfo", "ds"));
193 node->add_child("SignatureValue", "ds")->add_child_text (signature_value);
195 xmlpp::Element* key_info_node = node->add_child ("KeyInfo", "ds");
196 for (std::list<X509Data>::const_iterator i = x509_data.begin(); i != x509_data.end(); ++i) {
197 i->as_xml (key_info_node->add_child ("X509Data", "ds"));
201 SignedInfo signed_info;
202 string signature_value;
203 list<X509Data> x509_data;
206 class AuthenticatedPrivate
209 AuthenticatedPrivate () {}
211 explicit AuthenticatedPrivate (shared_ptr<const cxml::Node> node)
213 list<shared_ptr<cxml::Node> > encrypted_key_nodes = node->node_children ("EncryptedKey");
214 for (list<shared_ptr<cxml::Node> >::const_iterator i = encrypted_key_nodes.begin(); i != encrypted_key_nodes.end(); ++i) {
215 encrypted_key.push_back ((*i)->node_child("CipherData")->string_child ("CipherValue"));
219 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
221 references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate");
223 for (list<string>::const_iterator i = encrypted_key.begin(); i != encrypted_key.end(); ++i) {
224 xmlpp::Element* encrypted_key = node->add_child ("EncryptedKey", "enc");
225 /* XXX: hack for testing with Dolby */
226 encrypted_key->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
227 xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc");
228 encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
229 xmlpp::Element* digest_method = encryption_method->add_child ("DigestMethod", "ds");
230 /* XXX: hack for testing with Dolby */
231 digest_method->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
232 digest_method->set_attribute ("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
233 xmlpp::Element* cipher_data = encrypted_key->add_child ("CipherData", "enc");
234 cipher_data->add_child("CipherValue", "enc")->add_child_text (*i);
238 list<string> encrypted_key;
246 explicit TypedKeyId (shared_ptr<const cxml::Node> node)
247 : key_type (node->string_child ("KeyType"))
248 , key_id (remove_urn_uuid (node->string_child ("KeyId")))
253 TypedKeyId (string type, string id)
258 void as_xml (xmlpp::Element* node) const
260 xmlpp::Element* type = node->add_child("KeyType");
261 type->add_child_text (key_type);
262 node->add_child("KeyId")->add_child_text ("urn:uuid:" + key_id);
263 /* XXX: this feels like a bit of a hack */
264 if (key_type == "MDEK") {
265 type->set_attribute ("scope", "http://www.dolby.com/cp850/2012/KDM#kdm-key-type");
267 type->set_attribute ("scope", "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
280 explicit KeyIdList (shared_ptr<const cxml::Node> node)
282 list<shared_ptr<cxml::Node> > typed_key_id_nodes = node->node_children ("TypedKeyId");
283 for (list<shared_ptr<cxml::Node> >::const_iterator i = typed_key_id_nodes.begin(); i != typed_key_id_nodes.end(); ++i) {
284 typed_key_id.push_back (TypedKeyId (*i));
288 void as_xml (xmlpp::Element* node) const
290 for (list<TypedKeyId>::const_iterator i = typed_key_id.begin(); i != typed_key_id.end(); ++i) {
291 i->as_xml (node->add_child("TypedKeyId"));
295 list<TypedKeyId> typed_key_id;
298 class AuthorizedDeviceInfo
301 AuthorizedDeviceInfo () {}
303 explicit AuthorizedDeviceInfo (shared_ptr<const cxml::Node> node)
304 : device_list_identifier (remove_urn_uuid (node->string_child ("DeviceListIdentifier")))
305 , device_list_description (node->optional_string_child ("DeviceListDescription"))
307 BOOST_FOREACH (cxml::ConstNodePtr i, node->node_child("DeviceList")->node_children("CertificateThumbprint")) {
308 certificate_thumbprints.push_back (i->content ());
312 void as_xml (xmlpp::Element* node) const
314 node->add_child ("DeviceListIdentifier")->add_child_text ("urn:uuid:" + device_list_identifier);
315 if (device_list_description) {
316 node->add_child ("DeviceListDescription")->add_child_text (device_list_description.get());
318 xmlpp::Element* device_list = node->add_child ("DeviceList");
319 BOOST_FOREACH (string i, certificate_thumbprints) {
320 device_list->add_child("CertificateThumbprint")->add_child_text (i);
324 /** DeviceListIdentifier without the urn:uuid: prefix */
325 string device_list_identifier;
326 boost::optional<string> device_list_description;
327 std::list<string> certificate_thumbprints;
330 class X509IssuerSerial
333 X509IssuerSerial () {}
335 explicit X509IssuerSerial (shared_ptr<const cxml::Node> node)
336 : x509_issuer_name (node->string_child ("X509IssuerName"))
337 , x509_serial_number (node->string_child ("X509SerialNumber"))
342 void as_xml (xmlpp::Element* node) const
344 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
345 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
348 string x509_issuer_name;
349 string x509_serial_number;
357 explicit Recipient (shared_ptr<const cxml::Node> node)
358 : x509_issuer_serial (node->node_child ("X509IssuerSerial"))
359 , x509_subject_name (node->string_child ("X509SubjectName"))
364 void as_xml (xmlpp::Element* node) const
366 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial"));
367 node->add_child("X509SubjectName")->add_child_text (x509_subject_name);
370 X509IssuerSerial x509_issuer_serial;
371 string x509_subject_name;
374 class KDMRequiredExtensions
377 KDMRequiredExtensions () {}
379 explicit KDMRequiredExtensions (shared_ptr<const cxml::Node> node)
380 : recipient (node->node_child ("Recipient"))
381 , composition_playlist_id (remove_urn_uuid (node->string_child ("CompositionPlaylistId")))
382 , content_title_text (node->string_child ("ContentTitleText"))
383 , not_valid_before (node->string_child ("ContentKeysNotValidBefore"))
384 , not_valid_after (node->string_child ("ContentKeysNotValidAfter"))
385 , authorized_device_info (node->node_child ("AuthorizedDeviceInfo"))
386 , key_id_list (node->node_child ("KeyIdList"))
388 disable_forensic_marking_picture = false;
389 disable_forensic_marking_audio = optional<int>();
390 if (node->optional_node_child("ForensicMarkFlagList")) {
391 BOOST_FOREACH (cxml::ConstNodePtr i, node->node_child("ForensicMarkFlagList")->node_children("ForensicMarkFlag")) {
392 if (i->content() == picture_disable) {
393 disable_forensic_marking_picture = true;
394 } else if (starts_with(i->content(), audio_disable)) {
395 disable_forensic_marking_audio = 0;
396 string const above = audio_disable + "-above-channel-";
397 if (starts_with(i->content(), above)) {
398 string above_number = i->content().substr(above.length());
399 if (above_number == "") {
400 throw KDMFormatError("Badly-formatted ForensicMarkFlag");
402 disable_forensic_marking_audio = atoi(above_number.c_str());
409 void as_xml (xmlpp::Element* node) const
411 node->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM");
413 recipient.as_xml (node->add_child ("Recipient"));
414 node->add_child("CompositionPlaylistId")->add_child_text ("urn:uuid:" + composition_playlist_id);
415 node->add_child("ContentTitleText")->add_child_text (content_title_text);
416 if (content_authenticator) {
417 node->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ());
419 node->add_child("ContentKeysNotValidBefore")->add_child_text (not_valid_before.as_string ());
420 node->add_child("ContentKeysNotValidAfter")->add_child_text (not_valid_after.as_string ());
421 if (authorized_device_info) {
422 authorized_device_info->as_xml (node->add_child ("AuthorizedDeviceInfo"));
424 key_id_list.as_xml (node->add_child ("KeyIdList"));
426 if (disable_forensic_marking_picture || disable_forensic_marking_audio) {
427 xmlpp::Element* forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList");
428 if (disable_forensic_marking_picture) {
429 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text(picture_disable);
431 if (disable_forensic_marking_audio) {
432 string mrkflg = audio_disable;
433 if (*disable_forensic_marking_audio > 0) {
434 mrkflg += String::compose ("-above-channel-%1", *disable_forensic_marking_audio);
436 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text (mrkflg);
442 string composition_playlist_id;
443 boost::optional<string> content_authenticator;
444 string content_title_text;
445 LocalTime not_valid_before;
446 LocalTime not_valid_after;
447 bool disable_forensic_marking_picture;
448 optional<int> disable_forensic_marking_audio;
449 boost::optional<AuthorizedDeviceInfo> authorized_device_info;
450 KeyIdList key_id_list;
453 static const string picture_disable;
454 static const string audio_disable;
457 const string KDMRequiredExtensions::picture_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable";
458 const string KDMRequiredExtensions::audio_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable";
460 class RequiredExtensions
463 RequiredExtensions () {}
465 explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
466 : kdm_required_extensions (node->node_child ("KDMRequiredExtensions"))
471 void as_xml (xmlpp::Element* node) const
473 kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions"));
476 KDMRequiredExtensions kdm_required_extensions;
479 class AuthenticatedPublic
482 AuthenticatedPublic ()
483 : message_id (make_uuid ())
484 /* XXX: hack for Dolby to see if there must be a not-empty annotation text */
485 , annotation_text ("none")
486 , issue_date (LocalTime().as_string ())
489 explicit AuthenticatedPublic (shared_ptr<const cxml::Node> node)
490 : message_id (remove_urn_uuid (node->string_child ("MessageId")))
491 , annotation_text (node->optional_string_child ("AnnotationText"))
492 , issue_date (node->string_child ("IssueDate"))
493 , signer (node->node_child ("Signer"))
494 , required_extensions (node->node_child ("RequiredExtensions"))
499 void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
501 references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
503 node->add_child("MessageId")->add_child_text ("urn:uuid:" + message_id);
504 node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
505 if (annotation_text) {
506 node->add_child("AnnotationText")->add_child_text (annotation_text.get ());
508 node->add_child("IssueDate")->add_child_text (issue_date);
510 signer.as_xml (node->add_child ("Signer"));
511 required_extensions.as_xml (node->add_child ("RequiredExtensions"));
513 node->add_child ("NonCriticalExtensions");
517 optional<string> annotation_text;
520 RequiredExtensions required_extensions;
523 /** Class to describe our data. We use a class hierarchy as it's a bit nicer
524 * for XML data than a flat description.
526 class EncryptedKDMData
534 explicit EncryptedKDMData (shared_ptr<const cxml::Node> node)
535 : authenticated_public (node->node_child ("AuthenticatedPublic"))
536 , authenticated_private (node->node_child ("AuthenticatedPrivate"))
537 , signature (node->node_child ("Signature"))
542 shared_ptr<xmlpp::Document> as_xml () const
544 shared_ptr<xmlpp::Document> document (new xmlpp::Document ());
545 xmlpp::Element* root = document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM");
546 root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
547 root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
548 map<string, xmlpp::Attribute *> references;
549 authenticated_public.as_xml (root->add_child ("AuthenticatedPublic"), references);
550 authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references);
551 signature.as_xml (root->add_child ("Signature", "ds"));
553 for (map<string, xmlpp::Attribute*>::const_iterator i = references.begin(); i != references.end(); ++i) {
554 xmlAddID (0, document->cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ());
560 AuthenticatedPublic authenticated_public;
561 AuthenticatedPrivate authenticated_private;
568 EncryptedKDM::EncryptedKDM (string s)
571 shared_ptr<cxml::Document> doc (new cxml::Document ("DCinemaSecurityMessage"));
572 doc->read_string (s);
573 _data = new data::EncryptedKDMData (doc);
574 } catch (xmlpp::parse_error& e) {
575 throw KDMFormatError (e.what ());
579 EncryptedKDM::EncryptedKDM (
580 shared_ptr<const CertificateChain> signer,
581 Certificate recipient,
582 vector<Certificate> trusted_devices,
584 string content_title_text,
585 optional<string> annotation_text,
586 LocalTime not_valid_before,
587 LocalTime not_valid_after,
588 Formulation formulation,
589 bool disable_forensic_marking_picture,
590 optional<int> disable_forensic_marking_audio,
591 list<pair<string, string> > key_ids,
594 : _data (new data::EncryptedKDMData)
596 /* Fill our XML-ish description in with the juicy bits that the caller has given */
598 /* Our ideas, based on http://isdcf.com/papers/ISDCF-Doc5-kdm-certs.pdf, about the KDM types are:
600 * Type Trusted-device thumb ContentAuthenticator
601 * MODIFIED_TRANSITIONAL_1 assume-trust No
602 * MULTIPLE_MODIFIED_TRANSITIONAL_1 as specified No
603 * DCI_ANY assume-trust Yes
604 * DCI_SPECIFIC as specified Yes
607 data::AuthenticatedPublic& aup = _data->authenticated_public;
608 aup.signer.x509_issuer_name = signer->leaf().issuer ();
609 aup.signer.x509_serial_number = signer->leaf().serial ();
610 aup.annotation_text = annotation_text;
612 data::KDMRequiredExtensions& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
613 kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.issuer ();
614 kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
615 kre.recipient.x509_subject_name = recipient.subject ();
616 kre.composition_playlist_id = cpl_id;
617 if (formulation == DCI_ANY || formulation == DCI_SPECIFIC) {
618 kre.content_authenticator = signer->leaf().thumbprint ();
620 kre.content_title_text = content_title_text;
621 kre.not_valid_before = not_valid_before;
622 kre.not_valid_after = not_valid_after;
623 kre.disable_forensic_marking_picture = disable_forensic_marking_picture;
624 kre.disable_forensic_marking_audio = disable_forensic_marking_audio;
626 if (formulation != MODIFIED_TRANSITIONAL_TEST) {
627 kre.authorized_device_info = data::AuthorizedDeviceInfo ();
628 kre.authorized_device_info->device_list_identifier = make_uuid ();
629 string n = recipient.subject_common_name ();
630 if (n.find (".") != string::npos) {
631 n = n.substr (n.find (".") + 1);
633 kre.authorized_device_info->device_list_description = n;
635 if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) {
636 /* Use the "assume trust" thumbprint */
637 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
638 } else if (formulation == MULTIPLE_MODIFIED_TRANSITIONAL_1 || formulation == DCI_SPECIFIC) {
639 if (trusted_devices.empty ()) {
640 /* Fall back on the "assume trust" thumbprint so we
641 can generate "modified-transitional-1" KDMs
642 together with "multiple-modified-transitional-1"
643 KDMs in one go, and similarly for "dci-any" etc.
645 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
647 /* As I read the standard we should use the
648 recipient /and/ other trusted device thumbprints
649 here. MJD reports that this doesn't work with
650 his setup; a working KDM does not include the
651 recipient's thumbprint (recipient.thumbprint()).
652 Waimea uses only the trusted devices here, too.
654 BOOST_FOREACH (Certificate const & i, trusted_devices) {
655 kre.authorized_device_info->certificate_thumbprints.push_back (i.thumbprint ());
661 for (list<pair<string, string> >::const_iterator i = key_ids.begin(); i != key_ids.end(); ++i) {
662 kre.key_id_list.typed_key_id.push_back (data::TypedKeyId (i->first, i->second));
665 _data->authenticated_private.encrypted_key = keys;
667 /* Read the XML so far and sign it */
668 shared_ptr<xmlpp::Document> doc = _data->as_xml ();
669 xmlpp::Node::NodeList children = doc->get_root_node()->get_children ();
670 for (xmlpp::Node::NodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
671 if ((*i)->get_name() == "Signature") {
672 signer->add_signature_value (*i, "ds");
676 /* Read the bits that add_signature_value did back into our variables */
677 shared_ptr<cxml::Node> signed_doc (new cxml::Node (doc->get_root_node ()));
678 _data->signature = data::Signature (signed_doc->node_child ("Signature"));
681 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
682 : _data (new data::EncryptedKDMData (*other._data))
688 EncryptedKDM::operator= (EncryptedKDM const & other)
690 if (this == &other) {
695 _data = new data::EncryptedKDMData (*other._data);
699 EncryptedKDM::~EncryptedKDM ()
705 EncryptedKDM::as_xml (boost::filesystem::path path) const
707 FILE* f = fopen_boost (path, "w");
708 string const x = as_xml ();
709 fwrite (x.c_str(), 1, x.length(), f);
714 EncryptedKDM::as_xml () const
716 return _data->as_xml()->write_to_string ("UTF-8");
720 EncryptedKDM::keys () const
722 return _data->authenticated_private.encrypted_key;
726 EncryptedKDM::id () const
728 return _data->authenticated_public.message_id;
732 EncryptedKDM::annotation_text () const
734 return _data->authenticated_public.annotation_text;
738 EncryptedKDM::content_title_text () const
740 return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
744 EncryptedKDM::cpl_id () const
746 return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
750 EncryptedKDM::issue_date () const
752 return _data->authenticated_public.issue_date;
756 EncryptedKDM::not_valid_before () const
758 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
762 EncryptedKDM::not_valid_after () const
764 return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
768 EncryptedKDM::recipient_x509_subject_name () const
770 return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name;
774 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
776 /* Not exactly efficient... */
777 return a.as_xml() == b.as_xml();