Various probably quite untidy progress on KDMs.
[libdcp.git] / src / util.cc
1 /*
2     Copyright (C) 2012 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/util.cc
21  *  @brief Utility methods.
22  */
23
24 #include <stdexcept>
25 #include <sstream>
26 #include <iostream>
27 #include <iomanip>
28 #include <boost/filesystem.hpp>
29 #include <openssl/sha.h>
30 #include <libxml++/nodes/element.h>
31 #include <libxml++/document.h>
32 #include <xmlsec/xmldsig.h>
33 #include <xmlsec/dl.h>
34 #include <xmlsec/app.h>
35 #include "KM_util.h"
36 #include "KM_fileio.h"
37 #include "AS_DCP.h"
38 #include "util.h"
39 #include "exceptions.h"
40 #include "types.h"
41 #include "argb_frame.h"
42 #include "lut.h"
43 #include "certificates.h"
44
45 using std::string;
46 using std::cout;
47 using std::stringstream;
48 using std::min;
49 using std::max;
50 using std::list;
51 using boost::shared_ptr;
52 using namespace libdcp;
53
54 string
55 libdcp::make_uuid ()
56 {
57         char buffer[64];
58         Kumu::UUID id;
59         Kumu::GenRandomValue (id);
60         id.EncodeHex (buffer, 64);
61         return string (buffer);
62 }
63
64 string
65 libdcp::make_digest (string filename, boost::signals2::signal<void (float)>* progress)
66 {
67         int const file_size = boost::filesystem::file_size (filename);
68         
69         Kumu::FileReader reader;
70         if (ASDCP_FAILURE (reader.OpenRead (filename.c_str ()))) {
71                 throw FileError ("could not open file to compute digest", filename);
72         }
73         
74         SHA_CTX sha;
75         SHA1_Init (&sha);
76         
77         Kumu::ByteString read_buffer (65536);
78         int done = 0;
79         while (1) {
80                 ui32_t read = 0;
81                 Kumu::Result_t r = reader.Read (read_buffer.Data(), read_buffer.Capacity(), &read);
82                 
83                 if (r == Kumu::RESULT_ENDOFFILE) {
84                         break;
85                 } else if (ASDCP_FAILURE (r)) {
86                         throw FileError ("could not read file to compute digest", filename);
87                 }
88                 
89                 SHA1_Update (&sha, read_buffer.Data(), read);
90                 done += read;
91
92                 if (progress) {
93                         (*progress) (0.5 + (0.5 * done / file_size));
94                 }
95         }
96
97         byte_t byte_buffer[20];
98         SHA1_Final (byte_buffer, &sha);
99
100         stringstream s;
101         char digest[64];
102         return Kumu::base64encode (byte_buffer, 20, digest, 64);
103 }
104
105 string
106 libdcp::content_kind_to_string (ContentKind kind)
107 {
108         switch (kind) {
109         case FEATURE:
110                 return "feature";
111         case SHORT:
112                 return "short";
113         case TRAILER:
114                 return "trailer";
115         case TEST:
116                 return "test";
117         case TRANSITIONAL:
118                 return "transitional";
119         case RATING:
120                 return "rating";
121         case TEASER:
122                 return "teaser";
123         case POLICY:
124                 return "policy";
125         case PUBLIC_SERVICE_ANNOUNCEMENT:
126                 return "psa";
127         case ADVERTISEMENT:
128                 return "advertisement";
129         }
130
131         assert (false);
132 }
133
134 libdcp::ContentKind
135 libdcp::content_kind_from_string (string type)
136 {
137         if (type == "feature") {
138                 return FEATURE;
139         } else if (type == "short") {
140                 return SHORT;
141         } else if (type == "trailer" || type == "Trailer") {
142                 return TRAILER;
143         } else if (type == "test") {
144                 return TEST;
145         } else if (type == "transitional") {
146                 return TRANSITIONAL;
147         } else if (type == "rating") {
148                 return RATING;
149         } else if (type == "teaser" || type == "Teaser") {
150                 return TEASER;
151         } else if (type == "policy") {
152                 return POLICY;
153         } else if (type == "psa") {
154                 return PUBLIC_SERVICE_ANNOUNCEMENT;
155         } else if (type == "advertisement") {
156                 return ADVERTISEMENT;
157         }
158
159         assert (false);
160 }
161                 
162 bool
163 libdcp::starts_with (string big, string little)
164 {
165         if (little.size() > big.size()) {
166                 return false;
167         }
168
169         return big.substr (0, little.length()) == little;
170 }
171
172 bool
173 libdcp::ends_with (string big, string little)
174 {
175         if (little.size() > big.size()) {
176                 return false;
177         }
178
179         return big.compare (big.length() - little.length(), little.length(), little) == 0;
180 }
181
182 opj_image_t *
183 libdcp::decompress_j2k (uint8_t* data, int64_t size, int reduce)
184 {
185         opj_dinfo_t* decoder = opj_create_decompress (CODEC_J2K);
186         opj_dparameters_t parameters;
187         opj_set_default_decoder_parameters (&parameters);
188         parameters.cp_reduce = reduce;
189         opj_setup_decoder (decoder, &parameters);
190         opj_cio_t* cio = opj_cio_open ((opj_common_ptr) decoder, data, size);
191         opj_image_t* image = opj_decode (decoder, cio);
192         if (!image) {
193                 opj_destroy_decompress (decoder);
194                 opj_cio_close (cio);
195                 throw DCPReadError ("could not decode JPEG2000 codestream");
196         }
197
198         opj_cio_close (cio);
199
200         image->x1 = rint (float(image->x1) / pow (2, reduce));
201         image->y1 = rint (float(image->y1) / pow (2, reduce));
202         return image;
203 }
204
205 shared_ptr<ARGBFrame>
206 libdcp::xyz_to_rgb (opj_image_t* xyz_frame)
207 {
208         struct {
209                 double x, y, z;
210         } s;
211         
212         struct {
213                 double r, g, b;
214         } d;
215         
216         int* xyz_x = xyz_frame->comps[0].data;
217         int* xyz_y = xyz_frame->comps[1].data;
218         int* xyz_z = xyz_frame->comps[2].data;
219
220         shared_ptr<ARGBFrame> argb_frame (new ARGBFrame (xyz_frame->x1, xyz_frame->y1));
221
222         uint8_t* argb = argb_frame->data ();
223         
224         for (int y = 0; y < xyz_frame->y1; ++y) {
225                 uint8_t* argb_line = argb;
226                 for (int x = 0; x < xyz_frame->x1; ++x) {
227
228                         assert (*xyz_x >= 0 && *xyz_y >= 0 && *xyz_z >= 0 && *xyz_x < 4096 && *xyz_x < 4096 && *xyz_z < 4096);
229                         
230                         /* In gamma LUT */
231                         s.x = lut_in[*xyz_x++];
232                         s.y = lut_in[*xyz_y++];
233                         s.z = lut_in[*xyz_z++];
234                         
235                         /* DCI companding */
236                         s.x /= DCI_COEFFICIENT;
237                         s.y /= DCI_COEFFICIENT;
238                         s.z /= DCI_COEFFICIENT;
239                         
240                         /* XYZ to RGB */
241                         d.r = ((s.x * color_matrix[0][0]) + (s.y * color_matrix[0][1]) + (s.z * color_matrix[0][2]));
242                         d.g = ((s.x * color_matrix[1][0]) + (s.y * color_matrix[1][1]) + (s.z * color_matrix[1][2]));
243                         d.b = ((s.x * color_matrix[2][0]) + (s.y * color_matrix[2][1]) + (s.z * color_matrix[2][2]));
244                         
245                         d.r = min (d.r, 1.0);
246                         d.r = max (d.r, 0.0);
247                         
248                         d.g = min (d.g, 1.0);
249                         d.g = max (d.g, 0.0);
250                         
251                         d.b = min (d.b, 1.0);
252                         d.b = max (d.b, 0.0);
253                         
254                         /* Out gamma LUT */
255                         *argb_line++ = lut_out[(int) (d.b * COLOR_DEPTH)];
256                         *argb_line++ = lut_out[(int) (d.g * COLOR_DEPTH)];
257                         *argb_line++ = lut_out[(int) (d.r * COLOR_DEPTH)];
258                         *argb_line++ = 0xff;
259                 }
260                 
261                 argb += argb_frame->stride ();
262         }
263
264         return argb_frame;
265 }
266
267 bool
268 libdcp::empty_or_white_space (string s)
269 {
270         for (size_t i = 0; i < s.length(); ++i) {
271                 if (s[i] != ' ' && s[i] != '\n' && s[i] != '\t') {
272                         return false;
273                 }
274         }
275
276         return true;
277 }
278
279 void
280 libdcp::init ()
281 {
282         if (xmlSecInit() < 0) {
283                 throw MiscError ("could not initialise xmlsec");
284         }
285
286 #ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
287         if (xmlSecCryptoDLLoadLibrary (BAD_CAST XMLSEC_CRYPTO) < 0) {
288                 throw MiscError ("unable to load default xmlsec-crypto library");
289         }
290 #endif
291         
292         if (xmlSecCryptoAppInit (0) < 0) {
293                 throw MiscError ("could not initialise crypto library");
294         }
295         
296         if (xmlSecCryptoInit() < 0) {
297                 throw MiscError ("could not initialise xmlsec-crypto");
298         }
299 }
300
301 void
302 libdcp::add_signature_value (xmlpp::Element* parent, CertificateChain const & certificates, string const & signer_key, string const & ns)
303 {
304         parent->add_child("SignatureValue", ns);
305         
306         xmlpp::Element* key_info = parent->add_child("KeyInfo", ns);
307         list<shared_ptr<Certificate> > c = certificates.leaf_to_root ();
308         for (list<shared_ptr<Certificate> >::iterator i = c.begin(); i != c.end(); ++i) {
309                 xmlpp::Element* data = key_info->add_child("X509Data", ns);
310                 
311                 {
312                         xmlpp::Element* serial = data->add_child("X509IssuerSerial", ns);
313                         serial->add_child("X509IssuerName", ns)->add_child_text(
314                                 Certificate::name_for_xml ((*i)->issuer())
315                                 );
316                         serial->add_child("X509SerialNumber", ns)->add_child_text((*i)->serial());
317                 }
318                 
319                 data->add_child("X509Certificate", ns)->add_child_text((*i)->certificate());
320         }
321
322         xmlSecKeysMngrPtr keys_manager = xmlSecKeysMngrCreate();
323         if (!keys_manager) {
324                 throw MiscError ("could not create keys manager");
325         }
326         if (xmlSecCryptoAppDefaultKeysMngrInit (keys_manager) < 0) {
327                 throw MiscError ("could not initialise keys manager");
328         }
329         
330         xmlSecKeyPtr const key = xmlSecCryptoAppKeyLoad (signer_key.c_str(), xmlSecKeyDataFormatPem, 0, 0, 0);
331         if (key == 0) {
332                 throw MiscError ("could not load signer key");
333                 }
334         
335         if (xmlSecCryptoAppDefaultKeysMngrAdoptKey (keys_manager, key) < 0) {
336                 xmlSecKeyDestroy (key);
337                 throw MiscError ("could not use signer key");
338         }
339         
340         xmlSecDSigCtx signature_context;
341         
342         if (xmlSecDSigCtxInitialize (&signature_context, keys_manager) < 0) {
343                 throw MiscError ("could not initialise XMLSEC context");
344         }
345         
346         if (xmlSecDSigCtxSign (&signature_context, parent->cobj()) < 0) {
347                 throw MiscError ("could not sign");
348         }
349         
350         xmlSecDSigCtxFinalize (&signature_context);
351         xmlSecKeysMngrDestroy (keys_manager);
352 }
353
354
355 void
356 libdcp::add_signer (xmlpp::Element* parent, CertificateChain const & certificates, string const & ns)
357 {
358         xmlpp::Element* signer = parent->add_child("Signer");
359
360         {
361                 xmlpp::Element* data = signer->add_child("X509Data", ns);
362                 
363                 {
364                         xmlpp::Element* serial_element = data->add_child("X509IssuerSerial", ns);
365                         serial_element->add_child("X509IssuerName", ns)->add_child_text (
366                                 Certificate::name_for_xml (certificates.leaf()->issuer())
367                                 );
368                         serial_element->add_child("X509SerialNumber", ns)->add_child_text (
369                                 certificates.leaf()->serial()
370                                 );
371                 }
372                 
373                 data->add_child("X509SubjectName", ns)->add_child_text (Certificate::name_for_xml (certificates.leaf()->subject()));
374         }
375 }
376
377 void
378 libdcp::sign (xmlpp::Element* parent, CertificateChain const & certificates, string const & signer_key)
379 {
380         add_signer (parent, certificates, "dsig");
381
382         xmlpp::Element* signature = parent->add_child("Signature", "dsig");
383         
384         {
385                 xmlpp::Element* signed_info = signature->add_child ("SignedInfo", "dsig");
386                 signed_info->add_child("CanonicalizationMethod", "dsig")->set_attribute ("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
387                 signed_info->add_child("SignatureMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
388                 {
389                         xmlpp::Element* reference = signed_info->add_child("Reference", "dsig");
390                         reference->set_attribute ("URI", "");
391                         {
392                                 xmlpp::Element* transforms = reference->add_child("Transforms", "dsig");
393                                 transforms->add_child("Transform", "dsig")->set_attribute (
394                                         "Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
395                                         );
396                         }
397                         reference->add_child("DigestMethod", "dsig")->set_attribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
398                         /* This will be filled in by the signing later */
399                         reference->add_child("DigestValue", "dsig");
400                 }
401         }
402         
403         add_signature_value (signature, certificates, signer_key, "dsig");
404 }
405