More c++11 tidying.
[libdcp.git] / src / encrypted_kdm.cc
1 /*
2     Copyright (C) 2013-2021 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 "compose.hpp"
39 #include <libcxml/cxml.h>
40 #include <libxml++/document.h>
41 #include <libxml++/nodes/element.h>
42 #include <libxml/parser.h>
43 #include <boost/algorithm/string.hpp>
44 #include <boost/date_time/posix_time/posix_time.hpp>
45 #include <boost/format.hpp>
46
47 using std::list;
48 using std::vector;
49 using std::string;
50 using std::make_shared;
51 using std::map;
52 using std::pair;
53 using std::shared_ptr;
54 using boost::optional;
55 using boost::starts_with;
56 using namespace dcp;
57
58 namespace dcp {
59
60 /** Namespace for classes used to hold our data; they are internal to this .cc file */
61 namespace data {
62
63 class Signer
64 {
65 public:
66         Signer () {}
67
68         explicit Signer (shared_ptr<const cxml::Node> node)
69                 : x509_issuer_name (node->string_child ("X509IssuerName"))
70                 , x509_serial_number (node->string_child ("X509SerialNumber"))
71         {
72
73         }
74
75         void as_xml (xmlpp::Element* node) const
76         {
77                 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
78                 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
79         }
80
81         string x509_issuer_name;
82         string x509_serial_number;
83 };
84
85 class X509Data
86 {
87 public:
88         X509Data () {}
89
90         explicit X509Data (std::shared_ptr<const cxml::Node> node)
91                 : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial")))
92                 , x509_certificate (node->string_child ("X509Certificate"))
93         {
94                 node->done ();
95         }
96
97         void as_xml (xmlpp::Element* node) const
98         {
99                 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds"));
100                 node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate);
101         }
102
103         Signer x509_issuer_serial;
104         std::string x509_certificate;
105 };
106
107 class Reference
108 {
109 public:
110         Reference () {}
111
112         explicit Reference (string u)
113                 : uri (u)
114         {}
115
116         explicit Reference (shared_ptr<const cxml::Node> node)
117                 : uri (node->string_attribute ("URI"))
118                 , digest_value (node->string_child ("DigestValue"))
119         {
120
121         }
122
123         void as_xml (xmlpp::Element* node) const
124         {
125                 node->set_attribute ("URI", uri);
126                 node->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
127                 node->add_child("DigestValue", "ds")->add_child_text (digest_value);
128         }
129
130         string uri;
131         string digest_value;
132 };
133
134 class SignedInfo
135 {
136 public:
137         SignedInfo ()
138                 : authenticated_public ("#ID_AuthenticatedPublic")
139                 , authenticated_private ("#ID_AuthenticatedPrivate")
140         {}
141
142         explicit SignedInfo (shared_ptr<const cxml::Node> node)
143         {
144                 for (auto i: node->node_children ("Reference")) {
145                         if (i->string_attribute("URI") == "#ID_AuthenticatedPublic") {
146                                 authenticated_public = Reference(i);
147                         } else if (i->string_attribute("URI") == "#ID_AuthenticatedPrivate") {
148                                 authenticated_private = Reference(i);
149                         }
150
151                         /* XXX: do something if we don't recognise the node */
152                 }
153         }
154
155         void as_xml (xmlpp::Element* node) const
156         {
157                 node->add_child ("CanonicalizationMethod", "ds")->set_attribute (
158                         "Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
159                         );
160
161                 node->add_child ("SignatureMethod", "ds")->set_attribute (
162                         "Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
163                         );
164
165                 authenticated_public.as_xml (node->add_child ("Reference", "ds"));
166                 authenticated_private.as_xml (node->add_child ("Reference", "ds"));
167         }
168
169 private:
170         Reference authenticated_public;
171         Reference authenticated_private;
172 };
173
174 class Signature
175 {
176 public:
177         Signature () {}
178
179         explicit Signature (shared_ptr<const cxml::Node> node)
180                 : signed_info (node->node_child ("SignedInfo"))
181                 , signature_value (node->string_child ("SignatureValue"))
182         {
183                 for (auto i: node->node_child("KeyInfo")->node_children ("X509Data")) {
184                         x509_data.push_back(X509Data(i));
185                 }
186         }
187
188         void as_xml (xmlpp::Node* node) const
189         {
190                 signed_info.as_xml (node->add_child ("SignedInfo", "ds"));
191                 node->add_child("SignatureValue", "ds")->add_child_text (signature_value);
192
193                 auto key_info_node = node->add_child("KeyInfo", "ds");
194                 for (auto i: x509_data) {
195                         i.as_xml (key_info_node->add_child("X509Data", "ds"));
196                 }
197         }
198
199         SignedInfo signed_info;
200         string signature_value;
201         list<X509Data> x509_data;
202 };
203
204 class AuthenticatedPrivate
205 {
206 public:
207         AuthenticatedPrivate () {}
208
209         explicit AuthenticatedPrivate (shared_ptr<const cxml::Node> node)
210         {
211                 for (auto i: node->node_children ("EncryptedKey")) {
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 (auto i: encrypted_key) {
221                         auto 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                         auto 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                         auto 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                         auto 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                 for (auto i: node->node_children ("TypedKeyId")) {
280                         typed_key_id.push_back(TypedKeyId(i));
281                 }
282         }
283
284         void as_xml (xmlpp::Element* node) const
285         {
286                 for (auto const& i: typed_key_id) {
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                 for (auto 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                 auto device_list = node->add_child ("DeviceList");
315                 for (auto 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                 disable_forensic_marking_picture = false;
385                 disable_forensic_marking_audio = optional<int>();
386                 if (node->optional_node_child("ForensicMarkFlagList")) {
387                         for (auto i: node->node_child("ForensicMarkFlagList")->node_children("ForensicMarkFlag")) {
388                                 if (i->content() == picture_disable) {
389                                         disable_forensic_marking_picture = true;
390                                 } else if (starts_with(i->content(), audio_disable)) {
391                                         disable_forensic_marking_audio = 0;
392                                         string const above = audio_disable + "-above-channel-";
393                                         if (starts_with(i->content(), above)) {
394                                                 auto above_number = i->content().substr(above.length());
395                                                 if (above_number == "") {
396                                                         throw KDMFormatError("Badly-formatted ForensicMarkFlag");
397                                                 }
398                                                 disable_forensic_marking_audio = atoi(above_number.c_str());
399                                         }
400                                 }
401                         }
402                 }
403         }
404
405         void as_xml (xmlpp::Element* node) const
406         {
407                 node->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM");
408
409                 recipient.as_xml (node->add_child ("Recipient"));
410                 node->add_child("CompositionPlaylistId")->add_child_text ("urn:uuid:" + composition_playlist_id);
411                 node->add_child("ContentTitleText")->add_child_text (content_title_text);
412                 if (content_authenticator) {
413                         node->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ());
414                 }
415                 node->add_child("ContentKeysNotValidBefore")->add_child_text (not_valid_before.as_string ());
416                 node->add_child("ContentKeysNotValidAfter")->add_child_text (not_valid_after.as_string ());
417                 if (authorized_device_info) {
418                         authorized_device_info->as_xml (node->add_child ("AuthorizedDeviceInfo"));
419                 }
420                 key_id_list.as_xml (node->add_child ("KeyIdList"));
421
422                 if (disable_forensic_marking_picture || disable_forensic_marking_audio) {
423                         auto forensic_mark_flag_list = node->add_child ("ForensicMarkFlagList");
424                         if (disable_forensic_marking_picture) {
425                                 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text(picture_disable);
426                         }
427                         if (disable_forensic_marking_audio) {
428                                 auto mrkflg = audio_disable;
429                                 if (*disable_forensic_marking_audio > 0) {
430                                         mrkflg += String::compose ("-above-channel-%1", *disable_forensic_marking_audio);
431                                 }
432                                 forensic_mark_flag_list->add_child("ForensicMarkFlag")->add_child_text (mrkflg);
433                         }
434                 }
435         }
436
437         Recipient recipient;
438         string composition_playlist_id;
439         boost::optional<string> content_authenticator;
440         string content_title_text;
441         LocalTime not_valid_before;
442         LocalTime not_valid_after;
443         bool disable_forensic_marking_picture;
444         optional<int> disable_forensic_marking_audio;
445         boost::optional<AuthorizedDeviceInfo> authorized_device_info;
446         KeyIdList key_id_list;
447
448 private:
449         static const string picture_disable;
450         static const string audio_disable;
451 };
452
453 const string KDMRequiredExtensions::picture_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-picture-disable";
454 const string KDMRequiredExtensions::audio_disable = "http://www.smpte-ra.org/430-1/2006/KDM#mrkflg-audio-disable";
455
456 class RequiredExtensions
457 {
458 public:
459         RequiredExtensions () {}
460
461         explicit RequiredExtensions (shared_ptr<const cxml::Node> node)
462                 : kdm_required_extensions (node->node_child ("KDMRequiredExtensions"))
463         {
464
465         }
466
467         void as_xml (xmlpp::Element* node) const
468         {
469                 kdm_required_extensions.as_xml (node->add_child ("KDMRequiredExtensions"));
470         }
471
472         KDMRequiredExtensions kdm_required_extensions;
473 };
474
475 class AuthenticatedPublic
476 {
477 public:
478         AuthenticatedPublic ()
479                 : message_id (make_uuid ())
480                   /* XXX: hack for Dolby to see if there must be a not-empty annotation text */
481                 , annotation_text ("none")
482                 , issue_date (LocalTime().as_string ())
483         {}
484
485         explicit AuthenticatedPublic (shared_ptr<const cxml::Node> node)
486                 : message_id (remove_urn_uuid (node->string_child ("MessageId")))
487                 , annotation_text (node->optional_string_child ("AnnotationText"))
488                 , issue_date (node->string_child ("IssueDate"))
489                 , signer (node->node_child ("Signer"))
490                 , required_extensions (node->node_child ("RequiredExtensions"))
491         {
492
493         }
494
495         void as_xml (xmlpp::Element* node, map<string, xmlpp::Attribute *>& references) const
496         {
497                 references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
498
499                 node->add_child("MessageId")->add_child_text ("urn:uuid:" + message_id);
500                 node->add_child("MessageType")->add_child_text ("http://www.smpte-ra.org/430-1/2006/KDM#kdm-key-type");
501                 if (annotation_text) {
502                         node->add_child("AnnotationText")->add_child_text (annotation_text.get ());
503                 }
504                 node->add_child("IssueDate")->add_child_text (issue_date);
505
506                 signer.as_xml (node->add_child ("Signer"));
507                 required_extensions.as_xml (node->add_child ("RequiredExtensions"));
508
509                 node->add_child ("NonCriticalExtensions");
510         }
511
512         string message_id;
513         optional<string> annotation_text;
514         string issue_date;
515         Signer signer;
516         RequiredExtensions required_extensions;
517 };
518
519 /** Class to describe our data.  We use a class hierarchy as it's a bit nicer
520  *  for XML data than a flat description.
521  */
522 class EncryptedKDMData
523 {
524 public:
525         EncryptedKDMData ()
526         {
527
528         }
529
530         explicit EncryptedKDMData (shared_ptr<const cxml::Node> node)
531                 : authenticated_public (node->node_child ("AuthenticatedPublic"))
532                 , authenticated_private (node->node_child ("AuthenticatedPrivate"))
533                 , signature (node->node_child ("Signature"))
534         {
535
536         }
537
538         shared_ptr<xmlpp::Document> as_xml () const
539         {
540                 shared_ptr<xmlpp::Document> document (new xmlpp::Document ());
541                 xmlpp::Element* root = document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM");
542                 root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
543                 root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
544                 map<string, xmlpp::Attribute *> references;
545                 authenticated_public.as_xml (root->add_child ("AuthenticatedPublic"), references);
546                 authenticated_private.as_xml (root->add_child ("AuthenticatedPrivate"), references);
547                 signature.as_xml (root->add_child ("Signature", "ds"));
548
549                 for (auto i: references) {
550                         xmlAddID (0, document->cobj(), (const xmlChar *) i.first.c_str(), i.second->cobj());
551                 }
552
553                 indent (document->get_root_node(), 0);
554                 return document;
555         }
556
557         AuthenticatedPublic authenticated_public;
558         AuthenticatedPrivate authenticated_private;
559         Signature signature;
560 };
561
562 }
563 }
564
565 EncryptedKDM::EncryptedKDM (string s)
566 {
567         try {
568                 auto doc = make_shared<cxml::Document>("DCinemaSecurityMessage");
569                 doc->read_string (s);
570                 _data = new data::EncryptedKDMData (doc);
571         } catch (xmlpp::parse_error& e) {
572                 throw KDMFormatError (e.what ());
573         }
574 }
575
576 /** @param trusted_devices Trusted device thumbprints */
577 EncryptedKDM::EncryptedKDM (
578         shared_ptr<const CertificateChain> signer,
579         Certificate recipient,
580         vector<string> trusted_devices,
581         string cpl_id,
582         string content_title_text,
583         optional<string> annotation_text,
584         LocalTime not_valid_before,
585         LocalTime not_valid_after,
586         Formulation formulation,
587         bool disable_forensic_marking_picture,
588         optional<int> disable_forensic_marking_audio,
589         list<pair<string, string> > key_ids,
590         list<string> keys
591         )
592         : _data (new data::EncryptedKDMData)
593 {
594         /* Fill our XML-ish description in with the juicy bits that the caller has given */
595
596         /* Our ideas, based on http://isdcf.com/papers/ISDCF-Doc5-kdm-certs.pdf, about the KDM types are:
597          *
598          * Type                               Trusted-device thumb  ContentAuthenticator
599          * MODIFIED_TRANSITIONAL_1            assume-trust          No
600          * MULTIPLE_MODIFIED_TRANSITIONAL_1   as specified          No
601          * DCI_ANY                            assume-trust          Yes
602          * DCI_SPECIFIC                       as specified          Yes
603          */
604
605         data::AuthenticatedPublic& aup = _data->authenticated_public;
606         aup.signer.x509_issuer_name = signer->leaf().issuer ();
607         aup.signer.x509_serial_number = signer->leaf().serial ();
608         aup.annotation_text = annotation_text;
609
610         auto& kre = _data->authenticated_public.required_extensions.kdm_required_extensions;
611         kre.recipient.x509_issuer_serial.x509_issuer_name = recipient.issuer ();
612         kre.recipient.x509_issuer_serial.x509_serial_number = recipient.serial ();
613         kre.recipient.x509_subject_name = recipient.subject ();
614         kre.composition_playlist_id = cpl_id;
615         if (formulation == DCI_ANY || formulation == DCI_SPECIFIC) {
616                 kre.content_authenticator = signer->leaf().thumbprint ();
617         }
618         kre.content_title_text = content_title_text;
619         kre.not_valid_before = not_valid_before;
620         kre.not_valid_after = not_valid_after;
621         kre.disable_forensic_marking_picture = disable_forensic_marking_picture;
622         kre.disable_forensic_marking_audio = disable_forensic_marking_audio;
623
624         if (formulation != MODIFIED_TRANSITIONAL_TEST) {
625                 kre.authorized_device_info = data::AuthorizedDeviceInfo ();
626                 kre.authorized_device_info->device_list_identifier = make_uuid ();
627                 auto n = recipient.subject_common_name ();
628                 if (n.find (".") != string::npos) {
629                         n = n.substr (n.find (".") + 1);
630                 }
631                 kre.authorized_device_info->device_list_description = n;
632
633                 if (formulation == MODIFIED_TRANSITIONAL_1 || formulation == DCI_ANY) {
634                         /* Use the "assume trust" thumbprint */
635                         kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
636                 } else if (formulation == MULTIPLE_MODIFIED_TRANSITIONAL_1 || formulation == DCI_SPECIFIC) {
637                         if (trusted_devices.empty ()) {
638                                 /* Fall back on the "assume trust" thumbprint so we
639                                    can generate "modified-transitional-1" KDMs
640                                    together with "multiple-modified-transitional-1"
641                                    KDMs in one go, and similarly for "dci-any" etc.
642                                 */
643                                 kre.authorized_device_info->certificate_thumbprints.push_back ("2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
644                         } else {
645                                 /* As I read the standard we should use the
646                                    recipient /and/ other trusted device thumbprints
647                                    here. MJD reports that this doesn't work with
648                                    his setup; a working KDM does not include the
649                                    recipient's thumbprint (recipient.thumbprint()).
650                                    Waimea uses only the trusted devices here, too.
651                                 */
652                                 for (auto i: trusted_devices) {
653                                         kre.authorized_device_info->certificate_thumbprints.push_back(i);
654                                 }
655                         }
656                 }
657         }
658
659         for (auto i: key_ids) {
660                 kre.key_id_list.typed_key_id.push_back(data::TypedKeyId(i.first, i.second));
661         }
662
663         _data->authenticated_private.encrypted_key = keys;
664
665         /* Read the XML so far and sign it */
666         auto doc = _data->as_xml ();
667         for (auto i: doc->get_root_node()->get_children()) {
668                 if (i->get_name() == "Signature") {
669                         signer->add_signature_value(dynamic_cast<xmlpp::Element*>(i), "ds", false);
670                 }
671         }
672
673         /* Read the bits that add_signature_value did back into our variables */
674         auto signed_doc = make_shared<cxml::Node>(doc->get_root_node());
675         _data->signature = data::Signature (signed_doc->node_child ("Signature"));
676 }
677
678 EncryptedKDM::EncryptedKDM (EncryptedKDM const & other)
679         : _data (new data::EncryptedKDMData (*other._data))
680 {
681
682 }
683
684 EncryptedKDM &
685 EncryptedKDM::operator= (EncryptedKDM const & other)
686 {
687         if (this == &other) {
688                 return *this;
689         }
690
691         delete _data;
692         _data = new data::EncryptedKDMData (*other._data);
693         return *this;
694 }
695
696 EncryptedKDM::~EncryptedKDM ()
697 {
698         delete _data;
699 }
700
701 void
702 EncryptedKDM::as_xml (boost::filesystem::path path) const
703 {
704         auto f = fopen_boost (path, "w");
705         if (!f) {
706                 throw FileError ("Could not open KDM file for writing", path, errno);
707         }
708         auto const x = as_xml ();
709         size_t const written = fwrite (x.c_str(), 1, x.length(), f);
710         fclose (f);
711         if (written != x.length()) {
712                 throw FileError ("Could not write to KDM file", path, errno);
713         }
714 }
715
716 string
717 EncryptedKDM::as_xml () const
718 {
719         return _data->as_xml()->write_to_string ("UTF-8");
720 }
721
722 list<string>
723 EncryptedKDM::keys () const
724 {
725         return _data->authenticated_private.encrypted_key;
726 }
727
728 string
729 EncryptedKDM::id () const
730 {
731         return _data->authenticated_public.message_id;
732 }
733
734 optional<string>
735 EncryptedKDM::annotation_text () const
736 {
737         return _data->authenticated_public.annotation_text;
738 }
739
740 string
741 EncryptedKDM::content_title_text () const
742 {
743         return _data->authenticated_public.required_extensions.kdm_required_extensions.content_title_text;
744 }
745
746 string
747 EncryptedKDM::cpl_id () const
748 {
749         return _data->authenticated_public.required_extensions.kdm_required_extensions.composition_playlist_id;
750 }
751
752 string
753 EncryptedKDM::issue_date () const
754 {
755         return _data->authenticated_public.issue_date;
756 }
757
758 LocalTime
759 EncryptedKDM::not_valid_before () const
760 {
761         return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_before;
762 }
763
764 LocalTime
765 EncryptedKDM::not_valid_after () const
766 {
767         return _data->authenticated_public.required_extensions.kdm_required_extensions.not_valid_after;
768 }
769
770 string
771 EncryptedKDM::recipient_x509_subject_name () const
772 {
773         return _data->authenticated_public.required_extensions.kdm_required_extensions.recipient.x509_subject_name;
774 }
775
776 CertificateChain
777 EncryptedKDM::signer_certificate_chain () const
778 {
779         CertificateChain chain;
780         for (auto const& i: _data->signature.x509_data) {
781                 string s = "-----BEGIN CERTIFICATE-----\n" + i.x509_certificate + "\n-----END CERTIFICATE-----";
782                 chain.add (Certificate(s));
783         }
784         return chain;
785 }
786
787 bool
788 dcp::operator== (EncryptedKDM const & a, EncryptedKDM const & b)
789 {
790         /* Not exactly efficient... */
791         return a.as_xml() == b.as_xml();
792 }