PageRenderTime 40ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/mordor/streams/crypto.cpp

http://github.com/mozy/mordor
C++ | 252 lines | 191 code | 30 blank | 31 comment | 63 complexity | 53b53e782bda56f6d7e3b6c78a6871f6 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. #include "crypto.h"
  2. #include "ssl.h" // for OpenSSLException
  3. #include "mordor/assert.h"
  4. #include "mordor/streams/random.h"
  5. namespace Mordor {
  6. #define SSL_CHECK(x) if (!(x)) MORDOR_THROW_EXCEPTION(OpenSSLException()); else (void)0
  7. const std::string CryptoStream::RANDOM_IV;
  8. CryptoStream::CryptoStream(Stream::ptr p, const EVP_CIPHER *cipher, const std::string &key,
  9. const std::string &iv, Direction dir, Operation op, bool own) :
  10. MutatingFilterStream(p, own),
  11. m_iv(iv),
  12. m_dir(dir),
  13. m_op(op),
  14. m_eof(false),
  15. m_iv_to_extract(0)
  16. {
  17. if (m_dir == INFER) {
  18. MORDOR_ASSERT( parent()->supportsRead() ^ parent()->supportsWrite() );
  19. m_dir = parent()->supportsWrite() ? WRITE : READ;
  20. }
  21. if (m_op == AUTO) {
  22. m_op = (m_dir == WRITE) ? ENCRYPT : DECRYPT;
  23. }
  24. EVP_CIPHER_CTX_init(&m_ctx);
  25. try
  26. {
  27. // do preliminary initialization (everything except the IV)
  28. SSL_CHECK( EVP_CipherInit_ex(&m_ctx, cipher, NULL, NULL, NULL, (m_op == ENCRYPT) ? 1 : 0) );
  29. SSL_CHECK( EVP_CIPHER_CTX_set_key_length(&m_ctx, static_cast<int>(key.size())) );
  30. SSL_CHECK( EVP_CipherInit_ex(&m_ctx, NULL, NULL, (const unsigned char *)key.c_str(), NULL, -1) );
  31. m_blocksize = EVP_CIPHER_CTX_block_size(&m_ctx);
  32. // generate an IV, if necessary
  33. size_t iv_len = static_cast<size_t>(EVP_CIPHER_CTX_iv_length(&m_ctx));
  34. if (&iv == &RANDOM_IV) {
  35. if (m_op == ENCRYPT) {
  36. RandomStream random;
  37. random.read(m_buf, iv_len);
  38. MORDOR_ASSERT(m_buf.readAvailable() == iv_len);
  39. m_iv.assign((const char *)m_buf.readBuffer(iv_len, true).iov_base, iv_len);
  40. init_iv();
  41. // leave the IV in m_buf;
  42. // read() will return it ahead of the ciphertext;
  43. // write() will write it to the parent stream on its first call
  44. } else {
  45. // tell read() and write() how much data should be extracted for the iv
  46. m_iv_to_extract = iv_len;
  47. }
  48. } else {
  49. init_iv();
  50. }
  51. }
  52. catch(...)
  53. {
  54. EVP_CIPHER_CTX_cleanup(&m_ctx);
  55. throw;
  56. }
  57. }
  58. void CryptoStream::init_iv()
  59. {
  60. // note: I used to verify that, if m_iv.empty(), EVP_CIPHER_CTX_iv_length returns 0
  61. // however, some older versions of OpenSSL return a nonzero IV length for ECB mode
  62. if (!m_iv.empty()) {
  63. // make sure the size is correct
  64. if (static_cast<size_t>(EVP_CIPHER_CTX_iv_length(&m_ctx)) != m_iv.size())
  65. MORDOR_THROW_EXCEPTION(OpenSSLException("incorrect iv length"));
  66. // feed openssl the IV
  67. SSL_CHECK( EVP_CipherInit_ex(&m_ctx, NULL, NULL, NULL, (const unsigned char *)m_iv.c_str(), -1) );
  68. // clear data we don't need anymore
  69. m_iv.clear();
  70. }
  71. }
  72. CryptoStream::~CryptoStream()
  73. {
  74. EVP_CIPHER_CTX_cleanup(&m_ctx);
  75. }
  76. void CryptoStream::close(CloseType type)
  77. {
  78. if (!m_eof && (type == Stream::WRITE || type == BOTH)) {
  79. finalize();
  80. m_eof = true;
  81. }
  82. if (ownsParent())
  83. parent()->close(type);
  84. }
  85. size_t CryptoStream::read(Buffer &out, size_t len)
  86. {
  87. MORDOR_ASSERT( m_dir == READ );
  88. size_t copied = 0;
  89. for(;;)
  90. {
  91. // copy out [de]crypted data
  92. size_t to_copy = (std::min)(m_buf.readAvailable(), len - copied);
  93. if (to_copy > 0) {
  94. out.copyIn(m_buf, to_copy);
  95. m_buf.consume(to_copy);
  96. copied += to_copy;
  97. }
  98. if (m_eof || copied == len)
  99. return copied;
  100. MORDOR_ASSERT( m_buf.readAvailable() == 0 );
  101. // m_tmp has no content between calls; it's a member variable
  102. // solely to reduce allocations/deallocations
  103. MORDOR_ASSERT( m_tmp.readAvailable() == 0 );
  104. size_t to_read = len - copied;
  105. // make sure to read enough that we can make progress
  106. to_read = (std::max)(to_read, 2 * m_blocksize + m_iv_to_extract);
  107. while(to_read > 0) {
  108. size_t read = parent()->read(m_tmp, to_read);
  109. if (read == 0)
  110. break;
  111. to_read -= read;
  112. }
  113. // initialize the IV, if we haven't done that yet
  114. if (m_iv_to_extract > 0) {
  115. if (m_tmp.readAvailable() < m_iv_to_extract)
  116. MORDOR_THROW_EXCEPTION(OpenSSLException("missing iv"));
  117. m_iv.assign( (char *)m_tmp.readBuffer(m_iv_to_extract, true).iov_base,
  118. m_iv_to_extract );
  119. m_tmp.consume(m_iv_to_extract);
  120. m_iv_to_extract = 0;
  121. init_iv();
  122. }
  123. // encrypt/decrypt some data
  124. cipher(m_tmp, m_buf, m_tmp.readAvailable());
  125. m_tmp.consume(m_tmp.readAvailable());
  126. // check for EOF
  127. if (m_buf.readAvailable() == 0) {
  128. final(m_buf);
  129. m_eof = true;
  130. }
  131. }
  132. }
  133. // ciphers len bytes from src, skipping over skip bytes at the front of the buffer
  134. size_t CryptoStream::cipher(const Buffer &src, Buffer &dst, size_t len, size_t skip)
  135. {
  136. MORDOR_ASSERT(skip <= len);
  137. len -= skip;
  138. if (len == 0)
  139. return 0;
  140. int outlen = static_cast<int>(len) + m_blocksize;
  141. SSL_CHECK(EVP_CipherUpdate(&m_ctx,
  142. (unsigned char *)dst.writeBuffer(len + m_blocksize, true).iov_base, &outlen,
  143. (unsigned char *)src.readBuffer(len + skip, true).iov_base + skip, static_cast<int>(len)));
  144. dst.produce(outlen);
  145. return outlen;
  146. }
  147. // finalizes the cipher and writes the last few bytes to dst
  148. size_t CryptoStream::final(Buffer &dst)
  149. {
  150. int outlen = m_blocksize;
  151. SSL_CHECK(EVP_CipherFinal(&m_ctx,
  152. (unsigned char *)dst.writeBuffer(m_blocksize, true).iov_base, &outlen));
  153. dst.produce(outlen);
  154. return outlen;
  155. }
  156. // writes and consumes entire buffer
  157. void CryptoStream::write_buffer(Buffer &buffer)
  158. {
  159. size_t to_write = buffer.readAvailable();
  160. while(to_write > 0) {
  161. size_t written = parent()->write(buffer, to_write);
  162. buffer.consume(written);
  163. to_write -= written;
  164. }
  165. }
  166. size_t CryptoStream::write(const Buffer &buffer, size_t len)
  167. {
  168. MORDOR_ASSERT( m_dir == WRITE );
  169. size_t iv_skip = 0;
  170. if (m_iv_to_extract > 0) {
  171. // seed the IV, if we haven't done so yet
  172. MORDOR_ASSERT(m_op == DECRYPT);
  173. iv_skip = (std::min)(m_iv_to_extract, len);
  174. m_buf.copyIn(buffer, iv_skip);
  175. m_iv_to_extract -= iv_skip;
  176. if (m_iv_to_extract > 0)
  177. return len; // don't have the whole IV yet
  178. // now we have an IV, so we can initialize the cipher
  179. size_t iv_len = static_cast<size_t>(EVP_CIPHER_CTX_iv_length(&m_ctx));
  180. MORDOR_ASSERT(m_buf.readAvailable() == iv_len);
  181. m_iv.assign((char *)m_buf.readBuffer(iv_len, true).iov_base, iv_len);
  182. m_buf.clear();
  183. init_iv();
  184. if (iv_skip == len)
  185. return len; // have the IV but no payload yet
  186. } else if (m_buf.readAvailable() > 0) {
  187. // write the IV, if we haven't done so yet
  188. MORDOR_ASSERT(m_op == ENCRYPT);
  189. write_buffer(m_buf);
  190. MORDOR_ASSERT( m_buf.readAvailable() == 0 );
  191. }
  192. // now cipher and write the payload
  193. MORDOR_ASSERT( m_tmp.readAvailable() == 0 );
  194. cipher(buffer, m_tmp, len, iv_skip);
  195. write_buffer(m_tmp);
  196. MORDOR_ASSERT( m_tmp.readAvailable() == 0 );
  197. return len;
  198. }
  199. void CryptoStream::finalize()
  200. {
  201. if (!m_eof && m_dir == WRITE) {
  202. // if we're encrypting, and we haven't written an IV
  203. // (i.e., because the user never called write(),
  204. // because the file is empty) then do that now
  205. if (m_buf.readAvailable() > 0) {
  206. MORDOR_ASSERT(m_op == ENCRYPT);
  207. write_buffer(m_buf);
  208. MORDOR_ASSERT( m_buf.readAvailable() == 0 );
  209. }
  210. // finalize the cipher (if we actually finished initializing it;
  211. // if the caller never wrote the ciphertext with the leading IV,
  212. // then we never even started)
  213. if (m_iv_to_extract == 0) {
  214. MORDOR_ASSERT( m_tmp.readAvailable() == 0 );
  215. final(m_tmp);
  216. write_buffer(m_tmp);
  217. MORDOR_ASSERT( m_tmp.readAvailable() == 0 );
  218. }
  219. m_eof = true;
  220. }
  221. }
  222. }