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