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) {
108 VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
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
ConfigVariableFilename
This is a convenience class to specialize ConfigVariable as a Filename type.
Definition: configVariableFilename.h:27
ConfigVariableList
This class is similar to ConfigVariable, but it reports its value as a list of strings.
Definition: configVariableList.h:31
Filename
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
Filename::expand_from
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
VirtualFileSystem
A hierarchy of directories and files that appears to be one continuous file system,...
Definition: virtualFileSystem.h:40
VirtualFileSystem::get_global_ptr
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
Definition: virtualFileSystem.cxx:741
openSSLWrapper.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtualFileSystem.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.