PageRenderTime 452ms CodeModel.GetById 133ms app.highlight 176ms RepoModel.GetById 79ms app.codeStats 1ms

/mordor/streams/crypto.cpp

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