Tidying.
[libdcp.git] / src / certificate.cc
1 /*
2     Copyright (C) 2012-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/certificate.cc
36  *  @brief Certificate class
37  */
38
39
40 #include "certificate.h"
41 #include "compose.hpp"
42 #include "exceptions.h"
43 #include "util.h"
44 #include "dcp_assert.h"
45 #include <asdcp/KM_util.h>
46 #include <libxml++/nodes/element.h>
47 #include <openssl/x509.h>
48 #include <openssl/ssl.h>
49 #include <openssl/asn1.h>
50 #include <openssl/err.h>
51 #include <boost/algorithm/string.hpp>
52 #include <cerrno>
53 #include <iostream>
54 #include <algorithm>
55
56
57 using std::list;
58 using std::string;
59 using std::ostream;
60 using std::min;
61 using namespace dcp;
62
63
64 static string const begin_certificate = "-----BEGIN CERTIFICATE-----";
65 static string const end_certificate = "-----END CERTIFICATE-----";
66
67
68 Certificate::Certificate (X509* c)
69         : _certificate (c)
70 {
71
72 }
73
74
75 Certificate::Certificate (string cert)
76 {
77         auto const s = read_string (cert);
78         if (!s.empty()) {
79                 throw MiscError ("unexpected data after certificate");
80         }
81 }
82
83
84 Certificate::Certificate (Certificate const & other)
85 {
86         if (other._certificate) {
87                 read_string (other.certificate (true));
88         }
89 }
90
91
92 string
93 Certificate::read_string (string cert)
94 {
95         /* Reformat cert so that it has line breaks every 64 characters.
96            See http://comments.gmane.org/gmane.comp.encryption.openssl.user/55593
97         */
98
99         list<string> lines;
100         string line;
101
102         for (size_t i = 0; i < cert.length(); ++i) {
103                 line += cert[i];
104                 if (cert[i] == '\r' || cert[i] == '\n') {
105                         boost::algorithm::trim (line);
106                         lines.push_back (line);
107                         line = "";
108                 }
109         }
110
111         if (!line.empty()) {
112                 boost::algorithm::trim (line);
113                 lines.push_back (line);
114         }
115
116         auto i = lines.begin ();
117
118         /* BEGIN */
119         while (i != lines.end() && *i != begin_certificate) {
120                 ++i;
121         }
122
123         if (i == lines.end()) {
124                 throw MiscError ("missing BEGIN line in certificate");
125         }
126
127         /* Skip over the BEGIN line */
128         ++i;
129
130         /* The base64 data */
131         bool got_end = false;
132         string base64 = "";
133         while (i != lines.end()) {
134                 if (*i == end_certificate) {
135                         got_end = true;
136                         break;
137                 }
138                 base64 += *i;
139                 ++i;
140         }
141
142         if (!got_end) {
143                 throw MiscError ("missing END line in certificate");
144         }
145
146         /* Skip over the END line */
147         ++i;
148
149         /* Make up the fixed version */
150
151         string fixed = begin_certificate + "\n";
152         while (!base64.empty ()) {
153                 size_t const t = min (size_t(64), base64.length());
154                 fixed += base64.substr (0, t) + "\n";
155                 base64 = base64.substr (t, base64.length() - t);
156         }
157
158         fixed += end_certificate;
159
160         auto bio = BIO_new_mem_buf (const_cast<char *> (fixed.c_str ()), -1);
161         if (!bio) {
162                 throw MiscError ("could not create memory BIO");
163         }
164
165         _certificate = PEM_read_bio_X509 (bio, 0, 0, 0);
166         if (!_certificate) {
167                 throw MiscError ("could not read X509 certificate from memory BIO");
168         }
169
170         BIO_free (bio);
171
172         string extra;
173
174         while (i != lines.end()) {
175                 if (!i->empty()) {
176                         extra += *i + "\n";
177                 }
178                 ++i;
179         }
180
181         return extra;
182 }
183
184
185 Certificate::~Certificate ()
186 {
187         X509_free (_certificate);
188         RSA_free (_public_key);
189 }
190
191
192 Certificate &
193 Certificate::operator= (Certificate const & other)
194 {
195         if (this == &other) {
196                 return *this;
197         }
198
199         X509_free (_certificate);
200         _certificate = 0;
201         RSA_free (_public_key);
202         _public_key = 0;
203
204         read_string (other.certificate(true));
205
206         return *this;
207 }
208
209
210 string
211 Certificate::certificate (bool with_begin_end) const
212 {
213         DCP_ASSERT (_certificate);
214
215         auto bio = BIO_new (BIO_s_mem());
216         if (!bio) {
217                 throw MiscError ("could not create memory BIO");
218         }
219
220         PEM_write_bio_X509 (bio, _certificate);
221
222         string s;
223         char* data;
224         long int const data_length = BIO_get_mem_data (bio, &data);
225         for (long int i = 0; i < data_length; ++i) {
226                 s += data[i];
227         }
228
229         BIO_free (bio);
230
231         if (!with_begin_end) {
232                 boost::replace_all (s, begin_certificate + "\n", "");
233                 boost::replace_all (s, "\n" + end_certificate + "\n", "");
234         }
235
236         return s;
237 }
238
239
240 string
241 Certificate::issuer () const
242 {
243         DCP_ASSERT (_certificate);
244         return name_for_xml (X509_get_issuer_name(_certificate));
245 }
246
247
248 string
249 Certificate::asn_to_utf8 (ASN1_STRING* s)
250 {
251         unsigned char* buf = 0;
252         ASN1_STRING_to_UTF8 (&buf, s);
253         string const u (reinterpret_cast<char *> (buf));
254         OPENSSL_free (buf);
255         return u;
256 }
257
258
259 string
260 Certificate::get_name_part (X509_NAME* n, int nid)
261 {
262         int p = -1;
263         p = X509_NAME_get_index_by_NID (n, nid, p);
264         if (p == -1) {
265                 return "";
266         }
267         return asn_to_utf8 (X509_NAME_ENTRY_get_data(X509_NAME_get_entry(n, p)));
268 }
269
270
271 string
272 Certificate::name_for_xml (X509_NAME* name)
273 {
274         assert (name);
275
276         auto bio = BIO_new (BIO_s_mem ());
277         if (!bio) {
278                 throw MiscError ("could not create memory BIO");
279         }
280
281         X509_NAME_print_ex (bio, name, 0, XN_FLAG_RFC2253);
282         int n = BIO_pending (bio);
283         char* result = new char[n + 1];
284         n = BIO_read (bio, result, n);
285         result[n] = '\0';
286
287         BIO_free (bio);
288
289         string s = result;
290         delete[] result;
291
292         return s;
293 }
294
295 string
296 Certificate::subject () const
297 {
298         DCP_ASSERT (_certificate);
299
300         return name_for_xml (X509_get_subject_name(_certificate));
301 }
302
303
304 string
305 Certificate::subject_common_name () const
306 {
307         DCP_ASSERT (_certificate);
308
309         return get_name_part (X509_get_subject_name(_certificate), NID_commonName);
310 }
311
312
313 string
314 Certificate::subject_organization_name () const
315 {
316         DCP_ASSERT (_certificate);
317
318         return get_name_part (X509_get_subject_name(_certificate), NID_organizationName);
319 }
320
321
322 string
323 Certificate::subject_organizational_unit_name () const
324 {
325         DCP_ASSERT (_certificate);
326
327         return get_name_part (X509_get_subject_name(_certificate), NID_organizationalUnitName);
328 }
329
330
331 static
332 struct tm
333 convert_time (ASN1_TIME const * time)
334 {
335         struct tm t;
336         char const * s = (char const *) time->data;
337
338         if (time->type == V_ASN1_UTCTIME) {
339                 sscanf(s, "%2d%2d%2d%2d%2d%2d", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec);
340                 if (t.tm_year < 70) {
341                         t.tm_year += 100;
342                 }
343         } else if (time->type == V_ASN1_GENERALIZEDTIME) {
344                 sscanf(s, "%4d%2d%2d%2d%2d%2d", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec);
345                 t.tm_year -= 1900;
346         }
347
348         t.tm_mon--;
349
350         return t;
351 }
352
353
354 struct tm
355 Certificate::not_before () const
356 {
357         DCP_ASSERT (_certificate);
358 #if OPENSSL_VERSION_NUMBER > 0x10100000L
359         return convert_time(X509_get0_notBefore(_certificate));
360 #else
361         return convert_time(X509_get_notBefore(_certificate));
362 #endif
363 }
364
365
366 struct tm
367 Certificate::not_after () const
368 {
369         DCP_ASSERT (_certificate);
370 #if OPENSSL_VERSION_NUMBER > 0x10100000L
371         return convert_time(X509_get0_notAfter(_certificate));
372 #else
373         return convert_time(X509_get_notAfter(_certificate));
374 #endif
375 }
376
377
378 string
379 Certificate::serial () const
380 {
381         DCP_ASSERT (_certificate);
382
383         auto s = X509_get_serialNumber (_certificate);
384         DCP_ASSERT (s);
385
386         auto b = ASN1_INTEGER_to_BN (s, 0);
387         char* c = BN_bn2dec (b);
388         BN_free (b);
389
390         string st (c);
391         OPENSSL_free (c);
392
393         return st;
394 }
395
396
397 string
398 Certificate::thumbprint () const
399 {
400         DCP_ASSERT (_certificate);
401
402         uint8_t buffer[8192];
403         uint8_t* p = buffer;
404
405 #if OPENSSL_VERSION_NUMBER > 0x10100000L
406         i2d_re_X509_tbs(_certificate, &p);
407 #else
408         i2d_X509_CINF (_certificate->cert_info, &p);
409 #endif
410         unsigned int const length = p - buffer;
411         if (length > sizeof (buffer)) {
412                 throw MiscError ("buffer too small to generate thumbprint");
413         }
414
415         SHA_CTX sha;
416         SHA1_Init (&sha);
417         SHA1_Update (&sha, buffer, length);
418         uint8_t digest[20];
419         SHA1_Final (digest, &sha);
420
421         char digest_base64[64];
422         return Kumu::base64encode (digest, 20, digest_base64, 64);
423 }
424
425
426 RSA *
427 Certificate::public_key () const
428 {
429         DCP_ASSERT (_certificate);
430
431         if (_public_key) {
432                 return _public_key;
433         }
434
435         auto key = X509_get_pubkey (_certificate);
436         if (!key) {
437                 throw MiscError ("could not get public key from certificate");
438         }
439
440         _public_key = EVP_PKEY_get1_RSA (key);
441         if (!_public_key) {
442                 throw MiscError (String::compose ("could not get RSA public key (%1)", ERR_error_string (ERR_get_error(), 0)));
443         }
444
445         return _public_key;
446 }
447
448
449 static bool string_is_utf8 (X509_NAME* n, int nid)
450 {
451         int p = -1;
452         p = X509_NAME_get_index_by_NID (n, nid, p);
453         return p != -1 && X509_NAME_ENTRY_get_data(X509_NAME_get_entry(n, p))->type == V_ASN1_UTF8STRING;
454 }
455
456
457 bool
458 Certificate::has_utf8_strings () const
459 {
460         auto n = X509_get_subject_name (_certificate);
461         return string_is_utf8(n, NID_commonName) ||
462                 string_is_utf8(n, NID_organizationName) ||
463                 string_is_utf8(n, NID_organizationalUnitName);
464 }
465
466
467 bool
468 dcp::operator== (Certificate const & a, Certificate const & b)
469 {
470         return a.certificate() == b.certificate();
471 }
472
473
474 bool
475 dcp::operator< (Certificate const & a, Certificate const & b)
476 {
477         return a.certificate() < b.certificate();
478 }
479
480
481 ostream&
482 dcp::operator<< (ostream& s, Certificate const & c)
483 {
484         s << c.certificate();
485         return s;
486 }