Beispiel #1
0
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')
Beispiel #2
0
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')