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