[PATCH v14 0/1] MR10532: crypt32: Implement PFXExportCertStoreEx support
Part 2 of my attempt to run the [Niko Home Control programming software](https://appdb.winehq.org/objectManager.php?sClass=application&iId=21635) under Wine. This .NET application ends up calling \``PFXExportCertStoreEx` (or `PFXExportCertStore` ) since it redirects to it which is a stub at the moment. According to the [documentation](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-pfx...), if the `EXPORT_PRIVATE_KEYS` flag is set, we should export the private key as well. For this, we use `NCryptExportKey` which is part of `ncrypt` so we have to add `ncrypt` to the `Makefile.in` as well. For exporting, I used GnuTLS library. As it seems to be only available in the unix side, I added a helper in `unixlib.c`. **EDIT** Looking at the behavior of Windows with the tests, it seems that when `cert` is `NULL` we should not fail but instead produce a valid but empty `PKCS#12`. I changed this to reflect Windows' behavior. -- v14: crypt32: Implement PFXExportCertStoreEx support https://gitlab.winehq.org/wine/wine/-/merge_requests/10532
From: Benoît Legat <benoit.legat@gmail.com> --- dlls/crypt32/Makefile.in | 2 +- dlls/crypt32/crypt32_private.h | 12 ++ dlls/crypt32/pfx.c | 214 ++++++++++++++++++++++++++++++++- dlls/crypt32/tests/store.c | 165 +++++++++++++++++++++++++ dlls/crypt32/unixlib.c | 164 +++++++++++++++++++++++++ 5 files changed, 554 insertions(+), 3 deletions(-) diff --git a/dlls/crypt32/Makefile.in b/dlls/crypt32/Makefile.in index 7f35b0e4164..9af443bfb36 100644 --- a/dlls/crypt32/Makefile.in +++ b/dlls/crypt32/Makefile.in @@ -2,7 +2,7 @@ EXTRADEFS = -D_CRYPT32_ MODULE = crypt32.dll UNIXLIB = crypt32.so IMPORTLIB = crypt32 -IMPORTS = user32 advapi32 bcrypt +IMPORTS = user32 advapi32 bcrypt ncrypt DELAYIMPORTS = cryptnet UNIX_LIBS = $(SECURITY_LIBS) UNIX_CFLAGS = $(GNUTLS_CFLAGS) diff --git a/dlls/crypt32/crypt32_private.h b/dlls/crypt32/crypt32_private.h index 17253bde695..7b6bdfa9df7 100644 --- a/dlls/crypt32/crypt32_private.h +++ b/dlls/crypt32/crypt32_private.h @@ -513,6 +513,17 @@ struct enum_root_certs_params DWORD *needed; }; +struct export_cert_store_params +{ + const BYTE *cert_data; + DWORD cert_size; + const BYTE *key_blob; + DWORD key_blob_size; + const WCHAR *password; + BYTE *pfx_data; + DWORD *pfx_size; +}; + enum unix_funcs { unix_process_attach, @@ -522,6 +533,7 @@ enum unix_funcs unix_import_store_cert, unix_close_cert_store, unix_enum_root_certs, + unix_export_cert_store, unix_funcs_count, }; diff --git a/dlls/crypt32/pfx.c b/dlls/crypt32/pfx.c index a833fd42366..3a86fce3165 100644 --- a/dlls/crypt32/pfx.c +++ b/dlls/crypt32/pfx.c @@ -22,6 +22,8 @@ #include "windef.h" #include "winbase.h" #include "wincrypt.h" +#include "bcrypt.h" +#include "ncrypt.h" #include "snmp.h" #include "crypt32_private.h" @@ -241,9 +243,217 @@ BOOL WINAPI PFXExportCertStore( HCERTSTORE store, CRYPT_DATA_BLOB *pfx, const WC return PFXExportCertStoreEx( store, pfx, password, NULL, flags ); } +static void reverse_bytes( const BYTE *src, BYTE *dst, DWORD len ) +{ + DWORD i; + for (i = 0; i < len; i++) dst[i] = src[len - i - 1]; +} + +static BYTE *export_capi_key( HCRYPTPROV prov, DWORD key_spec, DWORD *out_size ) +{ + HCRYPTKEY hkey; + BYTE *capi_blob = NULL, *bcrypt_blob = NULL; + DWORD capi_size, bitlen, mod_len, prime_len, pos, out_pos; + BLOBHEADER *hdr; + RSAPUBKEY *rsakey; + BCRYPT_RSAKEY_BLOB *rsa_hdr; + + if (!CryptGetUserKey( prov, key_spec, &hkey )) + { + WARN( "CryptGetUserKey failed %08lx\n", GetLastError() ); + return NULL; + } + + /* Query size. */ + capi_size = 0; + if (!CryptExportKey( hkey, 0, PRIVATEKEYBLOB, 0, NULL, &capi_size )) + { + WARN( "CryptExportKey size query failed %08lx\n", GetLastError() ); + CryptDestroyKey( hkey ); + return NULL; + } + + capi_blob = CryptMemAlloc( capi_size ); + if (!capi_blob) + { + CryptDestroyKey( hkey ); + return NULL; + } + + if (!CryptExportKey( hkey, 0, PRIVATEKEYBLOB, 0, capi_blob, &capi_size )) + { + WARN( "CryptExportKey failed %08lx\n", GetLastError() ); + CryptMemFree( capi_blob ); + CryptDestroyKey( hkey ); + return NULL; + } + CryptDestroyKey( hkey ); + + hdr = (BLOBHEADER *)capi_blob; + rsakey = (RSAPUBKEY *)(hdr + 1); + bitlen = rsakey->bitlen; + mod_len = bitlen / 8; + prime_len = bitlen / 16; + + /* Build BCRYPT_RSAFULLPRIVATE_BLOB. */ + *out_size = sizeof(BCRYPT_RSAKEY_BLOB) + sizeof(rsakey->pubexp) + mod_len * 2 + prime_len * 5; + bcrypt_blob = CryptMemAlloc( *out_size ); + if (!bcrypt_blob) + { + CryptMemFree( capi_blob ); + return NULL; + } + + rsa_hdr = (BCRYPT_RSAKEY_BLOB *)bcrypt_blob; + rsa_hdr->Magic = BCRYPT_RSAFULLPRIVATE_MAGIC; + rsa_hdr->BitLength = bitlen; + rsa_hdr->cbPublicExp = sizeof(rsakey->pubexp); + rsa_hdr->cbModulus = mod_len; + rsa_hdr->cbPrime1 = prime_len; + rsa_hdr->cbPrime2 = prime_len; + + out_pos = sizeof(BCRYPT_RSAKEY_BLOB); + /* PublicExp (big-endian). */ + reverse_bytes( (const BYTE *)&rsakey->pubexp, bcrypt_blob + out_pos, sizeof(rsakey->pubexp) ); + out_pos += sizeof(rsakey->pubexp); + + /* CAPI data starts after BLOBHEADER + RSAPUBKEY. */ + pos = sizeof(BLOBHEADER) + sizeof(RSAPUBKEY); + + /* Modulus */ + reverse_bytes( capi_blob + pos, bcrypt_blob + out_pos, mod_len ); + pos += mod_len; out_pos += mod_len; + /* Prime1 */ + reverse_bytes( capi_blob + pos, bcrypt_blob + out_pos, prime_len ); + pos += prime_len; out_pos += prime_len; + /* Prime2 */ + reverse_bytes( capi_blob + pos, bcrypt_blob + out_pos, prime_len ); + pos += prime_len; out_pos += prime_len; + /* Exponent1 */ + reverse_bytes( capi_blob + pos, bcrypt_blob + out_pos, prime_len ); + pos += prime_len; out_pos += prime_len; + /* Exponent2 */ + reverse_bytes( capi_blob + pos, bcrypt_blob + out_pos, prime_len ); + pos += prime_len; out_pos += prime_len; + /* Coefficient */ + reverse_bytes( capi_blob + pos, bcrypt_blob + out_pos, prime_len ); + pos += prime_len; out_pos += prime_len; + /* PrivateExponent */ + reverse_bytes( capi_blob + pos, bcrypt_blob + out_pos, mod_len ); + + CryptMemFree( capi_blob ); + return bcrypt_blob; +} + BOOL WINAPI PFXExportCertStoreEx( HCERTSTORE store, CRYPT_DATA_BLOB *pfx, const WCHAR *password, void *reserved, DWORD flags ) { - FIXME( "(%p, %p, %p, %p, %08lx): stub\n", store, pfx, password, reserved, flags ); - return FALSE; + const CERT_CONTEXT *cert; + CERT_KEY_CONTEXT key_ctx; + struct export_cert_store_params params; + DWORD key_ctx_size, key_blob_size; + BYTE *key_blob = NULL; + SECURITY_STATUS sec_status; + NTSTATUS status; + BOOL ret = FALSE; + + TRACE( "(%p, %p, %p, %p, %08lx)\n", store, pfx, password, reserved, flags ); + + if (!store || !pfx) + { + SetLastError( ERROR_INVALID_PARAMETER ); + return FALSE; + } + + /* Find the first certificate in the store (may be NULL for empty stores). */ + cert = CertEnumCertificatesInStore( store, NULL ); + + /* Get the private key if EXPORT_PRIVATE_KEYS is requested. */ + if (cert && (flags & EXPORT_PRIVATE_KEYS)) + { + key_ctx_size = sizeof(key_ctx); + if (!CertGetCertificateContextProperty( cert, CERT_KEY_CONTEXT_PROP_ID, &key_ctx, &key_ctx_size )) + { + WARN( "no key context on certificate, error %08lx\n", GetLastError() ); + if (flags & REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY) + { + SetLastError( NTE_NOT_FOUND ); + CertFreeCertificateContext( cert ); + return FALSE; + } + } + else if (key_ctx.dwKeySpec == CERT_NCRYPT_KEY_SPEC) + { + /* Query key blob size first. */ + sec_status = NCryptExportKey( key_ctx.hCryptProv, 0, BCRYPT_RSAFULLPRIVATE_BLOB, NULL, + NULL, 0, &key_blob_size, 0 ); + if (sec_status) + { + WARN( "NCryptExportKey size query failed %08lx\n", sec_status ); + SetLastError( sec_status ); + CertFreeCertificateContext( cert ); + return FALSE; + } + + key_blob = CryptMemAlloc( key_blob_size ); + if (!key_blob) + { + SetLastError( ERROR_OUTOFMEMORY ); + CertFreeCertificateContext( cert ); + return FALSE; + } + + sec_status = NCryptExportKey( key_ctx.hCryptProv, 0, BCRYPT_RSAFULLPRIVATE_BLOB, NULL, + key_blob, key_blob_size, &key_blob_size, 0 ); + if (sec_status) + { + WARN( "NCryptExportKey failed %08lx\n", sec_status ); + SetLastError( sec_status ); + CryptMemFree( key_blob ); + CertFreeCertificateContext( cert ); + return FALSE; + } + } + else if (key_ctx.dwKeySpec == AT_KEYEXCHANGE || key_ctx.dwKeySpec == AT_SIGNATURE) + { + key_blob = export_capi_key( key_ctx.hCryptProv, key_ctx.dwKeySpec, &key_blob_size ); + if (!key_blob) + { + WARN( "failed to export CAPI key\n" ); + if (flags & REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY) + { + SetLastError( NTE_NOT_FOUND ); + CertFreeCertificateContext( cert ); + return FALSE; + } + } + } + else + { + FIXME( "dwKeySpec %lu not supported\n", key_ctx.dwKeySpec ); + if (flags & REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY) + { + SetLastError( NTE_NOT_FOUND ); + CertFreeCertificateContext( cert ); + return FALSE; + } + } + } + + params.cert_data = cert ? cert->pbCertEncoded : NULL; + params.cert_size = cert ? cert->cbCertEncoded : 0; + params.key_blob = key_blob; + params.key_blob_size = key_blob ? key_blob_size : 0; + params.password = password; + params.pfx_data = pfx->pbData; + params.pfx_size = &pfx->cbData; + status = CRYPT32_CALL( export_cert_store, ¶ms ); + if (status == STATUS_BUFFER_TOO_SMALL) status = STATUS_SUCCESS; + else if (status) WARN( "unix export_cert_store failed %08lx\n", status ); + else ret = TRUE; + + CryptMemFree( key_blob ); + CertFreeCertificateContext( cert ); + SetLastError( RtlNtStatusToDosError( status ) ); + return ret; } diff --git a/dlls/crypt32/tests/store.c b/dlls/crypt32/tests/store.c index 33e4e34a9e8..10b861675ca 100644 --- a/dlls/crypt32/tests/store.c +++ b/dlls/crypt32/tests/store.c @@ -3541,6 +3541,170 @@ static void test_PFXImportCertStore(void) CertCloseStore( store, 0 ); } +static void test_PFXExportCertStoreEx(void) +{ + HCERTSTORE store, store2; + CRYPT_DATA_BLOB pfx, exported; + const CERT_CONTEXT *cert, *cert2; + DWORD count, size; + BOOL ret; + + /* Test NULL parameters. */ + SetLastError( 0xdeadbeef ); + ret = PFXExportCertStoreEx( NULL, NULL, NULL, NULL, 0 ); + ok( !ret, "expected failure\n" ); + ok( GetLastError() == ERROR_INVALID_PARAMETER, "got %lx\n", GetLastError() ); + + exported.pbData = NULL; + exported.cbData = 0; + + SetLastError( 0xdeadbeef ); + ret = PFXExportCertStoreEx( NULL, &exported, NULL, NULL, 0 ); + ok( !ret, "expected failure\n" ); + ok( GetLastError() == ERROR_INVALID_PARAMETER, "got %lx\n", GetLastError() ); + + /* Empty store succeeds (produces a valid but empty PFX). */ + store = CertOpenStore( CERT_STORE_PROV_MEMORY, 0, 0, 0, NULL ); + ok( store != NULL, "CertOpenStore failed %lx\n", GetLastError() ); + + exported.pbData = NULL; + exported.cbData = 0; + ret = PFXExportCertStoreEx( store, &exported, NULL, NULL, 0 ); + ok( ret, "PFXExportCertStoreEx empty store size query failed %lx\n", GetLastError() ); + ok( exported.cbData > 0, "expected nonzero size\n" ); + CertCloseStore( store, 0 ); + + /* Import pfxdata (cert + private key), then export without private key. */ + pfx.pbData = (BYTE *)pfxdata; + pfx.cbData = sizeof(pfxdata); + store = PFXImportCertStore( &pfx, NULL, CRYPT_EXPORTABLE | CRYPT_USER_KEYSET | PKCS12_NO_PERSIST_KEY ); + ok( store != NULL, "PFXImportCertStore failed %lx\n", GetLastError() ); + + /* Size query. */ + exported.pbData = NULL; + exported.cbData = 0; + ret = PFXExportCertStoreEx( store, &exported, L"test", NULL, 0 ); + ok( ret, "PFXExportCertStoreEx size query failed %lx\n", GetLastError() ); + ok( exported.cbData > 0, "expected nonzero size\n" ); + + /* Buffer too small. */ + exported.pbData = HeapAlloc( GetProcessHeap(), 0, exported.cbData ); + ok( exported.pbData != NULL, "HeapAlloc failed\n" ); + size = exported.cbData; + exported.cbData = 1; + SetLastError( 0xdeadbeef ); + ret = PFXExportCertStoreEx( store, &exported, L"test", NULL, 0 ); + ok( !ret, "PFXExportCertStoreEx failed %lx\n", GetLastError() ); + ok( GetLastError() == ERROR_SUCCESS, "got %lx\n", GetLastError() ); + exported.cbData = size; + + /* Actual export. */ + exported.pbData = HeapAlloc( GetProcessHeap(), 0, exported.cbData ); + ok( exported.pbData != NULL, "HeapAlloc failed\n" ); + ret = PFXExportCertStoreEx( store, &exported, L"test", NULL, 0 ); + ok( ret, "PFXExportCertStoreEx failed %lx\n", GetLastError() ); + + /* Verify exported blob is valid PFX. */ + ret = PFXIsPFXBlob( &exported ); + ok( ret, "exported blob is not valid PFX\n" ); + + /* Re-import and verify certificate round-trip. */ + store2 = PFXImportCertStore( &exported, L"test", PKCS12_NO_PERSIST_KEY ); + ok( store2 != NULL, "PFXImportCertStore of exported data failed %lx\n", GetLastError() ); + if (store2) + { + count = countCertsInStore( store2 ); + ok( count == 1, "expected 1 cert, got %lu\n", count ); + + cert = CertFindCertificateInStore( store, X509_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL ); + cert2 = CertFindCertificateInStore( store2, X509_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL ); + ok( cert != NULL && cert2 != NULL, "failed to find certs\n" ); + if (cert && cert2) + { + ok( cert->cbCertEncoded == cert2->cbCertEncoded, + "cert size mismatch: %lu vs %lu\n", cert->cbCertEncoded, cert2->cbCertEncoded ); + ok( !memcmp( cert->pbCertEncoded, cert2->pbCertEncoded, cert->cbCertEncoded ), + "cert data mismatch\n" ); + } + if (cert) CertFreeCertificateContext( cert ); + if (cert2) CertFreeCertificateContext( cert2 ); + CertCloseStore( store2, 0 ); + } + HeapFree( GetProcessHeap(), 0, exported.pbData ); + + /* Export with EXPORT_PRIVATE_KEYS. */ + exported.pbData = NULL; + exported.cbData = 0; + ret = PFXExportCertStoreEx( store, &exported, L"test", NULL, EXPORT_PRIVATE_KEYS ); + ok( ret, "PFXExportCertStoreEx size query failed %lx\n", GetLastError() ); + ok( exported.cbData > 0, "expected nonzero size\n" ); + + exported.pbData = HeapAlloc( GetProcessHeap(), 0, exported.cbData ); + ok( exported.pbData != NULL, "HeapAlloc failed\n" ); + ret = PFXExportCertStoreEx( store, &exported, L"test", NULL, EXPORT_PRIVATE_KEYS ); + ok( ret, "PFXExportCertStoreEx with EXPORT_PRIVATE_KEYS failed %lx\n", GetLastError() ); + + ret = PFXIsPFXBlob( &exported ); + ok( ret, "exported blob with private key is not valid PFX\n" ); + + /* Re-import with private key and verify it's present. */ + store2 = PFXImportCertStore( &exported, L"test", PKCS12_NO_PERSIST_KEY ); + ok( store2 != NULL, "PFXImportCertStore failed %lx\n", GetLastError() ); + if (store2) + { + CERT_KEY_CONTEXT key; + DWORD size; + + cert2 = CertFindCertificateInStore( store2, X509_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL ); + ok( cert2 != NULL, "no cert in re-imported store %lx\n", GetLastError() ); + if (cert2) + { + size = sizeof(key); + ret = CertGetCertificateContextProperty( cert2, CERT_KEY_CONTEXT_PROP_ID, &key, &size ); + ok( ret, "no key context on re-imported cert %lx\n", GetLastError() ); + if (ret) + { + ok( key.hCryptProv != 0, "expected non-zero hCryptProv\n" ); + } + CertFreeCertificateContext( cert2 ); + } + CertCloseStore( store2, 0 ); + } + HeapFree( GetProcessHeap(), 0, exported.pbData ); + + /* Test cert-only export (import pfx_cert_only which has no private key). */ + CertCloseStore( store, 0 ); + pfx.pbData = (BYTE *)pfx_cert_only; + pfx.cbData = sizeof(pfx_cert_only); + store = PFXImportCertStore( &pfx, L"", 0 ); + ok( store != NULL, "PFXImportCertStore failed %lx\n", GetLastError() ); + + exported.pbData = NULL; + exported.cbData = 0; + ret = PFXExportCertStoreEx( store, &exported, L"", NULL, 0 ); + ok( ret, "PFXExportCertStoreEx cert-only size query failed %lx\n", GetLastError() ); + ok( exported.cbData > 0, "expected nonzero size\n" ); + + exported.pbData = HeapAlloc( GetProcessHeap(), 0, exported.cbData ); + ret = PFXExportCertStoreEx( store, &exported, L"", NULL, 0 ); + ok( ret, "PFXExportCertStoreEx cert-only failed %lx\n", GetLastError() ); + + ret = PFXIsPFXBlob( &exported ); + ok( ret, "cert-only exported blob is not valid PFX\n" ); + + store2 = PFXImportCertStore( &exported, L"", 0 ); + ok( store2 != NULL, "PFXImportCertStore of cert-only export failed %lx\n", GetLastError() ); + if (store2) + { + count = countCertsInStore( store2 ); + ok( count == 1, "expected 1 cert, got %lu\n", count ); + CertCloseStore( store2, 0 ); + } + HeapFree( GetProcessHeap(), 0, exported.pbData ); + + CertCloseStore( store, 0 ); +} + static void test_CryptQueryObject(void) { CRYPT_DATA_BLOB pfx; @@ -3605,5 +3769,6 @@ START_TEST(store) test_I_UpdateStore(); test_PFXImportCertStore(); + test_PFXExportCertStoreEx(); test_CryptQueryObject(); } diff --git a/dlls/crypt32/unixlib.c b/dlls/crypt32/unixlib.c index 5cfc54bf026..8a3755f4bde 100644 --- a/dlls/crypt32/unixlib.c +++ b/dlls/crypt32/unixlib.c @@ -41,6 +41,7 @@ #include "winbase.h" #include "winternl.h" #include "wincrypt.h" +#include "bcrypt.h" #include "crypt32_private.h" #include "wine/debug.h" @@ -65,10 +66,15 @@ MAKE_FUNCPTR(gnutls_pkcs12_bag_get_count); MAKE_FUNCPTR(gnutls_pkcs12_bag_get_data); MAKE_FUNCPTR(gnutls_pkcs12_bag_get_type); MAKE_FUNCPTR(gnutls_pkcs12_bag_init); +MAKE_FUNCPTR(gnutls_pkcs12_bag_set_crt); +MAKE_FUNCPTR(gnutls_pkcs12_bag_set_privkey); MAKE_FUNCPTR(gnutls_pkcs12_deinit); +MAKE_FUNCPTR(gnutls_pkcs12_export2); +MAKE_FUNCPTR(gnutls_pkcs12_generate_mac2); MAKE_FUNCPTR(gnutls_pkcs12_get_bag); MAKE_FUNCPTR(gnutls_pkcs12_import); MAKE_FUNCPTR(gnutls_pkcs12_init); +MAKE_FUNCPTR(gnutls_pkcs12_set_bag); MAKE_FUNCPTR(gnutls_pkcs12_verify_mac); MAKE_FUNCPTR(gnutls_x509_crt_deinit); MAKE_FUNCPTR(gnutls_x509_crt_export); @@ -78,6 +84,7 @@ MAKE_FUNCPTR(gnutls_x509_privkey_deinit); MAKE_FUNCPTR(gnutls_x509_privkey_export_rsa_raw2); MAKE_FUNCPTR(gnutls_x509_privkey_get_pk_algorithm2); MAKE_FUNCPTR(gnutls_x509_privkey_import_pkcs8); +MAKE_FUNCPTR(gnutls_x509_privkey_import_rsa_raw); MAKE_FUNCPTR(gnutls_x509_privkey_init); #undef MAKE_FUNCPTR @@ -125,10 +132,15 @@ static NTSTATUS process_attach( void *args ) LOAD_FUNCPTR(gnutls_pkcs12_bag_get_data) LOAD_FUNCPTR(gnutls_pkcs12_bag_get_type) LOAD_FUNCPTR(gnutls_pkcs12_bag_init) + LOAD_FUNCPTR(gnutls_pkcs12_bag_set_crt) + LOAD_FUNCPTR(gnutls_pkcs12_bag_set_privkey) LOAD_FUNCPTR(gnutls_pkcs12_deinit) + LOAD_FUNCPTR(gnutls_pkcs12_export2) + LOAD_FUNCPTR(gnutls_pkcs12_generate_mac2) LOAD_FUNCPTR(gnutls_pkcs12_get_bag) LOAD_FUNCPTR(gnutls_pkcs12_import) LOAD_FUNCPTR(gnutls_pkcs12_init) + LOAD_FUNCPTR(gnutls_pkcs12_set_bag) LOAD_FUNCPTR(gnutls_pkcs12_verify_mac) LOAD_FUNCPTR(gnutls_x509_crt_deinit) LOAD_FUNCPTR(gnutls_x509_crt_export) @@ -138,6 +150,7 @@ static NTSTATUS process_attach( void *args ) LOAD_FUNCPTR(gnutls_x509_privkey_export_rsa_raw2) LOAD_FUNCPTR(gnutls_x509_privkey_get_pk_algorithm2) LOAD_FUNCPTR(gnutls_x509_privkey_import_pkcs8) + LOAD_FUNCPTR(gnutls_x509_privkey_import_rsa_raw) LOAD_FUNCPTR(gnutls_x509_privkey_init) #undef LOAD_FUNCPTR @@ -521,6 +534,127 @@ static NTSTATUS close_cert_store( void *args ) return STATUS_SUCCESS; } +static NTSTATUS export_cert_store( void *args ) +{ + struct export_cert_store_params *params = args; + gnutls_pkcs12_t p12 = NULL; + gnutls_pkcs12_bag_t cert_bag = NULL, key_bag = NULL; + gnutls_x509_crt_t crt = NULL; + gnutls_x509_privkey_t key = NULL; + gnutls_datum_t out = { NULL, 0 }; + char *pwd = NULL; + int ret; + NTSTATUS status = STATUS_INVALID_PARAMETER; + + if (!libgnutls_handle) return STATUS_DLL_NOT_FOUND; + + if (params->password && !(pwd = password_to_ascii( params->password ))) + return STATUS_NO_MEMORY; + + /* Create cert bag (always initialized, even for empty stores). */ + if ((ret = pgnutls_pkcs12_bag_init( &cert_bag )) < 0) goto error; + + /* Import the certificate if provided. */ + if (params->cert_data && params->cert_size) + { + if ((ret = pgnutls_x509_crt_init( &crt )) < 0) goto error; + { + gnutls_datum_t cert_datum = { (unsigned char *)params->cert_data, params->cert_size }; + if ((ret = pgnutls_x509_crt_import( crt, &cert_datum, GNUTLS_X509_FMT_DER )) < 0) goto error; + } + if ((ret = pgnutls_pkcs12_bag_set_crt( cert_bag, crt )) < 0) goto error; + } + + /* Import private key from BCRYPT_RSAKEY_BLOB if provided. */ + if (params->key_blob && params->key_blob_size) + { + const BCRYPT_RSAKEY_BLOB *hdr = (const BCRYPT_RSAKEY_BLOB *)params->key_blob; + const BYTE *ptr = params->key_blob + sizeof(*hdr); + gnutls_datum_t m, e, d, p, q, u; + + if (hdr->Magic != BCRYPT_RSAFULLPRIVATE_MAGIC) + { + WARN( "unexpected key blob magic %08lx\n", (unsigned long)hdr->Magic ); + status = STATUS_INVALID_PARAMETER; + goto done; + } + + /* Layout after header: PublicExp, Modulus, Prime1, Prime2, Exponent1, Exponent2, Coefficient, PrivateExponent */ + e.data = (unsigned char *)ptr; e.size = hdr->cbPublicExp; + ptr += hdr->cbPublicExp; + m.data = (unsigned char *)ptr; m.size = hdr->cbModulus; + ptr += hdr->cbModulus; + p.data = (unsigned char *)ptr; p.size = hdr->cbPrime1; + ptr += hdr->cbPrime1; + q.data = (unsigned char *)ptr; q.size = hdr->cbPrime2; + ptr += hdr->cbPrime2; + /* Skip Exponent1 and Exponent2 - GnuTLS computes them. */ + ptr += hdr->cbPrime1; /* Exponent1 */ + ptr += hdr->cbPrime2; /* Exponent2 */ + u.data = (unsigned char *)ptr; u.size = hdr->cbPrime1; + ptr += hdr->cbPrime1; + d.data = (unsigned char *)ptr; d.size = hdr->cbModulus; + + if ((ret = pgnutls_x509_privkey_init( &key )) < 0) goto error; + if ((ret = pgnutls_x509_privkey_import_rsa_raw( key, &m, &e, &d, &p, &q, &u )) < 0) + { + pgnutls_x509_privkey_deinit( key ); + key = NULL; + goto error; + } + + /* Create key bag. */ + if ((ret = pgnutls_pkcs12_bag_init( &key_bag )) < 0) goto error; + if ((ret = pgnutls_pkcs12_bag_set_privkey( key_bag, key, pwd ? pwd : "", 0 )) < 0) goto error; + } + + /* Build PKCS#12. */ + if ((ret = pgnutls_pkcs12_init( &p12 )) < 0) goto error; + if ((ret = pgnutls_pkcs12_set_bag( p12, cert_bag )) < 0) goto error; + if (key_bag) + { + if ((ret = pgnutls_pkcs12_set_bag( p12, key_bag )) < 0) goto error; + } + + /* Generate MAC. */ + if ((ret = pgnutls_pkcs12_generate_mac2( p12, GNUTLS_MAC_SHA256, pwd ? pwd : "" )) < 0) goto error; + + /* Export. */ + if ((ret = pgnutls_pkcs12_export2( p12, GNUTLS_X509_FMT_DER, &out )) < 0) goto error; + + if (!params->pfx_data) + { + /* Size query. */ + *params->pfx_size = out.size + 8; + status = STATUS_SUCCESS; + } + else if (*params->pfx_size < out.size) + { + *params->pfx_size = out.size + 8; + status = STATUS_BUFFER_TOO_SMALL; + } + else + { + memcpy( params->pfx_data, out.data, out.size ); + *params->pfx_size = out.size; + status = STATUS_SUCCESS; + } + goto done; + +error: + pgnutls_perror( ret ); + status = STATUS_INVALID_PARAMETER; +done: + free( out.data ); + if (p12) pgnutls_pkcs12_deinit( p12 ); + if (key_bag) pgnutls_pkcs12_bag_deinit( key_bag ); + if (cert_bag) pgnutls_pkcs12_bag_deinit( cert_bag ); + if (key) pgnutls_x509_privkey_deinit( key ); + if (crt) pgnutls_x509_crt_deinit( crt ); + free( pwd ); + return status; +} + #else /* SONAME_LIBGNUTLS */ static NTSTATUS process_attach( void *args ) { return STATUS_SUCCESS; } @@ -529,6 +663,7 @@ static NTSTATUS open_cert_store( void *args ) { return STATUS_DLL_NOT_FOUND; } static NTSTATUS import_store_key( void *args ) { return STATUS_DLL_NOT_FOUND; } static NTSTATUS import_store_cert( void *args ) { return STATUS_DLL_NOT_FOUND; } static NTSTATUS close_cert_store( void *args ) { return STATUS_DLL_NOT_FOUND; } +static NTSTATUS export_cert_store( void *args ) { return STATUS_DLL_NOT_FOUND; } #endif /* SONAME_LIBGNUTLS */ @@ -860,6 +995,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = import_store_cert, close_cert_store, enum_root_certs, + export_cert_store, }; C_ASSERT( ARRAYSIZE(__wine_unix_call_funcs) == unix_funcs_count ); @@ -956,6 +1092,33 @@ static NTSTATUS wow64_enum_root_certs( void *args ) return enum_root_certs( ¶ms ); } +static NTSTATUS wow64_export_cert_store( void *args ) +{ + struct + { + PTR32 cert_data; + DWORD cert_size; + PTR32 key_blob; + DWORD key_blob_size; + PTR32 password; + PTR32 pfx_data; + PTR32 pfx_size; + } const *params32 = args; + + struct export_cert_store_params params = + { + ULongToPtr( params32->cert_data ), + params32->cert_size, + ULongToPtr( params32->key_blob ), + params32->key_blob_size, + ULongToPtr( params32->password ), + ULongToPtr( params32->pfx_data ), + ULongToPtr( params32->pfx_size ) + }; + + return export_cert_store( ¶ms ); +} + const unixlib_entry_t __wine_unix_call_wow64_funcs[] = { process_attach, @@ -965,6 +1128,7 @@ const unixlib_entry_t __wine_unix_call_wow64_funcs[] = wow64_import_store_cert, close_cert_store, wow64_enum_root_certs, + wow64_export_cert_store, }; C_ASSERT( ARRAYSIZE(__wine_unix_call_wow64_funcs) == unix_funcs_count ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10532
This merge request was approved by Hans Leidekker. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10532
participants (3)
-
Benoît Legat -
Benoît Legat (@blegat) -
Hans Leidekker (@hans)