From: Dmitry Timoshkov dmitry@baikal.ru
Signed-off-by: Dmitry Timoshkov dmitry@baikal.ru --- dlls/bcrypt/Makefile.in | 1 + dlls/bcrypt/aes-wrap.c | 93 +++++++++++++++++++++++++++++++++++ dlls/bcrypt/bcrypt_internal.h | 2 + dlls/bcrypt/bcrypt_main.c | 56 +++++++++++++++++++-- dlls/bcrypt/tests/bcrypt.c | 4 -- 5 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 dlls/bcrypt/aes-wrap.c
diff --git a/dlls/bcrypt/Makefile.in b/dlls/bcrypt/Makefile.in index a672ac8a3c9..7fde522f32b 100644 --- a/dlls/bcrypt/Makefile.in +++ b/dlls/bcrypt/Makefile.in @@ -6,6 +6,7 @@ UNIXLIB = bcrypt.so UNIX_CFLAGS = $(GNUTLS_CFLAGS)
SOURCES = \ + aes-wrap.c \ bcrypt_main.c \ gnutls.c \ version.rc diff --git a/dlls/bcrypt/aes-wrap.c b/dlls/bcrypt/aes-wrap.c new file mode 100644 index 00000000000..4f12be7f71a --- /dev/null +++ b/dlls/bcrypt/aes-wrap.c @@ -0,0 +1,93 @@ +/* + * AES Key Wrap Algorithm (RFC3394) + * + * Copyright (c) 2003-2007, Jouni Malinen j@w1.fi + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include <stdarg.h> +#include <stdlib.h> + +#include "tomcrypt.h" + +static void *aes_encrypt_init(const unsigned char *key, size_t key_len) +{ + symmetric_key *skey = malloc(sizeof(*skey)); + if (skey) aes_setup(key, key_len, 0, skey); + return skey; +} + +static void aes_encrypt_deinit(void *ctx) +{ + aes_done(ctx); + free(ctx); +} + +static void aes_encrypt(void *ctx, const unsigned char *plain, unsigned char *cipher) +{ + aes_ecb_encrypt(plain, cipher, ctx); +} + +/** + * aes_wrap - Wrap keys with AES Key Wrap Algorithm (RFC3394) + * @kek: Key encryption key (KEK) + * @kek_len: Length of KEK in octets + * @n: Length of the plaintext key in 64-bit units; e.g., 2 = 128-bit = 16 + * bytes + * @plain: Plaintext key to be wrapped, n * 64 bits + * @cipher: Wrapped key, (n + 1) * 64 bits + * Returns: 0 on success, -1 on failure + */ +int aes_wrap(const unsigned char *kek, size_t kek_len, int n, const unsigned char *plain, unsigned char *cipher) +{ + unsigned char *a, *r, b[16]; + int i, j; + void *ctx; + unsigned int t; + + a = cipher; + r = cipher + 8; + + /* 1) Initialize variables. */ + memset(a, 0xa6, 8); + memcpy(r, plain, 8 * n); + + ctx = aes_encrypt_init(kek, kek_len); + if (ctx == NULL) + return -1; + + /* 2) Calculate intermediate values. + * For j = 0 to 5 + * For i=1 to n + * B = AES(K, A | R[i]) + * A = MSB(64, B) ^ t where t = (n*j)+i + * R[i] = LSB(64, B) + */ + for (j = 0; j <= 5; j++) { + r = cipher + 8; + for (i = 1; i <= n; i++) { + memcpy(b, a, 8); + memcpy(b + 8, r, 8); + aes_encrypt(ctx, b, b); + memcpy(a, b, 8); + t = n * j + i; + a[7] ^= t; + a[6] ^= t >> 8; + a[5] ^= t >> 16; + a[4] ^= t >> 24; + memcpy(r, b + 8, 8); + r += 8; + } + } + aes_encrypt_deinit(ctx); + + /* 3) Output the results. + * + * These are already in @cipher due to the location of temporary + * variables. + */ + + return 0; +} diff --git a/dlls/bcrypt/bcrypt_internal.h b/dlls/bcrypt/bcrypt_internal.h index 20c9a2912a5..62e31bcae32 100644 --- a/dlls/bcrypt/bcrypt_internal.h +++ b/dlls/bcrypt/bcrypt_internal.h @@ -274,4 +274,6 @@ enum key_funcs unix_funcs_count, };
+int aes_wrap(const UCHAR *kek, size_t kek_len, int n, const UCHAR *plain, UCHAR *cipher); + #endif /* __BCRYPT_INTERNAL_H */ diff --git a/dlls/bcrypt/bcrypt_main.c b/dlls/bcrypt/bcrypt_main.c index ec1004760f0..abbfbe5d6bf 100644 --- a/dlls/bcrypt/bcrypt_main.c +++ b/dlls/bcrypt/bcrypt_main.c @@ -1232,7 +1232,7 @@ static NTSTATUS key_import( struct algorithm *alg, const WCHAR *type, BCRYPT_KEY return STATUS_NOT_IMPLEMENTED; }
-static NTSTATUS key_export( struct key *key, const WCHAR *type, UCHAR *output, ULONG output_len, ULONG *size ) +static NTSTATUS key_export( struct key *key, struct key *encrypt_key, const WCHAR *type, UCHAR *output, ULONG output_len, ULONG *size ) { struct key_asymmetric_export_params params;
@@ -1241,6 +1241,12 @@ static NTSTATUS key_export( struct key *key, const WCHAR *type, UCHAR *output, U BCRYPT_KEY_DATA_BLOB_HEADER *header = (BCRYPT_KEY_DATA_BLOB_HEADER *)output; ULONG req_size = sizeof(BCRYPT_KEY_DATA_BLOB_HEADER) + key->u.s.secret_len;
+ if (encrypt_key) + { + FIXME( "encryption of key not yet supported\n" ); + return STATUS_NOT_IMPLEMENTED; + } + *size = req_size; if (output_len < req_size) return STATUS_BUFFER_TOO_SMALL; if (output) @@ -1256,6 +1262,12 @@ static NTSTATUS key_export( struct key *key, const WCHAR *type, UCHAR *output, U { ULONG len, req_size = sizeof(len) + key->u.s.secret_len;
+ if (encrypt_key) + { + FIXME( "encryption of key not yet supported\n" ); + return STATUS_NOT_IMPLEMENTED; + } + *size = req_size; if (output_len < req_size) return STATUS_BUFFER_TOO_SMALL; if (output) @@ -1268,6 +1280,12 @@ static NTSTATUS key_export( struct key *key, const WCHAR *type, UCHAR *output, U else if (!wcscmp( type, BCRYPT_DSA_PRIVATE_BLOB ) || !wcscmp( type, LEGACY_DSA_V2_PRIVATE_BLOB ) || !wcscmp( type, BCRYPT_ECCPRIVATE_BLOB ) || !wcscmp( type, BCRYPT_DH_PRIVATE_BLOB )) { + if (encrypt_key) + { + FIXME( "encryption of key not yet supported\n" ); + return STATUS_NOT_IMPLEMENTED; + } + params.key = key; params.flags = 0; params.buf = output; @@ -1277,6 +1295,12 @@ static NTSTATUS key_export( struct key *key, const WCHAR *type, UCHAR *output, U } else if (!wcscmp( type, BCRYPT_RSAPRIVATE_BLOB ) || !wcscmp( type, BCRYPT_RSAFULLPRIVATE_BLOB )) { + if (encrypt_key) + { + FIXME( "encryption of key not yet supported\n" ); + return STATUS_NOT_IMPLEMENTED; + } + params.key = key; params.flags = (wcscmp( type, BCRYPT_RSAPRIVATE_BLOB )) ? KEY_EXPORT_FLAG_RSA_FULL : 0; params.buf = output; @@ -1288,6 +1312,12 @@ static NTSTATUS key_export( struct key *key, const WCHAR *type, UCHAR *output, U !wcscmp( type, BCRYPT_ECCPUBLIC_BLOB ) || !wcscmp( type, BCRYPT_RSAPUBLIC_BLOB ) || !wcscmp( type, BCRYPT_DH_PUBLIC_BLOB )) { + if (encrypt_key) + { + FIXME( "encryption of key not yet supported\n" ); + return STATUS_NOT_IMPLEMENTED; + } + params.key = key; params.flags = KEY_EXPORT_FLAG_PUBLIC; params.buf = output; @@ -1295,6 +1325,23 @@ static NTSTATUS key_export( struct key *key, const WCHAR *type, UCHAR *output, U params.ret_len = size; return UNIX_CALL( key_asymmetric_export, ¶ms ); } + else if (!wcscmp( type, BCRYPT_AES_WRAP_KEY_BLOB )) + { + ULONG req_size = key->u.s.secret_len + 8; + + if (!encrypt_key) + return STATUS_INVALID_PARAMETER; + + *size = req_size; + if (output) + { + if (output_len < req_size) return STATUS_BUFFER_TOO_SMALL; + + if (aes_wrap( encrypt_key->u.s.secret, encrypt_key->u.s.secret_len, key->u.s.secret_len / 8, key->u.s.secret, output ) != 0) + return STATUS_NO_MEMORY; + } + return STATUS_SUCCESS; + }
FIXME( "unsupported key type %s\n", debugstr_w(type) ); return STATUS_NOT_IMPLEMENTED; @@ -1942,6 +1989,7 @@ NTSTATUS WINAPI BCryptExportKey( BCRYPT_KEY_HANDLE export_key_handle, BCRYPT_KEY const WCHAR *type, UCHAR *output, ULONG output_len, ULONG *size, ULONG flags ) { struct key *key = get_key_object( export_key_handle ); + struct key *encrypt_key = NULL;
TRACE( "%p, %p, %s, %p, %lu, %p, %#lx\n", export_key_handle, encrypt_key_handle, debugstr_w(type), output, output_len, size, flags ); @@ -1950,11 +1998,11 @@ NTSTATUS WINAPI BCryptExportKey( BCRYPT_KEY_HANDLE export_key_handle, BCRYPT_KEY if (!type || !size) return STATUS_INVALID_PARAMETER; if (encrypt_key_handle) { - FIXME( "encryption of key not yet supported\n" ); - return STATUS_NOT_IMPLEMENTED; + encrypt_key = get_key_object( encrypt_key_handle ); + if (!encrypt_key) return STATUS_INVALID_HANDLE; }
- return key_export( key, type, output, output_len, size ); + return key_export( key, encrypt_key, type, output, output_len, size ); }
static NTSTATUS key_duplicate( struct key *key_orig, struct key **ret_key ) diff --git a/dlls/bcrypt/tests/bcrypt.c b/dlls/bcrypt/tests/bcrypt.c index 7e33dcf201c..bf3221a7943 100644 --- a/dlls/bcrypt/tests/bcrypt.c +++ b/dlls/bcrypt/tests/bcrypt.c @@ -2111,15 +2111,11 @@ static void test_key_import_export(void)
size = 0; ret = BCryptExportKey(key, key2, BCRYPT_AES_WRAP_KEY_BLOB, NULL, 0, &size, 0); - todo_wine ok(ret == STATUS_SUCCESS, "got %#lx\n", ret); - todo_wine ok(size == sizeof(buffer3), "got %lu\n", size);
ret = BCryptExportKey(key, key2, BCRYPT_AES_WRAP_KEY_BLOB, buffer3, size, &size, 0); - todo_wine ok(ret == STATUS_SUCCESS, "got %#lx\n", ret); - todo_wine ok(!memcmp(buffer3, encrypted_blob, sizeof(encrypted_blob)), "blobs didn't match\n");
key3 = NULL;
From: Dmitry Timoshkov dmitry@baikal.ru
Signed-off-by: Dmitry Timoshkov dmitry@baikal.ru --- dlls/bcrypt/Makefile.in | 1 + dlls/bcrypt/aes-unwrap.c | 97 +++++++++++++++++++++++++++++++++++ dlls/bcrypt/bcrypt_internal.h | 1 + dlls/bcrypt/bcrypt_main.c | 48 ++++++++++++++--- dlls/bcrypt/tests/bcrypt.c | 5 -- 5 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 dlls/bcrypt/aes-unwrap.c
diff --git a/dlls/bcrypt/Makefile.in b/dlls/bcrypt/Makefile.in index 7fde522f32b..905958749b0 100644 --- a/dlls/bcrypt/Makefile.in +++ b/dlls/bcrypt/Makefile.in @@ -7,6 +7,7 @@ UNIX_CFLAGS = $(GNUTLS_CFLAGS)
SOURCES = \ aes-wrap.c \ + aes-unwrap.c \ bcrypt_main.c \ gnutls.c \ version.rc diff --git a/dlls/bcrypt/aes-unwrap.c b/dlls/bcrypt/aes-unwrap.c new file mode 100644 index 00000000000..20d1f8a5243 --- /dev/null +++ b/dlls/bcrypt/aes-unwrap.c @@ -0,0 +1,97 @@ +/* + * AES key unwrap (RFC3394) + * + * Copyright (c) 2003-2007, Jouni Malinen j@w1.fi + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include <stdarg.h> +#include <stdlib.h> + +#include "tomcrypt.h" + +static void *aes_decrypt_init(const unsigned char *key, size_t key_len) +{ + symmetric_key *skey = malloc(sizeof(*skey)); + if (skey) aes_setup(key, key_len, 0, skey); + return skey; +} + +static void aes_decrypt_deinit(void *ctx) +{ + aes_done(ctx); + free(ctx); +} + +static void aes_decrypt(void *ctx, const unsigned char *plain, unsigned char *cipher) +{ + aes_ecb_decrypt(plain, cipher, ctx); +} + +/** + * aes_unwrap - Unwrap key with AES Key Wrap Algorithm (RFC3394) + * @kek: Key encryption key (KEK) + * @kek_len: Length of KEK in octets + * @n: Length of the plaintext key in 64-bit units; e.g., 2 = 128-bit = 16 + * bytes + * @cipher: Wrapped key to be unwrapped, (n + 1) * 64 bits + * @plain: Plaintext key, n * 64 bits + * Returns: 0 on success, -1 on failure (e.g., integrity verification failed) + */ +int aes_unwrap(const unsigned char *kek, size_t kek_len, int n, const unsigned char *cipher, + unsigned char *plain) +{ + unsigned char a[8], *r, b[16]; + int i, j; + void *ctx; + unsigned int t; + + /* 1) Initialize variables. */ + memcpy(a, cipher, 8); + r = plain; + memcpy(r, cipher + 8, 8 * n); + + ctx = aes_decrypt_init(kek, kek_len); + if (ctx == NULL) + return -1; + + /* 2) Compute intermediate values. + * For j = 5 to 0 + * For i = n to 1 + * B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i + * A = MSB(64, B) + * R[i] = LSB(64, B) + */ + for (j = 5; j >= 0; j--) { + r = plain + (n - 1) * 8; + for (i = n; i >= 1; i--) { + memcpy(b, a, 8); + t = n * j + i; + b[7] ^= t; + b[6] ^= t >> 8; + b[5] ^= t >> 16; + b[4] ^= t >> 24; + + memcpy(b + 8, r, 8); + aes_decrypt(ctx, b, b); + memcpy(a, b, 8); + memcpy(r, b + 8, 8); + r -= 8; + } + } + aes_decrypt_deinit(ctx); + + /* 3) Output results. + * + * These are already in @plain due to the location of temporary + * variables. Just verify that the IV matches with the expected value. + */ + for (i = 0; i < 8; i++) { + if (a[i] != 0xa6) + return -1; + } + + return 0; +} diff --git a/dlls/bcrypt/bcrypt_internal.h b/dlls/bcrypt/bcrypt_internal.h index 62e31bcae32..2698add38cc 100644 --- a/dlls/bcrypt/bcrypt_internal.h +++ b/dlls/bcrypt/bcrypt_internal.h @@ -275,5 +275,6 @@ enum key_funcs };
int aes_wrap(const UCHAR *kek, size_t kek_len, int n, const UCHAR *plain, UCHAR *cipher); +int aes_unwrap(const unsigned char *kek, size_t kek_len, int n, const unsigned char *cipher, unsigned char *plain);
#endif /* __BCRYPT_INTERNAL_H */ diff --git a/dlls/bcrypt/bcrypt_main.c b/dlls/bcrypt/bcrypt_main.c index abbfbe5d6bf..08cfd714c23 100644 --- a/dlls/bcrypt/bcrypt_main.c +++ b/dlls/bcrypt/bcrypt_main.c @@ -1198,8 +1198,8 @@ static NTSTATUS generate_symmetric_key( struct algorithm *alg, BCRYPT_KEY_HANDLE return status; }
-static NTSTATUS key_import( struct algorithm *alg, const WCHAR *type, BCRYPT_KEY_HANDLE *key, UCHAR *object, - ULONG object_len, UCHAR *input, ULONG input_len ) +static NTSTATUS key_import( struct algorithm *alg, struct key *decrypt_key, const WCHAR *type, BCRYPT_KEY_HANDLE *key, + UCHAR *object, ULONG object_len, UCHAR *input, ULONG input_len ) { ULONG len;
@@ -1207,6 +1207,12 @@ static NTSTATUS key_import( struct algorithm *alg, const WCHAR *type, BCRYPT_KEY { BCRYPT_KEY_DATA_BLOB_HEADER *header = (BCRYPT_KEY_DATA_BLOB_HEADER *)input;
+ if (decrypt_key) + { + FIXME( "decryption of key not yet supported\n" ); + return STATUS_NOT_IMPLEMENTED; + } + if (input_len < sizeof(BCRYPT_KEY_DATA_BLOB_HEADER)) return STATUS_BUFFER_TOO_SMALL; if (header->dwMagic != BCRYPT_KEY_DATA_BLOB_MAGIC) return STATUS_INVALID_PARAMETER; if (header->dwVersion != BCRYPT_KEY_DATA_BLOB_VERSION1) @@ -1221,12 +1227,39 @@ static NTSTATUS key_import( struct algorithm *alg, const WCHAR *type, BCRYPT_KEY } else if (!wcscmp( type, BCRYPT_OPAQUE_KEY_BLOB )) { + if (decrypt_key) + { + FIXME( "decryption of key not yet supported\n" ); + return STATUS_NOT_IMPLEMENTED; + } + if (input_len < sizeof(len)) return STATUS_BUFFER_TOO_SMALL; len = *(ULONG *)input; if (len + sizeof(len) > input_len) return STATUS_INVALID_PARAMETER;
return generate_symmetric_key( alg, key, input + sizeof(len), len ); } + else if (!wcscmp( type, BCRYPT_AES_WRAP_KEY_BLOB )) + { + UCHAR output[256 / 8]; + + if (!decrypt_key) + return STATUS_INVALID_PARAMETER; + + if (input_len < 8) + return STATUS_INVALID_PARAMETER; + + len = input_len - 8; + + if (len < 128 / 8 || (len & (BLOCK_LENGTH_AES - 1)) || len > sizeof(output)) + return STATUS_INVALID_PARAMETER; + + if (aes_unwrap( decrypt_key->u.s.secret, decrypt_key->u.s.secret_len, len / 8, input, output )) + return STATUS_NO_MEMORY; + + return generate_symmetric_key( alg, key, output, len ); + } +
FIXME( "unsupported key type %s\n", debugstr_w(type) ); return STATUS_NOT_IMPLEMENTED; @@ -1962,11 +1995,12 @@ NTSTATUS WINAPI BCryptFinalizeKeyPair( BCRYPT_KEY_HANDLE handle, ULONG flags ) return ret; }
-NTSTATUS WINAPI BCryptImportKey( BCRYPT_ALG_HANDLE handle, BCRYPT_KEY_HANDLE decrypt_key, const WCHAR *type, +NTSTATUS WINAPI BCryptImportKey( BCRYPT_ALG_HANDLE handle, BCRYPT_KEY_HANDLE decrypt_key_handle, const WCHAR *type, BCRYPT_KEY_HANDLE *ret_handle, UCHAR *object, ULONG object_len, UCHAR *input, ULONG input_len, ULONG flags ) { struct algorithm *alg = get_alg_object( handle ); + struct key *decrypt_key = NULL; NTSTATUS status;
TRACE( "%p, %p, %s, %p, %p, %lu, %p, %lu, %#lx\n", handle, decrypt_key, debugstr_w(type), ret_handle, object, @@ -1974,13 +2008,13 @@ NTSTATUS WINAPI BCryptImportKey( BCRYPT_ALG_HANDLE handle, BCRYPT_KEY_HANDLE dec
if (!alg) return STATUS_INVALID_HANDLE; if (!ret_handle || !type || !input) return STATUS_INVALID_PARAMETER; - if (decrypt_key) + if (decrypt_key_handle) { - FIXME( "decryption of key not yet supported\n" ); - return STATUS_NOT_IMPLEMENTED; + decrypt_key = get_key_object( decrypt_key_handle ); + if (!decrypt_key) return STATUS_INVALID_HANDLE; }
- if ((status = key_import( alg, type, ret_handle, object, object_len, input, input_len ))) return status; + if ((status = key_import( alg, decrypt_key, type, ret_handle, object, object_len, input, input_len ))) return status; TRACE( "returning handle %p\n", *ret_handle ); return STATUS_SUCCESS; } diff --git a/dlls/bcrypt/tests/bcrypt.c b/dlls/bcrypt/tests/bcrypt.c index bf3221a7943..abb19087a7d 100644 --- a/dlls/bcrypt/tests/bcrypt.c +++ b/dlls/bcrypt/tests/bcrypt.c @@ -2120,19 +2120,14 @@ static void test_key_import_export(void)
key3 = NULL; ret = BCryptImportKey(aes, key2, BCRYPT_AES_WRAP_KEY_BLOB, &key3, NULL, 0, buffer3, sizeof(buffer3), 0); - todo_wine ok(ret == STATUS_SUCCESS, "got %#lx\n", ret); - todo_wine ok(key3 != NULL, "key not set\n");
size = 0; memset(buffer2, 0xff, sizeof(buffer2)); ret = BCryptExportKey(key3, NULL, BCRYPT_KEY_DATA_BLOB, buffer2, sizeof(buffer2), &size, 0); - todo_wine ok(ret == STATUS_SUCCESS, "got %#lx\n", ret); - todo_wine ok(size == sizeof(buffer2), "Got %lu\n", size); - todo_wine ok(!memcmp(buffer1, buffer2, sizeof(buffer1)), "Expected exported key to match imported key\n");
BCryptDestroyKey(key3);
I would prefer to avoid importing sources since it's only a handful of functions. We implement AES in bcrypt itself so we don't need to depend on libtomcrypt here.
On Wed Apr 2 15:10:45 2025 +0000, Hans Leidekker wrote:
I would prefer to avoid importing sources since it's only a handful of functions. We implement AES in bcrypt itself so we don't need to depend on libtomcrypt here.
Could you please clarify a bit? libtomcrypt is already linked with bcrypt.dll, so it's not a new dependency. aes-wrap.c and aes-unwrap.c are imported in many other projects, and they are just a few lines of code.
On Wed Apr 2 15:10:45 2025 +0000, Dmitry Timoshkov wrote:
Could you please clarify a bit? libtomcrypt is already linked with bcrypt.dll, so it's not a new dependency. aes-wrap.c and aes-unwrap.c are imported in many other projects, and they are just a few lines of code.
Right, it's just a few lines of code spread over two new files which can easily be avoided by rewriting them.
Depending on libtomcrypt for AES has a support cost that we're already paying for the GnuTLS based implementation.
On Wed Apr 2 15:56:52 2025 +0000, Hans Leidekker wrote:
Right, it's just a few lines of code spread over two new files which can easily be avoided by rewriting them. Depending on libtomcrypt for AES has a support cost that we're already paying for the GnuTLS based implementation.
So, what would you suggest? Get rid of libtomcrypt dependency and implement AES support from scratch?
On Wed Apr 2 16:02:32 2025 +0000, Dmitry Timoshkov wrote:
So, what would you suggest? Get rid of libtomcrypt dependency and implement AES support from scratch?
No, we already have an AES implementation in bcrypt.
On Wed Apr 2 16:09:23 2025 +0000, Hans Leidekker wrote:
No, we already have an AES implementation in bcrypt.
Do you mean gnutls wrappers?
On Wed Apr 2 16:16:06 2025 +0000, Dmitry Timoshkov wrote:
Do you mean gnutls wrappers?
I suspect this can all be done on the PE side using helpers like create_symmetric_key() and key_symmetric_en/decrypt().
I guess that's outside of the patches in this MR, and I'm not planning to work on reimplementation of AES support.
Feel free to use the code from this MR as a reference.
This merge request was closed by Dmitry Timoshkov.