Add some accessors.
[libdcp.git] / src / encrypted_kdm.cc
1 /*
2     Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
3
4     This file is part of libdcp.
5
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.
10
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.
15
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/>.
18
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
23     including the two.
24
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.
32 */
33
34 #include "encrypted_kdm.h"
35 #include "util.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>
43
44 using std::list;
45 using std::vector;
46 using std::string;
47 using std::map;
48 using std::pair;
49 using boost::shared_ptr;
50 using boost::optional;
51 using namespace dcp;
52
53 namespace dcp {
54
55 /** Namespace for classes used to hold our data; they are internal to this .cc file */
56 namespace data {
57
58 class Signer
59 {
60 public:
61         Signer () {}
62
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"))
66         {
67
68         }
69
70         void as_xml (xmlpp::Element* node) const
71         {
72                 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
73                 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
74         }
75
76         string x509_issuer_name;
77         string x509_serial_number;
78 };
79
80 class X509Data
81 {
82 public:
83         X509Data () {}
84
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"))
88         {
89                 node->done ();
90         }
91
92         void as_xml (xmlpp::Element* node) const
93         {
94                 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds"));
95                 node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate);
96         }
97
98         Signer x509_issuer_serial;
99         std::string x509_certificate;
100 };
101
102 class Reference
103 {
104 public:
105         Reference () {}
106
107         explicit Reference (string u)
108                 : uri (u)
109         {}
110
111         explicit Reference (shared_ptr<const cxml::Node> node)
112                 : uri (node->string_attribute ("URI"))
113                 , digest_value (node->string_child ("DigestValue"))
114         {
115
116         }
117
118         void as_xml (xmlpp::Element* node) const
119         {
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);
123         }
124
125         string uri;
126         string digest_value;
127 };
128
129 class SignedInfo
130 {
131 public:
132         SignedInfo ()
133                 : authenticated_public ("#ID_AuthenticatedPublic")
134                 , authenticated_private ("#ID_AuthenticatedPrivate")
135         {}
136
137         explicit SignedInfo (shared_ptr<const cxml::Node> node)
138         {
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);
145                         }
146
147                         /* XXX: do something if we don't recognise the node */
148                 }
149         }
150
151         void as_xml (xmlpp::Element* node) const
152         {
153                 node->add_child ("CanonicalizationMethod", "ds")->set_attribute (
154                         "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
155                         );
156
157                 node->add_child ("SignatureMethod", "ds")->set_attribute (
158                         "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
159                         );
160
161                 authenticated_public.as_xml (node->add_child ("Reference", "ds"));
162                 authenticated_private.as_xml (node->add_child ("Reference", "ds"));
163         }
164
165 private:
166         Reference authenticated_public;
167         Reference authenticated_private;
168 };
169
170 class Signature
171 {
172 public:
173         Signature () {}
174
175         explicit Signature (shared_ptr<const cxml::Node> node)
176                 : signed_info (node->node_child ("SignedInfo"))
177                 , signature_value (node->string_child ("SignatureValue"))
178         {
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));
182                 }
183         }
184
185         void as_xml (xmlpp::Node* node) const
186         {
187                 signed_info.as_xml (node->add_child ("SignedInfo", "ds"));
188                 node->add_child("SignatureValue", "ds")->add_child_text (signature_value);
189
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"));
193                 }
194         }
195
196         SignedInfo signed_info;
197         string signature_value;
198         list<X509Data> x509_data;
199 };
200
201 class AuthenticatedPrivate
202 {
203 public:
204         AuthenticatedPrivate () {}
205
206         explicit AuthenticatedPrivate (shared_ptr<const cxml::Node> node)
207         {
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"));
211                 }
212         }
213
214         void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
215         {
216                 references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate");
217
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);
230                 }
231         }
232
233         list<string> encrypted_key;
234 };
235
236 class TypedKeyId
237 {
238 public:
239         TypedKeyId () {}
240
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")))
244         {
245
246         }
247
248         TypedKeyId (string type, string id)
249                 : key_type (type)
250                 , key_id (id)
251         {}
252
253         void as_xml (xmlpp::Element* node) const
254         {
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");
261                 }
262         }
263
264         string key_type;
265         string key_id;
266 };
267
268 class KeyIdList
269 {
270 public:
271         KeyIdList () {}
272
273         explicit KeyIdList (shared_ptr<const cxml::Node> node)
274         {
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));
278                 }
279         }
280
281         void as_xml (xmlpp::Element* node) const
282         {
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"));
285                 }
286         }
287
288         list<TypedKeyId> typed_key_id;
289 };
290
291 class AuthorizedDeviceInfo
292 {
293 public:
294         AuthorizedDeviceInfo () {}
295
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"))
299         {
300                 BOOST_FOREACH (cxml::ConstNodePtr i, node->node_child("DeviceList")->node_children("CertificateThumbprint")) {
301                         certificate_thumbprints.push_back (i->content ());
302                 }
303         }
304
305         void as_xml (xmlpp::Element* node) const
306         {
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());
310                 }
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);
314                 }
315         }
316
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;
321 };
322
323 class X509IssuerSerial
324 {
325 public:
326         X509IssuerSerial () {}
327
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"))
331         {
332
333         }
334
335         void as_xml (xmlpp::Element* node) const
336         {
337                 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
338                 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
339         }
340
341         string x509_issuer_name;
342         string x509_serial_number;
343 };
344
345 class Recipient
346 {
347 public:
348         Recipient () {}
349
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"))
353         {
354
355         }
356
357         void as_xml (xmlpp::Element* node) const
358         {
359                 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial"));
360                 node->add_child("X509SubjectName")->add_child_text (x509_subject_name);
361         }
362
363         X509IssuerSerial x509_issuer_serial;
364         string x509_subject_name;
365 };
366
367 class KDMRequiredExtensions
368 {
369 public:
370         KDMRequiredExtensions () {}
371
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"))
380         {
381
382         }
383
384         void as_xml (xmlpp::Element* node) const
385         {
386                 node->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM");
387
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 ());
393                 }
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"));
398
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");
402         }
403
404         Recipient recipient;
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;
412 };
413
414 class RequiredExtensions
415 {
416 public:
417         RequiredExtensions () {}
418
419         explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
420                 : kdm_required_extensions (node->node_child ("KDMRequiredExtensions"))
421         {
422
423         }
424
425         void as_xml (xmlpp::Element* node) const
426         {
427                 kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions"));
428         }
429
430         KDMRequiredExtensions kdm_required_extensions;
431 };
432
433 class AuthenticatedPublic
434 {
435 public:
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 ())
441         {}
442
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"))
449         {
450
451         }
452
453         void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
454         {
455                 references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
456
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 ());
461                 }
462                 node->add_child("IssueDate")->add_child_text (issue_date);
463
464                 signer.as_xml (node->add_child ("Signer"));
465                 required_extensions.as_xml (node->add_child ("RequiredExtensions"));
466
467                 node->add_child ("NonCriticalExtensions");
468         }
469
470         string message_id;
471         optional<string> annotation_text;
472         string issue_date;
473         Signer signer;
474         RequiredExtensions required_extensions;
475 };
476
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.
479  */
480 class EncryptedKDMData
481 {
482 public:
483         EncryptedKDMData ()
484         {
485
486         }
487
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"))
492         {
493
494         }
495
496         shared_ptr<xmlpp::Document> as_xml () const
497         {
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"));
506
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 ());
509                 }
510
511                 return document;
512         }
513
514         AuthenticatedPublic authenticated_public;
515         AuthenticatedPrivate authenticated_private;
516         Signature signature;
517 };
518
519 }
520 }
521
522 EncryptedKDM::EncryptedKDM (string s)
523 {
524         shared_ptr<cxml::Document> doc (new cxml::Document ("DCinemaSecurityMessage"));
525         doc->read_string (s);
526         _data = new data::EncryptedKDMData (doc);
527 }
528
529 EncryptedKDM::EncryptedKDM (
530         shared_ptr<const CertificateChain> signer,
531         Certificate recipient,
532         vector<Certificate> trusted_devices,
533         string device_list_description,
534         string cpl_id,
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,
541         list<string> keys
542         )
543         : _data (new data::EncryptedKDMData)
544 {
545         /* Fill our XML-ish description in with the juicy bits that the caller has given */
546
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;
551
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 ();
560         }
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);
568         }
569         kre.authorized_device_info.device_list_description = n;
570
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()).
580                 */
581                 BOOST_FOREACH (Certificate const & i, trusted_devices) {
582                         kre.authorized_device_info.certificate_thumbprints.push_back (i.thumbprint ());
583                 }
584         }
585
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));
588         }
589
590         _data->authenticated_private.encrypted_key = keys;
591
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");
598                 }
599         }
600
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"));
604 }
605
606 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
607         : _data (new data::EncryptedKDMData (*other._data))
608 {
609
610 }
611
612 EncryptedKDM &
613 EncryptedKDM::operator= (EncryptedKDM const & other)
614 {
615         if (this == &other) {
616                 return *this;
617         }
618
619         delete _data;
620         _data = new data::EncryptedKDMData (*other._data);
621         return *this;
622 }
623
624 EncryptedKDM::~EncryptedKDM ()
625 {
626         delete _data;
627 }
628
629 void
630 EncryptedKDM::as_xml (boost::filesystem::path path) const
631 {
632         FILE* f = fopen_boost (path, "w");
633         string const x = as_xml ();
634         fwrite (x.c_str(), 1, x.length(), f);
635         fclose (f);
636 }
637
638 string
639 EncryptedKDM::as_xml () const
640 {
641         return _data->as_xml()->write_to_string ("UTF-8");
642 }
643
644 list<string>
645 EncryptedKDM::keys () const
646 {
647         return _data->authenticated_private.encrypted_key;
648 }
649
650 optional<string>
651 EncryptedKDM::annotation_text () const
652 {
653         return _data->authenticated_public.annotation_text;
654 }
655
656 string
657 EncryptedKDM::content_title_text () const
658 {
659         return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
660 }
661
662 string
663 EncryptedKDM::cpl_id () const
664 {
665         return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
666 }
667
668 string
669 EncryptedKDM::issue_date () const
670 {
671         return _data->authenticated_public.issue_date;
672 }
673
674 LocalTime
675 EncryptedKDM::not_valid_before () const
676 {
677         return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
678 }
679
680 LocalTime
681 EncryptedKDM::not_valid_after () const
682 {
683         return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
684 }
685
686 bool
687 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
688 {
689         /* Not exactly efficient... */
690         return a.as_xml() == b.as_xml();
691 }