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"
19 #include "configVariableString.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.
27 static const int iteration_count_factor = 1000;
28 
29 /**
30  *
31  */
32 EncryptStreamBuf::
33 EncryptStreamBuf() {
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  */
94 EncryptStreamBuf::
95 ~EncryptStreamBuf() {
96  close_read();
97  close_write();
98 
99 #ifdef PHAVE_IOSTREAM
100  delete[] eback();
101 #endif
102 }
103 
104 /**
105  *
106  */
107 void EncryptStreamBuf::
108 open_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  */
190 void EncryptStreamBuf::
191 close_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  */
214 void EncryptStreamBuf::
215 open_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  */
302 void EncryptStreamBuf::
303 close_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  */
333 int EncryptStreamBuf::
334 overflow(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  */
354 int EncryptStreamBuf::
355 sync() {
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  */
375 int EncryptStreamBuf::
376 underflow() {
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  */
407 size_t EncryptStreamBuf::
408 read_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  */
483 void EncryptStreamBuf::
484 write_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.