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