Add comment.
[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                    Waimea uses only the trusted devices here, too.
581                 */
582                 BOOST_FOREACH (Certificate const & i, trusted_devices) {
583                         kre.authorized_device_info.certificate_thumbprints.push_back (i.thumbprint ());
584                 }
585         }
586
587         for (list<pair<string, string> >::const_iterator i = key_ids.begin(); i != key_ids.end(); ++i) {
588                 kre.key_id_list.typed_key_id.push_back (data::TypedKeyId (i->first, i->second));
589         }
590
591         _data->authenticated_private.encrypted_key = keys;
592
593         /* Read the XML so far and sign it */
594         shared_ptr<xmlpp::Document> doc = _data->as_xml ();
595         xmlpp::Node::NodeList children = doc->get_root_node()->get_children ();
596         for (xmlpp::Node::NodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
597                 if ((*i)->get_name() == "Signature") {
598                         signer->add_signature_value (*i, "ds");
599                 }
600         }
601
602         /* Read the bits that add_signature_value did back into our variables */
603         shared_ptr<cxml::Node> signed_doc (new cxml::Node (doc->get_root_node ()));
604         _data->signature = data::Signature (signed_doc->node_child ("Signature"));
605 }
606
607 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
608         : _data (new data::EncryptedKDMData (*other._data))
609 {
610
611 }
612
613 EncryptedKDM &
614 EncryptedKDM::operator= (EncryptedKDM const & other)
615 {
616         if (this == &other) {
617                 return *this;
618         }
619
620         delete _data;
621         _data = new data::EncryptedKDMData (*other._data);
622         return *this;
623 }
624
625 EncryptedKDM::~EncryptedKDM ()
626 {
627         delete _data;
628 }
629
630 void
631 EncryptedKDM::as_xml (boost::filesystem::path path) const
632 {
633         FILE* f = fopen_boost (path, "w");
634         string const x = as_xml ();
635         fwrite (x.c_str(), 1, x.length(), f);
636         fclose (f);
637 }
638
639 string
640 EncryptedKDM::as_xml () const
641 {
642         return _data->as_xml()->write_to_string ("UTF-8");
643 }
644
645 list<string>
646 EncryptedKDM::keys () const
647 {
648         return _data->authenticated_private.encrypted_key;
649 }
650
651 optional<string>
652 EncryptedKDM::annotation_text () const
653 {
654         return _data->authenticated_public.annotation_text;
655 }
656
657 string
658 EncryptedKDM::content_title_text () const
659 {
660         return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
661 }
662
663 string
664 EncryptedKDM::cpl_id () const
665 {
666         return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
667 }
668
669 string
670 EncryptedKDM::issue_date () const
671 {
672         return _data->authenticated_public.issue_date;
673 }
674
675 LocalTime
676 EncryptedKDM::not_valid_before () const
677 {
678         return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
679 }
680
681 LocalTime
682 EncryptedKDM::not_valid_after () const
683 {
684         return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
685 }
686
687 bool
688 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
689 {
690         /* Not exactly efficient... */
691         return a.as_xml() == b.as_xml();
692 }