break out cipher primitives, add unit tests

git-svn-id: http://encfs.googlecode.com/svn/trunk@94 db9cf616-1c43-0410-9cb8-a902689de0d6
This commit is contained in:
Valient Gough
2013-03-05 06:32:27 +00:00
parent 7799c88df6
commit f00ef5c6d3
29 changed files with 1235 additions and 130 deletions

29
cipher/BlockCipher.cpp Normal file
View File

@@ -0,0 +1,29 @@
#include "cipher/BlockCipher.h"
// TODO: add ifdef when OpenSSL becomes optional.
#include "cipher/openssl.h"
namespace encfs {
Registry<BlockCipher>& BlockCipher::GetRegistry()
{
static Registry<BlockCipher> registry;
static bool first = true;
if (first)
{
OpenSSL::registerCiphers();
first = false;
}
return registry;
}
BlockCipher::BlockCipher()
{
}
BlockCipher::~BlockCipher()
{
}
} // namespace encfs

28
cipher/BlockCipher.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef BLOCKCIPHER_H
#define BLOCKCIPHER_H
#include "base/Interface.h"
#include "base/Range.h"
#include "base/Registry.h"
#include "base/shared_ptr.h"
#include "base/types.h"
#include "cipher/StreamCipher.h"
namespace encfs {
// BlockCipher is a StreamCipher with a block size.
// Encryption and decryption must be in multiples of the block size.
class BlockCipher : public StreamCipher
{
public:
static Registry<BlockCipher>& GetRegistry();
BlockCipher();
virtual ~BlockCipher();
virtual int blockSize() const =0;
};
} // namespace encfs
#endif // BLOCKCIPHER_H

View File

@@ -0,0 +1,95 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2013 Valient Gough
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <list>
#include <gtest/gtest.h>
#include "base/shared_ptr.h"
#include "cipher/BlockCipher.h"
#include "cipher/MemoryPool.h"
using namespace encfs;
using std::list;
using std::string;
namespace {
void compare(const byte *a, const byte *b, int size) {
for (int i = 0; i < size; i++) {
bool match = (a[i] == b[i]);
ASSERT_TRUE(match) << "mismatched data at offset " << i
<< " of " << size;
if (!match)
break;
}
}
TEST(BlockEncryptionTest, BlockCipher) {
Registry<BlockCipher> registry = BlockCipher::GetRegistry();
list<string> ciphers = registry.GetAll();
for (const string &name : ciphers) {
const BlockCipher::Properties *properties = registry.GetProperties(name.c_str());
SCOPED_TRACE(testing::Message() << "Cipher " << name);
for (int keySize = properties->keySize.min();
keySize <= properties->keySize.max();
keySize += properties->keySize.inc()) {
SCOPED_TRACE(testing::Message() << "Key size " << keySize);
shared_ptr<BlockCipher> cipher (registry.Create(name.c_str()));
ASSERT_TRUE(cipher->randomKey(keySize / 8));
// Create some data to encrypt.
int blockSize = cipher->blockSize();
MemBlock mb;
mb.allocate(16 * blockSize);
for (int i = 0; i < 16 * blockSize; i++) {
mb.data[i] = i % 256;
}
MemBlock iv;
iv.allocate(blockSize);
for (int i = 0; i < blockSize; i++) {
iv.data[i] = i;
}
// Encrypt.
MemBlock encrypted;
encrypted.allocate(16 * blockSize);
ASSERT_TRUE(cipher->encrypt(iv.data, mb.data,
encrypted.data, 16 * blockSize));
// Decrypt.
MemBlock decrypted;
decrypted.allocate(16 * blockSize);
ASSERT_TRUE(cipher->decrypt(iv.data, encrypted.data,
decrypted.data, 16 * blockSize));
compare(mb.data, decrypted.data, 16 * blockSize);
}
}
}
} // namespace

View File

