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