/mordor/tests/crypto.cpp
C++ | 407 lines | 318 code | 51 blank | 38 comment | 19 complexity | dbf15bf54e191924fb97ca55549034a6 MD5 | raw file
Possible License(s): BSD-3-Clause
- // Copyright (c) 2011 - Mozy, Inc.
- #include <openssl/evp.h>
- #include "mordor/streams/crypto.h"
- #include "mordor/streams/random.h"
- #include "mordor/streams/memory.h"
- #include "mordor/streams/limited.h"
- #include "mordor/streams/hash.h"
- #include "mordor/streams/null.h"
- #include "mordor/streams/transfer.h"
- #include "mordor/streams/ssl.h"
- #include "mordor/test/test.h"
- #include "mordor/streams/singleplex.h"
- #include "mordor/util.h"
- using namespace Mordor;
- using namespace Mordor::Test;
- // ciphertext generated by
- // openssl enc -e -aes-256-cbc -in test.txt -out test.enc
- // -K 4938f9c3774681b6d3fe17c9e99e4c62b0603262bbd8afafa8a14c74e2056526
- // -iv faeebfd03bf8e8515c5c5c19af842b75
- static const unsigned char plaintext[269] = {
- 0x4d, 0x61, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73,
- 0x74, 0x69, 0x6e, 0x67, 0x75, 0x69, 0x73, 0x68, 0x65, 0x64,
- 0x2c, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
- 0x20, 0x62, 0x79, 0x20, 0x68, 0x69, 0x73, 0x20, 0x72, 0x65,
- 0x61, 0x73, 0x6f, 0x6e, 0x2c, 0x20, 0x62, 0x75, 0x74, 0x20,
- 0x62, 0x79, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x69,
- 0x6e, 0x67, 0x75, 0x6c, 0x61, 0x72, 0x20, 0x70, 0x61, 0x73,
- 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20,
- 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x61, 0x6e, 0x69, 0x6d,
- 0x61, 0x6c, 0x73, 0x2c, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68,
- 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x6c, 0x75, 0x73, 0x74,
- 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x69,
- 0x6e, 0x64, 0x2c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x62,
- 0x79, 0x20, 0x61, 0x20, 0x70, 0x65, 0x72, 0x73, 0x65, 0x76,
- 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x6f, 0x66, 0x20,
- 0x64, 0x65, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x20, 0x69, 0x6e,
- 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x69,
- 0x6e, 0x75, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69,
- 0x6e, 0x64, 0x65, 0x66, 0x61, 0x74, 0x69, 0x67, 0x61, 0x62,
- 0x6c, 0x65, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
- 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x6b, 0x6e, 0x6f,
- 0x77, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x2c, 0x20, 0x65, 0x78,
- 0x63, 0x65, 0x65, 0x64, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20,
- 0x73, 0x68, 0x6f, 0x72, 0x74, 0x20, 0x76, 0x65, 0x68, 0x65,
- 0x6d, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x61,
- 0x6e, 0x79, 0x20, 0x63, 0x61, 0x72, 0x6e, 0x61, 0x6c, 0x20,
- 0x70, 0x6c, 0x65, 0x61, 0x73, 0x75, 0x72, 0x65, 0x2e
- };
- static const unsigned char ciphertext[272] = {
- 0xe0, 0x1b, 0x5b, 0x95, 0xc8, 0x47, 0xa1, 0xa3, 0xb8, 0xd6,
- 0xf9, 0x8c, 0xb8, 0x16, 0x3a, 0xbb, 0x98, 0x9d, 0x71, 0xb4,
- 0x88, 0xd8, 0xa7, 0xe1, 0x67, 0x1a, 0xbc, 0xde, 0x10, 0xfb,
- 0x1b, 0x9b, 0x3c, 0xd4, 0x5c, 0xb0, 0xcd, 0x2e, 0x00, 0x46,
- 0x74, 0xd7, 0x24, 0x85, 0x17, 0xf1, 0x0e, 0x08, 0xc2, 0x69,
- 0x2d, 0x40, 0x77, 0xc9, 0x18, 0x63, 0x22, 0x0d, 0x1f, 0x24,
- 0x20, 0xfe, 0xb2, 0x23, 0xf2, 0xb9, 0x2a, 0x52, 0x68, 0x00,
- 0x59, 0x0a, 0x23, 0x6b, 0x4c, 0xed, 0x0e, 0x9b, 0x2f, 0x1b,
- 0x94, 0xfd, 0x28, 0x00, 0x28, 0xb7, 0x63, 0x72, 0x1b, 0x96,
- 0x25, 0x56, 0x33, 0x10, 0x59, 0x1f, 0xa7, 0x76, 0x32, 0x39,
- 0xac, 0x4d, 0xb7, 0xff, 0x7a, 0x1f, 0xae, 0xcd, 0xa8, 0x70,
- 0x14, 0x81, 0xb2, 0xce, 0xe2, 0x8c, 0xdb, 0x2f, 0xf3, 0x5e,
- 0x4b, 0xde, 0x54, 0xbd, 0xde, 0x9a, 0xe7, 0xa5, 0xcb, 0xbe,
- 0xf6, 0xef, 0x50, 0xcb, 0x1a, 0xb6, 0x00, 0xe5, 0x82, 0x77,
- 0xb9, 0x81, 0xbd, 0x35, 0x84, 0xe1, 0x9f, 0x31, 0xcc, 0xd7,
- 0x50, 0xa4, 0xc1, 0xce, 0x30, 0x9f, 0x78, 0x14, 0x92, 0x9f,
- 0x80, 0xb0, 0x21, 0xac, 0x9a, 0x2e, 0x71, 0x41, 0x61, 0xdd,
- 0xf0, 0xa6, 0xa0, 0x27, 0x12, 0x0b, 0x03, 0x90, 0x7d, 0xf6,
- 0x19, 0xce, 0x5e, 0x57, 0xe6, 0x2d, 0x31, 0xe1, 0xae, 0xba,
- 0x8c, 0x0c, 0x9e, 0x77, 0xfb, 0x0d, 0x4f, 0xe2, 0x68, 0x8d,
- 0x24, 0xc0, 0x47, 0x2d, 0x5e, 0xe2, 0x7c, 0x44, 0x71, 0x66,
- 0x35, 0x8f, 0x29, 0x38, 0xcd, 0x14, 0x53, 0xe2, 0x52, 0x9f,
- 0x4d, 0x33, 0x12, 0xfd, 0xf8, 0xbf, 0x3d, 0x3b, 0xf3, 0x9f,
- 0xed, 0x51, 0x6b, 0x34, 0x56, 0xcc, 0x1c, 0x00, 0x6b, 0x1b,
- 0xa5, 0xb3, 0xd0, 0x7b, 0x38, 0xe3, 0xca, 0x18, 0x26, 0xbf,
- 0x8b, 0x7b, 0x41, 0x22, 0x05, 0x2e, 0x08, 0xf8, 0xbf, 0x3e,
- 0x31, 0x46, 0x32, 0xa9, 0x1d, 0x90, 0xb0, 0x52, 0xf2, 0x25,
- 0xb9, 0xe7
- };
- static const unsigned char key[32] = {
- 0x49, 0x38, 0xf9, 0xc3, 0x77, 0x46, 0x81, 0xb6, 0xd3, 0xfe,
- 0x17, 0xc9, 0xe9, 0x9e, 0x4c, 0x62, 0xb0, 0x60, 0x32, 0x62,
- 0xbb, 0xd8, 0xaf, 0xaf, 0xa8, 0xa1, 0x4c, 0x74, 0xe2, 0x05,
- 0x65, 0x26
- };
- static std::string keyString()
- {
- std::string ret;
- ret.assign((const char *)key, sizeof(key));
- return ret;
- }
- static const unsigned char iv[16] = {
- 0xfa, 0xee, 0xbf, 0xd0, 0x3b, 0xf8, 0xe8, 0x51, 0x5c, 0x5c,
- 0x5c, 0x19, 0xaf, 0x84, 0x2b, 0x75
- };
- static std::string ivString()
- {
- std::string ret;
- ret.assign((const char *)iv, sizeof(iv));
- return ret;
- }
- MORDOR_UNITTEST(CryptoStream, encryptWrite)
- {
- Buffer plain;
- plain.copyIn(plaintext, sizeof(plaintext));
- Buffer cipher;
- cipher.copyIn(ciphertext, sizeof(ciphertext));
- MemoryStream src(plain);
- MemoryStream::ptr sink(new MemoryStream);
- CryptoStream cryptor(sink, EVP_aes_256_cbc(), keyString(), ivString(), CryptoStream::WRITE);
- transferStream(src, cryptor);
- cryptor.close();
- const Buffer &test = sink->buffer();
- MORDOR_TEST_ASSERT(test == cipher);
- }
- MORDOR_UNITTEST(CryptoStream, encryptRead)
- {
- Buffer plain;
- plain.copyIn(plaintext, sizeof(plaintext));
- Buffer cipher;
- cipher.copyIn(ciphertext, sizeof(ciphertext));
- MemoryStream::ptr src(new MemoryStream(plain));
- CryptoStream cryptor(src, EVP_aes_256_cbc(), keyString(), ivString(), CryptoStream::READ, CryptoStream::ENCRYPT);
- MemoryStream sink;
- transferStream(cryptor, sink);
- const Buffer &test = sink.buffer();
- MORDOR_TEST_ASSERT(test == cipher);
- }
- MORDOR_UNITTEST(CryptoStream, decryptWrite)
- {
- Buffer plain;
- plain.copyIn(plaintext, sizeof(plaintext));
- Buffer cipher;
- cipher.copyIn(ciphertext, sizeof(ciphertext));
- MemoryStream src(cipher);
- MemoryStream::ptr sink(new MemoryStream);
- CryptoStream decryptor(sink, EVP_aes_256_cbc(), keyString(), ivString(), CryptoStream::WRITE, CryptoStream::DECRYPT);
- transferStream(src, decryptor);
- decryptor.close();
- const Buffer &test = sink->buffer();
- MORDOR_TEST_ASSERT(test == plain);
- }
- MORDOR_UNITTEST(CryptoStream, decryptRead)
- {
- Buffer plain;
- plain.copyIn(plaintext, sizeof(plaintext));
- Buffer cipher;
- cipher.copyIn(ciphertext, sizeof(ciphertext));
- MemoryStream::ptr src(new MemoryStream(cipher));
- CryptoStream decryptor(src, EVP_aes_256_cbc(), keyString(), ivString(), CryptoStream::READ);
- MemoryStream sink;
- transferStream(decryptor, sink);
- const Buffer &test = sink.buffer();
- MORDOR_TEST_ASSERT(test == plain);
- }
- MORDOR_UNITTEST(CryptoStream, badKeyIvSize)
- {
- MemoryStream::ptr parent(new MemoryStream);
- CryptoStream::ptr crypto;
- MORDOR_TEST_ASSERT_EXCEPTION(crypto.reset(new CryptoStream(
- parent, EVP_aes_256_cbc(), keyString() + "bogus", ivString(),
- CryptoStream::WRITE)), OpenSSLException);
- MORDOR_TEST_ASSERT_EXCEPTION(crypto.reset(new CryptoStream(
- parent, EVP_aes_256_cbc(), keyString(), ivString() + "bogus",
- CryptoStream::WRITE)), OpenSSLException);
- MORDOR_TEST_ASSERT_EXCEPTION(crypto.reset(new CryptoStream(
- parent, EVP_aes_256_ecb(), keyString(), ivString() + "bogus",
- CryptoStream::WRITE)), OpenSSLException);
- }
- MORDOR_UNITTEST(CryptoStream, badPadding)
- {
- Buffer cipher;
- cipher.copyIn(ciphertext, sizeof(ciphertext) - 16);
- MemoryStream::ptr src(new MemoryStream(cipher));
- CryptoStream decryptor(src, EVP_aes_256_cbc(), keyString(), ivString(), CryptoStream::READ, CryptoStream::DECRYPT);
- MemoryStream sink;
- MORDOR_TEST_ASSERT_EXCEPTION(transferStream(decryptor, sink), OpenSSLException);
- }
- // test the stream in all four modes of operation
- // hashDecR <- decR <- hashEncR <- encR <- hashOrig <- random
- // encW -> hashEncW -> decW -> hashDecW -> null
- void TestStreaming(long long test_bytes, const std::string &iv, size_t transferBlock = 0)
- {
- // read side
- Stream::ptr random(new RandomStream);
- Stream::ptr source(new LimitedStream(random, test_bytes));
- HashStream::ptr hashOrig(new MD5Stream(source));
- Stream::ptr encR(
- new CryptoStream(hashOrig, EVP_aes_256_cbc(), keyString(), iv, CryptoStream::READ, CryptoStream::ENCRYPT));
- HashStream::ptr hashEncR(new MD5Stream(encR));
- Stream::ptr decR(
- new CryptoStream(hashEncR, EVP_aes_256_cbc(), keyString(), iv, CryptoStream::READ, CryptoStream::DECRYPT));
- HashStream::ptr hashDecR(new MD5Stream(decR));
- // write side
- HashStream::ptr hashDecW(new MD5Stream(NullStream::get_ptr()));
- Stream::ptr decW(
- new CryptoStream(hashDecW, EVP_aes_256_cbc(), keyString(), iv, CryptoStream::WRITE, CryptoStream::DECRYPT));
- HashStream::ptr hashEncW(new MD5Stream(decW));
- Stream::ptr encW(
- new CryptoStream(hashEncW, EVP_aes_256_cbc(), keyString(), iv, CryptoStream::WRITE, CryptoStream::ENCRYPT));
- // do it
- if (transferBlock == 0) {
- transferStream(hashDecR, encW);
- } else {
- std::vector<unsigned char> buf(transferBlock);
- size_t bytes;
- while(0 != (bytes = hashDecR->read(&buf[0], buf.size()))) {
- encW->write(&buf[0], bytes);
- }
- }
- encW->close();
- decW->close();
- // make sure the decrypted data matches the original, and does _not_ match the encrypted
- // (because otherwise any non-mutating filter stream would pass this test...)
- MORDOR_TEST_ASSERT(hashOrig->hash() != hashEncR->hash());
- MORDOR_TEST_ASSERT(hashOrig->hash() == hashDecR->hash());
- MORDOR_TEST_ASSERT(hashOrig->hash() != hashEncW->hash());
- MORDOR_TEST_ASSERT(hashOrig->hash() == hashDecW->hash());
- }
- MORDOR_UNITTEST(CryptoStream, streaming)
- {
- static const long long sizes[] = { 0, 1, 15, 16, 17, 131071, 131072, 131073 };
- size_t nsizes = sizeof(sizes) / sizeof(sizes[0]);
- for(size_t i = 0; i < nsizes; ++i) {
- TestStreaming(sizes[i], ivString());
- TestStreaming(sizes[i], CryptoStream::RANDOM_IV);
- }
- }
- MORDOR_UNITTEST(CryptoStream, oddBufferSizes)
- {
- static const long long file_sizes[] = { 0, 100, 4096 };
- size_t nfilesizes = sizeof(file_sizes) / sizeof(file_sizes[0]);
- static const size_t buffer_sizes[] = { 7, 16, 1000 };
- size_t nbuffersizes = sizeof(buffer_sizes) / sizeof(buffer_sizes[0]);
- for(size_t i = 0; i < nfilesizes; ++i) {
- for(size_t j = 0; j < nbuffersizes; ++j) {
- TestStreaming(file_sizes[i], ivString(), buffer_sizes[j]);
- TestStreaming(file_sizes[i], CryptoStream::RANDOM_IV, buffer_sizes[j]);
- }
- }
- }
- MORDOR_UNITTEST(CryptoStream, inferDirection)
- {
- MemoryStream::ptr parent(new MemoryStream);
- SingleplexStream::ptr sr(new SingleplexStream(parent, SingleplexStream::READ, false));
- SingleplexStream::ptr sw(new SingleplexStream(parent, SingleplexStream::WRITE, false));
- CryptoStream csr(sr, EVP_aes_256_cbc(), keyString(), ivString());
- MORDOR_TEST_ASSERT(csr.supportsRead());
- MORDOR_TEST_ASSERT(!csr.supportsWrite());
- CryptoStream csw(sw, EVP_aes_256_cbc(), keyString(), ivString());
- MORDOR_TEST_ASSERT(!csw.supportsRead());
- MORDOR_TEST_ASSERT(csw.supportsWrite());
- MORDOR_TEST_ASSERT_ASSERTED(CryptoStream(parent, EVP_aes_256_cbc(), keyString(), ivString()));
- }
- MORDOR_UNITTEST(CryptoStream, encryptRead_randomIV)
- {
- Buffer plain;
- plain.copyIn(plaintext, sizeof(plaintext));
- MemoryStream::ptr src(new MemoryStream(plain));
- // encrypt via read
- CryptoStream enc(src, EVP_aes_256_cbc(), keyString(),
- CryptoStream::RANDOM_IV, CryptoStream::READ, CryptoStream::ENCRYPT);
- MemoryStream::ptr intermediate(new MemoryStream);
- transferStream(enc, intermediate);
- const Buffer &cipher = intermediate->buffer();
- // the destination buffer should be the size of the ciphertext plus the size of the IV
- // (we can't compare its contents to ciphertext because we used a random IV)
- MORDOR_TEST_ASSERT_EQUAL(cipher.readAvailable(), sizeof(ciphertext) + 16);
- // decrypt via write
- intermediate->seek(0);
- MemoryStream::ptr dst(new MemoryStream);
- CryptoStream dec(dst, EVP_aes_256_cbc(), keyString(),
- CryptoStream::RANDOM_IV, CryptoStream::WRITE, CryptoStream::DECRYPT);
- transferStream(intermediate, dec);
- dec.close();
- const Buffer &decrypted = dst->buffer();
- // make sure we got the original plaintext out
- MORDOR_TEST_ASSERT(plain == decrypted);
- }
- MORDOR_UNITTEST(CryptoStream, encryptWrite_randomIV)
- {
- Buffer plain;
- plain.copyIn(plaintext, sizeof(plaintext));
- MemoryStream::ptr src(new MemoryStream(plain));
- // encrypt via write (just for fun, let CryptoStream infer defaults)
- MemoryStream::ptr intermediate(new MemoryStream);
- SingleplexStream::ptr spw(new SingleplexStream(intermediate, SingleplexStream::WRITE));
- CryptoStream enc(spw, EVP_aes_256_cbc(), keyString());
- transferStream(src, enc);
- enc.close();
- const Buffer &cipher = intermediate->buffer();
- // the destination buffer should be the size of the ciphertext plus the size of the IV
- // (we can't compare its contents to ciphertext because we used a random IV)
- MORDOR_TEST_ASSERT_EQUAL(cipher.readAvailable(), sizeof(ciphertext) + 16);
- // now decrypt via read (again, using a SingleplexStream so CryptoStream can infer read)
- MemoryStream::ptr dst(new MemoryStream);
- intermediate->seek(0);
- SingleplexStream::ptr spr(new SingleplexStream(intermediate, SingleplexStream::READ));
- CryptoStream dec(spr, EVP_aes_256_cbc(), keyString());
- transferStream(dec, dst);
- const Buffer &decrypted = dst->buffer();
- // make sure we got the original plaintext out
- MORDOR_TEST_ASSERT(plain == decrypted);
- }
- MORDOR_UNITTEST(CryptoStream, encryptRead_noIV)
- {
- Buffer plain;
- plain.copyIn(plaintext, sizeof(plaintext));
- MemoryStream::ptr src(new MemoryStream(plain));
- // encrypt via read
- CryptoStream enc(src, EVP_aes_256_ecb(), keyString(),
- std::string(), CryptoStream::READ, CryptoStream::ENCRYPT);
- MemoryStream::ptr intermediate(new MemoryStream);
- transferStream(enc, intermediate);
- const Buffer &cipher = intermediate->buffer();
- // the destination buffer should be the size as the reference ciphertext
- // (the content is, of course, different, because we're in ECB mode)
- MORDOR_TEST_ASSERT_EQUAL(cipher.readAvailable(), sizeof(ciphertext));
- // decrypt via write
- intermediate->seek(0);
- MemoryStream::ptr dst(new MemoryStream);
- CryptoStream dec(dst, EVP_aes_256_ecb(), keyString(),
- std::string(), CryptoStream::WRITE, CryptoStream::DECRYPT);
- transferStream(intermediate, dec);
- dec.close();
- const Buffer &decrypted = dst->buffer();
- // make sure we got the original plaintext out
- MORDOR_TEST_ASSERT(plain == decrypted);
- }
- MORDOR_UNITTEST(CryptoStream, encryptWrite_noIV)
- {
- Buffer plain;
- plain.copyIn(plaintext, sizeof(plaintext));
- MemoryStream::ptr src(new MemoryStream(plain));
- // encrypt via write (just for fun, let CryptoStream infer defaults)
- MemoryStream::ptr intermediate(new MemoryStream);
- SingleplexStream::ptr spw(new SingleplexStream(intermediate, SingleplexStream::WRITE));
- CryptoStream enc(spw, EVP_aes_256_ecb(), keyString());
- transferStream(src, enc);
- enc.close();
- //const Buffer &cipher = intermediate->buffer();
- // the destination buffer should be the size as the reference ciphertext
- // (the content is, of course, different, because we're in ECB mode)
- // EDIT: in this mode, we're generating an IV of the size OpenSSL tells us
- // and some versions of OpenSSL give a non-zero IV size in ECB mode
- // so don't actually test this.
- //MORDOR_TEST_ASSERT_EQUAL(cipher.readAvailable(), sizeof(ciphertext));
- // now decrypt via read (again, using a SingleplexStream so CryptoStream can infer read)
- MemoryStream::ptr dst(new MemoryStream);
- intermediate->seek(0);
- SingleplexStream::ptr spr(new SingleplexStream(intermediate, SingleplexStream::READ));
- CryptoStream dec(spr, EVP_aes_256_ecb(), keyString());
- transferStream(dec, dst);
- const Buffer &decrypted = dst->buffer();
- // make sure we got the original plaintext out
- MORDOR_TEST_ASSERT(plain == decrypted);
- }