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,...
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.