summaryrefslogtreecommitdiff
path: root/test/crypto/testcrypto.cpp
blob: 200bdd0feb95e1ce395489cdea7226ddd56cb994 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
// --------------------------------------------------------------------------
//
// File
//		Name:    testcrypto.cpp
//		Purpose: test lib/crypto
//		Created: 1/12/03
//
// --------------------------------------------------------------------------

#include "Box.h"

#include <string.h>
#include <openssl/rand.h>

#include "CipherContext.h"
#include "CipherBlowfish.h"
#include "CipherAES.h"
#include "CipherException.h"
#include "CollectInBufferStream.h"
#include "Guards.h"
#include "RollingChecksum.h"
#include "Random.h"
#include "Test.h"

#include "MemLeakFindOn.h"

#define STRING1	"Mary had a little lamb"
#define STRING2 "Skjdf sdjf sjksd fjkhsdfjk hsdfuiohcverfg sdfnj sdfgkljh sdfjb jlhdfvghsdip vjsdfv bsdfhjvg yuiosdvgpvj kvbn m,sdvb sdfuiovg sdfuivhsdfjkv"

#define KEY "0123456701234567012345670123456"
#define KEY2 "1234567012345670123456A"

#define CHECKSUM_DATA_SIZE			(128*1024)
#define CHECKSUM_BLOCK_SIZE_BASE	(65*1024)
#define CHECKSUM_BLOCK_SIZE_LAST	(CHECKSUM_BLOCK_SIZE_BASE + 64)
#define CHECKSUM_ROLLS				16

// Copied from BackupClientCryptoKeys.h
#define BACKUPCRYPTOKEYS_FILENAME_KEY_START				0
#define BACKUPCRYPTOKEYS_FILENAME_KEY_LENGTH			56
#define BACKUPCRYPTOKEYS_FILENAME_IV_START				(0 + BACKUPCRYPTOKEYS_FILENAME_KEY_LENGTH)
#define BACKUPCRYPTOKEYS_FILENAME_IV_LENGTH				8

void check_random_int(uint32_t max)
{
	for(int c = 0; c < 1024; ++c)
	{
		uint32_t v = Random::RandomInt(max);
		TEST_THAT(v >= 0 && v <= max);
	}
}

#define ZERO_BUFFER(x) ::memset(x, 0, sizeof(x));

