`SIZE_MAX` and `~(size_t) 0` are the same, but since the documentation says
"all-bits-one", write it that way in the test code.
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
The decrypted length reveals the amount of padding that was eliminated, and
thus reveals partial information about the last ciphertext block.
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
In internal `get_padding` functions, report whether the padding was invalid
through a separate output parameter, rather than the return code. Take
advantage of this to have `mbedtls_cipher_finish_padded()` be the easy path
that just passes the `invalid_padding` through. Make
`mbedtls_cipher_finish()` a wrapper around `mbedtls_cipher_finish_padded()`
that converts the invalid-padding output into an error code.
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
New function `mbedtls_cipher_finish_padded()`, similar to
`mbedtls_cipher_finish()`, but reporting padding errors through a separate
output parameter. This makes it easier to avoid leaking the presence of a
padding error, especially through timing. Thus the new function is
recommended to defend against padding oracle attacks.
In this commit, implement this function naively, with timing that depends on
whether an error happened. A subsequent commit will make this function
constant-time.
Copy the test decrypt_test_vec and decrypt_test_vec_cf test cases into
variants that call `mbedtls_cipher_finish_padded()`.
Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
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>