Add some basic constant-flow tests for `mbedtls_cipher_crypt()`. We already
test auxiliary functions and functional behavior pretty thoroughly
elsewhere, so here just focus on the interesting cases for constant-flow
behavior with this specific function: encrypt, valid decrypt and
invalid-padding decrypt.
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
The main goal is to validate that unpadding is constant-time, including
error reporting.
Use a separate test function, not annotations in the existing function, so
that the functional tests can run on any platform, and we know from test
outcomes where we have run the constant-time tests.
The tests can only be actually constant-time if AES is constant time, since
AES computations are part of what is checked. Thus this requires
hardware-accelerated AES. We can't run our AESNI (or AESCE?) code under
Msan (it doesn't detect when memory is written from assembly code), so these
tests can only be run with Valgrind.
Same test data as the newly introduced functional tests.
#!/usr/bin/env python3
from Crypto.Cipher import AES
KEYS = {
128: bytes.fromhex("ffffffffe00000000000000000000000"),
192: bytes.fromhex("000000000000000000000000000000000000000000000000"),
256: bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000"),
}
IV = bytes.fromhex("00000000000000000000000000000000")
def decrypt_test_vec(cf, bits, mode, padded_hex, padding_length, note=''):
depends = ['MBEDTLS_AES_C', 'MBEDTLS_CIPHER_MODE_CBC']
plaintext = bytes.fromhex(padded_hex)
plaintext_length = len(plaintext)
if bits != 128:
depends.append('!MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH')
key = KEYS[bits]
iv = IV
result = '0'
if mode == 'NONE':
padding_description = 'no padding'
assert padding_length == 0
else:
depends.append('MBEDTLS_CIPHER_PADDING_' + mode)
padding_description = mode
if padding_length is None:
result = 'MBEDTLS_ERR_CIPHER_INVALID_PADDING'
plaintext_length = 0
else:
plaintext_length -= padding_length
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
ciphertext = cipher.encrypt(plaintext)
function = 'decrypt_test_vec'
cf_maybe = ''
if cf:
function += '_cf'
cf_maybe = 'CF '
depends.append('HAVE_CONSTANT_TIME_AES')
if note:
note = f' ({note})'
print(f'''\
{cf_maybe}AES-{bits}-CBC Decrypt test vector, {padding_description}{note}
depends_on:{':'.join(depends)}
{function}:MBEDTLS_CIPHER_AES_{bits}_CBC:MBEDTLS_PADDING_{mode}:"{key.hex()}":"{iv.hex()}":"{ciphertext.hex()}":"{plaintext[:plaintext_length].hex()}":"":"":{result}:0
''')
def emit_tests(cf):
# Already existing tests
decrypt_test_vec(cf, 128, 'NONE', "00000000000000000000000000000000", 0)
decrypt_test_vec(cf, 192, 'NONE', "fffffffff80000000000000000000000", 0)
decrypt_test_vec(cf, 256, 'NONE', "ff000000000000000000000000000000", 0)
# New tests
decrypt_test_vec(cf, 128, 'PKCS7', "00000000000000000000000000000001", 1, 'good pad 1')
decrypt_test_vec(cf, 192, 'PKCS7', "fffffffff80000000000000000000001", 1, 'good pad 1')
decrypt_test_vec(cf, 256, 'PKCS7', "ff000000000000000000000000000001", 1, 'good pad 1')
decrypt_test_vec(cf, 128, 'PKCS7', "00000000000000000000000000000202", 2, 'good pad 2')
decrypt_test_vec(cf, 192, 'PKCS7', "fffffffff80000000000000000000202", 2, 'good pad 2')
decrypt_test_vec(cf, 256, 'PKCS7', "ff000000000000000000000000000202", 2, 'good pad 2')
decrypt_test_vec(cf, 128, 'PKCS7', "2a0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", 15, 'good pad 15')
decrypt_test_vec(cf, 192, 'PKCS7', "2a0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", 15, 'good pad 15')
decrypt_test_vec(cf, 256, 'PKCS7', "2a0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", 15, 'good pad 15')
decrypt_test_vec(cf, 128, 'PKCS7', "10101010101010101010101010101010", 16, 'good pad 16')
decrypt_test_vec(cf, 192, 'PKCS7', "10101010101010101010101010101010", 16, 'good pad 16')
decrypt_test_vec(cf, 256, 'PKCS7', "10101010101010101010101010101010", 16, 'good pad 16')
decrypt_test_vec(cf, 128, 'PKCS7', "00000000000000000000000000000000", None, 'bad pad 0')
decrypt_test_vec(cf, 192, 'PKCS7', "fffffffff80000000000000000000000", None, 'bad pad 0')
decrypt_test_vec(cf, 256, 'PKCS7', "ff000000000000000000000000000000", None, 'bad pad 0')
decrypt_test_vec(cf, 128, 'PKCS7', "00000000000000000000000000000102", None, 'bad pad 0102')
decrypt_test_vec(cf, 192, 'PKCS7', "fffffffff80000000000000000000102", None, 'bad pad 0102')
decrypt_test_vec(cf, 256, 'PKCS7', "ff000000000000000000000000000102", None, 'bad pad 0102')
decrypt_test_vec(cf, 128, 'PKCS7', "1111111111111111111111111111111111111111111111111111111111111111", None, 'long, bad pad 17')
decrypt_test_vec(cf, 192, 'PKCS7', "1111111111111111111111111111111111111111111111111111111111111111", None, 'long, bad pad 17')
decrypt_test_vec(cf, 256, 'PKCS7', "1111111111111111111111111111111111111111111111111111111111111111", None, 'long, bad pad 17')
decrypt_test_vec(cf, 128, 'PKCS7', "11111111111111111111111111111111", None, 'short, bad pad 17')
decrypt_test_vec(cf, 192, 'PKCS7', "11111111111111111111111111111111", None, 'short, bad pad 17')
decrypt_test_vec(cf, 256, 'PKCS7', "11111111111111111111111111111111", None, 'short, bad pad 17')
emit_tests(True)
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
When trying to decrypt data with an invalid key, we found that `mbedtls`
returned `0x6200` (`-25088`), which means "_CIPHER - Input data contains
invalid padding and is rejected_" from `mbedtls_cipher_finish`, but it also
set the output len as `18446744073709551516`.
In case we detect an error with padding, we leave the output len zero'ed
and return `MBEDTLS_ERR_CIPHER_INVALID_PADDING`.
Here's a reference for the way `openssl` checks the padding length:
- 1848c561ec/crypto/evp/evp_enc.c (L1023)
- b554eef43b
So add a check ensuring output is set to the least-harmful value in the
error cases.
With the robustness fix:
`PASSED (125 suites, 26644 tests run)`
Without the robustness fix:
`FAILED (125 suites, 26644 tests run)`
Signed-off-by: Andre Goddard Rosa <andre.goddard@gmail.com>
Signed-off-by: Andre Goddard Rosa <agoddardrosa@roku.com>
Calling mbedtls_cipher_free() on a context that was not initialised
is dangerous, and this could happen if the first test in
check_set_padding() failed.
Signed-off-by: Paul Elliott <paul.elliott@arm.com>
Commit changes name of check_iv to
iv_len_validity as this seems to better describe
its functionality.
Signed-off-by: Thomas Daubney <thomas.daubney@arm.com>
The implementation was silently overwriting the IV length to 12
even though the caller passed a different value.
Change the behavior to signal that a different length is not supported.
Signed-off-by: Andrzej Kurek <andrzej.kurek@arm.com>
The implementation was silently overwriting the IV length to 12
even though the caller passed a different value.
Change the behavior to signal that a different length is not supported.
Signed-off-by: Andrzej Kurek <andrzej.kurek@arm.com>
Can't call mbedtls_cipher_free(&invalid_ctx) in cleanup if
mbedtls_cipher_init(&invalid_ctx) hasn't been called.
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
Most supported ciphers have a 128-bit, 192-bit or 256-bit keys. List the
exceptions explicitly.
This commit fixes a test failure with the null cipher and an incorrect
comment that omitted several key lengths.
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
Add functions to read the type, mode, name and key_bitlen fields from
mbedtls_cipher_info_t. These are the fields that applications are most
likely to care about.
TLS code also uses iv_size and block_size, which it might make sense to
expose, but most applications shouldn't need those, so I'm not exposing them
for now.
Call the new functions in unit tests, so they're at least smoke-tested.
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>