template<typename CipherType, int BLOCKSIZE>
void test_cipher()
{
	{
		// Make a couple of cipher contexts
		CipherContext encrypt1;
		encrypt1.Reset();
		encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
		TEST_CHECK_THROWS(encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))),
			CipherException, AlreadyInitialised);
		// Encrpt something
		char buf1[256];
		unsigned int buf1_used = encrypt1.TransformBlock(buf1, sizeof(buf1), STRING1, sizeof(STRING1));
		TEST_THAT(buf1_used >= sizeof(STRING1));
		// Decrypt it
		CipherContext decrypt1;
		decrypt1.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
		char buf1_de[256];
		unsigned int buf1_de_used = decrypt1.TransformBlock(buf1_de, sizeof(buf1_de), buf1, buf1_used);
		TEST_THAT(buf1_de_used == sizeof(STRING1));
		TEST_THAT(memcmp(STRING1, buf1_de, sizeof(STRING1)) == 0);
		
		// Use them again...
		char buf1_de2[256];
		unsigned int buf1_de2_used = decrypt1.TransformBlock(buf1_de2, sizeof(buf1_de2), buf1, buf1_used);
		TEST_THAT(buf1_de2_used == sizeof(STRING1));
		TEST_THAT(memcmp(STRING1, buf1_de2, sizeof(STRING1)) == 0);
		
		// Test the interface
		char buf2[256];
		TEST_CHECK_THROWS(encrypt1.Transform(buf2, sizeof(buf2), STRING1, sizeof(STRING1)),
			CipherException, BeginNotCalled);
		TEST_CHECK_THROWS(encrypt1.Final(buf2, sizeof(buf2)),
			CipherException, BeginNotCalled);
		encrypt1.Begin();
		int e = 0;
		e = encrypt1.Transform(buf2, sizeof(buf2), STRING2, sizeof(STRING2) - 16);
		e += encrypt1.Transform(buf2 + e, sizeof(buf2) - e, STRING2 + sizeof(STRING2) - 16, 16);
		e += encrypt1.Final(buf2 + e, sizeof(buf2) - e);
		TEST_THAT(e >= (int)sizeof(STRING2));
		
		// Then decrypt
		char buf2_de[256];
		decrypt1.Begin();
		TEST_CHECK_THROWS(decrypt1.Transform(buf2_de, 2, buf2, e), CipherException, OutputBufferTooSmall);
		TEST_CHECK_THROWS(decrypt1.Final(buf2_de, 2), CipherException, OutputBufferTooSmall);
		int d = decrypt1.Transform(buf2_de, sizeof(buf2_de), buf2, e - 48);
		d += decrypt1.Transform(buf2_de + d, sizeof(buf2_de) - d, buf2 + e - 48, 48);
		d += decrypt1.Final(buf2_de + d, sizeof(buf2_de) - d);
		TEST_THAT(d == sizeof(STRING2));
		TEST_THAT(memcmp(STRING2, buf2_de, sizeof(STRING2)) == 0);
		
		// Try a reset and rekey
		encrypt1.Reset();
		encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY2, sizeof(KEY2)));
		buf1_used = encrypt1.TransformBlock(buf1, sizeof(buf1), STRING1, sizeof(STRING1));
	}
	
	// Test initialisation vectors
	{
		// Init with random IV
		CipherContext encrypt2;
		encrypt2.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
		int ivLen;
		char iv2[BLOCKSIZE];
		const void *ivGen = encrypt2.SetRandomIV(ivLen);
		TEST_THAT(ivLen == BLOCKSIZE);	// block size
		TEST_THAT(ivGen != 0);
		memcpy(iv2, ivGen, ivLen);
		
		char buf3[256];
		unsigned int buf3_used = encrypt2.TransformBlock(buf3, sizeof(buf3), STRING2, sizeof(STRING2));
		
		// Encrypt again with different IV
		char iv3[BLOCKSIZE];
		int ivLen3;
		const void *ivGen3 = encrypt2.SetRandomIV(ivLen3);
		TEST_THAT(ivLen3 == BLOCKSIZE);	// block size
		TEST_THAT(ivGen3 != 0);
		memcpy(iv3, ivGen3, ivLen3);
		// Check the two generated IVs are different
		TEST_THAT(memcmp(iv2, iv3, BLOCKSIZE) != 0);

		char buf4[256];
		unsigned int buf4_used = encrypt2.TransformBlock(buf4, sizeof(buf4), STRING2, sizeof(STRING2));

		// check encryptions are different
		TEST_THAT(buf3_used == buf4_used);
		TEST_THAT(memcmp(buf3, buf4, buf3_used) != 0);
		
		// Test that decryption with the right IV works
		CipherContext decrypt2;
		decrypt2.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY), iv2));
		char buf3_de[256];
		unsigned int buf3_de_used = decrypt2.TransformBlock(buf3_de, sizeof(buf3_de), buf3, buf3_used);
		TEST_THAT(buf3_de_used == sizeof(STRING2));
		TEST_THAT(memcmp(STRING2, buf3_de, sizeof(STRING2)) == 0);
		
		// And that using the wrong one doesn't
		decrypt2.SetIV(iv3);
		buf3_de_used = decrypt2.TransformBlock(buf3_de, sizeof(buf3_de), buf3, buf3_used);
		TEST_THAT(buf3_de_used == sizeof(STRING2));
		TEST_THAT(memcmp(STRING2, buf3_de, sizeof(STRING2)) != 0);		
	}
	
	// Test with padding off.
	{
		CipherContext encrypt3;
		encrypt3.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
		encrypt3.UsePadding(false);
		
		// Should fail because the encrypted size is not a multiple of the block size
		char buf4[256];
		encrypt3.Begin();
		ZERO_BUFFER(buf4);
		int buf4_used = encrypt3.Transform(buf4, sizeof(buf4), STRING2, 6);
		TEST_CHECK_THROWS(encrypt3.Final(buf4, sizeof(buf4)), CipherException, EVPFinalFailure);
		
		// Check a nice encryption with the correct block size
		CipherContext encrypt4;
		encrypt4.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
		encrypt4.UsePadding(false);
		encrypt4.Begin();
		ZERO_BUFFER(buf4);
		buf4_used = encrypt4.Transform(buf4, sizeof(buf4), STRING2, 16);
		buf4_used += encrypt4.Final(buf4+buf4_used, sizeof(buf4));
		TEST_THAT(buf4_used == 16);

		// Check it's encrypted to the same thing as when there's padding on
		CipherContext encrypt4b;
		encrypt4b.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
		encrypt4b.Begin();
		char buf4b[256];
		ZERO_BUFFER(buf4b);
		int buf4b_used = encrypt4b.Transform(buf4b, sizeof(buf4b), STRING2, 16);
		buf4b_used += encrypt4b.Final(buf4b + buf4b_used, sizeof(buf4b));
		TEST_THAT(buf4b_used == 16+BLOCKSIZE);
		TEST_THAT(::memcmp(buf4, buf4b, 16) == 0);

		// Decrypt
		char buf4_de[256];
		CipherContext decrypt4;
		decrypt4.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
		decrypt4.UsePadding(false);
		decrypt4.Begin();
		ZERO_BUFFER(buf4_de);
		int buf4_de_used = decrypt4.Transform(buf4_de, sizeof(buf4_de), buf4, 16);
		buf4_de_used += decrypt4.Final(buf4_de+buf4_de_used, sizeof(buf4_de));
		TEST_THAT(buf4_de_used == 16);
		TEST_THAT(::memcmp(buf4_de, STRING2, 16) == 0);
		
		// Test that the TransformBlock thing works as expected too with blocks the same size as the input
		TEST_THAT(encrypt4.TransformBlock(buf4, 16, STRING2, 16) == 16);
		// But that it exceptions if we try the trick with padding on
		encrypt4.UsePadding(true);
		TEST_CHECK_THROWS(encrypt4.TransformBlock(buf4, 16, STRING2, 16), CipherException, OutputBufferTooSmall);
	}

	// And again, but with different string size
	{
		char buf4[256];
		int buf4_used;

		// Check a nice encryption with the correct block size
		CipherContext encrypt4;
		encrypt4.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
		encrypt4.UsePadding(false);
		encrypt4.Begin();
		ZERO_BUFFER(buf4);
		buf4_used = encrypt4.Transform(buf4, sizeof(buf4), STRING2, (BLOCKSIZE*3)); // do three blocks worth
		buf4_used += encrypt4.Final(buf4+buf4_used, sizeof(buf4));
		TEST_THAT(buf4_used == (BLOCKSIZE*3));

		// Check it's encrypted to the same thing as when there's padding on
		CipherContext encrypt4b;
		encrypt4b.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
		encrypt4b.Begin();
		char buf4b[256];
		ZERO_BUFFER(buf4b);
		int buf4b_used = encrypt4b.Transform(buf4b, sizeof(buf4b), STRING2, (BLOCKSIZE*3));
		buf4b_used += encrypt4b.Final(buf4b + buf4b_used, sizeof(buf4b));
		TEST_THAT(buf4b_used == (BLOCKSIZE*4));
		TEST_THAT(::memcmp(buf4, buf4b, (BLOCKSIZE*3)) == 0);

		// Decrypt
		char buf4_de[256];
		CipherContext decrypt4;
		decrypt4.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY)));
		decrypt4.UsePadding(false);
		decrypt4.Begin();
		ZERO_BUFFER(buf4_de);
		int buf4_de_used = decrypt4.Transform(buf4_de, sizeof(buf4_de), buf4, (BLOCKSIZE*3));
		buf4_de_used += decrypt4.Final(buf4_de+buf4_de_used, sizeof(buf4_de));
		TEST_THAT(buf4_de_used == (BLOCKSIZE*3));
		TEST_THAT(::memcmp(buf4_de, STRING2, (BLOCKSIZE*3)) == 0);
		
		// Test that the TransformBlock thing works as expected too with blocks the same size as the input
		TEST_THAT(encrypt4.TransformBlock(buf4, (BLOCKSIZE*3), STRING2, (BLOCKSIZE*3)) == (BLOCKSIZE*3));
		// But that it exceptions if we try the trick with padding on
		encrypt4.UsePadding(true);
		TEST_CHECK_THROWS(encrypt4.TransformBlock(buf4, (BLOCKSIZE*3), STRING2, (BLOCKSIZE*3)), CipherException, OutputBufferTooSmall);
	}
}

