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 
100 /**
101  *
102  */
103 void EncryptStreamBuf::
104 open_read(std::istream *source, bool owns_source, const std::string &password) {
105  OpenSSL_add_all_algorithms();
106 
107  _source = source;
108  _owns_source = owns_source;
109 
110  if (_read_ctx != nullptr) {
111  EVP_CIPHER_CTX_free(_read_ctx);
112  _read_ctx = nullptr;
113  }
114 
115  // Now read the header information.
116  StreamReader sr(_source, false);
117  int nid = sr.get_uint16();
118  int key_length = sr.get_uint16();
119  int count = sr.get_uint16();
120 
121  const EVP_CIPHER *cipher = EVP_get_cipherbynid(nid);
122 
123  if (cipher == nullptr) {
124  prc_cat.error()
125  << "Unknown encryption algorithm in stream.\n";
126  return;
127  }
128 
129  _algorithm = OBJ_nid2sn(nid);
130  _key_length = key_length * 8;
131  _iteration_count = count * iteration_count_factor;
132 
133  if (prc_cat.is_debug()) {
134  prc_cat.debug()
135  << "Using decryption algorithm " << _algorithm << " with key length "
136  << _key_length << " bits.\n";
137  prc_cat.debug()
138  << "Key is hashed " << _iteration_count << " extra times.\n";
139  }
140 
141  int iv_length = EVP_CIPHER_iv_length(cipher);
142  _read_block_size = EVP_CIPHER_block_size(cipher);
143 
144  unsigned char *iv = (unsigned char *)alloca(iv_length);
145  iv_length = (int)sr.extract_bytes(iv, iv_length);
146 
147  _read_ctx = EVP_CIPHER_CTX_new();
148  nassertv(_read_ctx != nullptr);
149 
150  // Initialize the context
151  int result;
152  result = EVP_DecryptInit(_read_ctx, cipher, nullptr, (unsigned char *)iv);
153  nassertv(result > 0);
154 
155  result = EVP_CIPHER_CTX_set_key_length(_read_ctx, key_length);
156  if (result <= 0) {
157  prc_cat.error()
158  << "Invalid key length " << key_length * 8 << " bits for algorithm "
159  << OBJ_nid2sn(nid) << "\n";
160  EVP_CIPHER_CTX_free(_read_ctx);
161  _read_ctx = nullptr;
162  return;
163  }
164 
165  // Hash the supplied password into a key of the appropriate length.
166  unsigned char *key = (unsigned char *)alloca(key_length);
167  result =
168  PKCS5_PBKDF2_HMAC_SHA1((const char *)password.data(), password.length(),
169  iv, iv_length,
170  count * iteration_count_factor + 1,
171  key_length, key);
172  nassertv(result > 0);
173 
174  // Store the key within the context.
175  result = EVP_DecryptInit(_read_ctx, nullptr, key, nullptr);
176  nassertv(result > 0);
177 
178  _read_overflow_buffer = new unsigned char[_read_block_size];
179  _in_read_overflow_buffer = 0;
180  thread_consider_yield();
181 }
182 
183 /**
184  *
185  */
186 void EncryptStreamBuf::
187 close_read() {
188  if (_read_ctx != nullptr) {
189  EVP_CIPHER_CTX_free(_read_ctx);
190  _read_ctx = nullptr;
191  }
192 
193  if (_read_overflow_buffer != nullptr) {
194  delete[] _read_overflow_buffer;
195  _read_overflow_buffer = nullptr;
196  }
197 
198  if (_source != nullptr) {
199  if (_owns_source) {
200  delete _source;
201  _owns_source = false;
202  }
203  _source = nullptr;
204  }
205 }
206 
207 /**
208  *
209  */
210 void EncryptStreamBuf::
211 open_write(std::ostream *dest, bool owns_dest, const std::string &password) {
212  OpenSSL_add_all_algorithms();
213 
214  close_write();
215  _dest = dest;
216  _owns_dest = owns_dest;
217 
218  const EVP_CIPHER *cipher =
219  EVP_get_cipherbyname(_algorithm.c_str());
220 
221  if (cipher == nullptr) {
222  prc_cat.error()
223  << "Unknown encryption algorithm: " << _algorithm << "\n";
224  return;
225  }
226 
227  int nid = EVP_CIPHER_nid(cipher);
228 
229  int iv_length = EVP_CIPHER_iv_length(cipher);
230  _write_block_size = EVP_CIPHER_block_size(cipher);
231 
232  // Generate a random IV. It doesn't need to be cryptographically secure,
233  // just unique.
234  unsigned char *iv = (unsigned char *)alloca(iv_length);
235  RAND_bytes(iv, iv_length);
236 
237  _write_ctx = EVP_CIPHER_CTX_new();
238  nassertv(_write_ctx != nullptr);
239 
240  int result;
241  result = EVP_EncryptInit(_write_ctx, cipher, nullptr, iv);
242  nassertv(result > 0);
243 
244  // Store the appropriate key length in the context.
245  int key_length = (_key_length + 7) / 8;
246  if (key_length == 0) {
247  key_length = EVP_CIPHER_key_length(cipher);
248  }
249  result = EVP_CIPHER_CTX_set_key_length(_write_ctx, key_length);
250  if (result <= 0) {
251  prc_cat.error()
252  << "Invalid key length " << key_length * 8 << " bits for algorithm "
253  << OBJ_nid2sn(nid) << "\n";
254  EVP_CIPHER_CTX_free(_write_ctx);
255  _write_ctx = nullptr;
256  return;
257  }
258 
259  int count = _iteration_count / iteration_count_factor;
260 
261  if (prc_cat.is_debug()) {
262  prc_cat.debug()
263  << "Using encryption algorithm " << OBJ_nid2sn(nid) << " with key length "
264  << key_length * 8 << " bits.\n";
265  prc_cat.debug()
266  << "Hashing key " << count * iteration_count_factor
267  << " extra times.\n";
268  }
269 
270  // Hash the supplied password into a key of the appropriate length.
271  unsigned char *key = (unsigned char *)alloca(key_length);
272  result =
273  PKCS5_PBKDF2_HMAC_SHA1((const char *)password.data(), password.length(),
274  iv, iv_length, count * iteration_count_factor + 1,
275  key_length, key);
276  nassertv(result > 0);
277 
278  // Store the key in the context.
279  result = EVP_EncryptInit(_write_ctx, nullptr, key, nullptr);
280  nassertv(result > 0);
281 
282  // Now write the header information to the stream.
283  StreamWriter sw(_dest, false);
284  nassertv((uint16_t)nid == nid);
285  sw.add_uint16((uint16_t)nid);
286  nassertv((uint16_t)key_length == key_length);
287  sw.add_uint16((uint16_t)key_length);
288  nassertv((uint16_t)count == count);
289  sw.add_uint16((uint16_t)count);
290  sw.append_data(iv, iv_length);
291 
292  thread_consider_yield();
293 }
294 
295 /**
296  *
297  */
298 void EncryptStreamBuf::
299 close_write() {
300  if (_dest != nullptr) {
301  size_t n = pptr() - pbase();
302  write_chars(pbase(), n);
303  pbump(-(int)n);
304 
305  if (_write_ctx != nullptr) {
306  unsigned char *write_buffer = (unsigned char *)alloca(_write_block_size);
307  int bytes_written = 0;
308  EVP_EncryptFinal(_write_ctx, write_buffer, &bytes_written);
309  thread_consider_yield();
310 
311  _dest->write((const char *)write_buffer, bytes_written);
312 
313  EVP_CIPHER_CTX_free(_write_ctx);
314  _write_ctx = nullptr;
315  }
316 
317  if (_owns_dest) {
318  delete _dest;
319  _owns_dest = false;
320  }
321  _dest = nullptr;
322  }
323 }
324 
325 /**
326  * Called by the system ostream implementation when its internal buffer is
327  * filled, plus one character.
328  */
329 int EncryptStreamBuf::
330 overflow(int ch) {
331  size_t n = pptr() - pbase();
332  if (n != 0) {
333  write_chars(pbase(), n);
334  pbump(-(int)n);
335  }
336 
337  if (ch != EOF) {
338  // Write one more character.
339  char c = (char)ch;
340  write_chars(&c, 1);
341  }
342 
343  return 0;
344 }
345 
346 /**
347  * Called by the system iostream implementation to implement a flush
348  * operation.
349  */
350 int EncryptStreamBuf::
351 sync() {
352  if (_source != nullptr) {
353  size_t n = egptr() - gptr();
354  gbump((int)n);
355  }
356 
357  if (_dest != nullptr) {
358  size_t n = pptr() - pbase();
359  write_chars(pbase(), n);
360  pbump(-(int)n);
361  }
362 
363  _dest->flush();
364  return 0;
365 }
366 
367 /**
368  * Called by the system istream implementation when its internal buffer needs
369  * more characters.
370  */
371 int EncryptStreamBuf::
372 underflow() {
373  // Sometimes underflow() is called even if the buffer is not empty.
374  if (gptr() >= egptr()) {
375  size_t buffer_size = egptr() - eback();
376  gbump(-(int)buffer_size);
377 
378  size_t num_bytes = buffer_size;
379  size_t read_count = read_chars(gptr(), buffer_size);
380 
381  if (read_count != num_bytes) {
382  // Oops, we didn't read what we thought we would.
383  if (read_count == 0) {
384  gbump((int)num_bytes);
385  return EOF;
386  }
387 
388  // Slide what we did read to the top of the buffer.
389  nassertr(read_count < num_bytes, EOF);
390  size_t delta = num_bytes - read_count;
391  memmove(gptr() + delta, gptr(), read_count);
392  gbump((int)delta);
393  }
394  }
395 
396  return (unsigned char)*gptr();
397 }
398 
399 
400 /**
401  * Gets some characters from the source stream.
402  */
403 size_t EncryptStreamBuf::
404 read_chars(char *start, size_t length) {
405  if (length == 0) {
406  return 0;
407  }
408 
409  if (_in_read_overflow_buffer != 0) {
410  // Take from the overflow buffer.
411  length = std::min(length, _in_read_overflow_buffer);
412  memcpy(start, _read_overflow_buffer, length);
413  _in_read_overflow_buffer -= length;
414  memcpy(_read_overflow_buffer + length, _read_overflow_buffer, _in_read_overflow_buffer);
415  return length;
416  }
417 
418  unsigned char *source_buffer = (unsigned char *)alloca(length);
419  size_t max_read_buffer = length + _read_block_size;
420  unsigned char *read_buffer = (unsigned char *)alloca(max_read_buffer);
421 
422  int bytes_read = 0;
423 
424  do {
425  // Get more bytes from the stream.
426  if (_read_ctx == nullptr) {
427  return 0;
428  }
429 
430  _source->read((char *)source_buffer, length);
431  size_t source_length = _source->gcount();
432 
433  bytes_read = 0;
434  int result;
435  if (source_length != 0) {
436  result =
437  EVP_DecryptUpdate(_read_ctx, read_buffer, &bytes_read,
438  source_buffer, source_length);
439  } else {
440  result =
441  EVP_DecryptFinal(_read_ctx, read_buffer, &bytes_read);
442  EVP_CIPHER_CTX_free(_read_ctx);
443  _read_ctx = nullptr;
444  }
445 
446  if (result <= 0) {
447  prc_cat.error()
448  << "Error decrypting stream.\n";
449  if (_read_ctx != nullptr) {
450  EVP_CIPHER_CTX_free(_read_ctx);
451  _read_ctx = nullptr;
452  }
453  }
454  thread_consider_yield();
455 
456  } while (bytes_read == 0);
457 
458  // Now store the read bytes in the output stream.
459  if ((size_t)bytes_read <= length) {
460  // No overflow.
461  memcpy(start, read_buffer, bytes_read);
462  return bytes_read;
463 
464  } else {
465  // We have to save some of the returned bytes in the overflow buffer.
466  _in_read_overflow_buffer = bytes_read - length;
467  nassertr(_in_read_overflow_buffer <= _read_block_size, 0);
468 
469  memcpy(_read_overflow_buffer, read_buffer + length,
470  _in_read_overflow_buffer);
471  memcpy(start, read_buffer, length);
472  return length;
473  }
474 }
475 
476 /**
477  * Sends some characters to the dest stream.
478  */
479 void EncryptStreamBuf::
480 write_chars(const char *start, size_t length) {
481  if (_write_ctx != nullptr && length != 0) {
482  size_t max_write_buffer = length + _write_block_size;
483  unsigned char *write_buffer = (unsigned char *)alloca(max_write_buffer);
484 
485  int bytes_written = 0;
486  int result =
487  EVP_EncryptUpdate(_write_ctx, write_buffer, &bytes_written,
488  (unsigned char *)start, length);
489  if (result <= 0) {
490  prc_cat.error()
491  << "Error encrypting stream.\n";
492  }
493  thread_consider_yield();
494  _dest->write((const char *)write_buffer, bytes_written);
495  }
496 }
497 
498 #endif // HAVE_OPENSSL
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.
This is a convenience class to specialize ConfigVariable as a string type.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is a convenience class to specialize ConfigVariable as an integer type.
A class to read sequential binary data directly from an istream.
Definition: streamReader.h:28
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.