libdcp
certificate.cc
Go to the documentation of this file.
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 
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
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
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 LocalTime
333 convert_time (ASN1_TIME const * time)
334 {
335  LocalTime t;
336  char const * s = (char const *) time->data;
337 
338  if (time->type == V_ASN1_UTCTIME) {
339  return LocalTime::from_asn1_utc_time (s);
340  } else if (time->type == V_ASN1_GENERALIZEDTIME) {
341  return LocalTime::from_asn1_generalized_time (s);
342  }
343 
344  DCP_ASSERT (false);
345  return {};
346 }
347 
348 
349 LocalTime
350 Certificate::not_before () const
351 {
352  DCP_ASSERT (_certificate);
353 #if OPENSSL_VERSION_NUMBER > 0x10100000L
354  return convert_time(X509_get0_notBefore(_certificate));
355 #else
356  return convert_time(X509_get_notBefore(_certificate));
357 #endif
358 }
359 
360 
361 LocalTime
362 Certificate::not_after () const
363 {
364  DCP_ASSERT (_certificate);
365 #if OPENSSL_VERSION_NUMBER > 0x10100000L
366  return convert_time(X509_get0_notAfter(_certificate));
367 #else
368  return convert_time(X509_get_notAfter(_certificate));
369 #endif
370 }
371 
372 
373 string
374 Certificate::serial () const
375 {
376  DCP_ASSERT (_certificate);
377 
378  auto s = X509_get_serialNumber (_certificate);
379  DCP_ASSERT (s);
380 
381  auto b = ASN1_INTEGER_to_BN (s, 0);
382  char* c = BN_bn2dec (b);
383  BN_free (b);
384 
385  string st (c);
386  OPENSSL_free (c);
387 
388  return st;
389 }
390 
391 
392 string
394 {
395  DCP_ASSERT (_certificate);
396 
397  uint8_t buffer[8192];
398  uint8_t* p = buffer;
399 
400 #if OPENSSL_VERSION_NUMBER > 0x10100000L
401  i2d_re_X509_tbs(_certificate, &p);
402 #else
403  i2d_X509_CINF (_certificate->cert_info, &p);
404 #endif
405  unsigned int const length = p - buffer;
406  if (length > sizeof (buffer)) {
407  throw MiscError ("buffer too small to generate thumbprint");
408  }
409 
410  SHA_CTX sha;
411  SHA1_Init (&sha);
412  SHA1_Update (&sha, buffer, length);
413  uint8_t digest[20];
414  SHA1_Final (digest, &sha);
415 
416  char digest_base64[64];
417  return Kumu::base64encode (digest, 20, digest_base64, 64);
418 }
419 
420 
421 RSA *
423 {
424  DCP_ASSERT (_certificate);
425 
426  if (_public_key) {
427  return _public_key;
428  }
429 
430  auto key = X509_get_pubkey (_certificate);
431  if (!key) {
432  throw MiscError ("could not get public key from certificate");
433  }
434 
435  _public_key = EVP_PKEY_get1_RSA (key);
436  if (!_public_key) {
437  throw MiscError (String::compose ("could not get RSA public key (%1)", ERR_error_string (ERR_get_error(), 0)));
438  }
439 
440  return _public_key;
441 }
442 
443 
444 static bool string_is_utf8 (X509_NAME* n, int nid)
445 {
446  int p = -1;
447  p = X509_NAME_get_index_by_NID (n, nid, p);
448  return p != -1 && X509_NAME_ENTRY_get_data(X509_NAME_get_entry(n, p))->type == V_ASN1_UTF8STRING;
449 }
450 
451 
452 bool
453 Certificate::has_utf8_strings () const
454 {
455  auto n = X509_get_subject_name (_certificate);
456  return string_is_utf8(n, NID_commonName) ||
457  string_is_utf8(n, NID_organizationName) ||
458  string_is_utf8(n, NID_organizationalUnitName);
459 }
460 
461 
462 bool
463 dcp::operator== (Certificate const & a, Certificate const & b)
464 {
465  return a.certificate() == b.certificate();
466 }
467 
468 
469 bool
470 dcp::operator< (Certificate const & a, Certificate const & b)
471 {
472  return a.certificate() < b.certificate();
473 }
474 
475 
476 ostream&
477 dcp::operator<< (ostream& s, Certificate const & c)
478 {
479  s << c.certificate();
480  return s;
481 }
Certificate class.
A wrapper for an X509 certificate.
Definition: certificate.h:66
std::string read_string(std::string)
Definition: certificate.cc:93
std::string certificate(bool with_begin_end=false) const
Definition: certificate.cc:211
RSA * public_key() const
Definition: certificate.cc:422
std::string thumbprint() const
Definition: certificate.cc:393
std::string issuer() const
Definition: certificate.cc:241
A representation of a local time (down to the second), including its offset from GMT (equivalent to x...
Definition: local_time.h:64
A miscellaneous exception.
Definition: exceptions.h:94
DCP_ASSERT macro.
Exceptions thrown by libdcp.
Namespace for everything in libdcp.
Definition: array_data.h:50
Utility methods and classes.