crypto: support openssl crypto backend#354
Conversation
Support configurable cryptographic backends. To do this, we have to address a few limitations: 1. The current ssl_bindings.h requires the inclusion of mbedtls headers to figure out how to build. This is problematic if one wants to support additional crypto backends, whether in tree or out of tree. 2. Detect which backend is being requested, at configure time, and then use the bindings specified. Move everything into an include directory to make backend switching as easy as pointing it to a new backend directory for the proper ssl_bindings.h. Keep mbedtls as the default so nothing changes. This can be configured by cmake .. -DCRYPTO_BACKEND=mbedtls New backends can be added now by simply creating a directory and updating the CMake files to point to a new directory. Additionally, add some unit tests around the crypto routines, so new backend authors get some basic test coverage. Signed-off-by: Bill Roberts <bill.roberts@arm.com>
|
@Aorimn could you take a look at this? |
abhinavagarwal07
left a comment
There was a problem hiding this comment.
Nice work on the pluggable backend design — the include-path switching approach is clean and the tests are a welcome addition. A few bugs and one performance concern to sort out before this is ready to merge.
|
|
||
| target_link_libraries(crypto_tests PRIVATE ${PROJECT_NAME} dislocker_crypto_backend) | ||
|
|
||
| add_test(NAME cyrpto_tests COMMAND crypto_tests) |
There was a problem hiding this comment.
Typo: cyrpto_tests → crypto_tests
|
|
||
| AES_SETDEC_KEY(&ctx, key_aes_256, sizeof(key_aes_256) * 8); | ||
|
|
||
| AES_CBC(&ctx, AES_DECRYPT, sizeof(expected_cbc_128), iv, expected_cbc_256, buf); |
There was a problem hiding this comment.
This should be sizeof(expected_cbc_256). Both arrays happen to be 512 bytes so it passes today, but it'll silently break if the vectors ever diverge.
| /* mbedtls requires a mutable IV */ | ||
| NEW_ARRAY_FROM(char, iv, static_iv); | ||
|
|
||
| /* Test AES CBC 128 Encryption */ |
There was a problem hiding this comment.
Comments here and on line 121 say "CBC 128" but this function tests CBC 256.
| if (key_len > sizeof(ctx->key)) | ||
| return 1; | ||
|
|
||
| memcpy(ctx->key, key, key_len); |
There was a problem hiding this comment.
memcpy is used here but this header doesn't include <string.h>. It happens to work because callers include it first, but the header should be self-contained.
| #define DIS_OSSL_CIPHER_ECB 0 | ||
| #define DIS_OSSL_CIPHER_CBC 1 | ||
|
|
||
| static inline const EVP_CIPHER *dis_ossle_get_cipher(size_t key_len, int ossl_mode) |
There was a problem hiding this comment.
Typo: dis_ossle_get_cipher → dis_ossl_get_cipher (stray e)
| if (!ossl_cipher) | ||
| return rc; | ||
|
|
||
| EVP_CIPHER_CTX *ossl_ctx = EVP_CIPHER_CTX_new(); |
There was a problem hiding this comment.
This allocates and tears down an EVP context on every call. AES_ECB_ENC is called per 16-byte block from the XTS/XEX inner loops, so for a 512-byte sector you're doing ~33 malloc/init/free cycles. Over a full disk decryption that's tens of millions of unnecessary allocations — going to be dramatically slower than the mbedtls path.
Worth either caching the EVP_CIPHER_CTX in the dis_ossl_aes_ctx struct or initializing it once at set-key time.
There was a problem hiding this comment.
This allocates and tears down an EVP context on every call.
AES_ECB_ENCis called per 16-byte block from the XTS/XEX inner loops, so for a 512-byte sector you're doing ~33 malloc/init/free cycles. Over a full disk decryption that's tens of millions of unnecessary allocations — going to be dramatically slower than the mbedtls path.
Probably not as much as one thinks, since alloc and free are fairly cheap unless the heap needs to grow. I didn't notice a decryption speed difference. But I didn't like this approach either because of what you state.
Worth either caching the
EVP_CIPHER_CTXin thedis_ossl_aes_ctxstruct or initializing it once at set-key time.
I think it would be cleaner if I just refactored the crypto interface, where we have an init, use, free pattern by the underlying crypto routines.
| #define AES_CBC(ctx, mode, size, iv, in, out) dis_ossl_aes_crypt(ctx, mode, size, iv, in, out, DIS_OSSL_CIPHER_CBC) | ||
|
|
||
| /* | ||
| * OpenSSL doesn't provide XEX nor XTS, so just use the dislocker implementations. Note that |
There was a problem hiding this comment.
This isn't quite right — OpenSSL does provide XTS (EVP_aes_128_xts() / EVP_aes_256_xts()). It doesn't have XEX, which is correct. Using OpenSSL's native XTS would also get you hardware acceleration (AES-NI) for free.
There was a problem hiding this comment.
Ahh I see that it has XTS, I didn't see that before when I looked.
| ${CMAKE_SOURCE_DIR}/include/${PROJECT_NAME}/encryption/mbedtls | ||
| ) | ||
| elseif(CRYPTO_BACKEND STREQUAL "openssl") | ||
| find_package(OpenSSL REQUIRED) |
There was a problem hiding this comment.
The mbedtls path specifies a minimum version (find_package(MbedTLS 3 REQUIRED)). Should probably do the same here — something like find_package(OpenSSL 1.1.0 REQUIRED) given the EVP API usage.
| ADD_TEST(test_ecb_encrypt_256); | ||
|
|
||
| ADD_TEST(test_cbc_encrypt_128); | ||
| ADD_TEST(test_cbc_encrypt_256); |
There was a problem hiding this comment.
Any plans to add XTS/XEX test coverage? Those are the modes BitLocker actually uses and the paths most likely to diverge between backends.
| #ifndef TEST_VECTORS_H_ | ||
| #define TEST_VECTORS_H_ | ||
|
|
||
| static const char key_aes_128[] = { |
There was a problem hiding this comment.
These vectors are declared as char but the AES functions expect unsigned char *. The project builds with -Wall -Wextra which will flag -Wpointer-sign mismatches. Should be static const unsigned char throughout.
Support configurable cryptographic backends.
To do this, we have to address a few limitations:
The current ssl_bindings.h requires the inclusion of mbedtls headers to figure out how to build. This is problematic if one wants to support additional crypto backends, whether in tree or out of tree.
Detect which backend is being requested, at configure time, and then use the bindings specified. Move everything into an include directory to make backend switching as easy as pointing it to a new backend directory for the proper ssl_bindings.h. Keep mbedtls as the default so nothing changes.
This can be configured by cmake .. -DCRYPTO_BACKEND=mbedtls
New backends can be added now by simply creating a directory and updating the CMake files to point to a new directory.
Additionally, add some unit tests around the crypto routines, so new backend authors get some basic test coverage.