Add the "Multiple Modified Transitional 1" KDM formulation.
[libdcp.git] / src / encrypted_kdm.cc
1 /*
2     Copyright (C) 2013-2017 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 "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/date_time/posix_time/posix_time.hpp>
43 #include <boost/foreach.hpp>
44
45 using std::list;
46 using std::vector;
47 using std::string;
48 using std::map;
49 using std::pair;
50 using boost::shared_ptr;
51 using boost::optional;
52 using namespace dcp;
53
54 namespace dcp {
55
56 /** Namespace for classes used to hold our data; they are internal to this .cc file */
57 namespace data {
58
59 class Signer
60 {
61 public:
62         Signer () {}
63
64         explicit Signer (shared_ptr<const cxml::Node> node)
65                 : x509_issuer_name (node->string_child ("X509IssuerName"))
66                 , x509_serial_number (node->string_child ("X509SerialNumber"))
67         {
68
69         }
70
71         void as_xml (xmlpp::Element* node) const
72         {
73                 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
74                 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
75         }
76
77         string x509_issuer_name;
78         string x509_serial_number;
79 };
80
81 class X509Data
82 {
83 public:
84         X509Data () {}
85
86         explicit X509Data (boost::shared_ptr<const cxml::Node> node)
87                 : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial")))
88                 , x509_certificate (node->string_child ("X509Certificate"))
89         {
90                 node->done ();
91         }
92
93         void as_xml (xmlpp::Element* node) const
94         {
95                 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds"));
96                 node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate);
97         }
98
99         Signer x509_issuer_serial;
100         std::string x509_certificate;
101 };
102
103 class Reference
104 {
105 public:
106         Reference () {}
107
108         explicit Reference (string u)
109                 : uri (u)
110         {}
111
112         explicit Reference (shared_ptr<const cxml::Node> node)
113                 : uri (node->string_attribute ("URI"))
114                 , digest_value (node->string_child ("DigestValue"))
115         {
116
117         }
118
119         void as_xml (xmlpp::Element* node) const
120         {
121                 node->set_attribute ("URI", uri);
122                 node->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
123                 node->add_child("DigestValue", "ds")->add_child_text (digest_value);
124         }
125
126         string uri;
127         string digest_value;
128 };
129
130 class SignedInfo
131 {
132 public:
133         SignedInfo ()
134                 : authenticated_public ("#ID_AuthenticatedPublic")
135                 , authenticated_private ("#ID_AuthenticatedPrivate")
136         {}
137
138         explicit SignedInfo (shared_ptr<const cxml::Node> node)
139         {
140                 list<shared_ptr<cxml::Node> > references = node->node_children ("Reference");
141                 for (list<shared_ptr<cxml::Node> >::const_iterator i = references.begin(); i != references.end(); ++i) {
142                         if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPublic") {
143                                 authenticated_public = Reference (*i);
144                         } else if ((*i)->string_attribute ("URI") == "#ID_AuthenticatedPrivate") {
145                                 authenticated_private = Reference (*i);
146                         }
147
148                         /* XXX: do something if we don't recognise the node */
149                 }
150         }
151
152         void as_xml (xmlpp::Element* node) const
153         {
154                 node->add_child ("CanonicalizationMethod", "ds")->set_attribute (
155                         "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
156                         );
157
158                 node->add_child ("SignatureMethod", "ds")->set_attribute (
159                         "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
160                         );
161
162                 authenticated_public.as_xml (node->add_child ("Reference", "ds"));
163                 authenticated_private.as_xml (node->add_child ("Reference", "ds"));
164         }
165
166 private:
167         Reference authenticated_public;
168         Reference authenticated_private;
169 };
170
171 class Signature
172 {
173 public:
174         Signature () {}
175
176         explicit Signature (shared_ptr<const cxml::Node> node)
177                 : signed_info (node->node_child ("SignedInfo"))
178                 , signature_value (node->string_child ("SignatureValue"))
179         {
180                 list<shared_ptr<cxml::Node> > x509_data_nodes = node->node_child("KeyInfo")->node_children ("X509Data");
181                 for (list<shared_ptr<cxml::Node> >::const_iterator i = x509_data_nodes.begin(); i != x509_data_nodes.end(); ++i) {
182                         x509_data.push_back (X509Data (*i));
183                 }
184         }
185
186         void as_xml (xmlpp::Node* node) const
187         {
188                 signed_info.as_xml (node->add_child ("SignedInfo", "ds"));
189                 node->add_child("SignatureValue", "ds")->add_child_text (signature_value);
190
191                 xmlpp::Element* key_info_node = node->add_child ("KeyInfo", "ds");
192                 for (std::list<X509Data>::const_iterator i = x509_data.begin(); i != x509_data.end(); ++i) {
193                         i->as_xml (key_info_node->add_child ("X509Data", "ds"));
194                 }
195         }
196
197         SignedInfo signed_info;
198         string signature_value;
199         list<X509Data> x509_data;
200 };
201
202 class AuthenticatedPrivate
203 {
204 public:
205         AuthenticatedPrivate () {}
206
207         explicit AuthenticatedPrivate (shared_ptr<const cxml::Node> node)
208         {
209                 list<shared_ptr<cxml::Node> > encrypted_key_nodes = node->node_children ("EncryptedKey");
210                 for (list<shared_ptr<cxml::Node> >::const_iterator i = encrypted_key_nodes.begin(); i != encrypted_key_nodes.end(); ++i) {
211                         encrypted_key.push_back ((*i)->node_child("CipherData")->string_child ("CipherValue"));
212                 }
213         }
214
215         void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
216         {
217                 references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate");
218
219                 for (list<string>::const_iterator i = encrypted_key.begin(); i != encrypted_key.end(); ++i) {
220                         xmlpp::Element* encrypted_key = node->add_child ("EncryptedKey", "enc");
221                         /* XXX: hack for testing with Dolby */
222                         encrypted_key->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
223                         xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc");
224                         encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
225                         xmlpp::Element* digest_method = encryption_method->add_child ("DigestMethod", "ds");
226                         /* XXX: hack for testing with Dolby */
227                         digest_method->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
228                         digest_method->set_attribute ("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
229                         xmlpp::Element* cipher_data = encrypted_key->add_child ("CipherData", "enc");
230                         cipher_data->add_child("CipherValue", "enc")->add_child_text (*i);
231                 }
232         }
233
234         list<string> encrypted_key;
235 };
236
237 class TypedKeyId
238 {
239 public:
240         TypedKeyId () {}
241
242         explicit TypedKeyId (shared_ptr<const cxml::Node> node)
243                 : key_type (node->string_child ("KeyType"))
244                 , key_id (remove_urn_uuid (node->string_child ("KeyId")))
245         {
246
247         }
248
249         TypedKeyId (string type, string id)
250                 : key_type (type)
251                 , key_id (id)
252         {}
253
254         void as_xml (xmlpp::Element* node) const
255         {
256                 xmlpp::Element* type = node->add_child("KeyType");
257                 type->add_child_text (key_type);
258                 node->add_child("KeyId")->add_child_text ("urn:uuid:" + key_id);
259                 /* XXX: this feels like a bit of a hack */
260                 if (key_type == "MDEK") {
261                         type->set_attribute ("scope", "http://www.dolby.com/cp850/2012/KDM#kdm-key-type");
262                 } else {
263                         type->set_attribute ("scope", "http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
264                 }
265         }
266
267         string key_type;
268         string key_id;
269 };
270
271 class KeyIdList
272 {
273 public:
274         KeyIdList () {}
275
276         explicit KeyIdList (shared_ptr<const cxml::Node> node)
277         {
278                 list<shared_ptr<cxml::Node> > typed_key_id_nodes = node->node_children ("TypedKeyId");
279                 for (list<shared_ptr<cxml::Node> >::const_iterator i = typed_key_id_nodes.begin(); i != typed_key_id_nodes.end(); ++i) {
280                         typed_key_id.push_back (TypedKeyId (*i));
281                 }
282         }
283
284         void as_xml (xmlpp::Element* node) const
285         {
286                 for (list<TypedKeyId>::const_iterator i = typed_key_id.begin(); i != typed_key_id.end(); ++i) {
287                         i->as_xml (node->add_child("TypedKeyId"));
288                 }
289         }
290
291         list<TypedKeyId> typed_key_id;
292 };
293
294 class AuthorizedDeviceInfo
295 {
296 public:
297         AuthorizedDeviceInfo () {}
298
299         explicit AuthorizedDeviceInfo (shared_ptr<const cxml::Node> node)
300                 : device_list_identifier (remove_urn_uuid (node->string_child ("DeviceListIdentifier")))
301                 , device_list_description (node->optional_string_child ("DeviceListDescription"))
302         {
303                 BOOST_FOREACH (cxml::ConstNodePtr i, node->node_child("DeviceList")->node_children("CertificateThumbprint")) {
304                         certificate_thumbprints.push_back (i->content ());
305                 }
306         }
307
308         void as_xml (xmlpp::Element* node) const
309         {
310                 node->add_child ("DeviceListIdentifier")->add_child_text ("urn:uuid:" + device_list_identifier);
311                 if (device_list_description) {
312                         node->add_child ("DeviceListDescription")->add_child_text (device_list_description.get());
313                 }
314                 xmlpp::Element* device_list = node->add_child ("DeviceList");
315                 BOOST_FOREACH (string i, certificate_thumbprints) {
316                         device_list->add_child("CertificateThumbprint")->add_child_text (i);
317                 }
318         }
319
320         /** DeviceListIdentifier without the urn:uuid: prefix */
321         string device_list_identifier;
322         boost::optional<string> device_list_description;
323         std::list<string> certificate_thumbprints;
324 };
325
326 class X509IssuerSerial
327 {
328 public:
329         X509IssuerSerial () {}
330
331         explicit X509IssuerSerial (shared_ptr<const cxml::Node> node)
332                 : x509_issuer_name (node->string_child ("X509IssuerName"))
333                 , x509_serial_number (node->string_child ("X509SerialNumber"))
334         {
335
336         }
337
338         void as_xml (xmlpp::Element* node) const
339         {
340                 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
341                 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
342         }
343
344         string x509_issuer_name;
345         string x509_serial_number;
346 };
347
348 class Recipient
349 {
350 public:
351         Recipient () {}
352
353         explicit Recipient (shared_ptr<const cxml::Node> node)
354                 : x509_issuer_serial (node->node_child ("X509IssuerSerial"))
355                 , x509_subject_name (node->string_child ("X509SubjectName"))
356         {
357
358         }
359
360         void as_xml (xmlpp::Element* node) const
361         {
362                 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial"));
363                 node->add_child("X509SubjectName")->add_child_text (x509_subject_name);
364         }
365
366         X509IssuerSerial x509_issuer_serial;
367         string x509_subject_name;
368 };
369
370 class KDMRequiredExtensions
371 {
372 public:
373         KDMRequiredExtensions () {}
374
375         explicit KDMRequiredExtensions (shared_ptr<const cxml::Node> node)
376                 : recipient (node->node_child ("Recipient"))
377                 , composition_playlist_id (remove_urn_uuid (node->string_child ("CompositionPlaylistId")))
378                 , content_title_text (node->string_child ("ContentTitleText"))
379                 , not_valid_before (node->string_child ("ContentKeysNotValidBefore"))
380                 , not_valid_after (node->string_child ("ContentKeysNotValidAfter"))
381                 , authorized_device_info (node->node_child ("AuthorizedDeviceInfo"))
382                 , key_id_list (node->node_child ("KeyIdList"))
383         {
384
385         }
386
387         void as_xml (xmlpp::Element* node) const
388         {
389                 node->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM");
390
391                 recipient.as_xml (node->add_child ("Recipient"));
392                 node->add_child("CompositionPlaylistId")->add_child_text ("urn:uuid:" + composition_playlist_id);
393                 node->add_child("ContentTitleText")->add_child_text (content_title_text);
394                 if (content_authenticator) {
395                         node->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ());
396                 }
397                 node->add_child("ContentKeysNotValidBefore")->add_child_text (not_valid_before.as_string ());
398                 node->add_child("ContentKeysNotValidAfter")->add_child_text (not_valid_after.as_string ());
399                 if (authorized_device_info) {
400                         authorized_device_info->as_xml (node->add_child ("AuthorizedDeviceInfo"));
401                 }
402                 key_id_list.as_xml (node->add_child ("KeyIdList"));
403
404                 xmlpp::Element* forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList");
405                 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable");
406                 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable");
407         }
408
409         Recipient recipient;
410         string composition_playlist_id;
411         boost::optional<string> content_authenticator;
412         string content_title_text;
413         LocalTime not_valid_before;
414         LocalTime not_valid_after;
415         boost::optional<AuthorizedDeviceInfo> authorized_device_info;
416         KeyIdList key_id_list;
417 };
418
419 class RequiredExtensions
420 {
421 public:
422         RequiredExtensions () {}
423
424         explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
425                 : kdm_required_extensions (node->node_child ("KDMRequiredExtensions"))
426         {
427
428         }
429
430         void as_xml (xmlpp::Element* node) const
431         {
432                 kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions"));
433         }
434
435         KDMRequiredExtensions kdm_required_extensions;
436 };
437
438 class AuthenticatedPublic
439 {
440 public:
441         AuthenticatedPublic ()
442                 : message_id (make_uuid ())
443                   /* XXX: hack for Dolby to see if there must be a not-empty annotation text */
444                 , annotation_text ("none")
445                 , issue_date (LocalTime().as_string ())
446         {}
447
448         explicit AuthenticatedPublic (shared_ptr<const cxml::Node> node)
449                 : message_id (remove_urn_uuid (node->string_child ("MessageId")))
450                 , annotation_text (node->optional_string_child ("AnnotationText"))
451                 , issue_date (node->string_child ("IssueDate"))
452                 , signer (node->node_child ("Signer"))
453                 , required_extensions (node->node_child ("RequiredExtensions"))
454         {
455
456         }
457
458         void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
459         {
460                 references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
461
462                 node->add_child("MessageId")->add_child_text ("urn:uuid:" + message_id);
463                 node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
464                 if (annotation_text) {
465                         node->add_child("AnnotationText")->add_child_text (annotation_text.get ());
466                 }
467                 node->add_child("IssueDate")->add_child_text (issue_date);
468
469                 signer.as_xml (node->add_child ("Signer"));
470                 required_extensions.as_xml (node->add_child ("RequiredExtensions"));
471
472                 node->add_child ("NonCriticalExtensions");
473         }
474
475         string message_id;
476         optional<string> annotation_text;
477         string issue_date;
478         Signer signer;
479         RequiredExtensions required_extensions;
480 };
481
482 /** Class to describe our data.  We use a class hierarchy as it's a bit nicer
483  *  for XML data than a flat description.
484  */
485 class EncryptedKDMData
486 {
487 public:
488         EncryptedKDMData ()
489         {
490
491         }
492
493         explicit EncryptedKDMData (shared_ptr<const cxml::Node> node)
494                 : authenticated_public (node->node_child ("AuthenticatedPublic"))
495                 , authenticated_private (node->node_child ("AuthenticatedPrivate"))
496                 , signature (node->node_child ("Signature"))
497         {
498
499         }
500
501         shared_ptr<xmlpp::Document> as_xml () const
502         {
503                 shared_ptr<xmlpp::Document> document (new xmlpp::Document ());
504                 xmlpp::Element* root = document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM");
505                 root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
506                 root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
507                 map<string, xmlpp::Attribute *> references;
508                 authenticated_public.as_xml (root->add_child ("AuthenticatedPublic"), references);
509                 authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references);
510                 signature.as_xml (root->add_child ("Signature", "ds"));
511
512                 for (map<string, xmlpp::Attribute*>::const_iterator i = references.begin(); i != references.end(); ++i) {
513                         xmlAddID (0, document->cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ());
514                 }
515
516                 return document;
517         }
518
519         AuthenticatedPublic authenticated_public;
520         AuthenticatedPrivate authenticated_private;
521         Signature signature;
522 };
523
524 }
525 }
526
527 EncryptedKDM::EncryptedKDM (string s)
528 {
529         try {
530                 shared_ptr<cxml::Document> doc (new cxml::Document ("DCinemaSecurityMessage"));
531                 doc->read_string (s);
532                 _data = new data::EncryptedKDMData (doc);
533         } catch (xmlpp::parse_error& e) {
534                 throw KDMFormatError (e.what ());
535         }
536 }
537
538 EncryptedKDM::EncryptedKDM (
539         shared_ptr<const CertificateChain> signer,
540         Certificate recipient,
541         vector<Certificate> trusted_devices,
542         string cpl_id,
543         string content_title_text,
544         optional<string> annotation_text,
545         LocalTime not_valid_before,
546         LocalTime not_valid_after,
547         Formulation formulation,
548         list<pair<string, string> > key_ids,
549         list<string> keys
550         )
551         : _data (new data::EncryptedKDMData)
552 {
553         /* Fill our XML-ish description in with the juicy bits that the caller has given */
554
555         /* Our ideas, based on http://isdcf.com/papers/ISDCF-Doc5-kdm-certs.pdf, about the KDM types are:
556          *
557          * Type                               Trusted-device thumb  ContentAuthenticator
558          * MODIFIED_TRANSITIONAL_1            assume-trust          No
559          * MULTIPLE_MODIFIED_TRANSITIONAL_1   as specified          No
560          * DCI_ANY                            assume-trust          Yes
561          * DCI_SPECIFIC                       as specified          Yes
562          */
563
564         data::AuthenticatedPublic& aup = _data->authenticated_public;
565         aup.signer.x509_issuer_name = signer->leaf().issuer ();
566         aup.signer.x509_serial_number = signer->leaf().serial ();
567         aup.annotation_text = annotation_text;
568
569         data::KDMRequiredExtensions& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
570         kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.issuer ();
571         kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
572         kre.recipient.x509_subject_name = recipient.subject ();
573         kre.composition_playlist_id = cpl_id;
574         if (formulation == DCI_ANY || formulation == DCI_SPECIFIC) {
575                 kre.content_authenticator = signer->leaf().thumbprint ();
576         }
577         kre.content_title_text = content_title_text;
578         kre.not_valid_before = not_valid_before;
579         kre.not_valid_after = not_valid_after;
580
581         if (formulation != MODIFIED_TRANSITIONAL_TEST) {
582                 kre.authorized_device_info = data::AuthorizedDeviceInfo ();
583                 kre.authorized_device_info->device_list_identifier = make_uuid ();
584                 string n = recipient.subject_common_name ();
585                 if (n.find (".") != string::npos) {
586                         n = n.substr (n.find (".") + 1);
587                 }
588                 kre.authorized_device_info->device_list_description = n;
589
590                 if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) {
591                         /* Use the "assume trust" thumbprint */
592                         kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
593                 } else if (formulation == MULTIPLE_MODIFIED_TRANSITIONAL_1 || formulation == DCI_SPECIFIC) {
594                         if (trusted_devices.empty ()) {
595                                 /* Fall back on the "assume trust" thumbprint so we
596                                          can generate "modified-transitional-1" KDMs
597                                          together with "multiple-modified-transitional-1"
598                                          KDMs in one go, and similarly for "dci-any" etc.
599                                 */
600                                 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
601                         } else {
602                                 /* As I read the standard we should use the
603                                          recipient /and/ other trusted device thumbprints
604                                          here. MJD reports that this doesn't work with
605                                          his setup; a working KDM does not include the
606                                          recipient's thumbprint (recipient.thumbprint()).
607                                          Waimea uses only the trusted devices here, too.
608                                 */
609                                 BOOST_FOREACH (Certificate const & i, trusted_devices) {
610                                         kre.authorized_device_info->certificate_thumbprints.push_back (i.thumbprint ());
611                                 }
612                         }
613                 }
614         }
615
616         for (list<pair<string, string> >::const_iterator i = key_ids.begin(); i != key_ids.end(); ++i) {
617                 kre.key_id_list.typed_key_id.push_back (data::TypedKeyId (i->first, i->second));
618         }
619
620         _data->authenticated_private.encrypted_key = keys;
621
622         /* Read the XML so far and sign it */
623         shared_ptr<xmlpp::Document> doc = _data->as_xml ();
624         xmlpp::Node::NodeList children = doc->get_root_node()->get_children ();
625         for (xmlpp::Node::NodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
626                 if ((*i)->get_name() == "Signature") {
627                         signer->add_signature_value (*i, "ds");
628                 }
629         }
630
631         /* Read the bits that add_signature_value did back into our variables */
632         shared_ptr<cxml::Node> signed_doc (new cxml::Node (doc->get_root_node ()));
633         _data->signature = data::Signature (signed_doc->node_child ("Signature"));
634 }
635
636 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
637         : _data (new data::EncryptedKDMData (*other._data))
638 {
639
640 }
641
642 EncryptedKDM &
643 EncryptedKDM::operator= (EncryptedKDM const & other)
644 {
645         if (this == &other) {
646                 return *this;
647         }
648
649         delete _data;
650         _data = new data::EncryptedKDMData (*other._data);
651         return *this;
652 }
653
654 EncryptedKDM::~EncryptedKDM ()
655 {
656         delete _data;
657 }
658
659 void
660 EncryptedKDM::as_xml (boost::filesystem::path path) const
661 {
662         FILE* f = fopen_boost (path, "w");
663         string const x = as_xml ();
664         fwrite (x.c_str(), 1, x.length(), f);
665         fclose (f);
666 }
667
668 string
669 EncryptedKDM::as_xml () const
670 {
671         return _data->as_xml()->write_to_string ("UTF-8");
672 }
673
674 list<string>
675 EncryptedKDM::keys () const
676 {
677         return _data->authenticated_private.encrypted_key;
678 }
679
680 optional<string>
681 EncryptedKDM::annotation_text () const
682 {
683         return _data->authenticated_public.annotation_text;
684 }
685
686 string
687 EncryptedKDM::content_title_text () const
688 {
689         return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
690 }
691
692 string
693 EncryptedKDM::cpl_id () const
694 {
695         return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
696 }
697
698 string
699 EncryptedKDM::issue_date () const
700 {
701         return _data->authenticated_public.issue_date;
702 }
703
704 LocalTime
705 EncryptedKDM::not_valid_before () const
706 {
707         return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
708 }
709
710 LocalTime
711 EncryptedKDM::not_valid_after () const
712 {
713         return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
714 }
715
716 string
717 EncryptedKDM::recipient_x509_subject_name () const
718 {
719         return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name;
720 }
721
722 bool
723 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
724 {
725         /* Not exactly efficient... */
726         return a.as_xml() == b.as_xml();
727 }