Split tests up.
[libdcp.git] / src / xml / kdm_smpte.h
1 /*
2     Copyright (C) 2013 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/xml/kdm_smpte.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_XML_KDM_SMPTE_H
33 #define LIBDCP_XML_KDM_SMPTE_H
34
35 #include <string>
36 #include <list>
37 #include <boost/optional.hpp>
38 #include <boost/filesystem.hpp>
39 #include <libxml/parser.h>
40 #include <libxml++/libxml++.h>
41 #include <libcxml/cxml.h>
42 #include "../exceptions.h"
43
44 namespace libdcp {
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_title_text = c->string_child ("ContentTitleText");
174                 content_keys_not_valid_before = c->string_child ("ContentKeysNotValidBefore");
175                 content_keys_not_valid_after = c->string_child ("ContentKeysNotValidAfter");
176                 authorized_device_info = AuthorizedDeviceInfo (c->node_child ("AuthorizedDeviceInfo"));
177                 
178                 std::list<boost::shared_ptr<cxml::Node> > kil = c->node_child("KeyIdList")->node_children("TypedKeyId");
179                 for (std::list<boost::shared_ptr<cxml::Node> >::iterator i = kil.begin(); i != kil.end(); ++i) {
180                         key_id_list.push_back (TypedKeyId (*i));
181                 }
182                 
183                 boost::shared_ptr<cxml::Node> fmfl = c->optional_node_child("ForensicMarkFlagList");
184                 if (fmfl) {
185                         std::list<boost::shared_ptr<cxml::Node> > fmf = fmfl->node_children("ForensicMarkFlag");
186                         for (std::list<boost::shared_ptr<cxml::Node> >::iterator i = fmf.begin(); i != fmf.end(); ++i) {
187                                 forensic_mark_flag_list.push_back ((*i)->content ());
188                         }
189                 }
190                 
191                 node->ignore_child ("NonCriticalExtensions");
192                 node->done ();
193         }
194
195         void as_xml (Writer& writer, xmlpp::Element* node) const
196         {
197                 writer.references["ID_AuthenticatedPublic"] = node->set_attribute ("Id", "ID_AuthenticatedPublic");
198                 
199                 node->add_child("MessageId")->add_child_text (message_id);
200                 node->add_child("MessageType")->add_child_text (message_type);
201                 if (annotation_text) {
202                         node->add_child("AnnotationText")->add_child_text (annotation_text.get ());
203                 }
204                 node->add_child("IssueDate")->add_child_text (issue_date);
205                 signer.as_xml (node->add_child("Signer"));
206                 
207                 xmlpp::Element* kdm_required_extensions = node->add_child("RequiredExtensions")->add_child("KDMRequiredExtensions");
208                 kdm_required_extensions->set_attribute ("xmlns", "http://www.smpte-ra.org/schemas/430-1/2006/KDM");
209                 recipient.as_xml (kdm_required_extensions->add_child ("Recipient"));
210                 
211                 kdm_required_extensions->add_child("CompositionPlaylistId")->add_child_text (composition_playlist_id);
212                 kdm_required_extensions->add_child("ContentTitleText")->add_child_text (content_title_text);
213                 kdm_required_extensions->add_child("ContentKeysNotValidBefore")->add_child_text (content_keys_not_valid_before);
214                 kdm_required_extensions->add_child("ContentKeysNotValidAfter")->add_child_text (content_keys_not_valid_after);
215                 authorized_device_info.as_xml (kdm_required_extensions->add_child("AuthorizedDeviceInfo"));
216                 
217                 xmlpp::Element* kil = kdm_required_extensions->add_child("KeyIdList");
218                 for (std::list<TypedKeyId>::const_iterator i = key_id_list.begin(); i != key_id_list.end(); ++i) {
219                         i->as_xml (kil->add_child ("TypedKeyId"));
220                 }
221                 
222                 xmlpp::Element* fmfl = kdm_required_extensions->add_child ("ForensicMarkFlagList");
223                 for (std::list<std::string>::const_iterator i = forensic_mark_flag_list.begin(); i != forensic_mark_flag_list.end(); ++i) {
224                         fmfl->add_child("ForensicMarkFlag")->add_child_text (*i);
225                 }
226                 
227                 node->add_child ("NonCriticalExtensions");
228         }
229         
230         std::string message_id;
231         std::string message_type;
232         boost::optional<std::string> annotation_text;
233         std::string issue_date;
234         Signer signer;
235         Recipient recipient;
236         std::string composition_playlist_id;
237         std::string content_title_text;
238         std::string content_keys_not_valid_before;
239         std::string content_keys_not_valid_after;
240         AuthorizedDeviceInfo authorized_device_info;
241         std::list<TypedKeyId> key_id_list;
242         std::list<std::string> forensic_mark_flag_list;
243 };
244
245 class AuthenticatedPrivate
246 {
247 public:
248         AuthenticatedPrivate () {}
249         
250         AuthenticatedPrivate (boost::shared_ptr<const cxml::Node> node)
251         {
252                 std::list<boost::shared_ptr<cxml::Node> > ek = node->node_children ("EncryptedKey");
253                 for (std::list<boost::shared_ptr<cxml::Node> >::const_iterator i = ek.begin(); i != ek.end(); ++i) {
254                         encrypted_keys.push_back ((*i)->node_child("CipherData")->string_child("CipherValue"));
255                 }
256                 
257                 node->done ();
258         }
259         
260         void as_xml (Writer& writer, xmlpp::Element* node) const
261         {
262                 writer.references["ID_AuthenticatedPrivate"] = node->set_attribute ("Id", "ID_AuthenticatedPrivate");
263                 
264                 for (std::list<std::string>::const_iterator i = encrypted_keys.begin(); i != encrypted_keys.end(); ++i) {
265                         xmlpp::Element* encrypted_key = node->add_child ("EncryptedKey", "enc");
266                         xmlpp::Element* encryption_method = encrypted_key->add_child ("EncryptionMethod", "enc");
267                         encryption_method->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p");
268                         xmlpp::Element* digest_method = encryption_method->add_child ("DigestMethod", "ds");
269                         digest_method->set_attribute ("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
270                         xmlpp::Element* cipher_data = encrypted_key->add_child ("CipherData", "enc");
271                         cipher_data->add_child("CipherValue", "enc")->add_child_text (*i);
272                 }
273         }       
274         
275         std::list<std::string> encrypted_keys;
276 };
277
278 class X509Data
279 {
280 public:
281         X509Data () {}
282         X509Data (boost::shared_ptr<const cxml::Node> node)
283                 : x509_issuer_serial (Signer (node->node_child ("X509IssuerSerial")))
284                 , x509_certificate (node->string_child ("X509Certificate"))
285         {
286                 node->done ();
287         }
288
289         void as_xml (xmlpp::Element* node) const
290         {
291                 x509_issuer_serial.as_xml (node->add_child ("X509IssuerSerial", "ds"));
292                 node->add_child("X509Certificate", "ds")->add_child_text (x509_certificate);
293         }
294         
295         Signer x509_issuer_serial;
296         std::string x509_certificate;
297 };
298
299 class Reference
300 {
301 public:
302         Reference () {}
303         Reference (std::string u)
304                 : uri (u)
305         {}
306                   
307         Reference (boost::shared_ptr<const cxml::Node> node)
308                 : uri (node->string_attribute ("URI"))
309                 , digest_value (node->string_child ("DigestValue"))
310         {
311                 node->ignore_child ("DigestMethod");
312                 node->done ();
313         }
314
315         void as_xml (Writer& writer, xmlpp::Element* node) const
316         {
317                 xmlpp::Element* reference = node->add_child ("Reference", "ds");
318                 reference->set_attribute ("URI", uri);
319                 reference->add_child("DigestMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmlenc#sha256");
320                 reference->add_child("DigestValue", "ds")->add_child_text (digest_value);
321                 
322                 if (!uri.empty ()) {
323                         xmlAddID (0, writer.document->cobj(), (const xmlChar *) uri.substr(1).c_str(), writer.references[uri.substr(1)]->cobj ());
324                 }
325         }
326         
327         std::string uri;
328         std::string digest_value;
329 };
330
331 class Signature
332 {
333 public:
334         Signature ()
335                 : authenticated_public ("#ID_AuthenticatedPublic")
336                 , authenticated_private ("#ID_AuthenticatedPrivate")
337         {}
338         
339         Signature (boost::shared_ptr<const cxml::Node> node)
340         {
341                 std::list<boost::shared_ptr<cxml::Node> > refs = node->node_child("SignedInfo")->node_children ("Reference");
342                 for (std::list<boost::shared_ptr<cxml::Node> >::const_iterator i = refs.begin(); i != refs.end(); ++i) {
343                         if ((*i)->string_attribute("URI") == "#ID_AuthenticatedPublic") {
344                                 authenticated_public = Reference (*i);
345                         } else if ((*i)->string_attribute("URI") == "#ID_AuthenticatedPrivate") {
346                                 authenticated_private = Reference (*i);
347                         } else {
348                                 throw XMLError ("unrecognised reference URI");
349                         }
350                 }
351                 
352                 std::list<boost::shared_ptr<cxml::Node> > data = node->node_child("KeyInfo")->node_children ("X509Data");
353                 for (std::list<boost::shared_ptr<cxml::Node> >::const_iterator i = data.begin(); i != data.end(); ++i) {
354                         key_info.push_back (X509Data (*i));
355                 }
356
357                 signature_value = node->string_child ("SignatureValue");
358                 
359                 node->done ();
360         }
361         
362         void as_xml (Writer& writer, xmlpp::Element* node) const
363         {
364                 xmlpp::Element* si = node->add_child ("SignedInfo", "ds");
365                 si->add_child ("CanonicalizationMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments");
366                 si->add_child ("SignatureMethod", "ds")->set_attribute ("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
367                 
368                 authenticated_public.as_xml (writer, si);
369                 authenticated_private.as_xml (writer, si);
370                 
371                 node->add_child("SignatureValue", "ds")->add_child_text (signature_value);
372                 
373                 xmlpp::Element* ki = node->add_child ("KeyInfo", "ds");
374                 for (std::list<X509Data>::const_iterator i = key_info.begin(); i != key_info.end(); ++i) {
375                         i->as_xml (ki->add_child ("X509Data", "ds"));
376                 }
377         }
378
379         Reference authenticated_public;
380         Reference authenticated_private;
381         std::string signature_value;
382         std::list<X509Data> key_info;
383 };
384
385 class DCinemaSecurityMessage
386 {
387 public:
388         DCinemaSecurityMessage () {}
389         DCinemaSecurityMessage (boost::filesystem::path file)
390         {
391                 cxml::Document f ("DCinemaSecurityMessage");
392                 f.read_file (file.string ());
393                 
394                 authenticated_public = AuthenticatedPublic (f.node_child ("AuthenticatedPublic"));
395                 authenticated_private = AuthenticatedPrivate (f.node_child ("AuthenticatedPrivate"));
396                 signature = Signature (f.node_child ("Signature"));
397                 
398                 f.done ();
399         }
400         
401         boost::shared_ptr<xmlpp::Document> as_xml () const
402         {
403                 Writer writer;
404                 
405                 xmlpp::Element* root = writer.document->create_root_node ("DCinemaSecurityMessage", "http://www.smpte-ra.org/schemas/430-3/2006/ETM");
406                 root->set_namespace_declaration ("http://www.w3.org/2000/09/xmldsig#", "ds");
407                 root->set_namespace_declaration ("http://www.w3.org/2001/04/xmlenc#", "enc");
408                 
409                 authenticated_public.as_xml (writer, root->add_child ("AuthenticatedPublic"));
410                 authenticated_private.as_xml (writer, root->add_child ("AuthenticatedPrivate"));
411                 signature.as_xml (writer, root->add_child ("Signature", "ds"));
412                 
413                 return writer.document;
414         }
415
416         AuthenticatedPublic authenticated_public;
417         AuthenticatedPrivate authenticated_private;
418         Signature signature;
419 };
420
421 }
422 }
423
424 #endif
425