[PATCH v3 0/1] MR11317: crypt32: Open MS_ENH_RSA_AES_PROV in PFXImportCertStore.
Part 6 of my attempt to run the [Niko Home Control programming software](https://appdb.winehq.org/objectManager.php?sClass=application&iId=21635) under Wine. PFXImportCertStore's import_key opened a PROV_RSA_FULL / MS_ENHANCED_PROV context. That provider predates SHA-2 and rejects CryptCreateHash(CALG_SHA_256, ...) with NTE_BAD_ALGID. Any code path that later signs with the PFX-imported key using SHA-256 (which is every modern TLS 1.2+ handshake, all TLS 1.3, and every default .NET 8 signature) then fails at signature time. Open MS_ENH_RSA_AES_PROV / PROV_RSA_AES instead. It is a strict superset (same RSA, plus AES, plus SHA-2) and is what current Windows uses for PFX-imported keys. I added a test, without the change, the tests fail with ``` WINEDEBUG=fixme-all,err-all ./loader/wine dlls/crypt32/tests/x86_64-windows/crypt32_test.exe store store.c:1107: Test marked todo: CertOpenStore failed: 00000006 store.c:505: Test marked todo: Cert was not saved in AppData at 3 (3) store.c:2139: Test marked todo: Store registration (dwFlags=00040000) failed, last error 0 store.c:2143: Tests skipped: Nothing to test without registered store at 00040000 store.c:2139: Test marked todo: Store registration (dwFlags=00090000) failed, last error 0 store.c:2143: Tests skipped: Nothing to test without registered store at 00090000 store.c:389: Test marked todo: file store -> system store: expected size 188, got 156 store.c:398: Test marked todo: file store -> system store: unexpected value store.c:2864: Test marked todo: Unexpected cert3 store.c:3637: Test failed: PFX-imported key sits in provider type 1, expected PROV_RSA_AES (24) so that SHA-256 signing works; PROV_RSA_FULL (1) can only hash MD5 and SHA-1 0020:store: 655 tests executed (7 marked as todo, 0 as flaky, 1 failure), 2 skipped. ``` And after the fix, the tests pass ``` $ WINEDEBUG=fixme-all,err-all ./loader/wine dlls/crypt32/tests/x86_64-windows/crypt32_test.exe store store.c:1107: Test marked todo: CertOpenStore failed: 00000006 store.c:505: Test marked todo: Cert was not saved in AppData at 3 (3) store.c:2139: Test marked todo: Store registration (dwFlags=00040000) failed, last error 0 store.c:2143: Tests skipped: Nothing to test without registered store at 00040000 store.c:2139: Test marked todo: Store registration (dwFlags=00090000) failed, last error 0 store.c:2143: Tests skipped: Nothing to test without registered store at 00090000 store.c:389: Test marked todo: file store -> system store: expected size 188, got 156 store.c:398: Test marked todo: file store -> system store: unexpected value store.c:2864: Test marked todo: Unexpected cert3 0020:store: 657 tests executed (7 marked as todo, 0 as flaky, 0 failures), 2 skipped. ``` -- v3: crypt32: Open MS_ENH_RSA_AES_PROV in PFXImportCertStore. https://gitlab.winehq.org/wine/wine/-/merge_requests/11317
From: Benoît Legat <benoit.legat@gmail.com> PFXImportCertStore's import_key opened a PROV_RSA_FULL / MS_ENHANCED_PROV context. That provider predates SHA-2 and rejects CryptCreateHash(CALG_SHA_256, ...) with NTE_BAD_ALGID. Any code path that later signs with the PFX-imported key using SHA-256 (which is every modern TLS 1.2+ handshake, all TLS 1.3, and every default .NET 8 signature) then fails at signature time. Open MS_ENH_RSA_AES_PROV / PROV_RSA_AES instead. It is a strict superset (same RSA, plus AES, plus SHA-2) and is what current Windows uses for PFX-imported keys. --- dlls/crypt32/pfx.c | 2 +- dlls/crypt32/tests/store.c | 52 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/dlls/crypt32/pfx.c b/dlls/crypt32/pfx.c index a4a493e41ba..81f0faebd2b 100644 --- a/dlls/crypt32/pfx.c +++ b/dlls/crypt32/pfx.c @@ -51,7 +51,7 @@ static HCRYPTPROV import_key( cert_store_data_t data, DWORD flags ) if (CRYPT32_CALL( import_store_key, ¶ms ) != STATUS_BUFFER_TOO_SMALL) goto done; acquire_flags = (flags & CRYPT_MACHINE_KEYSET) | CRYPT_NEWKEYSET; - if (!CryptAcquireContextW( &prov, container, MS_ENHANCED_PROV_W, PROV_RSA_FULL, acquire_flags )) + if (!CryptAcquireContextW( &prov, container, MS_ENH_RSA_AES_PROV_W, PROV_RSA_AES, acquire_flags )) { WARN( "CryptAcquireContextW failed %08lx\n", GetLastError() ); goto done; diff --git a/dlls/crypt32/tests/store.c b/dlls/crypt32/tests/store.c index 81da1fad4e4..4240fd339a3 100644 --- a/dlls/crypt32/tests/store.c +++ b/dlls/crypt32/tests/store.c @@ -3596,6 +3596,57 @@ done: ; } +static void test_PFXImportCertStore_sha256_signing(void) +{ + CRYPT_DATA_BLOB pfx = { sizeof(pfxdata), (BYTE *)pfxdata }; + BYTE buf[512]; + CRYPT_KEY_PROV_INFO *info = (CRYPT_KEY_PROV_INFO *)buf; + HCERTSTORE store; + const CERT_CONTEXT *cert; + HCRYPTPROV prov; + HCRYPTHASH hash; + DWORD size; + BOOL ret; + + store = PFXImportCertStore( &pfx, NULL, CRYPT_EXPORTABLE | CRYPT_USER_KEYSET ); + ok( store != NULL, "PFXImportCertStore failed: %lu\n", GetLastError() ); + if (!store) return; + + cert = CertFindCertificateInStore( store, X509_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL ); + ok( cert != NULL, "no cert in store: %08lx\n", GetLastError() ); + if (!cert) goto done_close; + + size = sizeof(buf); + ret = CertGetCertificateContextProperty( cert, CERT_KEY_PROV_INFO_PROP_ID, info, &size ); + ok( ret, "cert has no KEY_PROV_INFO: %08lx\n", GetLastError() ); + if (!ret) goto done_cert; + + ok( info->dwProvType == PROV_RSA_AES, + "PFX-imported key sits in provider type %lu, expected PROV_RSA_AES (%u) so that " + "SHA-256 signing works; PROV_RSA_FULL (1) can only hash MD5 and SHA-1\n", + info->dwProvType, PROV_RSA_AES ); + + if (info->dwProvType == PROV_RSA_AES) + { + ret = CryptAcquireContextW( &prov, info->pwszContainerName, info->pwszProvName, + info->dwProvType, 0 ); + ok( ret, "CryptAcquireContextW(PROV_RSA_AES) failed: %08lx\n", GetLastError() ); + if (ret) + { + ret = CryptCreateHash( prov, CALG_SHA_256, 0, 0, &hash ); + ok( ret, "CryptCreateHash(CALG_SHA_256) failed on PFX-imported key's CSP: %08lx " + "— this is what NTE_BAD_ALGID looks like from userspace\n", GetLastError() ); + if (ret) CryptDestroyHash( hash ); + CryptReleaseContext( prov, 0 ); + } + } + +done_cert: + CertFreeCertificateContext( cert ); +done_close: + CertCloseStore( store, 0 ); +} + static void test_PFXExportCertStoreEx(void) { HCERTSTORE store, store2; @@ -3825,6 +3876,7 @@ START_TEST(store) test_I_UpdateStore(); test_PFXImportCertStore(); test_PFXImportCertStore_unique_containers(); + test_PFXImportCertStore_sha256_signing(); test_PFXExportCertStoreEx(); test_CryptQueryObject(); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11317
On Sun Jul 5 07:09:12 2026 +0000, Hans Leidekker wrote:
Looks good, thanks. Having the rationale in the commit message, implementation and test is a bit too much IMO. Let's keep the one in the commit message. Sounds good, I removed it from the test and implementations
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11317#note_144878
This merge request was approved by Hans Leidekker. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11317
participants (3)
-
Benoît Legat -
Benoît Legat (@blegat) -
Hans Leidekker (@hans)