CertOpenStore(CERT_STORE_PROV_SYSTEM_REGISTRY_, ..., "Root") is taking 80-250ms here when executed first time in a process. That triggers a race condition in some CEF / NWJS games (Purgo box is an example), where NWJS seems to depend on main thread initialization going faster than 3d initialization in the other thread (otherwise it crashes). The main hitter is the mentioned CertOpenStore(). Probably more important is that the delay is always there upon first time reading root certs per process start which happens, e. g., any time an app wants to establish a seure connection to public server.
The major hitter is rootstore.c:check_and_store_certs() which validates certificate chains as a part of host certificate import which is always done fully.
The idea is that we can persist registry cache (which load is relatively quick) only adding a new certificates, also making sure that none of the previously root certs was deleted. In the latter case the patches fallback to full reimport to guarantee that the chain validation is not affected.
The patchset brings CertOpenStore(CERT_STORE_PROV_SYSTEM_REGISTRY_, ..., "Root") time from 80-250ms to 5-10ms here after the registry cache was filled once and no cert was deleted on host. The full reinitialization time (on clean prefix or when a cert was deleted) stays nearly the same.
Patch 2 introduces a relatively small independent optimization (which avoids searching for certificate in store when it was needed). It has much lesser impact on the overall process (a few ms here) but is also much simplier.
From: Paul Gofman pgofman@codeweavers.com
--- dlls/crypt32/cert.c | 13 +++++++++++++ dlls/crypt32/serialize.c | 6 ++++++ dlls/crypt32/tests/cert.c | 20 ++++++++++++++++++++ 3 files changed, 39 insertions(+)
diff --git a/dlls/crypt32/cert.c b/dlls/crypt32/cert.c index a0b5747f2d3..b9645770ce1 100644 --- a/dlls/crypt32/cert.c +++ b/dlls/crypt32/cert.c @@ -714,6 +714,19 @@ static BOOL CertContext_SetProperty(cert_t *cert, DWORD dwPropId,
if (!cert->base.properties) ret = FALSE; + else if (dwPropId >= CERT_FIRST_USER_PROP_ID && dwPropId <= CERT_LAST_USER_PROP_ID) + { + if (pvData) + { + const CRYPT_DATA_BLOB *blob = pvData; + ret = ContextPropertyList_SetProperty(cert->base.properties, dwPropId, blob->pbData, blob->cbData); + } + else + { + ContextPropertyList_RemoveProperty(cert->base.properties, dwPropId); + ret = TRUE; + } + } else { switch (dwPropId) diff --git a/dlls/crypt32/serialize.c b/dlls/crypt32/serialize.c index 47ab834bf48..11d39188880 100644 --- a/dlls/crypt32/serialize.c +++ b/dlls/crypt32/serialize.c @@ -405,6 +405,12 @@ static BOOL CRYPT_ReadContextProp( SetLastError(ERROR_FILE_NOT_FOUND); ret = FALSE; } + else if (hdr->propID >= CERT_FIRST_USER_PROP_ID && hdr->propID <= CERT_LAST_USER_PROP_ID) + { + CRYPT_DATA_BLOB blob = { hdr->cb, (LPBYTE)pbElement }; + + ret = contextInterface->setProp(context, hdr->propID, 0, &blob); + } else if (hdr->propID != CERT_CERT_PROP_ID && hdr->propID != CERT_CRL_PROP_ID && hdr->propID != CERT_CTL_PROP_ID) { diff --git a/dlls/crypt32/tests/cert.c b/dlls/crypt32/tests/cert.c index 0de6aa717bf..83594560efa 100644 --- a/dlls/crypt32/tests/cert.c +++ b/dlls/crypt32/tests/cert.c @@ -369,6 +369,7 @@ static void testCertProperties(void) BYTE hash[20] = { 0 }, hashProperty[20]; CRYPT_DATA_BLOB blob; CERT_KEY_CONTEXT keyContext; + unsigned int value;
ok(context != NULL, "CertCreateCertificateContext failed: %08lx\n", GetLastError());
@@ -566,6 +567,25 @@ static void testCertProperties(void) free(buf); } } + + ret = CertGetCertificateContextProperty(context, CERT_LAST_USER_PROP_ID, NULL, &size); + ok(!ret && GetLastError() == CRYPT_E_NOT_FOUND, "got ret %d, error %#lx.\n", ret, GetLastError()); + + blob.cbData = sizeof(value); + blob.pbData = (BYTE *)&value; + value = 1; + ret = CertSetCertificateContextProperty(context, CERT_LAST_USER_PROP_ID, 0, &blob); + ok(ret, "got error %#lx.\n", GetLastError()); + value = 0xdeadbeef; + size = 0xdeadbeef; + ret = CertGetCertificateContextProperty(context, CERT_LAST_USER_PROP_ID, NULL, &size); + ok(ret, "got error %#lx.\n", GetLastError()); + ok(size == sizeof(value), "got size %lu.\n", size); + ret = CertGetCertificateContextProperty(context, CERT_LAST_USER_PROP_ID, &value, &size); + ok(ret, "got error %#lx.\n", GetLastError()); + ok(size == sizeof(value), "got size %lu.\n", size); + ok(value == 1, "got value %u.\n", value); + CertFreeCertificateContext(context);
context = CertCreateCertificateContext(X509_ASN_ENCODING,
From: Paul Gofman pgofman@codeweavers.com
--- dlls/crypt32/regstore.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/dlls/crypt32/regstore.c b/dlls/crypt32/regstore.c index 3899c805531..e472da5a773 100644 --- a/dlls/crypt32/regstore.c +++ b/dlls/crypt32/regstore.c @@ -56,8 +56,7 @@ static void CRYPT_HashToStr(const BYTE *hash, LPWSTR asciiHash) wsprintfW(asciiHash + i * 2, L"%02X", hash[i]); }
-static void CRYPT_RegReadSerializedFromReg(HKEY key, DWORD contextType, - HCERTSTORE store) +static void CRYPT_RegReadSerializedFromReg(HKEY key, DWORD contextType, HCERTSTORE store, DWORD disposition) { LONG rc; DWORD index = 0; @@ -130,7 +129,7 @@ static void CRYPT_RegReadSerializedFromReg(HKEY key, DWORD contextType, TRACE("hash matches, adding\n"); contextInterface->addContextToStore( store, context, - CERT_STORE_ADD_REPLACE_EXISTING, NULL); + disposition, NULL); } else TRACE("hash doesn't match, ignoring\n"); @@ -149,7 +148,7 @@ static void CRYPT_RegReadSerializedFromReg(HKEY key, DWORD contextType, } while (!rc); }
-static void CRYPT_RegReadFromReg(HKEY key, HCERTSTORE store) +static void CRYPT_RegReadFromReg(HKEY key, HCERTSTORE store, DWORD disposition) { static const WCHAR * const subKeys[] = { L"Certificates", L"CRLs", L"CTLs" }; static const DWORD contextFlags[] = { CERT_STORE_CERTIFICATE_CONTEXT_FLAG, @@ -165,7 +164,7 @@ static void CRYPT_RegReadFromReg(HKEY key, HCERTSTORE store) &hKey, NULL); if (!rc) { - CRYPT_RegReadSerializedFromReg(hKey, contextFlags[i], store); + CRYPT_RegReadSerializedFromReg(hKey, contextFlags[i], store, disposition); RegCloseKey(hKey); } } @@ -463,7 +462,7 @@ static BOOL WINAPI CRYPT_RegControl(HCERTSTORE hCertStore, DWORD dwFlags, CERT_STORE_CREATE_NEW_FLAG, NULL);
CRYPT_RegFlushStore(store, FALSE); - CRYPT_RegReadFromReg(store->key, memStore); + CRYPT_RegReadFromReg(store->key, memStore, CERT_STORE_ADD_REPLACE_EXISTING); I_CertUpdateStore(store->memStore, memStore, 0, 0); CertCloseStore(memStore, 0); break; @@ -551,7 +550,7 @@ WINECRYPT_CERTSTORE *CRYPT_RegOpenStore(HCRYPTPROV hCryptProv, DWORD dwFlags, list_init(®Info->certsToDelete); list_init(®Info->crlsToDelete); list_init(®Info->ctlsToDelete); - CRYPT_RegReadFromReg(regInfo->key, regInfo->memStore); + CRYPT_RegReadFromReg(regInfo->key, regInfo->memStore, CERT_STORE_ADD_ALWAYS); regInfo->dirty = FALSE; provInfo.cbSize = sizeof(provInfo); provInfo.cStoreProvFunc = ARRAY_SIZE(regProvFuncs);
From: Paul Gofman pgofman@codeweavers.com
--- dlls/crypt32/rootstore.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-)
diff --git a/dlls/crypt32/rootstore.c b/dlls/crypt32/rootstore.c index ab3a396b651..fb90c5bde7c 100644 --- a/dlls/crypt32/rootstore.c +++ b/dlls/crypt32/rootstore.c @@ -657,7 +657,7 @@ static HCERTSTORE create_root_store(void) void CRYPT_ImportSystemRootCertsToReg(void) { HCERTSTORE store = NULL; - HKEY key; + HKEY key = NULL; LONG rc; HANDLE hsem;
@@ -674,24 +674,28 @@ void CRYPT_ImportSystemRootCertsToReg(void) }
if(GetLastError() == ERROR_ALREADY_EXISTS) + { WaitForSingleObject(hsem, INFINITE); - else + goto done; + } + + rc = RegCreateKeyExW(HKEY_LOCAL_MACHINE, L"Software\Microsoft\SystemCertificates\Root\Certificates", 0, NULL, 0, + KEY_ALL_ACCESS, NULL, &key, 0); + if (rc) + goto done; + + if (!(store = create_root_store())) { - if ((store = create_root_store())) - { - rc = RegCreateKeyExW(HKEY_LOCAL_MACHINE, L"Software\Microsoft\SystemCertificates\Root\Certificates", 0, NULL, 0, - KEY_ALL_ACCESS, NULL, &key, 0); - if (!rc) - { - if (!CRYPT_SerializeContextsToReg(key, REG_OPTION_VOLATILE, pCertInterface, store)) - ERR("Failed to import system certs into registry, %08lx\n", GetLastError()); - RegCloseKey(key); - } - CertCloseStore(store, 0); - } else - ERR("Failed to create root store\n"); + ERR("Failed to create root store\n"); + goto done; }
+ if (!CRYPT_SerializeContextsToReg(key, REG_OPTION_VOLATILE, pCertInterface, store)) + ERR("Failed to import system certs into registry, %08lx\n", GetLastError()); + +done: + RegCloseKey(key); + CertCloseStore(store, 0); root_certs_imported = TRUE; ReleaseSemaphore(hsem, 1, NULL); CloseHandle(hsem);
From: Paul Gofman pgofman@codeweavers.com
--- dlls/crypt32/rootstore.c | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-)
diff --git a/dlls/crypt32/rootstore.c b/dlls/crypt32/rootstore.c index fb90c5bde7c..85efe5354bd 100644 --- a/dlls/crypt32/rootstore.c +++ b/dlls/crypt32/rootstore.c @@ -614,29 +614,30 @@ static void add_ms_root_certs(HCERTSTORE to) */ static void read_trusted_roots_from_known_locations(HCERTSTORE store) { - HCERTSTORE from = CertOpenStore(CERT_STORE_PROV_MEMORY, - X509_ASN_ENCODING, 0, CERT_STORE_CREATE_NEW_FLAG, NULL); + HCERTSTORE new; DWORD needed; struct enum_root_certs_params params = { NULL, 2048, &needed };
- if (from) + new = CertOpenStore( CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, 0, CERT_STORE_CREATE_NEW_FLAG, NULL ); + if (!new) return; + + params.buffer = CryptMemAlloc( params.size ); + while (!CRYPT32_CALL( enum_root_certs, ¶ms )) { - params.buffer = CryptMemAlloc( params.size ); - while (!CRYPT32_CALL( enum_root_certs, ¶ms )) + if (needed > params.size) { - if (needed > params.size) - { - CryptMemFree( params.buffer ); - params.buffer = CryptMemAlloc( needed ); - params.size = needed; - } - else CertAddEncodedCertificateToStore( from, X509_ASN_ENCODING, params.buffer, needed, - CERT_STORE_ADD_NEW, NULL ); + CryptMemFree( params.buffer ); + params.buffer = CryptMemAlloc( needed ); + params.size = needed; + continue; } - CryptMemFree( params.buffer ); - check_and_store_certs(from, store); + CertAddEncodedCertificateToStore( new, X509_ASN_ENCODING, params.buffer, needed, + CERT_STORE_ADD_NEW, NULL ); } - CertCloseStore(from, 0); + CryptMemFree( params.buffer ); + check_and_store_certs( new, store ); + + CertCloseStore( new, 0 ); }
static HCERTSTORE create_root_store(void)
From: Paul Gofman pgofman@codeweavers.com
--- dlls/crypt32/crypt32_private.h | 2 + dlls/crypt32/regstore.c | 2 +- dlls/crypt32/rootstore.c | 124 ++++++++++++++++++++++++++++----- 3 files changed, 110 insertions(+), 18 deletions(-)
diff --git a/dlls/crypt32/crypt32_private.h b/dlls/crypt32/crypt32_private.h index 41c5ec523be..e29249b1136 100644 --- a/dlls/crypt32/crypt32_private.h +++ b/dlls/crypt32/crypt32_private.h @@ -349,6 +349,8 @@ WINECRYPT_CERTSTORE *CRYPT_FileNameOpenStoreW(HCRYPTPROV hCryptProv, void CRYPT_ImportSystemRootCertsToReg(void) DECLSPEC_HIDDEN; BOOL CRYPT_SerializeContextsToReg(HKEY key, DWORD flags, const WINE_CONTEXT_INTERFACE *contextInterface, HCERTSTORE memStore) DECLSPEC_HIDDEN; +void CRYPT_RegReadSerializedFromReg(HKEY key, DWORD contextType, + HCERTSTORE store, DWORD disposition) DECLSPEC_HIDDEN;
DWORD CRYPT_IsCertificateSelfSigned(const CERT_CONTEXT *cert) DECLSPEC_HIDDEN;
diff --git a/dlls/crypt32/regstore.c b/dlls/crypt32/regstore.c index e472da5a773..4f8914798f6 100644 --- a/dlls/crypt32/regstore.c +++ b/dlls/crypt32/regstore.c @@ -56,7 +56,7 @@ static void CRYPT_HashToStr(const BYTE *hash, LPWSTR asciiHash) wsprintfW(asciiHash + i * 2, L"%02X", hash[i]); }
-static void CRYPT_RegReadSerializedFromReg(HKEY key, DWORD contextType, HCERTSTORE store, DWORD disposition) +void CRYPT_RegReadSerializedFromReg(HKEY key, DWORD contextType, HCERTSTORE store, DWORD disposition) { LONG rc; DWORD index = 0; diff --git a/dlls/crypt32/rootstore.c b/dlls/crypt32/rootstore.c index 85efe5354bd..cfddcc143ac 100644 --- a/dlls/crypt32/rootstore.c +++ b/dlls/crypt32/rootstore.c @@ -98,7 +98,7 @@ static const char *get_cert_common_name(PCCERT_CONTEXT cert) return name; }
-static void check_and_store_certs(HCERTSTORE from, HCERTSTORE to) +static void check_and_store_certs(HCERTSTORE cached, HCERTSTORE new, HCERTSTORE to) { DWORD root_count = 0; CERT_CHAIN_ENGINE_CONFIG chainEngineConfig = @@ -114,14 +114,14 @@ static void check_and_store_certs(HCERTSTORE from, HCERTSTORE to) PCCERT_CONTEXT cert = NULL;
do { - cert = CertEnumCertificatesInStore(from, cert); + cert = CertEnumCertificatesInStore(new, cert); if (cert) { CERT_CHAIN_PARA chainPara = { sizeof(chainPara), { 0 } }; PCCERT_CHAIN_CONTEXT chain; BOOL ret;
- ret = CertGetCertificateChain(engine, cert, NULL, from, + ret = CertGetCertificateChain(engine, cert, NULL, cached, &chainPara, CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL, NULL, &chain); if (!ret) TRACE("rejecting %s: %s\n", get_cert_common_name(cert), @@ -595,16 +595,28 @@ static const struct CONST_BLOB { { rootcertauthority2011, sizeof(rootcertauthority2011) }, };
-static void add_ms_root_certs(HCERTSTORE to) +static void add_ms_root_certs(HCERTSTORE to, HCERTSTORE cached) { + PCCERT_CONTEXT cert, existing; DWORD i;
TRACE("\n");
for (i = 0; i < ARRAY_SIZE(msRootCerts); i++) + { if (!CertAddEncodedCertificateToStore(to, X509_ASN_ENCODING, - msRootCerts[i].pb, msRootCerts[i].cb, CERT_STORE_ADD_NEW, NULL)) + msRootCerts[i].pb, msRootCerts[i].cb, CERT_STORE_ADD_NEW, &cert)) + { WARN("adding root cert %ld failed: %08lx\n", i, GetLastError()); + continue; + } + if ((existing = CertFindCertificateInStore(cached, X509_ASN_ENCODING, 0, CERT_FIND_EXISTING, cert, NULL))) + { + CertDeleteCertificateFromStore(existing); + CertFreeCertificateContext(existing); + } + CertFreeCertificateContext(cert); + } }
/* Reads certificates from the list of known locations into store. Stops when @@ -612,15 +624,29 @@ static void add_ms_root_certs(HCERTSTORE to) * adding redundant certificates, e.g. when both a certificate bundle and * individual certificates exist in the same directory. */ -static void read_trusted_roots_from_known_locations(HCERTSTORE store) +static void read_trusted_roots_from_known_locations(HCERTSTORE store, HCERTSTORE cached, BOOL *delete) { HCERTSTORE new; - DWORD needed; + DWORD needed, size; struct enum_root_certs_params params = { NULL, 2048, &needed }; + HCRYPTPROV prov; + HCRYPTHASH hash; + BYTE hashval[20]; + DWORD hashlen; + CRYPT_HASH_BLOB hash_blob = { sizeof(hashval), hashval }; + CRYPT_DATA_BLOB exists_blob = { 0, NULL }; + PCCERT_CONTEXT cert, existing; + unsigned int existing_count = 0, new_count = 0; + unsigned int cached_count = 0;
new = CertOpenStore( CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, 0, CERT_STORE_CREATE_NEW_FLAG, NULL ); if (!new) return;
+ existing = NULL; + while ((existing = CertEnumCertificatesInStore( cached, existing ))) + ++cached_count; + + CryptAcquireContextW( &prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ); params.buffer = CryptMemAlloc( params.size ); while (!CRYPT32_CALL( enum_root_certs, ¶ms )) { @@ -631,24 +657,78 @@ static void read_trusted_roots_from_known_locations(HCERTSTORE store) params.size = needed; continue; } - CertAddEncodedCertificateToStore( new, X509_ASN_ENCODING, params.buffer, needed, - CERT_STORE_ADD_NEW, NULL ); + CryptCreateHash( prov, CALG_SHA1, 0, 0, &hash ); + CryptHashData( hash, params.buffer, needed, 0 ); + hashlen = sizeof(hashval); + CryptGetHashParam( hash, HP_HASHVAL, hashval, &hashlen, 0 ); + CryptDestroyHash( hash ); + if ((existing = CertFindCertificateInStore( cached, X509_ASN_ENCODING, 0, + CERT_FIND_SHA1_HASH, &hash_blob, NULL ))) + { + /* Skip certificate which is already cached. CERT_FIRST_USER_PROP_ID is set once the cached cert + * is found among host imports. */ + if (!CertGetCertificateContextProperty( existing, CERT_FIRST_USER_PROP_ID, NULL, &size )) + { + if (!CertSetCertificateContextProperty( existing, CERT_FIRST_USER_PROP_ID, 0, &exists_blob )) + ERR( "Failed to set property.\n" ); + ++existing_count; + } + CertFreeCertificateContext( existing ); + continue; + } + CertAddEncodedCertificateToStore( new, X509_ASN_ENCODING, params.buffer, needed, CERT_STORE_ADD_ALWAYS, &cert ); + /* Add to cached so we can catch duplicates and check_and_store_certs() has the full chains. */ + CertAddCertificateContextToStore( cached, cert, CERT_STORE_ADD_ALWAYS, NULL ); + if (!CertSetCertificateContextProperty( cert, CERT_FIRST_USER_PROP_ID, 0, &exists_blob )) + ERR("Failed to set property.\n"); + CertFreeCertificateContext( cert ); + ++new_count; } CryptMemFree( params.buffer ); - check_and_store_certs( new, store ); + CryptReleaseContext( prov, 0 ); + + if (existing_count < cached_count) + { + /* Some certs were removed on host. Clean up the cache and add all the certificates so cert chains + * get revalidated. The certs present on host are now in 'cached' store and are marked with + * CERT_FIRST_USER_PROP_ID property. */ + TRACE( "Some keys were removed, reimporting, cached %u, existing %u, new %u.\n", + cached_count, existing_count, new_count ); + *delete = TRUE; + existing_count = 0; + existing = NULL; + while ((existing = CertEnumCertificatesInStore( cached, existing ))) + { + if (!CertGetCertificateContextProperty( existing, CERT_FIRST_USER_PROP_ID, NULL, &size )) + continue; + CertAddCertificateContextToStore( new, existing, CERT_STORE_ADD_NEW, NULL ); + ++new_count; + } + } + if (new_count) + { + /* Clear custom property so it is not serialized and seen by apps. */ + cert = NULL; + while ((cert = CertEnumCertificatesInStore( new, cert ))) + CertSetCertificateContextProperty( cert, CERT_FIRST_USER_PROP_ID, 0, NULL ); + check_and_store_certs( cached, new, store ); + }
CertCloseStore( new, 0 ); + TRACE( "existing %u, new %u.\n", existing_count, new_count ); }
-static HCERTSTORE create_root_store(void) +static HCERTSTORE create_root_store(HCERTSTORE cached, BOOL *delete) { HCERTSTORE memStore = CertOpenStore(CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, 0, CERT_STORE_CREATE_NEW_FLAG, NULL);
+ + *delete = FALSE; if (memStore) { - read_trusted_roots_from_known_locations(memStore); - add_ms_root_certs(memStore); + add_ms_root_certs(memStore, cached); + read_trusted_roots_from_known_locations(memStore, cached, delete); }
TRACE("returning %p\n", memStore); @@ -657,10 +737,11 @@ static HCERTSTORE create_root_store(void)
void CRYPT_ImportSystemRootCertsToReg(void) { - HCERTSTORE store = NULL; + HCERTSTORE store = NULL, reg = NULL; HKEY key = NULL; LONG rc; HANDLE hsem; + BOOL delete;
static BOOL root_certs_imported = FALSE;
@@ -685,18 +766,27 @@ void CRYPT_ImportSystemRootCertsToReg(void) if (rc) goto done;
- if (!(store = create_root_store())) + if (!(reg = CertOpenStore(CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, 0, CERT_STORE_CREATE_NEW_FLAG, NULL))) { - ERR("Failed to create root store\n"); + ERR("Failed to create memory store.\n"); goto done; } + CRYPT_RegReadSerializedFromReg(key, CERT_STORE_CERTIFICATE_CONTEXT_FLAG, reg, CERT_STORE_ADD_ALWAYS);
- if (!CRYPT_SerializeContextsToReg(key, REG_OPTION_VOLATILE, pCertInterface, store)) + if (!(store = create_root_store(reg, &delete))) + { + ERR("Failed to create root store\n"); + goto done; + } + if (delete && RegDeleteTreeW(key, NULL)) + ERR("Error deleting key.\n"); + if (!CRYPT_SerializeContextsToReg(key, 0, pCertInterface, store)) ERR("Failed to import system certs into registry, %08lx\n", GetLastError());
done: RegCloseKey(key); CertCloseStore(store, 0); + CertCloseStore(reg, 0); root_certs_imported = TRUE; ReleaseSemaphore(hsem, 1, NULL); CloseHandle(hsem);
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=131002
Your paranoid android.
=== build (build log) ===
error: patch failed: dlls/crypt32/regstore.c:56 error: patch failed: dlls/crypt32/rootstore.c:657 Task: Patch failed to apply
=== debian11 (build log) ===
error: patch failed: dlls/crypt32/regstore.c:56 error: patch failed: dlls/crypt32/rootstore.c:657 Task: Patch failed to apply
=== debian11b (build log) ===
error: patch failed: dlls/crypt32/regstore.c:56 error: patch failed: dlls/crypt32/rootstore.c:657 Task: Patch failed to apply