Panda3D
Loading...
Searching...
No Matches
encryptStreamBuf.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 encryptStreamBuf.cxx
10 * @author drose
11 * @date 2004-09-01
12 */
13
14#include "encryptStreamBuf.h"
15#include "config_prc.h"
16#include "streamReader.h"
17#include "streamWriter.h"
18#include "configVariableInt.h"
20
21#ifdef HAVE_OPENSSL
22
23#include <openssl/rand.h>
24#include <openssl/evp.h>
25
26#if OPENSSL_VERSION_MAJOR >= 3
27#include <openssl/provider.h>
28
29/**
30 * Tries to load the legacy provider in OpenSSL. Returns true if the provider
31 * was just loaded, false if it was already loaded or couldn't be loaded.
32 */
33static bool load_legacy_provider() {
34 static bool tried = false;
35 if (!tried) {
36 tried = true;
37 if (OSSL_PROVIDER_try_load(nullptr, "legacy", 1) != nullptr) {
38 if (prc_cat.is_debug()) {
39 prc_cat.debug()
40 << "Loaded legacy OpenSSL provider.\n";
41 }
42 return true;
43 } else {
44 prc_cat.warning()
45 << "Failed to load legacy OpenSSL provider.\n";
46 }
47 }
48 return false;
49}
50#endif // OPENSSL_VERSION_MAJOR
51
52// The iteration count is scaled by this factor for writing to the stream.
53static const int iteration_count_factor = 1000;
54
55/**
56 *
57 */
58EncryptStreamBuf::
59EncryptStreamBuf() {
60 _source = nullptr;
61 _owns_source = false;
62 _dest = nullptr;
63 _owns_dest = false;
64
65 ConfigVariableString encryption_algorithm
66 ("encryption-algorithm", "bf-cbc",
67 PRC_DESC("This defines the OpenSSL encryption algorithm which is used to "
68 "encrypt any streams created by the current runtime. The default is "
69 "Blowfish; the complete set of available algorithms is defined by "
70 "the current version of OpenSSL. This value is used only to control "
71 "encryption; the correct algorithm will automatically be selected on "
72 "decryption."));
73
74 ConfigVariableInt encryption_key_length
75 ("encryption-key-length", 0,
76 PRC_DESC("This defines the key length, in bits, for the selected encryption "
77 "algorithm. Some algorithms have a variable key length. Specifying "
78 "a value of 0 here means to use the default key length for the "
79 "algorithm as defined by OpenSSL. This value is used only to "
80 "control encryption; the correct key length will automatically be "
81 "selected on decryption."));
82
83 ConfigVariableInt encryption_iteration_count
84 ("encryption-iteration-count", 100000,
85 PRC_DESC("This defines the number of times a password is hashed to generate a "
86 "key when encrypting. Its purpose is to make it computationally "
87 "more expensive for an attacker to search the key space "
88 "exhaustively. This should be a multiple of 1,000 and should not "
89 "exceed about 65 million; the value 0 indicates just one application "
90 "of the hashing algorithm. This value is used only to control "
91 "encryption; the correct count will automatically be selected on "
92 "decryption."));
93
94 _algorithm = encryption_algorithm;
95 _key_length = encryption_key_length;
96 _iteration_count = encryption_iteration_count;
97
98 _read_ctx = nullptr;
99 _write_ctx = nullptr;
100
101 _read_overflow_buffer = nullptr;
102 _in_read_overflow_buffer = 0;
103
104#ifdef PHAVE_IOSTREAM
105 char *buf = new char[4096];
106 char *ebuf = buf + 4096;
107 setg(buf, ebuf, ebuf);
108 setp(buf, ebuf);
109
110#else
111 allocate();
112 setg(base(), ebuf(), ebuf());
113 setp(base(), ebuf());
114#endif
115}
116
117/**
118 *
119 */
120EncryptStreamBuf::
121~EncryptStreamBuf() {
122 close_read();
123 close_write();
124
125#ifdef PHAVE_IOSTREAM
126 delete[] eback();
127#endif
128}
129
130/**
131 *
132 */
133void EncryptStreamBuf::
134open_read(std::istream *source, bool owns_source, const std::string &password) {
135 OpenSSL_add_all_algorithms();
136
137 _source = source;
138 _owns_source = owns_source;
139
140 if (_read_ctx != nullptr) {
141 EVP_CIPHER_CTX_free(_read_ctx);
142 _read_ctx = nullptr;
143 }
144
145 // Now read the header information.
146 StreamReader sr(_source, false);
147 int nid = sr.get_uint16();
148 int key_length = sr.get_uint16();
149 int count = sr.get_uint16();
150
151#if OPENSSL_VERSION_MAJOR >= 3
152 // First, convert the cipher's nid to its full name.
153 const char *cipher_name = OBJ_nid2ln(nid);
154
155 const EVP_CIPHER *cipher = nullptr;
156 if (cipher_name != nullptr) {
157 // Now, fetch the cipher known by this name.
158 cipher = EVP_CIPHER_fetch(nullptr, cipher_name, nullptr);
159
160 if (cipher == nullptr && EVP_get_cipherbynid(nid) != nullptr) {
161 if (load_legacy_provider()) {
162 cipher = EVP_CIPHER_fetch(nullptr, cipher_name, nullptr);
163 }
164
165 if (cipher == nullptr) {
166 prc_cat.error()
167 << "No implementation available for encryption algorithm in stream: "
168 << cipher_name << "\n";
169 return;
170 }
171 }
172 }
173#else
174 const EVP_CIPHER *cipher = EVP_get_cipherbynid(nid);
175#endif
176
177 if (cipher == nullptr) {
178 prc_cat.error()
179 << "Unknown encryption algorithm in stream.\n";
180 return;
181 }
182
183 _algorithm = OBJ_nid2sn(nid);
184 _key_length = key_length * 8;
185 _iteration_count = count * iteration_count_factor;
186
187 if (prc_cat.is_debug()) {
188 prc_cat.debug()
189 << "Using decryption algorithm " << _algorithm << " with key length "
190 << _key_length << " bits.\n";
191 prc_cat.debug()
192 << "Key is hashed " << _iteration_count << " extra times.\n";
193 }
194
195 int iv_length = EVP_CIPHER_iv_length(cipher);
196 _read_block_size = EVP_CIPHER_block_size(cipher);
197
198 unsigned char *iv = (unsigned char *)alloca(iv_length);
199 iv_length = (int)sr.extract_bytes(iv, iv_length);
200
201 _read_ctx = EVP_CIPHER_CTX_new();
202 nassertv(_read_ctx != nullptr);
203
204 // Initialize the context
205 int result;
206 result = EVP_DecryptInit(_read_ctx, cipher, nullptr, (unsigned char *)iv);
207 nassertv(result > 0);
208
209 result = EVP_CIPHER_CTX_set_key_length(_read_ctx, key_length);
210 if (result <= 0) {
211 prc_cat.error()
212 << "Invalid key length " << key_length * 8 << " bits for algorithm "
213 << OBJ_nid2sn(nid) << "\n";
214 EVP_CIPHER_CTX_free(_read_ctx);
215 _read_ctx = nullptr;
216 return;
217 }
218
219 // Hash the supplied password into a key of the appropriate length.
220 unsigned char *key = (unsigned char *)alloca(key_length);
221 result =
222 PKCS5_PBKDF2_HMAC_SHA1((const char *)password.data(), password.length(),
223 iv, iv_length,
224 count * iteration_count_factor + 1,
225 key_length, key);
226 nassertv(result > 0);
227
228 // Store the key within the context.
229 result = EVP_DecryptInit(_read_ctx, nullptr, key, nullptr);
230 nassertv(result > 0);
231
232 _read_overflow_buffer = new unsigned char[_read_block_size];
233 _in_read_overflow_buffer = 0;
234 thread_consider_yield();
235}
236
237/**
238 *
239 */
240void EncryptStreamBuf::
241close_read() {
242 if (_read_ctx != nullptr) {
243 EVP_CIPHER_CTX_free(_read_ctx);
244 _read_ctx = nullptr;
245 }
246
247 if (_read_overflow_buffer != nullptr) {
248 delete[] _read_overflow_buffer;
249 _read_overflow_buffer = nullptr;
250 }
251
252 if (_source != nullptr) {
253 if (_owns_source) {
254 delete _source;
255 _owns_source = false;
256 }
257 _source = nullptr;
258 }
259}
260
261/**
262 *
263 */
264void EncryptStreamBuf::
265open_write(std::ostream *dest, bool owns_dest, const std::string &password) {
266 OpenSSL_add_all_algorithms();
267
268 close_write();
269 _dest = dest;
270 _owns_dest = owns_dest;
271
272#if OPENSSL_VERSION_MAJOR >= 3
273 // This checks that there is actually an implementation available.
274 const EVP_CIPHER *cipher =
275 EVP_CIPHER_fetch(nullptr, _algorithm.c_str(), nullptr);
276
277 if (cipher == nullptr &&
278 EVP_get_cipherbyname(_algorithm.c_str()) != nullptr) {
279 // The cipher does exist, though, do we need to load the legacy provider?
280 if (load_legacy_provider()) {
281 cipher = EVP_CIPHER_fetch(nullptr, _algorithm.c_str(), nullptr);
282 }
283
284 if (cipher == nullptr) {
285 prc_cat.error()
286 << "No implementation available for encryption algorithm: "
287 << _algorithm << "\n";
288 return;
289 }
290 }
291#else
292 const EVP_CIPHER *cipher =
293 EVP_get_cipherbyname(_algorithm.c_str());
294#endif
295
296 if (cipher == nullptr) {
297 prc_cat.error()
298 << "Unknown encryption algorithm: " << _algorithm << "\n";
299 return;
300 }
301
302 int nid = EVP_CIPHER_nid(cipher);
303
304 int iv_length = EVP_CIPHER_iv_length(cipher);
305 _write_block_size = EVP_CIPHER_block_size(cipher);
306
307 // Generate a random IV. It doesn't need to be cryptographically secure,
308 // just unique.
309 unsigned char *iv = (unsigned char *)alloca(iv_length);
310 RAND_bytes(iv, iv_length);
311
312 _write_ctx = EVP_CIPHER_CTX_new();
313 nassertv(_write_ctx != nullptr);
314
315 int result;
316 result = EVP_EncryptInit(_write_ctx, cipher, nullptr, iv);
317 nassertv(result > 0);
318
319 // Store the appropriate key length in the context.
320 int key_length = (_key_length + 7) / 8;
321 if (key_length == 0) {
322 key_length = EVP_CIPHER_key_length(cipher);
323 }
324 result = EVP_CIPHER_CTX_set_key_length(_write_ctx, key_length);
325 if (result <= 0) {
326 prc_cat.error()
327 << "Invalid key length " << key_length * 8 << " bits for algorithm "
328 << OBJ_nid2sn(nid) << "\n";
329 EVP_CIPHER_CTX_free(_write_ctx);
330 _write_ctx = nullptr;
331 return;
332 }
333
334 int count = _iteration_count / iteration_count_factor;
335
336 if (prc_cat.is_debug()) {
337 prc_cat.debug()
338 << "Using encryption algorithm " << OBJ_nid2sn(nid) << " with key length "
339 << key_length * 8 << " bits.\n";
340 prc_cat.debug()
341 << "Hashing key " << count * iteration_count_factor
342 << " extra times.\n";
343 }
344
345 // Hash the supplied password into a key of the appropriate length.
346 unsigned char *key = (unsigned char *)alloca(key_length);
347 result =
348 PKCS5_PBKDF2_HMAC_SHA1((const char *)password.data(), password.length(),
349 iv, iv_length, count * iteration_count_factor + 1,
350 key_length, key);
351 nassertv(result > 0);
352
353 // Store the key in the context.
354 result = EVP_EncryptInit(_write_ctx, nullptr, key, nullptr);
355 nassertv(result > 0);
356
357 // Now write the header information to the stream.
358 StreamWriter sw(_dest, false);
359 nassertv((uint16_t)nid == nid);
360 sw.add_uint16((uint16_t)nid);
361 nassertv((uint16_t)key_length == key_length);
362 sw.add_uint16((uint16_t)key_length);
363 nassertv((uint16_t)count == count);
364 sw.add_uint16((uint16_t)count);
365 sw.append_data(iv, iv_length);
366
367 thread_consider_yield();
368}
369
370/**
371 *
372 */
373void EncryptStreamBuf::
374close_write() {
375 if (_dest != nullptr) {
376 size_t n = pptr() - pbase();
377 write_chars(pbase(), n);
378 pbump(-(int)n);
379
380 if (_write_ctx != nullptr) {
381 unsigned char *write_buffer = (unsigned char *)alloca(_write_block_size);
382 int bytes_written = 0;
383 EVP_EncryptFinal(_write_ctx, write_buffer, &bytes_written);
384 thread_consider_yield();
385
386 _dest->write((const char *)write_buffer, bytes_written);
387
388 EVP_CIPHER_CTX_free(_write_ctx);
389 _write_ctx = nullptr;
390 }
391
392 if (_owns_dest) {
393 delete _dest;
394 _owns_dest = false;
395 }
396 _dest = nullptr;
397 }
398}
399
400/**
401 * Called by the system ostream implementation when its internal buffer is
402 * filled, plus one character.
403 */
404int EncryptStreamBuf::
405overflow(int ch) {
406 size_t n = pptr() - pbase();
407 if (n != 0) {
408 write_chars(pbase(), n);
409 pbump(-(int)n);
410 }
411
412 if (ch != EOF) {
413 // Write one more character.
414 char c = (char)ch;
415 write_chars(&c, 1);
416 }
417
418 return 0;
419}
420
421/**
422 * Called by the system iostream implementation to implement a flush
423 * operation.
424 */
425int EncryptStreamBuf::
426sync() {
427 if (_source != nullptr) {
428 size_t n = egptr() - gptr();
429 gbump((int)n);
430 }
431
432 if (_dest != nullptr) {
433 size_t n = pptr() - pbase();
434 write_chars(pbase(), n);
435 pbump(-(int)n);
436 _dest->flush();
437 }
438
439 return 0;
440}
441
442/**
443 * Called by the system istream implementation when its internal buffer needs
444 * more characters.
445 */
446int EncryptStreamBuf::
447underflow() {
448 // Sometimes underflow() is called even if the buffer is not empty.
449 if (gptr() >= egptr()) {
450 size_t buffer_size = egptr() - eback();
451 gbump(-(int)buffer_size);
452
453 size_t num_bytes = buffer_size;
454 size_t read_count = read_chars(gptr(), buffer_size);
455
456 if (read_count != num_bytes) {
457 // Oops, we didn't read what we thought we would.
458 if (read_count == 0) {
459 gbump((int)num_bytes);
460 return EOF;
461 }
462
463 // Slide what we did read to the top of the buffer.
464 nassertr(read_count < num_bytes, EOF);
465 size_t delta = num_bytes - read_count;
466 memmove(gptr() + delta, gptr(), read_count);
467 gbump((int)delta);
468 }
469 }
470
471 return (unsigned char)*gptr();
472}
473
474
475/**
476 * Gets some characters from the source stream.
477 */
478size_t EncryptStreamBuf::
479read_chars(char *start, size_t length) {
480 if (length == 0) {
481 return 0;
482 }
483
484 if (_in_read_overflow_buffer != 0) {
485 // Take from the overflow buffer.
486 length = std::min(length, _in_read_overflow_buffer);
487 memcpy(start, _read_overflow_buffer, length);
488 _in_read_overflow_buffer -= length;
489 memcpy(_read_overflow_buffer + length, _read_overflow_buffer, _in_read_overflow_buffer);
490 return length;
491 }
492
493 unsigned char *source_buffer = (unsigned char *)alloca(length);
494 size_t max_read_buffer = length + _read_block_size;
495 unsigned char *read_buffer = (unsigned char *)alloca(max_read_buffer);
496
497 int bytes_read = 0;
498
499 do {
500 // Get more bytes from the stream.
501 if (_read_ctx == nullptr) {
502 return 0;
503 }
504
505 _source->read((char *)source_buffer, length);
506 size_t source_length = _source->gcount();
507
508 bytes_read = 0;
509 int result;
510 if (source_length != 0) {
511 result =
512 EVP_DecryptUpdate(_read_ctx, read_buffer, &bytes_read,
513 source_buffer, source_length);
514 } else {
515 result =
516 EVP_DecryptFinal(_read_ctx, read_buffer, &bytes_read);
517 EVP_CIPHER_CTX_free(_read_ctx);
518 _read_ctx = nullptr;
519 }
520
521 if (result <= 0) {
522 prc_cat.error()
523 << "Error decrypting stream.\n";
524 if (_read_ctx != nullptr) {
525 EVP_CIPHER_CTX_free(_read_ctx);
526 _read_ctx = nullptr;
527 }
528 }
529 thread_consider_yield();
530
531 } while (bytes_read == 0);
532
533 // Now store the read bytes in the output stream.
534 if ((size_t)bytes_read <= length) {
535 // No overflow.
536 memcpy(start, read_buffer, bytes_read);
537 return bytes_read;
538
539 } else {
540 // We have to save some of the returned bytes in the overflow buffer.
541 _in_read_overflow_buffer = bytes_read - length;
542 nassertr(_in_read_overflow_buffer <= _read_block_size, 0);
543
544 memcpy(_read_overflow_buffer, read_buffer + length,
545 _in_read_overflow_buffer);
546 memcpy(start, read_buffer, length);
547 return length;
548 }
549}
550
551/**
552 * Sends some characters to the dest stream.
553 */
554void EncryptStreamBuf::
555write_chars(const char *start, size_t length) {
556 if (_write_ctx != nullptr && length != 0) {
557 size_t max_write_buffer = length + _write_block_size;
558 unsigned char *write_buffer = (unsigned char *)alloca(max_write_buffer);
559
560 int bytes_written = 0;
561 int result =
562 EVP_EncryptUpdate(_write_ctx, write_buffer, &bytes_written,
563 (unsigned char *)start, length);
564 if (result <= 0) {
565 prc_cat.error()
566 << "Error encrypting stream.\n";
567 }
568 thread_consider_yield();
569 _dest->write((const char *)write_buffer, bytes_written);
570 }
571}
572
573#endif // HAVE_OPENSSL
This is a convenience class to specialize ConfigVariable as an integer type.
This is a convenience class to specialize ConfigVariable as a string type.
A class to read sequential binary data directly from an istream.
A StreamWriter object is used to write sequential binary data directly to an ostream.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.