PageRenderTime 287ms CodeModel.GetById 100ms app.highlight 121ms RepoModel.GetById 61ms app.codeStats 0ms

/mordor/tests/crypto.cpp

http://github.com/mozy/mordor
C++ | 407 lines | 318 code | 51 blank | 38 comment | 19 complexity | dbf15bf54e191924fb97ca55549034a6 MD5 | raw file
  1// Copyright (c) 2011 - Mozy, Inc.
  2
  3#include <openssl/evp.h>
  4#include "mordor/streams/crypto.h"
  5#include "mordor/streams/random.h"
  6#include "mordor/streams/memory.h"
  7#include "mordor/streams/limited.h"
  8#include "mordor/streams/hash.h"
  9#include "mordor/streams/null.h"
 10#include "mordor/streams/transfer.h"
 11#include "mordor/streams/ssl.h"
 12#include "mordor/test/test.h"
 13#include "mordor/streams/singleplex.h"
 14#include "mordor/util.h"
 15
 16using namespace Mordor;
 17using namespace Mordor::Test;
 18
 19// ciphertext generated by
 20// openssl enc -e -aes-256-cbc -in test.txt -out test.enc
 21//    -K 4938f9c3774681b6d3fe17c9e99e4c62b0603262bbd8afafa8a14c74e2056526
 22//    -iv faeebfd03bf8e8515c5c5c19af842b75
 23
 24static const unsigned char plaintext[269] = {
 250x4d, 0x61, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73,
 260x74, 0x69, 0x6e, 0x67, 0x75, 0x69, 0x73, 0x68, 0x65, 0x64,
 270x2c, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
 280x20, 0x62, 0x79, 0x20, 0x68, 0x69, 0x73, 0x20, 0x72, 0x65,
 290x61, 0x73, 0x6f, 0x6e, 0x2c, 0x20, 0x62, 0x75, 0x74, 0x20,
 300x62, 0x79, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x69,
 310x6e, 0x67, 0x75, 0x6c, 0x61, 0x72, 0x20, 0x70, 0x61, 0x73,
 320x73, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20,
 330x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x61, 0x6e, 0x69, 0x6d,
 340x61, 0x6c, 0x73, 0x2c, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68,
 350x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x6c, 0x75, 0x73, 0x74,
 360x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x69,
 370x6e, 0x64, 0x2c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x62,
 380x79, 0x20, 0x61, 0x20, 0x70, 0x65, 0x72, 0x73, 0x65, 0x76,
 390x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x6f, 0x66, 0x20,
 400x64, 0x65, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x20, 0x69, 0x6e,
 410x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x69,
 420x6e, 0x75, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69,
 430x6e, 0x64, 0x65, 0x66, 0x61, 0x74, 0x69, 0x67, 0x61, 0x62,
 440x6c, 0x65, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
 450x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x6b, 0x6e, 0x6f,
 460x77, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x2c, 0x20, 0x65, 0x78,
 470x63, 0x65, 0x65, 0x64, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20,
 480x73, 0x68, 0x6f, 0x72, 0x74, 0x20, 0x76, 0x65, 0x68, 0x65,
 490x6d, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x61,
 500x6e, 0x79, 0x20, 0x63, 0x61, 0x72, 0x6e, 0x61, 0x6c, 0x20,
 510x70, 0x6c, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x2e
 52};
 53
 54static const unsigned char ciphertext[272] = {
 550xe0, 0x1b, 0x5b, 0x95, 0xc8, 0x47, 0xa1, 0xa3, 0xb8, 0xd6,
 560xf9, 0x8c, 0xb8, 0x16, 0x3a, 0xbb, 0x98, 0x9d, 0x71, 0xb4,
 570x88, 0xd8, 0xa7, 0xe1, 0x67, 0x1a, 0xbc, 0xde, 0x10, 0xfb,
 580x1b, 0x9b, 0x3c, 0xd4, 0x5c, 0xb0, 0xcd, 0x2e, 0x00, 0x46,
 590x74, 0xd7, 0x24, 0x85, 0x17, 0xf1, 0x0e, 0x08, 0xc2, 0x69,
 600x2d, 0x40, 0x77, 0xc9, 0x18, 0x63, 0x22, 0x0d, 0x1f, 0x24,
 610x20, 0xfe, 0xb2, 0x23, 0xf2, 0xb9, 0x2a, 0x52, 0x68, 0x00,
 620x59, 0x0a, 0x23, 0x6b, 0x4c, 0xed, 0x0e, 0x9b, 0x2f, 0x1b,
 630x94, 0xfd, 0x28, 0x00, 0x28, 0xb7, 0x63, 0x72, 0x1b, 0x96,
 640x25, 0x56, 0x33, 0x10, 0x59, 0x1f, 0xa7, 0x76, 0x32, 0x39,
 650xac, 0x4d, 0xb7, 0xff, 0x7a, 0x1f, 0xae, 0xcd, 0xa8, 0x70,
 660x14, 0x81, 0xb2, 0xce, 0xe2, 0x8c, 0xdb, 0x2f, 0xf3, 0x5e,
 670x4b, 0xde, 0x54, 0xbd, 0xde, 0x9a, 0xe7, 0xa5, 0xcb, 0xbe,
 680xf6, 0xef, 0x50, 0xcb, 0x1a, 0xb6, 0x00, 0xe5, 0x82, 0x77,
 690xb9, 0x81, 0xbd, 0x35, 0x84, 0xe1, 0x9f, 0x31, 0xcc, 0xd7,
 700x50, 0xa4, 0xc1, 0xce, 0x30, 0x9f, 0x78, 0x14, 0x92, 0x9f,
 710x80, 0xb0, 0x21, 0xac, 0x9a, 0x2e, 0x71, 0x41, 0x61, 0xdd,
 720xf0, 0xa6, 0xa0, 0x27, 0x12, 0x0b, 0x03, 0x90, 0x7d, 0xf6,
 730x19, 0xce, 0x5e, 0x57, 0xe6, 0x2d, 0x31, 0xe1, 0xae, 0xba,
 740x8c, 0x0c, 0x9e, 0x77, 0xfb, 0x0d, 0x4f, 0xe2, 0x68, 0x8d,
 750x24, 0xc0, 0x47, 0x2d, 0x5e, 0xe2, 0x7c, 0x44, 0x71, 0x66,
 760x35, 0x8f, 0x29, 0x38, 0xcd, 0x14, 0x53, 0xe2, 0x52, 0x9f,
 770x4d, 0x33, 0x12, 0xfd, 0xf8, 0xbf, 0x3d, 0x3b, 0xf3, 0x9f,
 780xed, 0x51, 0x6b, 0x34, 0x56, 0xcc, 0x1c, 0x00, 0x6b, 0x1b,
 790xa5, 0xb3, 0xd0, 0x7b, 0x38, 0xe3, 0xca, 0x18, 0x26, 0xbf,
 800x8b, 0x7b, 0x41, 0x22, 0x05, 0x2e, 0x08, 0xf8, 0xbf, 0x3e,
 810x31, 0x46, 0x32, 0xa9, 0x1d, 0x90, 0xb0, 0x52, 0xf2, 0x25,
 820xb9, 0xe7
 83};
 84
 85static const unsigned char key[32] = {
 860x49, 0x38, 0xf9, 0xc3, 0x77, 0x46, 0x81, 0xb6, 0xd3, 0xfe,
 870x17, 0xc9, 0xe9, 0x9e, 0x4c, 0x62, 0xb0, 0x60, 0x32, 0x62,
 880xbb, 0xd8, 0xaf, 0xaf, 0xa8, 0xa1, 0x4c, 0x74, 0xe2, 0x05,
 890x65, 0x26
 90};
 91
 92static std::string keyString()
 93{
 94    std::string ret;
 95    ret.assign((const char *)key, sizeof(key));
 96    return ret;
 97}
 98
 99static const unsigned char iv[16] = {
1000xfa, 0xee, 0xbf, 0xd0, 0x3b, 0xf8, 0xe8, 0x51, 0x5c, 0x5c,
1010x5c, 0x19, 0xaf, 0x84, 0x2b, 0x75
102};
103
104static std::string ivString()
105{
106    std::string ret;
107    ret.assign((const char *)iv, sizeof(iv));
108    return ret;
109}
110
111MORDOR_UNITTEST(CryptoStream, encryptWrite)
112{
113    Buffer plain;
114    plain.copyIn(plaintext, sizeof(plaintext));
115    Buffer cipher;
116    cipher.copyIn(ciphertext, sizeof(ciphertext));
117
118    MemoryStream src(plain);
119    MemoryStream::ptr sink(new MemoryStream);
120    CryptoStream cryptor(sink, EVP_aes_256_cbc(), keyString(), ivString(), CryptoStream::WRITE);
121    transferStream(src, cryptor);
122    cryptor.close();
123    const Buffer &test = sink->buffer();
124    MORDOR_TEST_ASSERT(test == cipher);
125}
126
127MORDOR_UNITTEST(CryptoStream, encryptRead)
128{
129    Buffer plain;
130    plain.copyIn(plaintext, sizeof(plaintext));
131    Buffer cipher;
132    cipher.copyIn(ciphertext, sizeof(ciphertext));
133
134    MemoryStream::ptr src(new MemoryStream(plain));
135    CryptoStream cryptor(src, EVP_aes_256_cbc(), keyString(), ivString(), CryptoStream::READ, CryptoStream::ENCRYPT);
136    MemoryStream sink;
137    transferStream(cryptor, sink);
138    const Buffer &test = sink.buffer();
139    MORDOR_TEST_ASSERT(test == cipher);
140}
141
142MORDOR_UNITTEST(CryptoStream, decryptWrite)
143{
144    Buffer plain;
145    plain.copyIn(plaintext, sizeof(plaintext));
146    Buffer cipher;
147    cipher.copyIn(ciphertext, sizeof(ciphertext));
148
149    MemoryStream src(cipher);
150    MemoryStream::ptr sink(new MemoryStream);
151    CryptoStream decryptor(sink, EVP_aes_256_cbc(), keyString(), ivString(), CryptoStream::WRITE, CryptoStream::DECRYPT);
152    transferStream(src, decryptor);
153    decryptor.close();
154
155    const Buffer &test = sink->buffer();
156    MORDOR_TEST_ASSERT(test == plain);
157}
158
159MORDOR_UNITTEST(CryptoStream, decryptRead)
160{
161    Buffer plain;
162    plain.copyIn(plaintext, sizeof(plaintext));
163    Buffer cipher;
164    cipher.copyIn(ciphertext, sizeof(ciphertext));
165
166    MemoryStream::ptr src(new MemoryStream(cipher));
167    CryptoStream decryptor(src, EVP_aes_256_cbc(), keyString(), ivString(), CryptoStream::READ);
168    MemoryStream sink;
169    transferStream(decryptor, sink);
170    const Buffer &test = sink.buffer();
171    MORDOR_TEST_ASSERT(test == plain);
172}
173
174MORDOR_UNITTEST(CryptoStream, badKeyIvSize)
175{
176    MemoryStream::ptr parent(new MemoryStream);
177    CryptoStream::ptr crypto;
178
179    MORDOR_TEST_ASSERT_EXCEPTION(crypto.reset(new CryptoStream(
180        parent, EVP_aes_256_cbc(), keyString() + "bogus", ivString(),
181        CryptoStream::WRITE)), OpenSSLException);
182    MORDOR_TEST_ASSERT_EXCEPTION(crypto.reset(new CryptoStream(
183        parent, EVP_aes_256_cbc(), keyString(), ivString() + "bogus",
184        CryptoStream::WRITE)), OpenSSLException);
185    MORDOR_TEST_ASSERT_EXCEPTION(crypto.reset(new CryptoStream(
186        parent, EVP_aes_256_ecb(), keyString(), ivString() + "bogus",
187        CryptoStream::WRITE)), OpenSSLException);
188}
189
190MORDOR_UNITTEST(CryptoStream, badPadding)
191{
192    Buffer cipher;
193    cipher.copyIn(ciphertext, sizeof(ciphertext) - 16);
194
195    MemoryStream::ptr src(new MemoryStream(cipher));
196    CryptoStream decryptor(src, EVP_aes_256_cbc(), keyString(), ivString(), CryptoStream::READ, CryptoStream::DECRYPT);
197    MemoryStream sink;
198    MORDOR_TEST_ASSERT_EXCEPTION(transferStream(decryptor, sink), OpenSSLException);
199}
200
201// test the stream in all four modes of operation
202// hashDecR <- decR <- hashEncR <- encR <- hashOrig <- random
203// encW -> hashEncW -> decW -> hashDecW -> null
204void TestStreaming(long long test_bytes, const std::string &iv, size_t transferBlock = 0)
205{
206    // read side
207    Stream::ptr random(new RandomStream);
208    Stream::ptr source(new LimitedStream(random, test_bytes));
209    HashStream::ptr hashOrig(new MD5Stream(source));
210    Stream::ptr encR(
211        new CryptoStream(hashOrig, EVP_aes_256_cbc(), keyString(), iv, CryptoStream::READ, CryptoStream::ENCRYPT));
212    HashStream::ptr hashEncR(new MD5Stream(encR));
213    Stream::ptr decR(
214        new CryptoStream(hashEncR, EVP_aes_256_cbc(), keyString(), iv, CryptoStream::READ, CryptoStream::DECRYPT));
215    HashStream::ptr hashDecR(new MD5Stream(decR));
216
217    // write side
218    HashStream::ptr hashDecW(new MD5Stream(NullStream::get_ptr()));
219    Stream::ptr decW(
220        new CryptoStream(hashDecW, EVP_aes_256_cbc(), keyString(), iv, CryptoStream::WRITE, CryptoStream::DECRYPT));
221    HashStream::ptr hashEncW(new MD5Stream(decW));
222    Stream::ptr encW(
223        new CryptoStream(hashEncW, EVP_aes_256_cbc(), keyString(), iv, CryptoStream::WRITE, CryptoStream::ENCRYPT));
224
225    // do it
226    if (transferBlock == 0) {
227        transferStream(hashDecR, encW);
228    } else {
229        std::vector<unsigned char> buf(transferBlock);
230        size_t bytes;
231        while(0 != (bytes = hashDecR->read(&buf[0], buf.size()))) {
232            encW->write(&buf[0], bytes);
233        }
234    }
235    encW->close();
236    decW->close();
237
238    // make sure the decrypted data matches the original, and does _not_ match the encrypted
239    // (because otherwise any non-mutating filter stream would pass this test...)
240    MORDOR_TEST_ASSERT(hashOrig->hash() != hashEncR->hash());
241    MORDOR_TEST_ASSERT(hashOrig->hash() == hashDecR->hash());
242    MORDOR_TEST_ASSERT(hashOrig->hash() != hashEncW->hash());
243    MORDOR_TEST_ASSERT(hashOrig->hash() == hashDecW->hash());
244}
245
246MORDOR_UNITTEST(CryptoStream, streaming)
247{
248    static const long long sizes[] = { 0, 1, 15, 16, 17, 131071, 131072, 131073 };
249    size_t nsizes = sizeof(sizes) / sizeof(sizes[0]);
250
251    for(size_t i = 0; i < nsizes; ++i) {
252        TestStreaming(sizes[i], ivString());
253        TestStreaming(sizes[i], CryptoStream::RANDOM_IV);
254    }
255}
256
257MORDOR_UNITTEST(CryptoStream, oddBufferSizes)
258{
259    static const long long file_sizes[] = { 0, 100, 4096 };
260    size_t nfilesizes = sizeof(file_sizes) / sizeof(file_sizes[0]);
261
262    static const size_t buffer_sizes[] = { 7, 16, 1000 };
263    size_t nbuffersizes = sizeof(buffer_sizes) / sizeof(buffer_sizes[0]);
264
265    for(size_t i = 0; i < nfilesizes; ++i) {
266        for(size_t j = 0; j < nbuffersizes; ++j) {
267            TestStreaming(file_sizes[i], ivString(), buffer_sizes[j]);
268            TestStreaming(file_sizes[i], CryptoStream::RANDOM_IV, buffer_sizes[j]);
269        }
270    }
271}
272
273MORDOR_UNITTEST(CryptoStream, inferDirection)
274{
275    MemoryStream::ptr parent(new MemoryStream);
276    SingleplexStream::ptr sr(new SingleplexStream(parent, SingleplexStream::READ, false));
277    SingleplexStream::ptr sw(new SingleplexStream(parent, SingleplexStream::WRITE, false));
278
279    CryptoStream csr(sr, EVP_aes_256_cbc(), keyString(), ivString());
280    MORDOR_TEST_ASSERT(csr.supportsRead());
281    MORDOR_TEST_ASSERT(!csr.supportsWrite());
282
283    CryptoStream csw(sw, EVP_aes_256_cbc(), keyString(), ivString());
284    MORDOR_TEST_ASSERT(!csw.supportsRead());
285    MORDOR_TEST_ASSERT(csw.supportsWrite());
286
287    MORDOR_TEST_ASSERT_ASSERTED(CryptoStream(parent, EVP_aes_256_cbc(), keyString(), ivString()));
288}
289
290MORDOR_UNITTEST(CryptoStream, encryptRead_randomIV)
291{
292    Buffer plain;
293    plain.copyIn(plaintext, sizeof(plaintext));
294    MemoryStream::ptr src(new MemoryStream(plain));
295
296    // encrypt via read
297    CryptoStream enc(src, EVP_aes_256_cbc(), keyString(),
298        CryptoStream::RANDOM_IV, CryptoStream::READ, CryptoStream::ENCRYPT);
299    MemoryStream::ptr intermediate(new MemoryStream);
300    transferStream(enc, intermediate);
301    const Buffer &cipher = intermediate->buffer();
302    // the destination buffer should be the size of the ciphertext plus the size of the IV
303    // (we can't compare its contents to ciphertext because we used a random IV)
304    MORDOR_TEST_ASSERT_EQUAL(cipher.readAvailable(), sizeof(ciphertext) + 16);
305
306    // decrypt via write
307    intermediate->seek(0);
308    MemoryStream::ptr dst(new MemoryStream);
309    CryptoStream dec(dst, EVP_aes_256_cbc(), keyString(),
310        CryptoStream::RANDOM_IV, CryptoStream::WRITE, CryptoStream::DECRYPT);
311    transferStream(intermediate, dec);
312    dec.close();
313    const Buffer &decrypted = dst->buffer();
314
315    // make sure we got the original plaintext out
316    MORDOR_TEST_ASSERT(plain == decrypted);
317}
318
319MORDOR_UNITTEST(CryptoStream, encryptWrite_randomIV)
320{
321    Buffer plain;
322    plain.copyIn(plaintext, sizeof(plaintext));
323    MemoryStream::ptr src(new MemoryStream(plain));
324
325    // encrypt via write (just for fun, let CryptoStream infer defaults)
326    MemoryStream::ptr intermediate(new MemoryStream);
327    SingleplexStream::ptr spw(new SingleplexStream(intermediate, SingleplexStream::WRITE));
328    CryptoStream enc(spw, EVP_aes_256_cbc(), keyString());
329    transferStream(src, enc);
330    enc.close();
331    const Buffer &cipher = intermediate->buffer();
332    // the destination buffer should be the size of the ciphertext plus the size of the IV
333    // (we can't compare its contents to ciphertext because we used a random IV)
334    MORDOR_TEST_ASSERT_EQUAL(cipher.readAvailable(), sizeof(ciphertext) + 16);
335
336    // now decrypt via read (again, using a SingleplexStream so CryptoStream can infer read)
337    MemoryStream::ptr dst(new MemoryStream);
338    intermediate->seek(0);
339    SingleplexStream::ptr spr(new SingleplexStream(intermediate, SingleplexStream::READ));
340    CryptoStream dec(spr, EVP_aes_256_cbc(), keyString());
341    transferStream(dec, dst);
342    const Buffer &decrypted = dst->buffer();
343
344    // make sure we got the original plaintext out
345    MORDOR_TEST_ASSERT(plain == decrypted);
346}
347
348MORDOR_UNITTEST(CryptoStream, encryptRead_noIV)
349{
350    Buffer plain;
351    plain.copyIn(plaintext, sizeof(plaintext));
352    MemoryStream::ptr src(new MemoryStream(plain));
353
354    // encrypt via read
355    CryptoStream enc(src, EVP_aes_256_ecb(), keyString(),
356        std::string(), CryptoStream::READ, CryptoStream::ENCRYPT);
357    MemoryStream::ptr intermediate(new MemoryStream);
358    transferStream(enc, intermediate);
359    const Buffer &cipher = intermediate->buffer();
360    // the destination buffer should be the size as the reference ciphertext
361    // (the content is, of course, different, because we're in ECB mode)
362    MORDOR_TEST_ASSERT_EQUAL(cipher.readAvailable(), sizeof(ciphertext));
363
364    // decrypt via write
365    intermediate->seek(0);
366    MemoryStream::ptr dst(new MemoryStream);
367    CryptoStream dec(dst, EVP_aes_256_ecb(), keyString(),
368        std::string(), CryptoStream::WRITE, CryptoStream::DECRYPT);
369    transferStream(intermediate, dec);
370    dec.close();
371    const Buffer &decrypted = dst->buffer();
372
373    // make sure we got the original plaintext out
374    MORDOR_TEST_ASSERT(plain == decrypted);
375}
376
377MORDOR_UNITTEST(CryptoStream, encryptWrite_noIV)
378{
379    Buffer plain;
380    plain.copyIn(plaintext, sizeof(plaintext));
381    MemoryStream::ptr src(new MemoryStream(plain));
382
383    // encrypt via write (just for fun, let CryptoStream infer defaults)
384    MemoryStream::ptr intermediate(new MemoryStream);
385    SingleplexStream::ptr spw(new SingleplexStream(intermediate, SingleplexStream::WRITE));
386    CryptoStream enc(spw, EVP_aes_256_ecb(), keyString());
387    transferStream(src, enc);
388    enc.close();
389    //const Buffer &cipher = intermediate->buffer();
390    // the destination buffer should be the size as the reference ciphertext
391    // (the content is, of course, different, because we're in ECB mode)
392    // EDIT: in this mode, we're generating an IV of the size OpenSSL tells us
393    // and some versions of OpenSSL give a non-zero IV size in ECB mode
394    // so don't actually test this.
395    //MORDOR_TEST_ASSERT_EQUAL(cipher.readAvailable(), sizeof(ciphertext));
396
397    // now decrypt via read (again, using a SingleplexStream so CryptoStream can infer read)
398    MemoryStream::ptr dst(new MemoryStream);
399    intermediate->seek(0);
400    SingleplexStream::ptr spr(new SingleplexStream(intermediate, SingleplexStream::READ));
401    CryptoStream dec(spr, EVP_aes_256_ecb(), keyString());
402    transferStream(dec, dst);
403    const Buffer &decrypted = dst->buffer();
404
405    // make sure we got the original plaintext out
406    MORDOR_TEST_ASSERT(plain == decrypted);
407}