class TestCrypto(unittest.TestCase): def setUp(self): self.crypto = Crypto({}) def test_create_encryption_context(self): value = 'encrypt me' * 100 # more than one cipher block key = os.urandom(32) iv = os.urandom(16) ctxt = self.crypto.create_encryption_ctxt(key, iv) expected = Cipher(algorithms.AES(key), modes.CTR(iv), backend=default_backend()).encryptor().update(value) self.assertEqual(expected, ctxt.update(value)) for bad_iv in ('a little too long', 'too short'): self.assertRaises(ValueError, self.crypto.create_encryption_ctxt, key, bad_iv) for bad_key in ('objKey', 'a' * 31, 'a' * 33, 'a' * 16, 'a' * 24): self.assertRaises(ValueError, self.crypto.create_encryption_ctxt, bad_key, iv) def test_create_decryption_context(self): value = 'decrypt me' * 100 # more than one cipher block key = os.urandom(32) iv = os.urandom(16) ctxt = self.crypto.create_decryption_ctxt(key, iv, 0) expected = Cipher(algorithms.AES(key), modes.CTR(iv), backend=default_backend()).decryptor().update(value) self.assertEqual(expected, ctxt.update(value)) for bad_iv in ('a little too long', 'too short'): self.assertRaises(ValueError, self.crypto.create_decryption_ctxt, key, bad_iv, 0) for bad_key in ('objKey', 'a' * 31, 'a' * 33, 'a' * 16, 'a' * 24): self.assertRaises(ValueError, self.crypto.create_decryption_ctxt, bad_key, iv, 0) with self.assertRaises(ValueError) as cm: self.crypto.create_decryption_ctxt(key, iv, -1) self.assertEqual("Offset must not be negative", cm.exception.message) def test_enc_dec_small_chunks(self): self.enc_dec_chunks(['encrypt me', 'because I', 'am sensitive']) def test_enc_dec_large_chunks(self): self.enc_dec_chunks([os.urandom(65536), os.urandom(65536)]) def enc_dec_chunks(self, chunks): key = 'objL7wjV6L79Sfs4y7dy41273l0k6Wki' iv = self.crypto.create_iv() enc_ctxt = self.crypto.create_encryption_ctxt(key, iv) enc_val = [enc_ctxt.update(chunk) for chunk in chunks] self.assertTrue(''.join(enc_val) != chunks) dec_ctxt = self.crypto.create_decryption_ctxt(key, iv, 0) dec_val = [dec_ctxt.update(chunk) for chunk in enc_val] self.assertEqual( ''.join(chunks), ''.join(dec_val), 'Expected value {%s} but got {%s}' % (''.join(chunks), ''.join(dec_val))) def test_decrypt_range(self): chunks = ['0123456789abcdef', 'ghijklmnopqrstuv'] key = 'objL7wjV6L79Sfs4y7dy41273l0k6Wki' iv = self.crypto.create_iv() enc_ctxt = self.crypto.create_encryption_ctxt(key, iv) enc_val = [enc_ctxt.update(chunk) for chunk in chunks] self.assertTrue(''.join(enc_val) != chunks) # Simulate a ranged GET from byte 19 to 32 : 'jklmnopqrstuv' dec_ctxt = self.crypto.create_decryption_ctxt(key, iv, 19) ranged_chunks = [enc_val[1][3:]] dec_val = [dec_ctxt.update(chunk) for chunk in ranged_chunks] self.assertEqual( 'jklmnopqrstuv', ''.join(dec_val), 'Expected value {%s} but got {%s}' % ('jklmnopqrstuv', ''.join(dec_val))) def test_create_decryption_context_non_zero_offset(self): # Verify that iv increments for each 16 bytes of offset. # For a ranged GET we pass a non-zero offset so that the decrypter # counter is incremented to the correct value to start decrypting at # that offset into the object body. The counter should increment by one # from the starting IV value for every 16 bytes offset into the object # body, until it reaches 2^128 -1 when it should wrap to zero. We check # that is happening by verifying a decrypted value using various # offsets. key = 'objL7wjV6L79Sfs4y7dy41273l0k6Wki' def do_test(): for offset, exp_iv in mappings.items(): dec_ctxt = self.crypto.create_decryption_ctxt(key, iv, offset) offset_in_block = offset % 16 cipher = Cipher(algorithms.AES(key), modes.CTR(exp_iv), backend=default_backend()) expected = cipher.decryptor().update('p' * offset_in_block + 'ciphertext') actual = dec_ctxt.update('ciphertext') expected = expected[offset % 16:] self.assertEqual( expected, actual, 'Expected %r but got %r, iv=%s and offset=%s' % (expected, actual, iv, offset)) iv = '0000000010000000' mappings = { 2: '0000000010000000', 16: '0000000010000001', 19: '0000000010000001', 48: '0000000010000003', 1024: '000000001000000p', 5119: '000000001000001o' } do_test() # choose max iv value and test that it wraps to zero iv = chr(0xff) * 16 mappings = { 2: iv, 16: str(bytearray.fromhex('00' * 16)), # iv wraps to 0 19: str(bytearray.fromhex('00' * 16)), 48: str(bytearray.fromhex('00' * 15 + '02')), 1024: str(bytearray.fromhex('00' * 15 + '3f')), 5119: str(bytearray.fromhex('00' * 14 + '013E')) } do_test() iv = chr(0x0) * 16 mappings = { 2: iv, 16: str(bytearray.fromhex('00' * 15 + '01')), 19: str(bytearray.fromhex('00' * 15 + '01')), 48: str(bytearray.fromhex('00' * 15 + '03')), 1024: str(bytearray.fromhex('00' * 15 + '40')), 5119: str(bytearray.fromhex('00' * 14 + '013F')) } do_test() iv = chr(0x0) * 8 + chr(0xff) * 8 mappings = { 2: iv, 16: str(bytearray.fromhex('00' * 7 + '01' + '00' * 8)), 19: str(bytearray.fromhex('00' * 7 + '01' + '00' * 8)), 48: str(bytearray.fromhex('00' * 7 + '01' + '00' * 7 + '02')), 1024: str(bytearray.fromhex('00' * 7 + '01' + '00' * 7 + '3F')), 5119: str(bytearray.fromhex('00' * 7 + '01' + '00' * 6 + '013E')) } do_test() def test_check_key(self): for key in ('objKey', 'a' * 31, 'a' * 33, 'a' * 16, 'a' * 24): with self.assertRaises(ValueError) as cm: self.crypto.check_key(key) self.assertEqual("Key must be length 32 bytes", cm.exception.message) def test_check_crypto_meta(self): meta = {'cipher': 'AES_CTR_256'} with self.assertRaises(EncryptionException) as cm: self.crypto.check_crypto_meta(meta) self.assertEqual("Bad crypto meta: Missing 'iv'", cm.exception.message) for bad_iv in ('a little too long', 'too short'): meta['iv'] = bad_iv with self.assertRaises(EncryptionException) as cm: self.crypto.check_crypto_meta(meta) self.assertEqual("Bad crypto meta: IV must be length 16 bytes", cm.exception.message) meta = {'iv': os.urandom(16)} with self.assertRaises(EncryptionException) as cm: self.crypto.check_crypto_meta(meta) self.assertEqual("Bad crypto meta: Missing 'cipher'", cm.exception.message) meta['cipher'] = 'Mystery cipher' with self.assertRaises(EncryptionException) as cm: self.crypto.check_crypto_meta(meta) self.assertEqual("Bad crypto meta: Cipher must be AES_CTR_256", cm.exception.message) def test_create_iv(self): self.assertEqual(16, len(self.crypto.create_iv())) # crude check that we get back different values on each call self.assertNotEqual(self.crypto.create_iv(), self.crypto.create_iv()) def test_get_crypto_meta(self): meta = self.crypto.create_crypto_meta() self.assertIsInstance(meta, dict) # this is deliberately brittle so that if new items are added then the # test will need to be updated self.assertEqual(2, len(meta)) self.assertIn('iv', meta) self.assertEqual(16, len(meta['iv'])) self.assertIn('cipher', meta) self.assertEqual('AES_CTR_256', meta['cipher']) self.crypto.check_crypto_meta(meta) # sanity check meta2 = self.crypto.create_crypto_meta() self.assertNotEqual(meta['iv'], meta2['iv']) # crude sanity check def test_create_random_key(self): # crude check that we get unique keys on each call keys = set() for i in range(10): key = self.crypto.create_random_key() self.assertEqual(32, len(key)) keys.add(key) self.assertEqual(10, len(keys)) def test_wrap_unwrap_key(self): wrapping_key = os.urandom(32) key_to_wrap = os.urandom(32) iv = os.urandom(16) with mock.patch( 'swift.common.middleware.crypto.crypto_utils.Crypto.create_iv', return_value=iv): wrapped = self.crypto.wrap_key(wrapping_key, key_to_wrap) cipher = Cipher(algorithms.AES(wrapping_key), modes.CTR(iv), backend=default_backend()) expected = {'key': cipher.encryptor().update(key_to_wrap), 'iv': iv} self.assertEqual(expected, wrapped) unwrapped = self.crypto.unwrap_key(wrapping_key, wrapped) self.assertEqual(key_to_wrap, unwrapped) def test_unwrap_bad_key(self): # verify that ValueError is raised if unwrapped key is invalid wrapping_key = os.urandom(32) for length in (0, 16, 24, 31, 33): key_to_wrap = os.urandom(length) wrapped = self.crypto.wrap_key(wrapping_key, key_to_wrap) with self.assertRaises(ValueError) as cm: self.crypto.unwrap_key(wrapping_key, wrapped) self.assertEqual(cm.exception.message, 'Key must be length 32 bytes')
class TestCrypto(unittest.TestCase): def setUp(self): self.crypto = Crypto({}) def test_create_encryption_context(self): value = b'encrypt me' * 100 # more than one cipher block key = os.urandom(32) iv = os.urandom(16) ctxt = self.crypto.create_encryption_ctxt(key, iv) expected = Cipher( algorithms.AES(key), modes.CTR(iv), backend=default_backend()).encryptor().update(value) self.assertEqual(expected, ctxt.update(value)) for bad_iv in (b'a little too long', b'too short'): self.assertRaises( ValueError, self.crypto.create_encryption_ctxt, key, bad_iv) for bad_key in (b'objKey', b'a' * 31, b'a' * 33, b'a' * 16, b'a' * 24): self.assertRaises( ValueError, self.crypto.create_encryption_ctxt, bad_key, iv) def test_create_decryption_context(self): value = b'decrypt me' * 100 # more than one cipher block key = os.urandom(32) iv = os.urandom(16) ctxt = self.crypto.create_decryption_ctxt(key, iv, 0) expected = Cipher( algorithms.AES(key), modes.CTR(iv), backend=default_backend()).decryptor().update(value) self.assertEqual(expected, ctxt.update(value)) for bad_iv in (b'a little too long', b'too short'): self.assertRaises( ValueError, self.crypto.create_decryption_ctxt, key, bad_iv, 0) for bad_key in (b'objKey', b'a' * 31, b'a' * 33, b'a' * 16, b'a' * 24): self.assertRaises( ValueError, self.crypto.create_decryption_ctxt, bad_key, iv, 0) with self.assertRaises(ValueError) as cm: self.crypto.create_decryption_ctxt(key, iv, -1) self.assertEqual("Offset must not be negative", cm.exception.args[0]) def test_enc_dec_small_chunks(self): self.enc_dec_chunks([b'encrypt me', b'because I', b'am sensitive']) def test_enc_dec_large_chunks(self): self.enc_dec_chunks([os.urandom(65536), os.urandom(65536)]) def enc_dec_chunks(self, chunks): key = b'objL7wjV6L79Sfs4y7dy41273l0k6Wki' iv = self.crypto.create_iv() enc_ctxt = self.crypto.create_encryption_ctxt(key, iv) enc_val = [enc_ctxt.update(chunk) for chunk in chunks] self.assertTrue(b''.join(enc_val) != chunks) dec_ctxt = self.crypto.create_decryption_ctxt(key, iv, 0) dec_val = [dec_ctxt.update(chunk) for chunk in enc_val] self.assertEqual(b''.join(chunks), b''.join(dec_val), 'Expected value {%s} but got {%s}' % (b''.join(chunks), b''.join(dec_val))) def test_decrypt_range(self): chunks = [b'0123456789abcdef', b'ghijklmnopqrstuv'] key = b'objL7wjV6L79Sfs4y7dy41273l0k6Wki' iv = self.crypto.create_iv() enc_ctxt = self.crypto.create_encryption_ctxt(key, iv) enc_val = [enc_ctxt.update(chunk) for chunk in chunks] # Simulate a ranged GET from byte 19 to 32 : 'jklmnopqrstuv' dec_ctxt = self.crypto.create_decryption_ctxt(key, iv, 19) ranged_chunks = [enc_val[1][3:]] dec_val = [dec_ctxt.update(chunk) for chunk in ranged_chunks] self.assertEqual(b'jklmnopqrstuv', b''.join(dec_val), 'Expected value {%s} but got {%s}' % (b'jklmnopqrstuv', b''.join(dec_val))) def test_create_decryption_context_non_zero_offset(self): # Verify that iv increments for each 16 bytes of offset. # For a ranged GET we pass a non-zero offset so that the decrypter # counter is incremented to the correct value to start decrypting at # that offset into the object body. The counter should increment by one # from the starting IV value for every 16 bytes offset into the object # body, until it reaches 2^128 -1 when it should wrap to zero. We check # that is happening by verifying a decrypted value using various # offsets. key = b'objL7wjV6L79Sfs4y7dy41273l0k6Wki' def do_test(): for offset, exp_iv in mappings.items(): dec_ctxt = self.crypto.create_decryption_ctxt(key, iv, offset) offset_in_block = offset % 16 cipher = Cipher(algorithms.AES(key), modes.CTR(exp_iv), backend=default_backend()) expected = cipher.decryptor().update( b'p' * offset_in_block + b'ciphertext') actual = dec_ctxt.update(b'ciphertext') expected = expected[offset % 16:] self.assertEqual(expected, actual, 'Expected %r but got %r, iv=%s and offset=%s' % (expected, actual, iv, offset)) iv = b'0000000010000000' mappings = { 2: b'0000000010000000', 16: b'0000000010000001', 19: b'0000000010000001', 48: b'0000000010000003', 1024: b'000000001000000p', 5119: b'000000001000001o' } do_test() # choose max iv value and test that it wraps to zero iv = b'\xff' * 16 mappings = { 2: iv, 16: bytes(bytearray.fromhex('00' * 16)), # iv wraps to 0 19: bytes(bytearray.fromhex('00' * 16)), 48: bytes(bytearray.fromhex('00' * 15 + '02')), 1024: bytes(bytearray.fromhex('00' * 15 + '3f')), 5119: bytes(bytearray.fromhex('00' * 14 + '013E')) } do_test() iv = b'\x00' * 16 mappings = { 2: iv, 16: bytes(bytearray.fromhex('00' * 15 + '01')), 19: bytes(bytearray.fromhex('00' * 15 + '01')), 48: bytes(bytearray.fromhex('00' * 15 + '03')), 1024: bytes(bytearray.fromhex('00' * 15 + '40')), 5119: bytes(bytearray.fromhex('00' * 14 + '013F')) } do_test() iv = b'\x00' * 8 + b'\xff' * 8 mappings = { 2: iv, 16: bytes(bytearray.fromhex('00' * 7 + '01' + '00' * 8)), 19: bytes(bytearray.fromhex('00' * 7 + '01' + '00' * 8)), 48: bytes(bytearray.fromhex('00' * 7 + '01' + '00' * 7 + '02')), 1024: bytes(bytearray.fromhex('00' * 7 + '01' + '00' * 7 + '3F')), 5119: bytes(bytearray.fromhex('00' * 7 + '01' + '00' * 6 + '013E')) } do_test() def test_check_key(self): for key in ('objKey', 'a' * 31, 'a' * 33, 'a' * 16, 'a' * 24): with self.assertRaises(ValueError) as cm: self.crypto.check_key(key) self.assertEqual("Key must be length 32 bytes", cm.exception.args[0]) def test_check_crypto_meta(self): meta = {'cipher': 'AES_CTR_256'} with self.assertRaises(EncryptionException) as cm: self.crypto.check_crypto_meta(meta) self.assertEqual("Bad crypto meta: Missing 'iv'", cm.exception.args[0]) for bad_iv in ('a little too long', 'too short'): meta['iv'] = bad_iv with self.assertRaises(EncryptionException) as cm: self.crypto.check_crypto_meta(meta) self.assertEqual("Bad crypto meta: IV must be length 16 bytes", cm.exception.args[0]) meta = {'iv': os.urandom(16)} with self.assertRaises(EncryptionException) as cm: self.crypto.check_crypto_meta(meta) self.assertEqual("Bad crypto meta: Missing 'cipher'", cm.exception.args[0]) meta['cipher'] = 'Mystery cipher' with self.assertRaises(EncryptionException) as cm: self.crypto.check_crypto_meta(meta) self.assertEqual("Bad crypto meta: Cipher must be AES_CTR_256", cm.exception.args[0]) def test_create_iv(self): self.assertEqual(16, len(self.crypto.create_iv())) # crude check that we get back different values on each call self.assertNotEqual(self.crypto.create_iv(), self.crypto.create_iv()) def test_get_crypto_meta(self): meta = self.crypto.create_crypto_meta() self.assertIsInstance(meta, dict) # this is deliberately brittle so that if new items are added then the # test will need to be updated self.assertEqual(2, len(meta)) self.assertIn('iv', meta) self.assertEqual(16, len(meta['iv'])) self.assertIn('cipher', meta) self.assertEqual('AES_CTR_256', meta['cipher']) self.crypto.check_crypto_meta(meta) # sanity check meta2 = self.crypto.create_crypto_meta() self.assertNotEqual(meta['iv'], meta2['iv']) # crude sanity check def test_create_random_key(self): # crude check that we get unique keys on each call keys = set() for i in range(10): key = self.crypto.create_random_key() self.assertEqual(32, len(key)) keys.add(key) self.assertEqual(10, len(keys)) def test_wrap_unwrap_key(self): wrapping_key = os.urandom(32) key_to_wrap = os.urandom(32) iv = os.urandom(16) with mock.patch( 'swift.common.middleware.crypto.crypto_utils.Crypto.create_iv', return_value=iv): wrapped = self.crypto.wrap_key(wrapping_key, key_to_wrap) cipher = Cipher(algorithms.AES(wrapping_key), modes.CTR(iv), backend=default_backend()) expected = {'key': cipher.encryptor().update(key_to_wrap), 'iv': iv} self.assertEqual(expected, wrapped) unwrapped = self.crypto.unwrap_key(wrapping_key, wrapped) self.assertEqual(key_to_wrap, unwrapped) def test_unwrap_bad_key(self): # verify that ValueError is raised if unwrapped key is invalid wrapping_key = os.urandom(32) for length in (0, 16, 24, 31, 33): key_to_wrap = os.urandom(length) wrapped = self.crypto.wrap_key(wrapping_key, key_to_wrap) with self.assertRaises(ValueError) as cm: self.crypto.unwrap_key(wrapping_key, wrapped) self.assertEqual( cm.exception.args[0], 'Key must be length 32 bytes')