@@ -2,41 +2,42 @@ include_directories (${OPENSSL_INCLUDE_DIR})
link_directories (${Encfs_BINARY_DIR}/base)
enable_testing ()
find_package (GTest REQUIRED)
add_library (encfs-cipher
readpassphrase.cpp
BlockCipher.cpp
Cipher.cpp
CipherKey.cpp
MAC.cpp
MemoryPool.cpp
NullCipher.cpp
openssl.cpp
PBKDF.cpp
readpassphrase.cpp
SSL_Cipher.cpp
StreamCipher.cpp
)
target_link_libraries (encfs-cipher
${OPENSSL_LIBRARIES}
)
#include_directories (${GTEST_INCLUDE_DIR})
#add_executable (unittests
#MemBlockFileIO.cpp
#MemFileIO.cpp
#testing.cpp
#test_IO.cpp
#test_BlockIO.cpp
#)
if (GTEST_FOUND)
link_directories (${PROJECT_BINARY_DIR}/base)
include_directories (${GTEST_INCLUDE_DIR})
#target_link_libraries (unittests
#${GTEST_BOTH_LIBRARIES}
#encfs-fs
#encfs-base
#${GLOG_LIBRARIES}
#)
file (GLOB TEST_FILES "*_test.cpp")
#add_test (UnitTests unittests)
#GTEST_ADD_TESTS (unittests "${UnitTestArgs}" test_IO.cpp test_BlockIO.cpp)
#add_custom_target (test COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS unittests)
add_executable (cipher-tests
testing.cpp
${TEST_FILES}
)
target_link_libraries (cipher-tests
${GTEST_BOTH_LIBRARIES}
encfs-cipher
encfs-base
${GLOG_LIBRARIES}
)
add_test (CipherTests cipher-tests)
GTEST_ADD_TESTS (cipher-tests "${CipherTestArgs}" ${TEST_FILES})
endif (GTEST_FOUND)

16
cipher/MAC.cpp Normal file
View File

@@ -0,0 +1,16 @@
#include "cipher/MAC.h"
namespace encfs {
DEFINE_REGISTERABLE_TYPE(MessageAuthenticationCode)
MessageAuthenticationCode::MessageAuthenticationCode()
{
}
MessageAuthenticationCode::~MessageAuthenticationCode()
{
}
} // namespace encfs

43
cipher/MAC.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef ENCFS_MAC_H
#define ENCFS_MAC_H
#include <string>
#include "base/Registry.h"
#include "base/types.h"
namespace encfs {
// MessageAuthenticationCode provides keyed MAC algorithms, eg HMAC.
class MessageAuthenticationCode
{
public:
DECLARE_REGISTERABLE_TYPE(MessageAuthenticationCode);
struct Properties {
int blockSize; // Block length of hash function.
std::string hashFunction;
std::string mode;
std::string library;
std::string toString() const {
return hashFunction + "/" + mode;
}
};
MessageAuthenticationCode();
virtual ~MessageAuthenticationCode();
virtual int outputSize() const =0;
virtual bool setKey(const byte *key, int keyLength) =0;
virtual bool randomKey(int keyLength) =0;
virtual void reset() =0;
virtual bool update(const byte *in, int length) =0;
virtual bool write(byte *out) =0;
};
} // namespace encfs
#endif // ENCFS_MAC_H

88
cipher/MAC_test.cpp Normal file
View File

@@ -0,0 +1,88 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2013 Valient Gough
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <gtest/gtest.h>
#include "base/shared_ptr.h"
#include "cipher/MAC.h"
#include "cipher/testing.h"
using namespace encfs;
namespace {
TEST(HMacSha1Test, MessageAuthenticationCode) {
Registry<MessageAuthenticationCode> registry =
MessageAuthenticationCode::GetRegistry();
shared_ptr<MessageAuthenticationCode> hmac(
registry.CreateForMatch( "SHA-1/HMAC" ));
ASSERT_FALSE(!hmac);
// Test cases from rfc2202
// Test case 1
byte key[20];
byte out[20];
for (int i = 0; i < 20; ++i)
key[i] = 0x0b;
hmac->setKey(key, 20);
hmac->reset();
hmac->update((byte *)"Hi There", 8);
hmac->write(out);
ASSERT_EQ("b617318655057264e28bc0b6fb378c8ef146be00", stringToHex(out, 20));
// Test case 2
strcpy((char *)key, "Jefe");
hmac->setKey(key, 4);
hmac->reset();
hmac->update((byte *)"what do ya want for nothing?", 28);
hmac->write(out);
ASSERT_EQ("effcdf6ae5eb2fa2d27416d5f184df9c259a7c79", stringToHex(out, 20));
// Test case 3
for (int i = 0; i < 20; ++i)
key[i] = 0xaa;
hmac->setKey(key, 20);
hmac->reset();
{
byte data[50];
memset(data, 0xdd, 50);
hmac->update(data, 50);
}
hmac->write(out);
ASSERT_EQ("125d7342b9ac11cd91a39af48aa17b4f63f175d3", stringToHex(out, 20));
// Test #7
byte longKey[80];
memset(longKey, 0xaa, 80);
hmac->setKey(longKey, 80);
hmac->reset();
hmac->update((byte *)"Test Using Larger Than Block-Size Key and Larger "
"Than One Block-Size Data", 73);
hmac->write(out);
ASSERT_EQ("e8e99d0f45237d786d6bbaa7965c7808bbff1a91", stringToHex(out, 20));
}
} // namespace

View File

