70b7847e5708470af567bd5437f950fee0dda7e3
[libdcp.git] / src / kdm_smpte_xml.h
1 /*
2     Copyright (C) 2013-2014 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 /** @file src/kdm_smpte_xml.h
21  *  @brief 1:1ish C++ representations of the XML schema for a SMPTE KDM.
22  *
23  *  This file contains classes which map pretty-much 1:1 to the elements in a SMPTE KDM
24  *  (Key Delivery Message).  The `main' KDM class contains a pointer to a DCinemaSecurityMessage
25  *  from this file.
26  *
27  *  This should probably have been automatically generated from the XSD,
28  *  but I think it's too much trouble considering that the XSD does not
29  *  change very often.
30  */
31
32 #ifndef LIBDCP_KDM_SMPTE_XML_H
33 #define LIBDCP_KDM_SMPTE_XML_H
34
35 #include "exceptions.h"
36 #include <libcxml/cxml.h>
37 #include <libxml/parser.h>
38 #include <libxml++/libxml++.h>
39 #include <boost/optional.hpp>
40 #include <boost/filesystem.hpp>
41 #include <string>
42 #include <list>
43
44 namespace dcp {
45 namespace xml {
46
47 class Writer
48 {
49 public:
50         Writer ()
51                 : document (new xmlpp::Document)
52         {}
53         
54         boost::shared_ptr<xmlpp::Document> document;
55         std::map<std::string, xmlpp::Attribute *> references;
56 };
57
58 class Signer
59 {
60 public:
61         Signer () {}
62         Signer (boost::shared_ptr<const cxml::Node> node)
63                 : x509_issuer_name (node->string_child ("X509IssuerName"))
64                 , x509_serial_number (node->string_child ("X509SerialNumber"))
65         {
66                 node->done ();
67         }
68
69         void as_xml (xmlpp::Element* node) const
70         {
71                 node->add_child("X509IssuerName", "ds")->add_child_text (x509_issuer_name);
72                 node->add_child("X509SerialNumber", "ds")->add_child_text (x509_serial_number);
73         }
74         
75         std::string x509_issuer_name;
76         std::string x509_serial_number;
77 };
78
79 class Recipient
80 {
81 public:
82         Recipient () {}
83         Recipient (boost::shared_ptr<const cxml::Node> node)
84                 : x509_issuer_serial (node->node_child ("X509IssuerSerial"))
85                 , x509_subject_name (node->string_child ("X509SubjectName"))
86         {
87                 node->done ();
88         }
89
90         void as_xml (xmlpp::Element* node) const
91         {
92                 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial"));
93                 node->add_child("X509SubjectName")->add_child_text (x509_subject_name);
94         }
95         
96         Signer x509_issuer_serial;
97         std::string x509_subject_name;
98 };
99
100 class AuthorizedDeviceInfo
101 {
102 public:
103         AuthorizedDeviceInfo () {}
104         AuthorizedDeviceInfo (boost::shared_ptr<const cxml::Node> node)
105                 : device_list_identifier (node->string_child ("DeviceListIdentifier"))
106                 , device_list_description (node->string_child ("DeviceListDescription"))
107         {
108                 std::list<boost::shared_ptr<cxml::Node> > ct = node->node_child("DeviceList")->node_children("CertificateThumbprint");
109                 for (std::list<boost::shared_ptr<cxml::Node> >::const_iterator i = ct.begin(); i != ct.end(); ++i) {
110                         device_list.push_back ((*i)->content ());
111                 }
112
113                 node->done ();
114         }
115         
116         void as_xml (xmlpp::Element* node) const
117         {
118                 node->add_child ("DeviceListIdentifier")->add_child_text (device_list_identifier);
119                 node->add_child ("DeviceListDescription")->add_child_text (device_list_description);
120                 xmlpp::Element* dl = node->add_child ("DeviceList");
121                 for (std::list<std::string>::const_iterator i = device_list.begin(); i != device_list.end(); ++i) {
122                         dl->add_child("CertificateThumbprint")->add_child_text (*i);
123                 }
124         }
125         
126         std::string device_list_identifier;
127         std::string device_list_description;
128         std::list<std::string> device_list;
129 };
130
131 class TypedKeyId
132 {
133 public:
134         TypedKeyId () {}
135
136         TypedKeyId (std::string t, std::string i)
137                 : key_type (t)
138                 , key_id (i)
139         {}
140         
141         TypedKeyId (boost::shared_ptr<const cxml::Node> node)
142                 : key_type (node->string_child ("KeyType"))
143                 , key_id (node->string_child ("KeyId"))
144         {
145                 node->done ();
146         }
147         
148         void as_xml (xmlpp::Element* node) const
149         {
150                 node->add_child("KeyType")->add_child_text (key_type);
151                 node->add_child("KeyId")->add_child_text (key_id);
152         }
153
154         std::string key_type;
155         std::string key_id;
156 };
157
158 class AuthenticatedPublic
159 {
160 public:
161         AuthenticatedPublic () {}
162         AuthenticatedPublic (boost::shared_ptr<const cxml::Node> node)
163                 : message_id (node->string_child ("MessageId"))
164                 , message_type (node->string_child ("MessageType"))
165                 , annotation_text (node->optional_string_child ("AnnotationText"))
166                 , issue_date (node->string_child ("IssueDate"))
167                 , signer (node->node_child ("Signer"))
168         {
169                 boost::shared_ptr<const cxml::Node> c = node->node_child ("RequiredExtensions");
170                 c = c->node_child ("KDMRequiredExtensions");
171                 recipient = Recipient (c->node_child ("Recipient"));
172                 composition_playlist_id = c->string_child ("CompositionPlaylistId");
173                 content_authenticator = c->optional_string_child ("ContentAuthenticator");
174                 content_title_text = c->string_child ("ContentTitleText");
175                 content_keys_not_valid_before = c->string_child ("ContentKeysNotValidBefore");
176                 content_keys_not_valid_after = c->string_child ("ContentKeysNotValidAfter");
177                 authorized_device_info = AuthorizedDeviceInfo (c->node_child ("AuthorizedDeviceInfo"));
178                 
179                 std::list<boost::shared_ptr<cxml::Node> > kil = c->node_child("KeyIdList")->node_children("TypedKeyId");
180                 for (std::list<boost::shared_ptr<cxml::Node> >::iterator i = kil.begin(); i != kil.end(); ++i) {
181                         key_id_list.push_back (TypedKeyId (*i));
182                 }
183                 
184                 boost::shared_ptr<cxml::Node> fmfl = c->optional_node_child("ForensicMarkFlagList");
185                 if (fmfl) {
186                         std::list<boost::shared_ptr<cxml::Node> > fmf = fmfl->node_children("ForensicMarkFlag");
187                         for (std::list<boost::shared_ptr<cxml::Node> >::iterator i = fmf.begin(); i != fmf.end(); ++i) {
188                                 forensic_mark_flag_list.push_back ((*i)->content ());
189                         }
190                 }
191                 
192                 node->ignore_child ("NonCriticalExtensions");
193                 node->done ();
194         }
195
196         void as_xml (Writer& writer, xmlpp::Element* node) const
197         {
198                 writer.references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
199                 
200                 node->add_child("MessageId")->add_child_text (message_id);
201                 node->add_child("MessageType")->add_child_text (message_type);
202                 if (annotation_text) {
203                         node->add_child("AnnotationText")->add_child_text (annotation_text.get ());
204                 }
205                 node->add_child("IssueDate")->add_child_text (issue_date);
206                 signer.as_xml (node->add_child("Signer"));
207                 
208                 xmlpp::Element* kdm_required_extensions = node->add_child("RequiredExtensions")->add_child("KDMRequiredExtensions");
209                 kdm_required_extensions->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM");
210                 recipient.as_xml (kdm_required_extensions->add_child ("Recipient"));
211                 
212                 kdm_required_extensions->add_child("CompositionPlaylistId")->add_child_text (composition_playlist_id);
213                 if (content_authenticator) {
214                         kdm_required_extensions->add_child("ContentAuthenticator")->add_child_text (content_authenticator.get ());
215                 }
216                 kdm_required_extensions->add_child("ContentTitleText")->add_child_text (content_title_text);
217                 kdm_required_extensions->add_child("ContentKeysNotValidBefore")->add_child_text (content_keys_not_valid_before);
218                 kdm_required_extensions->add_child("ContentKeysNotValidAfter")->add_child_text (content_keys_not_valid_after);
219                 authorized_device_info.as_xml (kdm_required_extensions->add_child("AuthorizedDeviceInfo"));
220                 
221                 xmlpp::Element* kil = kdm_required_extensions->add_child("KeyIdList");
222                 for (std::list<TypedKeyId>::const_iterator i = key_id_list.begin(); i != key_id_list.end(); ++i) {
223                         i->as_xml (kil->add_child ("TypedKeyId"));
224                 }
225                 
226                 xmlpp::Element* fmfl = kdm_required_extensions->add_child ("ForensicMarkFlagList");
227                 for (std::list<std::string>::const_iterator i = forensic_mark_flag_list.begin(); i != forensic_mark_flag_list.end(); ++i) {
228                         fmfl->add_child("ForensicMarkFlag")->add_child_text (*i);
229                 }
230                 
231                 node->add_child ("NonCriticalExtensions");
232         }
233         
234         std::string message_id;
235         std::string message_type;
236         boost::optional<std::string> annotation_text;
237         std::string issue_date;
238         Signer signer;
239         Recipient recipient;
240         std::string composition_playlist_id;
241         boost::optional<std::string> content_authenticator;
242         std::string content_title_text;
243         std::string content_keys_not_valid_before;
244         std::string content_keys_not_valid_after;
245         AuthorizedDeviceInfo authorized_device_info;
246         std::list<TypedKeyId> key_id_list;
247         std::list<std::string> forensic_mark_flag_list;
248 };
249
250 class AuthenticatedPrivate
251 {
252 public:
253         AuthenticatedPrivate () {}
254         
255         AuthenticatedPrivate (boost::shared_ptr<const cxml::Node> node)
256         {
257                 std::list<boost::shared_ptr<cxml::Node> > ek = node->node_children ("EncryptedKey");
258                 for (std::list<boost::shared_ptr<cxml::Node> >::const_iterator i = ek.begin(); i != ek.end(); ++i) {
259                         encrypted_keys.push_back ((*i)->node_child("CipherData")->string_child("CipherValue"));
260                 }
261                 
262                 node->done ();
263         }
264         
265         void as_xml (Writer& writer, xmlpp::Element* node) const
266         {
267                 writer.references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate");
268                 
269                 for (std::list<std::string>::const_iterator i = encrypted_keys.begin(); i != encrypted_keys.end(); ++i) {
270                         xmlpp::Element* encrypted_key = node->add_child ("EncryptedKey", "enc");
271                         xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc");
272                         encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
273                         xmlpp::Element* digest_method = encryption_method->add_child ("DigestMethod", "ds");
274                         digest_method->set_attribute ("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
275                         xmlpp::Element* cipher_data = encrypted_key->add_child ("CipherData", "enc");
276                         cipher_data->add_child("CipherValue", "enc")->add_child_text (*i);
277                 }
278         }       
279         
280         std::list<std::string> encrypted_keys;
281 };
282
283 class X509Data
284 {
285 public:
286         X509Data () {}
287         X509Data (boost::shared_ptr<const cxml::Node> node)
288                 : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial")))
289                 , x509_certificate (node->string_child ("X509Certificate"))
290         {
291                 node->done ();
292         }
293
294         void as_xml (xmlpp::Element* node) const
295         {
296                 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds"));
297                 node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate);
298         }
299         
300         Signer x509_issuer_serial;
301         std::string x509_certificate;
302 };
303
304 class Reference
305 {
306 public:
307         Reference () {}
308         Reference (std::string u)
309                 : uri (u)
310         {}
311                   
312         Reference (boost::shared_ptr<const cxml::Node> node)
313                 : uri (node->string_attribute ("URI"))
314                 , digest_value (node->string_child ("DigestValue"))
315         {
316                 node->ignore_child ("DigestMethod");
317                 node->done ();
318         }
319
320         void as_xml (xmlpp::Element* node) const
321         {
322                 xmlpp::Element* reference = node->add_child ("Reference", "ds");
323                 reference->set_attribute ("URI", uri);
324                 reference->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
325                 reference->add_child("DigestValue", "ds")->add_child_text (digest_value);
326         }
327         
328         std::string uri;
329         std::string digest_value;
330 };
331
332 class Signature
333 {
334 public:
335         Signature ()
336                 : authenticated_public ("#ID_AuthenticatedPublic")
337                 , authenticated_private ("#ID_AuthenticatedPrivate")
338         {}
339         
340         Signature (boost::shared_ptr<const cxml::Node> node)
341         {
342                 std::list<boost::shared_ptr<cxml::Node> > refs = node->node_child("SignedInfo")->node_children ("Reference");
343                 for (std::list<boost::shared_ptr<cxml::Node> >::const_iterator i = refs.begin(); i != refs.end(); ++i) {
344                         if ((*i)->string_attribute("URI") == "#ID_AuthenticatedPublic") {
345                                 authenticated_public = Reference (*i);
346                         } else if ((*i)->string_attribute("URI") == "#ID_AuthenticatedPrivate") {
347                                 authenticated_private = Reference (*i);
348                         } else {
349                                 throw XMLError ("unrecognised reference URI");
350                         }
351                 }
352                 
353                 std::list<boost::shared_ptr<cxml::Node> > data = node->node_child("KeyInfo")->node_children ("X509Data");
354                 for (std::list<boost::shared_ptr<cxml::Node> >::const_iterator i = data.begin(); i != data.end(); ++i) {
355                         key_info.push_back (X509Data (*i));
356                 }
357
358                 signature_value = node->string_child ("SignatureValue");
359                 
360                 node->done ();
361         }
362         
363         void as_xml (xmlpp::Element* node) const
364         {
365                 xmlpp::Element* si = node->add_child ("SignedInfo", "ds");
366                 si->add_child ("CanonicalizationMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments");
367                 si->add_child ("SignatureMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
368                 
369                 authenticated_public.as_xml (si);
370                 authenticated_private.as_xml (si);
371                 
372                 node->add_child("SignatureValue", "ds")->add_child_text (signature_value);
373                 
374                 xmlpp::Element* ki = node->add_child ("KeyInfo", "ds");
375                 for (std::list<X509Data>::const_iterator i = key_info.begin(); i != key_info.end(); ++i) {
376                         i->as_xml (ki->add_child ("X509Data", "ds"));
377                 }
378         }
379
380         Reference authenticated_public;
381         Reference authenticated_private;
382         std::string signature_value;
383         std::list<X509Data> key_info;
384 };
385
386 class DCinemaSecurityMessage
387 {
388 public:
389         DCinemaSecurityMessage () {}
390         DCinemaSecurityMessage (boost::filesystem::path file)
391         {
392                 cxml::Document f ("DCinemaSecurityMessage");
393                 f.read_file (file.string ());
394                 
395                 authenticated_public = AuthenticatedPublic (f.node_child ("AuthenticatedPublic"));
396                 authenticated_private = AuthenticatedPrivate (f.node_child ("AuthenticatedPrivate"));
397                 signature = Signature (f.node_child ("Signature"));
398                 
399                 f.done ();
400         }
401         
402         boost::shared_ptr<xmlpp::Document> as_xml () const
403         {
404                 Writer writer;
405                 
406                 xmlpp::Element* root = writer.document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM");
407                 root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
408                 root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
409                 
410                 authenticated_public.as_xml (writer, root->add_child ("AuthenticatedPublic"));
411                 authenticated_private.as_xml (writer, root->add_child ("AuthenticatedPrivate"));
412                 signature.as_xml (root->add_child ("Signature", "ds"));
413
414                 for (std::map<std::string, xmlpp::Attribute*>::const_iterator i = writer.references.begin(); i != writer.references.end(); ++i) {
415                         xmlAddID (0, writer.document->cobj(), (const xmlChar *) i->first.c_str(), i->second->cobj ());
416                 }
417                 
418                 return writer.document;
419         }
420
421         AuthenticatedPublic authenticated_public;
422         AuthenticatedPrivate authenticated_private;
423         Signature signature;
424 };
425
426 }
427 }
428
429 #endif
430