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