int test(int argc, const char *argv[])
{
	Random::Initialise();

	// Cipher type
	::printf("Blowfish...\n");
	test_cipher<CipherBlowfish, 8>();
#ifndef HAVE_OLD_SSL
	::printf("AES...\n");
	test_cipher<CipherAES, 16>();
#else
	::printf("Skipping AES -- not supported by version of OpenSSL in use.\n");
#endif
	
	// Test with known plaintext and ciphertext (correct algorithm used, etc)
	{
		FileStream keyfile("testfiles/bbackupd.keys");
		// Ideally we would use a 448 bit (56 byte) key here, since that's what we do in
		// real life, but unfortunately the OpenSSL command-line tool only supports 128-bit
		// Blowfish keys, so it's hard to generate a reference ciphertext unless we restrict
		// ourselves to what OpenSSL can support too.
		// https://security.stackexchange.com/questions/25393/openssl-blowfish-key-limited-to-256-bits
		char key[16], iv[BACKUPCRYPTOKEYS_FILENAME_IV_LENGTH];

		if(!keyfile.ReadFullBuffer(key, sizeof(key), 0))
		{
			TEST_FAIL_WITH_MESSAGE("Failed to read full key length from file");
		}

		keyfile.Seek(BACKUPCRYPTOKEYS_FILENAME_KEY_LENGTH, IOStream::SeekType_Absolute);
		if(!keyfile.ReadFullBuffer(iv, sizeof(iv), 0))
		{
			TEST_FAIL_WITH_MESSAGE("Failed to read full IV length from file");
		}

		CipherContext encryptor;
		CipherContext decryptor;

		encryptor.Reset();
		encryptor.Init(CipherContext::Encrypt, CipherBlowfish(CipherDescription::Mode_CBC, key, sizeof(key)));
		ASSERT(encryptor.GetIVLength() == sizeof(iv));
		encryptor.SetIV(iv);

		decryptor.Reset();
		decryptor.Init(CipherContext::Decrypt, CipherBlowfish(CipherDescription::Mode_CBC, key, sizeof(key)));
		ASSERT(decryptor.GetIVLength() == sizeof(iv));
		decryptor.SetIV(iv);

		// The encrypted file bfdlink.h.enc was generated with the following command:
		// key=`dd if=bbackupd.keys bs=1 count=16 | hexdump -e '/1 "%02x"'`
		// iv=`dd if=bbackupd.keys bs=1 skip=56 count=8 | hexdump -e '/1 "%02x"'`
		// openssl enc -bf -in bfdlink.h -K $key -iv $iv
		// And has MD5 checksum 586b65fdd07474bc139c0795d344d8ad
		FileStream plaintext_file("testfiles/bfdlink.h", O_RDONLY);
		FileStream ciphertext_file("testfiles/bfdlink.h.enc", O_RDONLY);

		CollectInBufferStream plaintext, ciphertext;
		plaintext_file.CopyStreamTo(plaintext);
		ciphertext_file.CopyStreamTo(ciphertext);
		plaintext.SetForReading();
		ciphertext.SetForReading();

		MemoryBlockGuard<void *> encrypted(
			encryptor.MaxOutSizeForInBufferSize(ciphertext.GetSize()));

		int encrypted_size = encryptor.TransformBlock(encrypted.GetPtr(),
			encrypted.GetSize(), plaintext.GetBuffer(), plaintext.GetSize());
		TEST_EQUAL(ciphertext.GetSize(), encrypted_size);
		TEST_EQUAL(0, memcmp(encrypted.GetPtr(), ciphertext.GetBuffer(), encrypted_size));

		MemoryBlockGuard<void *> decrypted(ciphertext.GetSize() + 16);

		int decrypted_size = decryptor.TransformBlock(decrypted.GetPtr(),
			decrypted.GetSize(), encrypted.GetPtr(), encrypted_size);
		TEST_EQUAL(plaintext.GetSize(), decrypted_size);
		TEST_EQUAL(0, memcmp(decrypted.GetPtr(), plaintext.GetBuffer(), decrypted_size));
	}

	::printf("Misc...\n");
	// Check rolling checksums
	uint8_t *checkdata_blk = (uint8_t *)malloc(CHECKSUM_DATA_SIZE);
	uint8_t *checkdata = checkdata_blk;
	RAND_bytes(checkdata, CHECKSUM_DATA_SIZE);
	for(int size = CHECKSUM_BLOCK_SIZE_BASE; size <= CHECKSUM_BLOCK_SIZE_LAST; ++size)
	{
		// Test skip-roll code
		RollingChecksum rollFast(checkdata, size);
		rollFast.RollForwardSeveral(checkdata, checkdata+size, size, CHECKSUM_ROLLS/2);
		RollingChecksum calc(checkdata + (CHECKSUM_ROLLS/2), size);
		TEST_THAT(calc.GetChecksum() == rollFast.GetChecksum());

		//printf("size = %d\n", size);
		// Checksum to roll
		RollingChecksum roll(checkdata, size);
		
		// Roll forward
		for(int l = 0; l < CHECKSUM_ROLLS; ++l)
		{			
			// Calculate new one
			RollingChecksum calc(checkdata, size);

			//printf("%08X %08X %d %d\n", roll.GetChecksum(), calc.GetChecksum(), checkdata[0], checkdata[size]);

			// Compare them!
			TEST_THAT(calc.GetChecksum() == roll.GetChecksum());
			
			// Roll it onwards
			roll.RollForward(checkdata[0], checkdata[size], size);
	
			// increment
			++checkdata;
		}
	}
	::free(checkdata_blk);

	// Random integers
	check_random_int(0);
	check_random_int(1);
	check_random_int(5);
	check_random_int(15);	// all 1's
	check_random_int(1022);

	return 0;
}