[PATCH 0/2] MR11561: bcrypt: Support BCRYPT_INITIALIZATION_VECTOR
Games like Forza Motorsport imports an AES-128 key, sets the initialization vector with `BCryptSetProperty()` and then calls `BCryptDecrypt()` with a NULL pbIV. `set_key_property()` rejects the property with STATUS_NOT_IMPLEMENTED, so the game never decrypts any of its content. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11561
From: Allan Vester <vesterallan246@gmail.com> --- dlls/bcrypt/tests/bcrypt.c | 86 ++++++++++++++++++++++++++++++++++++++ include/bcrypt.h | 2 + 2 files changed, 88 insertions(+) diff --git a/dlls/bcrypt/tests/bcrypt.c b/dlls/bcrypt/tests/bcrypt.c index 1da30417b73..7e28408d00f 100644 --- a/dlls/bcrypt/tests/bcrypt.c +++ b/dlls/bcrypt/tests/bcrypt.c @@ -4229,6 +4229,91 @@ static void test_aes_vector(void) ok(!ret, "got %#lx\n", ret); } +static void test_aes_vector_property(void) +{ + static const UCHAR secret[] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10}; + static const UCHAR vector[] = {0x0f,0x0e,0x0d,0x0c,0x0b,0x0a,0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x00}; + static const UCHAR expect[] = {0x3e,0xfa,0x1a,0xc8,0x92,0x54,0xe4,0x21,0x1a,0x3d,0xfd,0x42,0x1c,0xc0,0x7d,0x20}; + static const UCHAR expect2[] = {0xb0,0xcb,0xf5,0x80,0xd4,0xe3,0x55,0x23,0x6e,0x19,0x5b,0xdb,0xfe,0xe0,0x6c,0xd3}; + static UCHAR input[] = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p'}; + UCHAR data[sizeof(BCRYPT_KEY_DATA_BLOB_HEADER) + sizeof(secret)]; + BCRYPT_KEY_DATA_BLOB_HEADER *blob = (BCRYPT_KEY_DATA_BLOB_HEADER *)data; + UCHAR output[16], iv[16]; + BCRYPT_ALG_HANDLE alg; + BCRYPT_KEY_HANDLE key; + ULONG size; + NTSTATUS ret; + + ret = BCryptOpenAlgorithmProvider(&alg, BCRYPT_AES_ALGORITHM, NULL, 0); + ok(!ret, "got %#lx\n", ret); + + size = sizeof(BCRYPT_CHAIN_MODE_CBC); + ret = BCryptSetProperty(alg, BCRYPT_CHAINING_MODE, (UCHAR *)BCRYPT_CHAIN_MODE_CBC, size, 0); + ok(!ret, "got %#lx\n", ret); + + blob->dwMagic = BCRYPT_KEY_DATA_BLOB_MAGIC; + blob->dwVersion = BCRYPT_KEY_DATA_BLOB_VERSION1; + blob->cbKeyData = sizeof(secret); + memcpy(data + sizeof(*blob), secret, sizeof(secret)); + size = sizeof(BCRYPT_KEY_DATA_BLOB_HEADER) + sizeof(secret); + ret = BCryptImportKey(alg, NULL, BCRYPT_KEY_DATA_BLOB, &key, NULL, 0, data, size, 0); + ok(!ret, "got %#lx\n", ret); + + ret = BCryptSetProperty(key, BCRYPT_INITIALIZATION_VECTOR, (UCHAR *)vector, sizeof(vector) - 1, 0); + todo_wine ok(ret == STATUS_INVALID_PARAMETER, "got %#lx\n", ret); + + /* the initialization vector can be set on the key instead of being passed to BCryptEncrypt() */ + ret = BCryptSetProperty(key, BCRYPT_INITIALIZATION_VECTOR, (UCHAR *)vector, sizeof(vector), 0); + todo_wine ok(!ret, "got %#lx\n", ret); + + size = 0; + memset(output, 0, sizeof(output)); + ret = BCryptEncrypt(key, input, sizeof(input), NULL, NULL, 0, output, sizeof(output), &size, 0); + ok(!ret, "got %#lx\n", ret); + ok(size == 16, "got %lu\n", size); + todo_wine ok(!memcmp(output, expect, sizeof(expect)), "wrong cipher text\n"); + + /* setting it again restarts the chain instead of continuing it */ + ret = BCryptSetProperty(key, BCRYPT_INITIALIZATION_VECTOR, (UCHAR *)vector, sizeof(vector), 0); + todo_wine ok(!ret, "got %#lx\n", ret); + + size = 0; + memset(output, 0, sizeof(output)); + ret = BCryptEncrypt(key, input, sizeof(input), NULL, NULL, 0, output, sizeof(output), &size, 0); + ok(!ret, "got %#lx\n", ret); + ok(size == 16, "got %lu\n", size); + todo_wine ok(!memcmp(output, expect, sizeof(expect)), "wrong cipher text\n"); + + /* a vector passed to BCryptEncrypt() overrides the one set on the key */ + ret = BCryptSetProperty(key, BCRYPT_INITIALIZATION_VECTOR, (UCHAR *)vector, sizeof(vector), 0); + todo_wine ok(!ret, "got %#lx\n", ret); + + size = 0; + memset(iv, 0, sizeof(iv)); + memset(output, 0, sizeof(output)); + ret = BCryptEncrypt(key, input, sizeof(input), NULL, iv, sizeof(iv), output, sizeof(output), &size, 0); + ok(!ret, "got %#lx\n", ret); + ok(size == 16, "got %lu\n", size); + ok(!memcmp(output, expect2, sizeof(expect2)), "wrong cipher text\n"); + + /* and the same vector decrypts what it encrypted */ + ret = BCryptSetProperty(key, BCRYPT_INITIALIZATION_VECTOR, (UCHAR *)vector, sizeof(vector), 0); + todo_wine ok(!ret, "got %#lx\n", ret); + + size = 0; + memset(output, 0, sizeof(output)); + ret = BCryptDecrypt(key, (UCHAR *)expect, sizeof(expect), NULL, NULL, 0, output, sizeof(output), &size, 0); + ok(!ret, "got %#lx\n", ret); + ok(size == 16, "got %lu\n", size); + todo_wine ok(!memcmp(output, input, sizeof(input)), "wrong plain text\n"); + + ret = BCryptDestroyKey(key); + ok(!ret, "got %#lx\n", ret); + + ret = BCryptCloseAlgorithmProvider(alg, 0); + ok(!ret, "got %#lx\n", ret); +} + static void test_BcryptDeriveKeyCapi(void) { static const UCHAR expect[] = @@ -5227,6 +5312,7 @@ START_TEST(bcrypt) test_BCryptSignHash(); test_BCryptEnumAlgorithms(); test_aes_vector(); + test_aes_vector_property(); test_BcryptDeriveKeyCapi(); test_DSA(); test_SecretAgreement(); diff --git a/include/bcrypt.h b/include/bcrypt.h index 73336849926..eac5abdcc69 100644 --- a/include/bcrypt.h +++ b/include/bcrypt.h @@ -58,6 +58,7 @@ extern "C" { #define BCRYPT_HASH_BLOCK_LENGTH L"HashBlockLength" #define BCRYPT_HASH_LENGTH L"HashDigestLength" #define BCRYPT_HASH_OID_LIST L"HashOIDList" +#define BCRYPT_INITIALIZATION_VECTOR L"IV" #define BCRYPT_KEY_LENGTH L"KeyLength" #define BCRYPT_KEY_LENGTHS L"KeyLengths" #define BCRYPT_KEY_OBJECT_LENGTH L"KeyObjectLength" @@ -157,6 +158,7 @@ static const WCHAR BCRYPT_EFFECTIVE_KEY_LENGTH[] = {'E','f','f','e','c','t','i', static const WCHAR BCRYPT_HASH_BLOCK_LENGTH[] = {'H','a','s','h','B','l','o','c','k','L','e','n','g','t','h',0}; static const WCHAR BCRYPT_HASH_LENGTH[] = {'H','a','s','h','D','i','g','e','s','t','L','e','n','g','t','h',0}; static const WCHAR BCRYPT_HASH_OID_LIST[] = {'H','a','s','h','O','I','D','L','i','s','t',0}; +static const WCHAR BCRYPT_INITIALIZATION_VECTOR[] = {'I','V',0}; static const WCHAR BCRYPT_KEY_LENGTH[] = {'K','e','y','L','e','n','g','t','h',0}; static const WCHAR BCRYPT_KEY_LENGTHS[] = {'K','e','y','L','e','n','g','t','h','s',0}; static const WCHAR BCRYPT_KEY_OBJECT_LENGTH[] = {'K','e','y','O','b','j','e','c','t','L','e','n','g','t','h',0}; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11561
From: Allan Vester <vesterallan246@gmail.com> Forza Motorsport imports an AES-128 key, sets the initialization vector with BCryptSetProperty() and then calls BCryptDecrypt() with a NULL pbIV. set_key_property() rejects the property with STATUS_NOT_IMPLEMENTED, so the game never decrypts any of its content. --- dlls/bcrypt/bcrypt_main.c | 10 ++++++++++ dlls/bcrypt/tests/bcrypt.c | 16 ++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/dlls/bcrypt/bcrypt_main.c b/dlls/bcrypt/bcrypt_main.c index 7260ac62e9d..c76480bd857 100644 --- a/dlls/bcrypt/bcrypt_main.c +++ b/dlls/bcrypt/bcrypt_main.c @@ -1127,6 +1127,16 @@ static NTSTATUS set_key_property( struct key *key, const WCHAR *prop, UCHAR *val return STATUS_NOT_IMPLEMENTED; } } + else if (!wcscmp( prop, BCRYPT_INITIALIZATION_VECTOR )) + { + if (!is_symmetric_key( key )) return STATUS_INVALID_HANDLE; + if (size != key->s.block_size || size > sizeof(key->s.vector)) return STATUS_INVALID_PARAMETER; + + EnterCriticalSection( &key->s.cs ); + memcpy( key->s.vector, value, size ); + LeaveCriticalSection( &key->s.cs ); + return STATUS_SUCCESS; + } else if (!wcscmp( prop, BCRYPT_KEY_LENGTH )) { if (size < sizeof(DWORD)) return STATUS_INVALID_PARAMETER; diff --git a/dlls/bcrypt/tests/bcrypt.c b/dlls/bcrypt/tests/bcrypt.c index 7e28408d00f..d11567e7ef5 100644 --- a/dlls/bcrypt/tests/bcrypt.c +++ b/dlls/bcrypt/tests/bcrypt.c @@ -4260,33 +4260,33 @@ static void test_aes_vector_property(void) ok(!ret, "got %#lx\n", ret); ret = BCryptSetProperty(key, BCRYPT_INITIALIZATION_VECTOR, (UCHAR *)vector, sizeof(vector) - 1, 0); - todo_wine ok(ret == STATUS_INVALID_PARAMETER, "got %#lx\n", ret); + ok(ret == STATUS_INVALID_PARAMETER, "got %#lx\n", ret); /* the initialization vector can be set on the key instead of being passed to BCryptEncrypt() */ ret = BCryptSetProperty(key, BCRYPT_INITIALIZATION_VECTOR, (UCHAR *)vector, sizeof(vector), 0); - todo_wine ok(!ret, "got %#lx\n", ret); + ok(!ret, "got %#lx\n", ret); size = 0; memset(output, 0, sizeof(output)); ret = BCryptEncrypt(key, input, sizeof(input), NULL, NULL, 0, output, sizeof(output), &size, 0); ok(!ret, "got %#lx\n", ret); ok(size == 16, "got %lu\n", size); - todo_wine ok(!memcmp(output, expect, sizeof(expect)), "wrong cipher text\n"); + ok(!memcmp(output, expect, sizeof(expect)), "wrong cipher text\n"); /* setting it again restarts the chain instead of continuing it */ ret = BCryptSetProperty(key, BCRYPT_INITIALIZATION_VECTOR, (UCHAR *)vector, sizeof(vector), 0); - todo_wine ok(!ret, "got %#lx\n", ret); + ok(!ret, "got %#lx\n", ret); size = 0; memset(output, 0, sizeof(output)); ret = BCryptEncrypt(key, input, sizeof(input), NULL, NULL, 0, output, sizeof(output), &size, 0); ok(!ret, "got %#lx\n", ret); ok(size == 16, "got %lu\n", size); - todo_wine ok(!memcmp(output, expect, sizeof(expect)), "wrong cipher text\n"); + ok(!memcmp(output, expect, sizeof(expect)), "wrong cipher text\n"); /* a vector passed to BCryptEncrypt() overrides the one set on the key */ ret = BCryptSetProperty(key, BCRYPT_INITIALIZATION_VECTOR, (UCHAR *)vector, sizeof(vector), 0); - todo_wine ok(!ret, "got %#lx\n", ret); + ok(!ret, "got %#lx\n", ret); size = 0; memset(iv, 0, sizeof(iv)); @@ -4298,14 +4298,14 @@ static void test_aes_vector_property(void) /* and the same vector decrypts what it encrypted */ ret = BCryptSetProperty(key, BCRYPT_INITIALIZATION_VECTOR, (UCHAR *)vector, sizeof(vector), 0); - todo_wine ok(!ret, "got %#lx\n", ret); + ok(!ret, "got %#lx\n", ret); size = 0; memset(output, 0, sizeof(output)); ret = BCryptDecrypt(key, (UCHAR *)expect, sizeof(expect), NULL, NULL, 0, output, sizeof(output), &size, 0); ok(!ret, "got %#lx\n", ret); ok(size == 16, "got %lu\n", size); - todo_wine ok(!memcmp(output, input, sizeof(input)), "wrong plain text\n"); + ok(!memcmp(output, input, sizeof(input)), "wrong plain text\n"); ret = BCryptDestroyKey(key); ok(!ret, "got %#lx\n", ret); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11561
Looks good, thanks! -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11561#note_147799
This merge request was approved by Hans Leidekker. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11561
participants (3)
-
Allan Vester -
Allan Vester (@AllanVester) -
Hans Leidekker (@hans)