Panda3D
openSSLWrapper.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file openSSLWrapper.cxx
10  * @author drose
11  * @date 2009-09-05
12  */
13 
14 #include "openSSLWrapper.h"
15 
16 #ifdef HAVE_OPENSSL
17 
18 #include "virtualFileSystem.h"
19 #include "ca_bundle_data_src.c"
20 
21 OpenSSLWrapper *OpenSSLWrapper::_global_ptr = nullptr;
22 
23 /**
24  *
25  */
26 OpenSSLWrapper::
27 OpenSSLWrapper() {
28  // It is necessary to call this before making any other OpenSSL call, per
29  // the docs. Also, the docs say that making this call will seed the random
30  // number generator. Apparently you can get away with not calling it in
31  // versions prior to 0.9.8, however.
32  SSL_library_init();
33 
34  OpenSSL_add_all_algorithms();
35 
36  _x509_store = X509_STORE_new();
37  X509_STORE_set_default_paths(_x509_store);
38 
39  // Load in the well-known certificate authorities compiled into this
40  // program.
41  load_certificates_from_der_ram((const char *)ca_bundle_data, ca_bundle_data_len);
42 
43  // Load in any default certificates listed in the Config.prc file.
44  ConfigVariableFilename ca_bundle_filename
45  ("ca-bundle-filename", "",
46  PRC_DESC("This names the certificate authority file for OpenSSL "
47  "to use to verify whether SSL certificates are trusted or not. "
48  "The file named by this setting should contain one or more "
49  "PEM-formatted certificates from trusted certificate "
50  "authorities. This is a fairly standard file; a copy of "
51  "ca-bundle.crt is included in the OpenSSL distribution, and "
52  "is also included with Panda."));
53 
54  if (!ca_bundle_filename.empty()) {
55  load_certificates(ca_bundle_filename);
56  }
57 
58  ConfigVariableList ssl_certificates
59  ("ssl-certificates",
60  PRC_DESC("This variable lists additional filenames, on top of the file "
61  "named by ca-bundle-filename, that contain trusted SSL "
62  "certificates or certificate authorities."));
63 
64  int num_certs = ssl_certificates.get_num_unique_values();
65  for (int ci = 0; ci < num_certs; ci++) {
66  std::string cert_file = ssl_certificates.get_unique_value(ci);
67  Filename filename = Filename::expand_from(cert_file);
68  load_certificates(filename);
69  }
70 }
71 
72 /**
73  *
74  */
75 OpenSSLWrapper::
76 ~OpenSSLWrapper() {
77  // Actually, the destructor is never called.
78  X509_STORE_free(_x509_store);
79 }
80 
81 /**
82  * Removes all the certificates from the global store, including the compiled-
83  * in certificates loaded from ca_bundle_data.c. You can add new certificates
84  * by calling load_certificates().
85  */
86 void OpenSSLWrapper::
87 clear_certificates() {
88  // We do this by deleting the store and creating a new one.
89  X509_STORE_free(_x509_store);
90  _x509_store = X509_STORE_new();
91 
92  // We don't set the default path either. We want a squeaky-clean store.
93  // X509_STORE_set_default_paths(_x509_store);
94 }
95 
96 /**
97  * Reads the PEM-formatted certificate(s) (delimited by -----BEGIN
98  * CERTIFICATE----- and -----END CERTIFICATE-----) from the indicated file and
99  * adds them to the global store object, retrieved via get_x509_store().
100  *
101  * Returns the number of certificates read on success, or 0 on failure.
102  *
103  * You should call this only with trusted, locally-stored certificates; not
104  * with certificates received from an untrusted source.
105  */
106 int OpenSSLWrapper::
107 load_certificates(const Filename &filename) {
109 
110  // First, read the complete file into memory.
111  std::string data;
112  if (!vfs->read_file(filename, data, true)) {
113  // Could not find or read file.
114  express_cat.info()
115  << "Could not read " << filename << ".\n";
116  return 0;
117  }
118 
119  int result = load_certificates_from_pem_ram(data.data(), data.size());
120 
121  if (result <= 0) {
122  express_cat.info()
123  << "Could not load certificates from " << filename << ".\n";
124  notify_ssl_errors();
125  return 0;
126  }
127 
128  if (express_cat.is_debug()) {
129  express_cat.debug()
130  << "Appending " << result << " SSL certificates from "
131  << filename << "\n";
132  }
133 
134  return result;
135 }
136 
137 /**
138  * Reads a chain of trusted certificates from the indicated data buffer and
139  * adds them to the X509_STORE object. The data buffer should be PEM-
140  * formatted. Returns the number of certificates read on success, or 0 on
141  * failure.
142  *
143  * You should call this only with trusted, locally-stored certificates; not
144  * with certificates received from an untrusted source.
145  */
146 int OpenSSLWrapper::
147 load_certificates_from_pem_ram(const char *data, size_t data_size) {
148  STACK_OF(X509_INFO) *inf;
149 
150  // Create an in-memory BIO to read the "file" from the buffer, and call the
151  // low-level routines to read the certificates from the BIO.
152  BIO *mbio = BIO_new_mem_buf((void *)data, data_size);
153 
154  // We have to be sure and clear the OpenSSL error state before we call this
155  // function, or it will get confused.
156  ERR_clear_error();
157  inf = PEM_X509_INFO_read_bio(mbio, nullptr, nullptr, nullptr);
158  BIO_free(mbio);
159 
160  if (!inf) {
161  // Could not scan certificates.
162  express_cat.info()
163  << "PEM_X509_INFO_read_bio() returned NULL.\n";
164  notify_ssl_errors();
165  return 0;
166  }
167 
168  if (express_cat.is_spam()) {
169  express_cat.spam()
170  << "PEM_X509_INFO_read_bio() found " << sk_X509_INFO_num(inf)
171  << " entries.\n";
172  }
173 
174  // Now add the certificates to the store.
175 
176  int count = 0;
177  int num_entries = sk_X509_INFO_num(inf);
178  for (int i = 0; i < num_entries; i++) {
179  X509_INFO *itmp = sk_X509_INFO_value(inf, i);
180 
181  if (itmp->x509) {
182  int result = X509_STORE_add_cert(_x509_store, itmp->x509);
183  if (result == 0) {
184  notify_debug_ssl_errors();
185  } else {
186  ++count;
187  }
188 
189  if (express_cat.is_spam()) {
190  express_cat.spam()
191  << "Entry " << i << " is x509\n";
192  }
193 
194  } else if (itmp->crl) {
195  int result = X509_STORE_add_crl(_x509_store, itmp->crl);
196  if (result == 0) {
197  notify_debug_ssl_errors();
198  } else {
199  ++count;
200  }
201 
202  if (express_cat.is_spam()) {
203  express_cat.spam()
204  << "Entry " << i << " is crl\n";
205  }
206 
207  } else if (itmp->x_pkey) {
208  if (express_cat.is_spam()) {
209  express_cat.spam()
210  << "Entry " << i << " is pkey\n";
211  }
212 
213  } else {
214  if (express_cat.is_spam()) {
215  express_cat.spam()
216  << "Entry " << i << " is unknown type\n";
217  }
218  }
219  }
220  sk_X509_INFO_pop_free(inf, X509_INFO_free);
221 
222  if (express_cat.is_spam()) {
223  express_cat.spam()
224  << "successfully loaded " << count << " entries.\n";
225  }
226 
227  return count;
228 }
229 
230 /**
231  * Reads a chain of trusted certificates from the indicated data buffer and
232  * adds them to the X509_STORE object. The data buffer should be DER-
233  * formatted. Returns the number of certificates read on success, or 0 on
234  * failure.
235  *
236  * You should call this only with trusted, locally-stored certificates; not
237  * with certificates received from an untrusted source.
238  */
239 int OpenSSLWrapper::
240 load_certificates_from_der_ram(const char *data, size_t data_size) {
241  if (express_cat.is_spam()) {
242  express_cat.spam()
243  << "load_certificates_from_der_ram(" << (void *)data
244  << ", " << data_size << ")\n";
245  }
246 
247  int count = 0;
248 
249 #if OPENSSL_VERSION_NUMBER >= 0x00908000L
250  // Beginning in 0.9.8, d2i_X509() accepted a const unsigned char **.
251  const unsigned char *bp, *bp_end;
252 #else
253  // Prior to 0.9.8, d2i_X509() accepted an unsigned char **.
254  unsigned char *bp, *bp_end;
255 #endif
256 
257  bp = (unsigned char *)data;
258  bp_end = bp + data_size;
259  while (bp < bp_end) {
260  X509 *x509 = d2i_X509(nullptr, &bp, bp_end - bp);
261  if (x509 == nullptr) {
262  notify_ssl_errors();
263  break;
264  }
265 
266  int result = X509_STORE_add_cert(_x509_store, x509);
267  if (result == 0) {
268  notify_debug_ssl_errors();
269  } else {
270  ++count;
271  }
272  }
273 
274 
275  if (express_cat.is_spam()) {
276  express_cat.spam()
277  << "loaded " << count << " certificates\n";
278  }
279 
280  return count;
281 }
282 
283 /**
284  * Returns the global X509_STORE object.
285  *
286  * It has to be a global object, because OpenSSL seems to store some global
287  * pointers associated with this object whether you want it to or not, and
288  * keeping independent copies of a local X509_STORE object doesn't seem to
289  * work that well. So, we have one store that keeps all certificates the
290  * application might need.
291  */
292 X509_STORE *OpenSSLWrapper::
293 get_x509_store() {
294  return _x509_store;
295 }
296 
297 /**
298  * A convenience function that is itself a wrapper around the OpenSSL
299  * convenience function to output the recent OpenSSL errors. This function
300  * sends the error string to express_cat.warning(). If REPORT_OPENSSL_ERRORS
301  * is not defined, the function does nothing.
302  */
303 void OpenSSLWrapper::
304 notify_ssl_errors() {
305 #ifdef REPORT_OPENSSL_ERRORS
306  static bool strings_loaded = false;
307  if (!strings_loaded) {
308  SSL_load_error_strings();
309  strings_loaded = true;
310  }
311 
312  unsigned long e = ERR_get_error();
313  while (e != 0) {
314  static const size_t buffer_len = 256;
315  char buffer[buffer_len];
316  ERR_error_string_n(e, buffer, buffer_len);
317  express_cat.warning() << buffer << "\n";
318  e = ERR_get_error();
319  }
320 #endif // REPORT_OPENSSL_ERRORS
321 }
322 
323 /**
324  * As notify_ssl_errors(), but sends the output to debug instead of warning.
325  */
326 void OpenSSLWrapper::
327 notify_debug_ssl_errors() {
328 #ifdef REPORT_OPENSSL_ERRORS
329  static bool strings_loaded = false;
330  if (!strings_loaded) {
331  SSL_load_error_strings();
332  strings_loaded = true;
333  }
334 
335  unsigned long e = ERR_get_error();
336  while (e != 0) {
337  if (express_cat.is_debug()) {
338  static const size_t buffer_len = 256;
339  char buffer[buffer_len];
340  ERR_error_string_n(e, buffer, buffer_len);
341  express_cat.debug() << buffer << "\n";
342  }
343  e = ERR_get_error();
344  }
345 #endif // REPORT_OPENSSL_ERRORS
346 }
347 
348 /**
349  *
350  */
351 OpenSSLWrapper *OpenSSLWrapper::
352 get_global_ptr() {
353  if (_global_ptr == nullptr) {
354  _global_ptr = new OpenSSLWrapper;
355  }
356  return _global_ptr;
357 }
358 
359 #endif // HAVE_OPENSSL
This is a convenience class to specialize ConfigVariable as a Filename type.
A hierarchy of directories and files that appears to be one continuous file system, even though the files may originate from several different sources that may not be related to the actual OS&#39;s file system.
static Filename expand_from(const std::string &user_string, Type type=T_general)
Returns the same thing as from_os_specific(), but embedded environment variable references (e...
Definition: filename.cxx:407
This class is similar to ConfigVariable, but it reports its value as a list of strings.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.