@@ -134,7 +134,7 @@ void MemoryPool::destroyAll()
SecureMem::SecureMem(int len)
{
rAssert(len > 0);
data = (char *)OPENSSL_malloc(len);
data = (byte *)OPENSSL_malloc(len);
if (data)
{
size = len;

View File

@@ -29,7 +29,8 @@ namespace encfs {
Memory Pool for fixed sized objects.
Usage:
MemBlock mb( size );
MemBlock mb;
mb.allocate( size );
// do things with storage in mb.data
byte *buffer = mb.data;
@@ -59,7 +60,7 @@ namespace MemoryPool
struct SecureMem
{
int size;
char *data;
byte *data;
SecureMem(int len);
~SecureMem();

16
cipher/PBKDF.cpp Normal file
View File

@@ -0,0 +1,16 @@
#include "cipher/PBKDF.h"
namespace encfs {
DEFINE_REGISTERABLE_TYPE(PBKDF)
PBKDF::PBKDF()
{
}
PBKDF::~PBKDF()
{
}
} // namespace encfs

35
cipher/PBKDF.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef ENCFS_PBKDF_H
#define ENCFS_PBKDF_H
#include <string>
#include "base/Registry.h"
#include "base/types.h"
namespace encfs {
// Password Based Key Derivation Function.
class PBKDF
{
public:
DECLARE_REGISTERABLE_TYPE(PBKDF);
struct Properties {
std::string mode;
std::string library;
std::string toString() const { return mode; }
};
PBKDF();
virtual ~PBKDF();
virtual bool makeKey(const char *password, int passwordLength,
const byte *salt, int saltLength,
int numIterations,
byte *outKey, int keyLength) const = 0;
};
} // namespace encfs
#endif // ENCFS_PBKDF_H

78
cipher/PBKDF_test.cpp Normal file
View File

@@ -0,0 +1,78 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2013 Valient Gough
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <gtest/gtest.h>
#include "base/shared_ptr.h"
#include "cipher/PBKDF.h"
#include "cipher/testing.h"
using namespace encfs;
namespace {
TEST(PKCS5_PBKDF2_HMAC_SHA1, PBKDF) {
Registry<PBKDF> registry = PBKDF::GetRegistry();
shared_ptr<PBKDF> impl( registry.CreateForMatch( "PKCS5_PBKDF2_HMAC_SHA1" ));
ASSERT_FALSE(!impl);
// Test cases from rfc6070
// Test case 1
{
byte key[20];
bool ok = impl->makeKey("password", 8,
(byte*)"salt", 4,
1,
key, sizeof(key));
ASSERT_TRUE(ok);
ASSERT_EQ("0c60c80f961f0e71f3a9b524af6012062fe037a6",
stringToHex(key, sizeof(key)));
}
{
byte key[25];
bool ok = impl->makeKey("passwordPASSWORDpassword", 24,
(byte*)"saltSALTsaltSALTsaltSALTsaltSALTsalt", 36,
4096,
key, sizeof(key));
ASSERT_TRUE(ok);
ASSERT_EQ("3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038",
stringToHex(key, sizeof(key)));
}
{
byte key[16];
bool ok = impl->makeKey("pass\0word", 9,
(byte*)"sa\0lt", 5,
4096,
key, sizeof(key));
ASSERT_TRUE(ok);
ASSERT_EQ("56fa6aa75548099dcc37d7f03425e0c3",
stringToHex(key, sizeof(key)));
}
}
} // namespace

View File

@@ -28,6 +28,7 @@
#include "cipher/SSL_Cipher.h"
#include "cipher/MemoryPool.h"
#include "cipher/BlockCipher.h"
#include "base/Error.h"
#include "base/Mutex.h"
#include "base/Range.h"
@@ -65,13 +66,13 @@ inline int MIN(int a, int b)
DEPRECATED: this is here for backward compatibilty only. Use PBKDF
*/
int BytesToKey( int keyLen, int ivLen, const EVP_MD *md,
int BytesToKey(int keyLen, int ivLen, const EVP_MD *md,
const byte *data, int dataLen,
unsigned int rounds, byte *key, byte *iv)
{
if( data == NULL || dataLen == 0 )
return 0; // OpenSSL returns nkey here, but why? It is a failure..
byte mdBuf[ EVP_MAX_MD_SIZE ];
unsigned int mds=0;
int addmd =0;
@@ -110,7 +111,7 @@ int BytesToKey( int keyLen, int ivLen, const EVP_MD *md,
{
memcpy( iv, mdBuf+offset, toCopy );
iv += toCopy;
niv -= toCopy;
niv -= toCopy;
offset += toCopy;
}
if((nkey == 0) && (niv == 0)) break;
@@ -128,9 +129,9 @@ long time_diff(const timeval &end, const timeval &start)
}
int SSL_Cipher::TimedPBKDF2(const char *pass, int passlen,
const byte *salt, int saltlen,
int keylen, byte *out,
long desiredPDFTime)
const byte *salt, int saltlen,
int keylen, byte *out,
long desiredPDFTime)
{
int iter = 1000;
timeval start, end;
@@ -188,8 +189,9 @@ static shared_ptr<Cipher> NewBFCipher( const Interface &iface, int keyLen )
const EVP_CIPHER *blockCipher = EVP_bf_cbc();
const EVP_CIPHER *streamCipher = EVP_bf_cfb();
return shared_ptr<Cipher>( new SSL_Cipher(iface, BlowfishInterface,
blockCipher, streamCipher, keyLen / 8) );
return shared_ptr<Cipher>(
new SSL_Cipher(iface, BlowfishInterface,
blockCipher, streamCipher, keyLen / 8) );
}
static bool BF_Cipher_registered = Cipher::Register(
@@ -239,8 +241,8 @@ static shared_ptr<Cipher> NewAESCipher( const Interface &iface, int keyLen )
}
static bool AES_Cipher_registered = Cipher::Register(
"AES", "16 byte block cipher",
AESInterface, AESKeyRange, AESBlockRange, NewAESCipher, true);
"AES", "16 byte block cipher",
AESInterface, AESKeyRange, AESBlockRange, NewAESCipher, true);
#endif
#if defined(HAVE_EVP_AES_XTS)
@@ -276,35 +278,35 @@ static shared_ptr<Cipher> NewAesXtsCipher( const Interface &iface, int keyLen )
}
static bool AES_XTS_Cipher_registered = Cipher::Register(
"AES_XTS", "Tweakable wide-block cipher",
AesXtsInterface, AesXtsKeyRange, AesXtsBlockRange, NewAesXtsCipher, false);
"AES_XTS", "Tweakable wide-block cipher",
AesXtsInterface, AesXtsKeyRange, AesXtsBlockRange, NewAesXtsCipher, false);
#endif
class SSLKey : public AbstractCipherKey
{
public:
pthread_mutex_t mutex;
public:
pthread_mutex_t mutex;
unsigned int keySize; // in bytes
unsigned int ivLength;
unsigned int keySize; // in bytes
unsigned int ivLength;
// key data is first _keySize bytes,
// followed by iv of _ivLength bytes,
SecureMem buf;
// key data is first _keySize bytes,
// followed by iv of _ivLength bytes,
SecureMem buf;
EVP_CIPHER_CTX block_enc;
EVP_CIPHER_CTX block_dec;
EVP_CIPHER_CTX stream_enc;
EVP_CIPHER_CTX stream_dec;
EVP_CIPHER_CTX block_enc;
EVP_CIPHER_CTX block_dec;
EVP_CIPHER_CTX stream_enc;
EVP_CIPHER_CTX stream_dec;
HMAC_CTX mac_ctx;
HMAC_CTX mac_ctx;
SSLKey(int keySize, int ivLength);
~SSLKey();
SSLKey(int keySize, int ivLength);
~SSLKey();
};
SSLKey::SSLKey(int keySize_, int ivLength_)
: buf(keySize_ + ivLength_)
: buf(keySize_ + ivLength_)
{
rAssert(keySize_ >= 8);
rAssert(ivLength_ >= 8);
@@ -341,7 +343,7 @@ inline byte* IVData( const shared_ptr<SSLKey> &key )
}
void initKey(const shared_ptr<SSLKey> &key, const EVP_CIPHER *_blockCipher,
const EVP_CIPHER *_streamCipher, int _keySize)
const EVP_CIPHER *_streamCipher, int _keySize)
{
Lock lock( key->mutex );
// initialize the cipher context once so that we don't have to do it for
@@ -377,10 +379,10 @@ void initKey(const shared_ptr<SSLKey> &key, const EVP_CIPHER *_blockCipher,
SSL_Cipher::SSL_Cipher(const Interface &iface_,
const Interface &realIface_,
const EVP_CIPHER *blockCipher,
const EVP_CIPHER *streamCipher,
int keySize_)
const Interface &realIface_,
const EVP_CIPHER *blockCipher,
const EVP_CIPHER *streamCipher,
int keySize_)
{
this->iface = iface_;
this->realIface = realIface_;
@@ -393,8 +395,8 @@ SSL_Cipher::SSL_Cipher(const Interface &iface_,
rAssert(_ivLength <= _keySize);
VLOG(1) << "allocated cipher " << iface.name()
<< ", keySize " << _keySize
<< ", ivlength " << _ivLength;
<< ", keySize " << _keySize
<< ", ivlength " << _ivLength;
// EVP_CIPHER_key_length isn't useful for variable-length ciphers like
// Blowfish. Version 1 relied upon it incorrectly.
@@ -402,8 +404,8 @@ SSL_Cipher::SSL_Cipher(const Interface &iface_,
&& iface.major() == 1)
{
LOG(WARNING) << "Running in backward compatibilty mode for 1.0 - \n"
<< "key is really " << EVP_CIPHER_key_length( _blockCipher ) * 8
<< " bits, not " << _keySize * 8;
<< "key is really " << EVP_CIPHER_key_length( _blockCipher ) * 8
<< " bits, not " << _keySize * 8;
}
}
@@ -422,10 +424,10 @@ Interface SSL_Cipher::interface() const
This algorithm must remain constant for backward compatibility, as this key
is used to encipher/decipher the master key.
*/
*/
CipherKey SSL_Cipher::newKey(const char *password, int passwdLength,
int &iterationCount, long desiredDuration,
const byte *salt, int saltLen)
int &iterationCount, long desiredDuration,
const byte *salt, int saltLen)
{
shared_ptr<SSLKey> key( new SSLKey( _keySize, _ivLength) );
@@ -477,7 +479,7 @@ CipherKey SSL_Cipher::newKey(const char *password, int passwdLength)
if(bytes != (int)_keySize)
{
LOG(WARNING) << "newKey: BytesToKey returned " << bytes
<< ", expecting " << _keySize << " key bytes";
<< ", expecting " << _keySize << " key bytes";
}
} else
{
@@ -499,7 +501,7 @@ CipherKey SSL_Cipher::newKey(const char *password, int passwdLength)
This algorithm can change at any time without affecting backward
compatibility.
*/
*/
CipherKey SSL_Cipher::newRandomKey()
{
const int bufLen = MAX_KEYLENGTH;
@@ -531,11 +533,11 @@ CipherKey SSL_Cipher::newRandomKey()
/*
Compute a 64-bit check value for the data using HMAC.
*/
*/
static uint64_t _checksum_64(SSLKey *key,
const byte *data,
int dataLen,
uint64_t *chainedIV)
const byte *data,
int dataLen,
uint64_t *chainedIV)
{
rAssert( dataLen > 0 );
Lock lock( key->mutex );
@@ -576,7 +578,7 @@ static uint64_t _checksum_64(SSLKey *key,
}
bool SSL_Cipher::randomize( byte *buf, int len,
bool strongRandom ) const
bool strongRandom ) const
{
// to avoid warnings of uninitialized data from valgrind
memset(buf, 0, len);
@@ -599,7 +601,7 @@ bool SSL_Cipher::randomize( byte *buf, int len,
}
uint64_t SSL_Cipher::MAC_64( const byte *data, int len,
const CipherKey &key, uint64_t *chainedIV ) const
const CipherKey &key, uint64_t *chainedIV ) const
{
shared_ptr<SSLKey> mk = dynamic_pointer_cast<SSLKey>(key);
uint64_t tmp = _checksum_64( mk.get(), data, len, chainedIV );
@@ -611,7 +613,7 @@ uint64_t SSL_Cipher::MAC_64( const byte *data, int len,
}
CipherKey SSL_Cipher::readKey(const byte *data,
const CipherKey &masterKey, bool checkKey)
const CipherKey &masterKey, bool checkKey)
{
shared_ptr<SSLKey> mk = dynamic_pointer_cast<SSLKey>(masterKey);
rAssert(mk->keySize == _keySize);
@@ -638,8 +640,8 @@ CipherKey SSL_Cipher::readKey(const byte *data,
if(checksum2 != checksum && checkKey)
{
VLOG(1) << "checksum mismatch: expected " << checksum
<< ", got " << checksum2
<< "on decode of " << _keySize + _ivLength << " bytes";
<< ", got " << checksum2
<< "on decode of " << _keySize + _ivLength << " bytes";
OPENSSL_cleanse(tmpBuf, sizeof(tmpBuf));
return CipherKey();
}
@@ -656,7 +658,7 @@ CipherKey SSL_Cipher::readKey(const byte *data,
}
void SSL_Cipher::writeKey(const CipherKey &ckey, byte *data,
const CipherKey &masterKey)
const CipherKey &masterKey)
{
shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
rAssert(key->keySize == _keySize);
@@ -771,7 +773,7 @@ void SSL_Cipher::setIVec_old(byte *ivec,
{
unsigned int var1 = 0x060a4011 * seed;
unsigned int var2 = 0x0221040d * (seed ^ 0xD3FEA11C);
memcpy( ivec, IVData(key), _ivLength );
ivec[0] ^= (var1 >> 24) & 0xff;
@@ -861,7 +863,7 @@ bool SSL_Cipher::streamEncode(byte *buf, int size,
dstLen += tmpLen;
LOG_IF(ERROR, dstLen != size) << "encoding " << size
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
return true;
}
@@ -897,7 +899,7 @@ bool SSL_Cipher::streamDecode(byte *buf, int size,
dstLen += tmpLen;
LOG_IF(ERROR, dstLen != size) << "encoding " << size
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
return true;
}
@@ -928,7 +930,7 @@ bool SSL_Cipher::blockEncode(byte *buf, int size,
dstLen += tmpLen;
LOG_IF(ERROR, dstLen != size) << "encoding " << size
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
return true;
}
@@ -958,7 +960,7 @@ bool SSL_Cipher::blockDecode(byte *buf, int size,
dstLen += tmpLen;
LOG_IF(ERROR, dstLen != size) << "decoding " << size
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
return true;
}

20
cipher/StreamCipher.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include "cipher/StreamCipher.h"
namespace encfs {
Registry<StreamCipher>& StreamCipher::GetRegistry()
{
static Registry<StreamCipher> registry;
return registry;
}
StreamCipher::StreamCipher()
{
}
StreamCipher::~StreamCipher()
{
}
} // namespace encfs

60
cipher/StreamCipher.h Normal file
View File

@@ -0,0 +1,60 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2013 Valient Gough
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _STREAMCIPHER_incl_
#define _STREAMCIPHER_incl_
#include "base/Range.h"
#include "base/Registry.h"
#include "base/shared_ptr.h"
#include "base/types.h"
namespace encfs {
class StreamCipher
{
public:
static Registry<StreamCipher>& GetRegistry();
struct Properties {
Range keySize;
std::string cipher;
std::string mode;
std::string library;
};
StreamCipher();
virtual ~StreamCipher();
virtual bool setKey(const byte *key, int keyLength) =0;
virtual bool randomKey(int keyLength) =0;
virtual bool encrypt(const byte *iv, const byte *in,
byte *out, int numBytes) =0;
virtual bool decrypt(const byte *iv, const byte *in,
byte *out, int numBytes) =0;
};
} // namespace encfs
#endif

View File

@@ -2,7 +2,7 @@
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2007, Valient Gough
* Copyright (c) 2007-2013, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
@@ -20,10 +20,16 @@
#include "cipher/openssl.h"
#include <cstring>
#include <ctime>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <glog/logging.h>
#include "base/config.h"
#define NO_DES
#include <openssl/ssl.h>
#include <openssl/rand.h>
@@ -31,8 +37,345 @@
#include <openssl/engine.h>
#endif
#include <openssl/blowfish.h>
#include <openssl/err.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include "base/Error.h"
#include "base/i18n.h"
#include "base/Mutex.h"
#include "base/Range.h"
#include "cipher/BlockCipher.h"
#include "cipher/MAC.h"
#include "cipher/MemoryPool.h"
#include "cipher/PBKDF.h"
#include "cipher/StreamCipher.h"
using namespace std;
namespace encfs {
const int MAX_KEYLENGTH = 64; // in bytes (256 bit)
const int MAX_IVLENGTH = 16;
const int KEY_CHECKSUM_BYTES = 4;
#ifndef MIN
inline int MIN(int a, int b)
{
return (a < b) ? a : b;
}
#endif
// Base for {Block,Stream}Cipher implementation.
class OpenSSLCipher : public BlockCipher {
public:
OpenSSLCipher() {
}
virtual ~OpenSSLCipher() {
EVP_CIPHER_CTX_cleanup( &enc );
EVP_CIPHER_CTX_cleanup( &dec );
}
bool rekey(const EVP_CIPHER *cipher, const byte *key, int length) {
EVP_CIPHER_CTX_init( &enc );
EVP_EncryptInit_ex( &enc, cipher, NULL, NULL, NULL);
EVP_CIPHER_CTX_set_key_length( &enc, length );
EVP_CIPHER_CTX_set_padding( &enc, 0 );
EVP_EncryptInit_ex( &enc, NULL, NULL, key, NULL);
EVP_CIPHER_CTX_init( &dec );
EVP_DecryptInit_ex( &dec, cipher, NULL, NULL, NULL);
EVP_CIPHER_CTX_set_key_length( &dec, length );
EVP_CIPHER_CTX_set_padding( &dec, 0 );
EVP_DecryptInit_ex( &dec, NULL, NULL, key, NULL);
return true;
}
static bool randomize(byte *out, int len) {
int result = RAND_bytes( out, len );
if(result != 1)
{
char errStr[120]; // specs require string at least 120 bytes long..
unsigned long errVal = 0;
if((errVal = ERR_get_error()) != 0)
LOG(ERROR) << "openssl error: " << ERR_error_string( errVal, errStr );
return false;
}
return true;
}
// Rekey with random key.
bool rekey(const EVP_CIPHER *cipher, int keyLength) {
SecureMem key(keyLength);
if (!randomize(key.data, key.size))
return false;
return rekey(cipher, key.data, key.size);
}
virtual int blockSize() const {
return EVP_CIPHER_CTX_block_size(&enc);
}
virtual bool encrypt(const byte *ivec, const byte *in,
byte *out, int size) {
int dstLen = 0, tmpLen = 0;
EVP_EncryptInit_ex( &enc, NULL, NULL, NULL, ivec);
EVP_EncryptUpdate( &enc, out, &dstLen, in, size);
EVP_EncryptFinal_ex( &enc, out+dstLen, &tmpLen );
dstLen += tmpLen;
if (dstLen != size) {
LOG(ERROR) << "encoding " << size
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
return false;
}
return true;
}
virtual bool decrypt(const byte *ivec, const byte *in,
byte *out, int size) {
int dstLen = 0, tmpLen = 0;
EVP_DecryptInit_ex( &dec, NULL, NULL, NULL, ivec);
EVP_DecryptUpdate( &dec, out, &dstLen, in, size );
EVP_DecryptFinal_ex( &dec, out+dstLen, &tmpLen );
dstLen += tmpLen;
if (dstLen != size) {
LOG(ERROR) << "decoding " << size
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
return false;
}
return true;
}
private:
EVP_CIPHER_CTX enc;
EVP_CIPHER_CTX dec;
};
#if defined(HAVE_EVP_BF)
static Range BfKeyRange(128,256,32);
class BfCbcBlockCipher : public OpenSSLCipher {
public:
BfCbcBlockCipher() {}
virtual ~BfCbcBlockCipher() {}
virtual bool setKey(const byte *key, int length) {
if (BfKeyRange.allowed(length * 8))
return rekey(EVP_bf_cbc(), key, length);
else
return false;
}
virtual bool randomKey(int length) {
return BfKeyRange.allowed(length * 8) && rekey(EVP_bf_cbc(), length);
}
static Properties GetProperties() {
Properties props;
props.keySize = BfKeyRange;
props.cipher = "Blowfish";
props.mode = "CBC";
props.library = "OpenSSL";
return props;
}
};
REGISTER_CLASS(BfCbcBlockCipher, BlockCipher);
class BfCfbStreamCipher : public OpenSSLCipher {
public:
BfCfbStreamCipher() {}
virtual ~BfCfbStreamCipher() {}
virtual bool setKey(const byte *key, int length) {
return BfKeyRange.allowed(length * 8) && rekey(EVP_bf_cfb(), key, length);
}
virtual bool randomKey(int length) {
return BfKeyRange.allowed(length * 8) && rekey(EVP_bf_cfb(), length);
}
static Properties GetProperties() {
Properties props;
props.keySize = BfKeyRange;
props.cipher = "Blowfish";
props.mode = "CFB";
props.library = "OpenSSL";
return props;
}
};
REGISTER_CLASS(BfCfbStreamCipher, StreamCipher);
#endif
#if defined(HAVE_EVP_AES)
static Range AesKeyRange(128,256,64);
class AesCbcBlockCipher : public OpenSSLCipher {
public:
AesCbcBlockCipher() {}
virtual ~AesCbcBlockCipher() {}
virtual bool setKey(const byte *key, int length) {
const EVP_CIPHER *cipher = getCipher(length);
return (cipher != NULL) && rekey(cipher, key, length);
}
virtual bool randomKey(int length) {
const EVP_CIPHER *cipher = getCipher(length);
return (cipher != NULL) && rekey(cipher, length);
}
static const EVP_CIPHER *getCipher(int keyLength) {
switch(keyLength * 8)
{
case 128: return EVP_aes_128_cbc();
case 192: return EVP_aes_192_cbc();
case 256: return EVP_aes_256_cbc();
default:
return NULL;
}
}
static Properties GetProperties() {
Properties props;
props.keySize = AesKeyRange;
props.cipher = "AES";
props.mode = "CBC";
props.library = "OpenSSL";
return props;
}
};
REGISTER_CLASS(AesCbcBlockCipher, BlockCipher);
#endif
#if defined(HAVE_EVP_AES_XTS)
static Range AesXtsKeyRange(128,256,128);
class AesXtsBlockCipher : public OpenSSLCipher {
public:
AesXtsBlockCipher() {}
virtual ~AesXtsBlockCipher() {}
virtual bool setKey(const byte *key, int length) {
const EVP_CIPHER *cipher = getCipher(length);
return (cipher != NULL) && rekey(cipher, key, length);
}
virtual bool randomKey(int length) {
const EVP_CIPHER *cipher = getCipher(length);
return (cipher != NULL) && rekey(cipher, length);
}
static const EVP_CIPHER *getCipher(int keyLength) {
switch(keyLength * 8)
{
case 128: return EVP_aes_128_xts();
case 256: return EVP_aes_256_xts();
default: return NULL;
}
}
static Properties GetProperties() {
Properties props;
props.keySize = AesXtsKeyRange;
props.cipher = "AES";
props.mode = "XTS";
props.library = "OpenSSL";
return props;
}
};
REGISTER_CLASS(AesXtsBlockCipher, BlockCipher);
#endif
class Sha1HMac : public MessageAuthenticationCode {
public:
Sha1HMac() {}
virtual ~Sha1HMac() {
HMAC_CTX_cleanup(&ctx);
}
virtual int outputSize() const {
return 20; // 160 bit.
}
virtual bool setKey(const byte *key, int keyLength) {
HMAC_CTX_init(&ctx);
HMAC_Init_ex(&ctx, key, keyLength, EVP_sha1(), 0);
return true;
}
virtual bool randomKey(int keyLength) {
SecureMem key(keyLength);
return OpenSSLCipher::randomize(key.data, key.size)
&& setKey(key.data, key.size);
}
virtual void reset() {
HMAC_Init_ex(&ctx, 0, 0, 0, 0);
}
virtual bool update(const byte *in, int length) {
HMAC_Update(&ctx, in, length);
return true;
}
virtual bool write(byte *out) {
unsigned int outSize = 0;
HMAC_Final(&ctx, (unsigned char *)out, &outSize);
CHECK_EQ(outputSize(), outSize) << "Invalid HMAC output size";
return true;
}
static Properties GetProperties() {
Properties props;
props.blockSize = 20;
props.hashFunction = "SHA-1";
props.mode = "HMAC";
props.library = "OpenSSL";
return props;
}
private:
HMAC_CTX ctx;
};
REGISTER_CLASS(Sha1HMac, MessageAuthenticationCode);
class PbkdfPkcs5HmacSha1 : public PBKDF {
public:
PbkdfPkcs5HmacSha1() {}
virtual ~PbkdfPkcs5HmacSha1() {}
virtual bool makeKey(const char *password, int passwordLength,
const byte *salt, int saltLength,
int numIterations,
byte *outKey, int keyLength) const {
return PKCS5_PBKDF2_HMAC_SHA1(
password, passwordLength,
const_cast<byte *>(salt), saltLength,
numIterations, keyLength, outKey) == 1;
}
static Properties GetProperties() {
Properties props;
props.mode = "PKCS5_PBKDF2_HMAC_SHA1";
props.library = "OpenSSL";
return props;
}
};
REGISTER_CLASS(PbkdfPkcs5HmacSha1, PBKDF);
unsigned long pthreads_thread_id()
{
return (unsigned long)pthread_self();
@@ -73,7 +416,7 @@ void pthreads_locking_cleanup()
}
}
void openssl_init(bool threaded)
void OpenSSL::init(bool threaded)
{
// initialize the SSL library
SSL_load_error_strings();
@@ -99,7 +442,7 @@ void openssl_init(bool threaded)
}
}
void openssl_shutdown(bool threaded)
void OpenSSL::shutdown(bool threaded)
{
#ifndef OPENSSL_NO_ENGINE
ENGINE_cleanup();
@@ -109,4 +452,10 @@ void openssl_shutdown(bool threaded)
pthreads_locking_cleanup();
}
void OpenSSL::registerCiphers()
{
// Nothing required.. Just need to reference this code block to get static
// initializers.
}
} // namespace encfs

View File

@@ -21,10 +21,17 @@
#ifndef _openssl_incl_
#define _openssl_incl_
#include "base/Registry.h"
namespace encfs {
void openssl_init(bool isThreaded);
void openssl_shutdown(bool isThreaded);
class OpenSSL {
public:
static void init(bool isThreaded);
static void shutdown(bool isThreaded);
static void registerCiphers();
};
} // namespace encfs

30
cipher/testing.cpp Normal file
View File

@@ -0,0 +1,30 @@
#include <gtest/gtest.h>
#include "cipher/testing.h"
namespace encfs {
std::string stringToHex(const byte *data, int len) {
static const char lookup[] = "0123456789abcdef";
std::string out;
out.reserve(2 * len);
for (int i = 0; i < len; ++i) {
unsigned int c = (unsigned int)data[i] & 0xff;
int first = (unsigned int)c >> 4;
int second = (unsigned int)c & 15;
out.push_back(lookup[first]);
out.push_back(lookup[second]);
}
return out;
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
} // namespace encfs

36
cipher/testing.h Normal file
View File

@@ -0,0 +1,36 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2013 Valient Gough
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _CIPHER_TESTING_incl_
#define _CIPHER_TESTING_incl_
#include <string>
#include "base/types.h"
namespace encfs {
std::string stringToHex(const byte *data, int len);
} // namespace encfs
#endif