From 013966c78dbb0f8151336977153748006f7bde50 Mon Sep 17 00:00:00 2001 From: Aleksey Sanin Date: Sat, 3 Oct 2026 11:26:11 -0400 Subject: [PATCH 1/4] (xmlsec-mscng, xmlsec-mscrypto) Enforced CRL verification against trusted store; improved Windows build --- include/xmlsec/mscng/x509.h | 7 +- include/xmlsec/mscrypto/app.h | 9 + include/xmlsec/mscrypto/symbols.h | 16 +- include/xmlsec/mscrypto/x509.h | 27 ++- scripts/build_release.sh | 2 +- scripts/build_windows.sh | 8 +- src/mscng/app.c | 59 ++++-- src/mscng/certkeys.c | 74 ++++--- src/mscng/certkeys_dsa.c | 5 +- src/mscng/kw_rfc_3394.c | 6 +- src/mscng/x509.c | 3 + src/mscng/x509vfy.c | 199 ++++++++++++++++--- src/mscrypto/certkeys.c | 5 +- src/mscrypto/ciphers.c | 16 ++ src/mscrypto/keysstore.c | 19 +- src/mscrypto/kw_rfc_3394.c | 6 +- src/mscrypto/x509.c | 2 + src/mscrypto/x509vfy.c | 315 ++++++++++++++++++++++++------ win32/Makefile.msvc | 25 ++- win32/configure.ps1 | 8 +- win32/mycfg.bat | 6 +- 21 files changed, 670 insertions(+), 147 deletions(-) diff --git a/include/xmlsec/mscng/x509.h b/include/xmlsec/mscng/x509.h index f520867dc..079a7e6c2 100644 --- a/include/xmlsec/mscng/x509.h +++ b/include/xmlsec/mscng/x509.h @@ -72,8 +72,11 @@ XMLSEC_CRYPTO_EXPORT int xmlSecMSCngX509StoreAdoptTrustedStore (x XMLSEC_CRYPTO_EXPORT int xmlSecMSCngX509StoreAdoptUntrustedStore (xmlSecKeyDataStorePtr store, HCERTSTORE untrustedStore); XMLSEC_CRYPTO_EXPORT PCCERT_CONTEXT xmlSecMSCngX509StoreVerify (xmlSecKeyDataStorePtr store, - HCERTSTORE certs, - xmlSecKeyInfoCtxPtr keyInfoCtx); + HCERTSTORE certs, + xmlSecKeyInfoCtxPtr keyInfoCtx); +XMLSEC_CRYPTO_EXPORT int xmlSecMSCngX509StoreVerifyKey (xmlSecKeyDataStorePtr store, + xmlSecKeyPtr key, + xmlSecKeyInfoCtxPtr keyInfoCtx); /****************************************************************************** * diff --git a/include/xmlsec/mscrypto/app.h b/include/xmlsec/mscrypto/app.h index 1d4151976..f3881008b 100644 --- a/include/xmlsec/mscrypto/app.h +++ b/include/xmlsec/mscrypto/app.h @@ -35,6 +35,15 @@ extern "C" { *****************************************************************************/ XMLSEC_CRYPTO_EXPORT int xmlSecMSCryptoAppInit (const char* config); XMLSEC_CRYPTO_EXPORT int xmlSecMSCryptoAppShutdown (void); + +/* This getter returns LPCTSTR (const TCHAR*). The width of TCHAR is fixed at + * compile time by the UNICODE/_UNICODE macro: wchar_t (2 bytes) when UNICODE + * is defined, char (1 byte) otherwise. The underlying string is allocated by + * the library with the library's own TCHAR width, so the consuming application + * must be compiled with the same UNICODE/_UNICODE setting as the xmlsec + * library. If the character sets mismatch, the returned pointer misinterprets + * the string (e.g. a wide string read as a narrow one), which is undefined + * behavior. */ XMLSEC_CRYPTO_EXPORT LPCTSTR xmlSecMSCryptoAppGetCertStoreName (void); /****************************************************************************** diff --git a/include/xmlsec/mscrypto/symbols.h b/include/xmlsec/mscrypto/symbols.h index a036f3f84..dfbc99eba 100644 --- a/include/xmlsec/mscrypto/symbols.h +++ b/include/xmlsec/mscrypto/symbols.h @@ -45,15 +45,19 @@ extern "C" { #define xmlSecKeyDataGostR3410_2012_512Id xmlSecMSCryptoKeyDataGost2012_512Id #define xmlSecKeyDataHmacId xmlSecMSCryptoKeyDataHmacId #define xmlSecKeyDataRsaId xmlSecMSCryptoKeyDataRsaId +#ifndef XMLSEC_NO_X509 #define xmlSecKeyDataX509Id xmlSecMSCryptoKeyDataX509Id #define xmlSecKeyDataRawX509CertId xmlSecMSCryptoKeyDataRawX509CertId +#endif /* XMLSEC_NO_X509 */ /****************************************************************************** * * Key data store ids - * - *****************************************************************************/ + * + *****************************************************************************/ +#ifndef XMLSEC_NO_X509 #define xmlSecX509StoreId xmlSecMSCryptoX509StoreId +#endif /* XMLSEC_NO_X509 */ /****************************************************************************** * @@ -124,7 +128,7 @@ extern "C" { * * High-level routines for the xmlsec command-line utility * - *****************************************************************************/ + *****************************************************************************/ #define xmlSecCryptoAppInit xmlSecMSCryptoAppInit #define xmlSecCryptoAppShutdown xmlSecMSCryptoAppShutdown #define xmlSecCryptoAppDefaultKeysMngrInit xmlSecMSCryptoAppDefaultKeysMngrInit @@ -132,17 +136,23 @@ extern "C" { #define xmlSecCryptoAppDefaultKeysMngrVerifyKey xmlSecMSCryptoAppDefaultKeysMngrVerifyKey #define xmlSecCryptoAppDefaultKeysMngrLoad xmlSecMSCryptoAppDefaultKeysMngrLoad #define xmlSecCryptoAppDefaultKeysMngrSave xmlSecMSCryptoAppDefaultKeysMngrSave +#ifndef XMLSEC_NO_X509 #define xmlSecCryptoAppKeysMngrCertLoad xmlSecMSCryptoAppKeysMngrCertLoad #define xmlSecCryptoAppKeysMngrCertLoadMemory xmlSecMSCryptoAppKeysMngrCertLoadMemory #define xmlSecCryptoAppKeysMngrCrlLoad xmlSecMSCryptoAppKeysMngrCrlLoad #define xmlSecCryptoAppKeysMngrCrlLoadMemory xmlSecMSCryptoAppKeysMngrCrlLoadMemory #define xmlSecCryptoAppKeysMngrCrlLoadAndVerify xmlSecMSCryptoAppKeysMngrCrlLoadAndVerify +#endif /* XMLSEC_NO_X509 */ #define xmlSecCryptoAppKeyLoadEx xmlSecMSCryptoAppKeyLoadEx +#ifndef XMLSEC_NO_X509 #define xmlSecCryptoAppPkcs12Load xmlSecMSCryptoAppPkcs12Load #define xmlSecCryptoAppKeyCertLoad xmlSecMSCryptoAppKeyCertLoad +#endif /* XMLSEC_NO_X509 */ #define xmlSecCryptoAppKeyLoadMemory xmlSecMSCryptoAppKeyLoadMemory +#ifndef XMLSEC_NO_X509 #define xmlSecCryptoAppPkcs12LoadMemory xmlSecMSCryptoAppPkcs12LoadMemory #define xmlSecCryptoAppKeyCertLoadMemory xmlSecMSCryptoAppKeyCertLoadMemory +#endif /* XMLSEC_NO_X509 */ #define xmlSecCryptoAppGetDefaultPwdCallback xmlSecMSCryptoAppGetDefaultPwdCallback #endif /* XMLSEC_CRYPTO_MSCRYPTO */ diff --git a/include/xmlsec/mscrypto/x509.h b/include/xmlsec/mscrypto/x509.h index fbffcd45d..fd5a87d97 100644 --- a/include/xmlsec/mscrypto/x509.h +++ b/include/xmlsec/mscrypto/x509.h @@ -79,8 +79,33 @@ XMLSEC_CRYPTO_EXPORT int xmlSecMSCryptoX509StoreAdoptUntrustedSto HCERTSTORE untrustedStore); XMLSEC_CRYPTO_EXPORT void xmlSecMSCryptoX509StoreEnableSystemTrustedCerts(xmlSecKeyDataStorePtr store, - int val); + int val); +XMLSEC_CRYPTO_EXPORT PCCERT_CONTEXT xmlSecMSCryptoX509StoreVerify (xmlSecKeyDataStorePtr store, + HCERTSTORE certs, + xmlSecKeyInfoCtxPtr keyInfoCtx); + +/****************************************************************************** + * + * DEPRECATED + * + *****************************************************************************/ +XMLSEC_CRYPTO_EXPORT XMLSEC_DEPRECATED PCCERT_CONTEXT xmlSecMSCryptoX509StoreFindCert (xmlSecKeyDataStorePtr store, + xmlChar *subjectName, + xmlChar *issuerName, + xmlChar *issuerSerial, + xmlChar *ski, + xmlSecKeyInfoCtxPtr keyInfoCtx); +XMLSEC_CRYPTO_EXPORT XMLSEC_DEPRECATED PCCERT_CONTEXT xmlSecMSCryptoX509StoreFindCert_ex (xmlSecKeyDataStorePtr store, + xmlChar* subjectName, + xmlChar* issuerName, + xmlChar* issuerSerial, + xmlSecByte* ski, + xmlSecSize skiSize, + xmlSecKeyInfoCtxPtr keyInfoCtx); +XMLSEC_CRYPTO_EXPORT XMLSEC_DEPRECATED PCCERT_CONTEXT xmlSecMSCryptoX509FindCertBySubject (HCERTSTORE store, + LPCTSTR wcSubject, + DWORD dwCertEncodingType); #ifdef __cplusplus diff --git a/scripts/build_release.sh b/scripts/build_release.sh index 063b52e9f..3bb3cacf9 100755 --- a/scripts/build_release.sh +++ b/scripts/build_release.sh @@ -89,7 +89,7 @@ echo "RUN MANUALLY (smtp): cd /home/apps/www/aleksey.com/xmlsec/ && sudo ln -sfn echo "Verify that the website is working correctly." echo "Check the Windows build script, build the Windows version, and upload it to smtp.aleksey.com:" -echo "RUN MANUALLY (Windows): scp d:\home\aleksey\distro\xmlsec1-${full_version}-win64.zip smtp.aleksey.com:" +echo "RUN MANUALLY (Windows): scp d:\home\aleksey\distro.release\xmlsec1-${full_version}-win64.zip smtp.aleksey.com:" echo "RUN MANUALLY (smtp): sudo cp ~/xmlsec1-${full_version}-win64.zip /home/apps/www/aleksey.com/xmlsec/download/win64/" echo "RUN MANUALLY (smtp): cd /home/apps/www/aleksey.com/xmlsec/download/" echo "Move old versions to the 'older-releases' folder" diff --git a/scripts/build_windows.sh b/scripts/build_windows.sh index fdb5df741..5fffe40cc 100755 --- a/scripts/build_windows.sh +++ b/scripts/build_windows.sh @@ -20,7 +20,9 @@ openssl_version="4.0.0" xmlsec_version="1.3.13-rc1" orig_pwd=$(pwd) -script_dir=$(dirname "$0") +# "$0" may use Windows backslash separators (e.g. "scripts\build_windows.sh"); +# normalize them to forward slashes so dirname() resolves the script directory. +script_dir=$(dirname "${0//\\//}") # Build locations, overridable via environment variables. @@ -219,7 +221,7 @@ function build_openssl { echo "*** Configuring \"${full_name}\" ..." OLD_PATH="$PATH" - PATH="$PATH;$PERL_PATH" + PATH="${PATH}:${PERL_PATH}" cd "${full_name}" || return 1 perl Configure no-unit-test --prefix="${openssl_install_dir_win}" ${OPENSSL_XMLSEC_CONFIG} VC-WIN64A-HYBRIDCRT rc=$? @@ -303,7 +305,7 @@ function build_xmlsec { function create_readme { echo "*** Creating README..." cd "${orig_pwd}" || return 1 - cat "${script_dir}\\README-WINDOWS.md.in" | \ + cat "${script_dir}/README-WINDOWS.md.in" | \ sed "s/@libxml2_version@/${libxml2_version}/g" | \ sed "s/@libxslt_version@/${libxslt_version}/g" | \ sed "s/@openssl_version@/${openssl_version}/g" | \ diff --git a/src/mscng/app.c b/src/mscng/app.c index a181ad932..943b84dbb 100644 --- a/src/mscng/app.c +++ b/src/mscng/app.c @@ -460,9 +460,14 @@ xmlSecMSCngAppKeyLoadMemory(const xmlSecByte* data, xmlSecSize dataSize, xmlSecK * @return 0 on success or a negative value otherwise. */ int -xmlSecMSCngAppKeyCertLoad(xmlSecKeyPtr key, const char* filename, - xmlSecKeyDataFormat format) { +xmlSecMSCngAppKeyCertLoad( + xmlSecKeyPtr key, + const char* filename, + xmlSecKeyDataFormat format +) { xmlSecBuffer buffer; + const xmlSecByte* bufferData; + xmlSecSize bufferSize; int ret; xmlSecAssert2(key != NULL, -1); @@ -483,14 +488,23 @@ xmlSecMSCngAppKeyCertLoad(xmlSecKeyPtr key, const char* filename, return(-1); } - ret = xmlSecMSCngAppKeyCertLoadMemory(key, xmlSecBufferGetData(&buffer), - xmlSecBufferGetSize(&buffer), format); + bufferData = xmlSecBufferGetData(&buffer); + bufferSize = xmlSecBufferGetSize(&buffer); + if((bufferData == NULL) || (bufferSize == 0)) { + xmlSecOtherError2(XMLSEC_ERRORS_R_INVALID_DATA, NULL, + "empty file: %s", xmlSecErrorsSafeString(filename)); + xmlSecBufferFinalize(&buffer); + return(-1); + } + + ret = xmlSecMSCngAppKeyCertLoadMemory(key, bufferData, bufferSize, format); if(ret < 0) { xmlSecInternalError("xmlSecMSCngAppKeyCertLoadMemory", NULL); xmlSecBufferFinalize(&buffer); return(-1); } + /* done */ xmlSecBufferFinalize(&buffer); return(0); } @@ -579,7 +593,7 @@ xmlSecMSCngAppKeyCertLoadMemory(xmlSecKeyPtr key, const xmlSecByte* data, xmlSec * in format=xmlSecKeyDataFormatPkcs12. * * @param filename the PKCS12 key filename. - * @param pwd the PKCS12 file password. + * @param pwd the PKCS12 file password (UTF-8 encoded). * @param pwdCallback the password callback. Not supported by the MSCng * back-end and ignored; the password must be supplied via @p pwd. * @param pwdCallbackCtx the user context for password callback. Not supported @@ -587,8 +601,10 @@ xmlSecMSCngAppKeyCertLoadMemory(xmlSecKeyPtr key, const xmlSecByte* data, xmlSec * @return pointer to the key or NULL if an error occurs. */ xmlSecKeyPtr -xmlSecMSCngAppPkcs12Load(const char *filename, - const char *pwd, void* pwdCallback, void* pwdCallbackCtx +xmlSecMSCngAppPkcs12Load( + const char *filename, + const char *pwd, + void* pwdCallback, void* pwdCallbackCtx ) { xmlSecBuffer buffer; xmlSecByte* data; @@ -596,7 +612,11 @@ xmlSecMSCngAppPkcs12Load(const char *filename, int ret; xmlSecAssert2(filename != NULL, NULL); - xmlSecAssert2(pwd != NULL, NULL); + if(pwd == NULL) { + xmlSecOtherError(XMLSEC_ERRORS_R_INVALID_DATA, NULL, + "password is required; password callbacks are not supported by the MSCng back-end"); + return(NULL); + } ret = xmlSecBufferInitialize(&buffer, 0); if(ret < 0) { @@ -668,7 +688,7 @@ xmlSecMSCngIsPrivateKeyCert(PCCERT_CONTEXT cert, BOOL isPersistentKey) { * * @param data the key binary data. * @param dataSize the key binary data size. - * @param pwd the PKCS12 password. + * @param pwd the PKCS12 password (UTF-8 encoded). * @param pwdCallback the password callback. Not supported by the MSCng * back-end and ignored; the password must be supplied via @p pwd. * @param pwdCallbackCtx the user context for password callback. Not supported @@ -676,9 +696,11 @@ xmlSecMSCngIsPrivateKeyCert(PCCERT_CONTEXT cert, BOOL isPersistentKey) { * @return pointer to the key or NULL if an error occurs. */ xmlSecKeyPtr -xmlSecMSCngAppPkcs12LoadMemory(const xmlSecByte* data, xmlSecSize dataSize, const char *pwd, - void *pwdCallback, - void* pwdCallbackCtx) { +xmlSecMSCngAppPkcs12LoadMemory( + const xmlSecByte* data, xmlSecSize dataSize, + const char *pwd, + void *pwdCallback, void* pwdCallbackCtx +) { XMLSEC_UNREFERENCED(pwdCallback); XMLSEC_UNREFERENCED(pwdCallbackCtx); CRYPT_DATA_BLOB pfx; @@ -694,7 +716,11 @@ xmlSecMSCngAppPkcs12LoadMemory(const xmlSecByte* data, xmlSecSize dataSize, cons xmlSecAssert2(data != NULL, NULL); xmlSecAssert2(dataSize > 0, NULL); - xmlSecAssert2(pwd != NULL, NULL); + if(pwd == NULL) { + xmlSecOtherError(XMLSEC_ERRORS_R_INVALID_DATA, NULL, + "password is required; password callbacks are not supported by the MSCng back-end"); + return(NULL); + } memset(&pfx, 0, sizeof(pfx)); pfx.pbData = (BYTE *)data; @@ -706,9 +732,9 @@ xmlSecMSCngAppPkcs12LoadMemory(const xmlSecByte* data, xmlSecSize dataSize, cons return(NULL); } - pwdWideChar = xmlSecWin32ConvertLocaleToUnicode(pwd); + pwdWideChar = xmlSecWin32ConvertUtf8ToUnicode((const xmlChar*)pwd); if(pwdWideChar == NULL) { - xmlSecInternalError("xmlSecWin32ConvertLocaleToUnicode", NULL); + xmlSecInternalError("xmlSecWin32ConvertUtf8ToUnicode", NULL); goto cleanup; } @@ -1366,6 +1392,7 @@ xmlSecMSCngAppDefaultKeysMngrSave(xmlSecKeysMngrPtr mngr, const char* filename, */ void* xmlSecMSCngAppGetDefaultPwdCallback(void) { - /* TODO: MSCNG doesn't support password callback */ + /* The MSCng backend does not support password callbacks; the password (if any) + * must be supplied explicitly via the pwd parameter of the load functions. */ return(NULL); } diff --git a/src/mscng/certkeys.c b/src/mscng/certkeys.c index 4d23a3011..46fd0532d 100644 --- a/src/mscng/certkeys.c +++ b/src/mscng/certkeys.c @@ -395,7 +395,10 @@ xmlSecMSCngKeyDataGetPubkey(xmlSecKeyDataPtr data) { * @param data the key data to retrieve the private key from. * * @return the private key on success or 0 otherwise. The returned key is - * owned by the key data; the caller must not destroy it. + * owned by the key data; the caller must not destroy it. Note that DH and + * X25519 private keys are stored as a BCRYPT_KEY_HANDLE (not an + * NCRYPT_KEY_HANDLE) and are not accessible via this function; it returns 0 + * for such keys. */ NCRYPT_KEY_HANDLE xmlSecMSCngKeyDataGetPrivkey(xmlSecKeyDataPtr data) { @@ -793,9 +796,38 @@ xmlSecMSCngCertKeyDataGetType(xmlSecKeyDataPtr data) { return(xmlSecKeyDataTypePublic); } +/** + * @brief Gets the key strength (in bits) of a CNG public key handle. + * @param hKey the CNG public key handle. + * @return the key strength in bits, or 0 on failure. + */ +static xmlSecSize +xmlSecMSCngGetPubkeyStrengthInBits(BCRYPT_KEY_HANDLE hKey) { + NTSTATUS status; + DWORD length = 0; + DWORD lenlen = sizeof(length); + xmlSecSize res; + + /* Returns the number of bits in the key + * https://learn.microsoft.com/en-us/windows/win32/seccng/cng-property-identifiers */ + status = BCryptGetProperty(hKey, + BCRYPT_KEY_STRENGTH, + (PUCHAR)&length, + lenlen, + &lenlen, + 0); + if(status != STATUS_SUCCESS) { + xmlSecMSCngNtError("BCryptGetProperty", NULL, status); + return(0); + } + xmlSecAssert2(lenlen == sizeof(length), 0); + + XMLSEC_SAFE_CAST_ULONG_TO_SIZE(length, res, return(0), NULL); + return(res); +} + xmlSecSize xmlSecMSCngCertKeyDataGetSizeInBits(xmlSecKeyDataPtr data) { - NTSTATUS status; xmlSecMSCngKeyDataCtxPtr ctx; DWORD length = 0; xmlSecSize res; @@ -812,32 +844,30 @@ xmlSecMSCngCertKeyDataGetSizeInBits(xmlSecKeyDataPtr data) { * https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetpublickeylength */ length = CertGetPublicKeyLength(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &ctx->cert->pCertInfo->SubjectPublicKeyInfo); - if(length == 0) { - xmlSecMSCngLastError("CertGetPublicKeyLength", NULL); - return(0); + if(length != 0) { + XMLSEC_SAFE_CAST_ULONG_TO_SIZE(length, res, return(0), NULL); + return(res); } - } else if(ctx->pubkey != 0) { - DWORD lenlen = sizeof(length); - /* Returns the number of bits in the key - * https://learn.microsoft.com/en-us/windows/win32/seccng/cng-property-identifiers */ - status = BCryptGetProperty(ctx->pubkey, - BCRYPT_KEY_STRENGTH, - (PUCHAR)&length, - lenlen, - &lenlen, - 0); - if(status != STATUS_SUCCESS) { - xmlSecMSCngNtError("BCryptGetProperty", NULL, status); - return(0); + /* CertGetPublicKeyLength only understands RSA and some legacy EC/DSA + * encodings; it fails for X25519, DH (X9.42) and DSA > 1024. + * Fall back to the CNG key strength when the public key handle is available. */ + if(ctx->pubkey != 0) { + return(xmlSecMSCngGetPubkeyStrengthInBits(ctx->pubkey)); } - xmlSecAssert2(lenlen == sizeof(length), 0); - } else if(ctx->privkey != 0) { + xmlSecMSCngLastError("CertGetPublicKeyLength", NULL); + return(0); + } + + if(ctx->pubkey != 0) { + return(xmlSecMSCngGetPubkeyStrengthInBits(ctx->pubkey)); + } + + if(ctx->privkey != 0) { xmlSecNotImplementedError("MSCNG doesn't support getting key length from private key"); return(0); } - XMLSEC_SAFE_CAST_ULONG_TO_SIZE(length, res, return(0), NULL); - return(res); + return(0); } #define XMLSEC_MSCNG_CERTKEY_KLASS_EX(klassName, xmlName, usage, dataNodeName, dataNodeNs, generate, xmlRead, xmlWrite) \ diff --git a/src/mscng/certkeys_dsa.c b/src/mscng/certkeys_dsa.c index beb0bd089..c3a90855a 100755 --- a/src/mscng/certkeys_dsa.c +++ b/src/mscng/certkeys_dsa.c @@ -127,8 +127,8 @@ xmlSecMSCngKeyDataCertGetDsaPubkey(PCERT_PUBLIC_KEY_INFO spki, BCRYPT_KEY_HANDLE xmlSecInvalidSizeMoreThanError("DSA Q size", (xmlSecSize)qSize, (xmlSecSize)XMLSEC_MSCNG_DSA_V2_Q_SIZE, NULL); goto done; } - if((gSize > pSize) || (ySize > pSize)) { - xmlSecInvalidDataError("invalid DSA key parameters (g/y longer than p)", NULL); + if((qSize > pSize) || (gSize > pSize) || (ySize > pSize)) { + xmlSecInvalidDataError("invalid DSA key parameters (q/g/y longer than p)", NULL); goto done; } @@ -452,6 +452,7 @@ xmlSecMSCngKeyDataDsaRead(xmlSecKeyDataId id, xmlSecKeyValueDsaPtr dsaValue) { xmlSecInvalidSizeMoreThanError("DSA P size", (xmlSecSize)pSize, (xmlSecSize)XMLSEC_MSCNG_DSA_MAX_P_SIZE, NULL); goto done; } + xmlSecAssert2(qSize <= pSize, NULL); xmlSecAssert2(gSize <= pSize, NULL); xmlSecAssert2(ySize <= pSize, NULL); diff --git a/src/mscng/kw_rfc_3394.c b/src/mscng/kw_rfc_3394.c index 928254d4c..239ea8a7e 100644 --- a/src/mscng/kw_rfc_3394.c +++ b/src/mscng/kw_rfc_3394.c @@ -389,7 +389,7 @@ xmlSecMSCngKWAesBlockEncrypt(xmlSecTransformPtr transform, const xmlSecByte* in, blobSize = xmlSecBufferGetSize(&blob); XMLSEC_SAFE_CAST_SIZE_TO_ULONG(blobSize, dwBlobSize, goto done, NULL); - /* perform the actual import */ + /* perform the key import */ status = BCryptImportKey(hAlg, NULL, BCRYPT_KEY_DATA_BLOB, @@ -404,6 +404,7 @@ xmlSecMSCngKWAesBlockEncrypt(xmlSecTransformPtr transform, const xmlSecByte* in, goto done; } + /* perform the encryption */ cbData = 0; XMLSEC_SAFE_CAST_SIZE_TO_ULONG(inSize, dwInSize, goto done, NULL); status = BCryptEncrypt(hKey, @@ -539,7 +540,7 @@ xmlSecMSCngKWAesBlockDecrypt(xmlSecTransformPtr transform, const xmlSecByte* in, blobSize = xmlSecBufferGetSize(&blob); XMLSEC_SAFE_CAST_SIZE_TO_ULONG(blobSize, dwBlobSize, goto done, NULL); - /* perform the actual import */ + /* perform the key import */ status = BCryptImportKey(hAlg, NULL, BCRYPT_KEY_DATA_BLOB, @@ -554,6 +555,7 @@ xmlSecMSCngKWAesBlockDecrypt(xmlSecTransformPtr transform, const xmlSecByte* in, goto done; } + /* perform the decryption */ cbData = 0; XMLSEC_SAFE_CAST_SIZE_TO_ULONG(inSize, dwInSize, goto done, NULL); status = BCryptDecrypt(hKey, diff --git a/src/mscng/x509.c b/src/mscng/x509.c index 9e77568c4..c5c330d90 100644 --- a/src/mscng/x509.c +++ b/src/mscng/x509.c @@ -211,11 +211,14 @@ xmlSecMSCngKeyDataX509AdoptKeyCert(xmlSecKeyDataPtr data, PCCERT_CONTEXT cert) { CertFreeCertificateContext(cert); /* caller expects data to own the cert on success. */ return(0); } + /* replace the existing key certificate, duplicate to ensure the private key is copied */ if(ctx->keyCert != NULL) { CertFreeCertificateContext(ctx->keyCert); ctx->keyCert = NULL; } + + /* replace the existing key certificate, duplicate to ensure the private key is copied */ ctx->keyCert = CertDuplicateCertificateContext(cert); if (ctx->keyCert == NULL) { xmlSecMSCngLastError("CertDuplicateCertificateContext", NULL); diff --git a/src/mscng/x509vfy.c b/src/mscng/x509vfy.c index e9f07380e..20fd85844 100644 --- a/src/mscng/x509vfy.c +++ b/src/mscng/x509vfy.c @@ -56,8 +56,16 @@ XMLSEC_KEY_DATA_STORE_DECLARE(MSCngX509Store, xmlSecMSCngX509StoreCtx) #define XMLSEC_CLOSE_STORE_FLAG (0) #endif // _DEBUG -static int xmlSecMSCngUnixTimeToFileTime (time_t in, - LPFILETIME out); +static int xmlSecMSCngUnixTimeToFileTime (time_t in, + LPFILETIME out); + +static int xmlSecMSCngX509StoreVerifyCertificateChain (PCCERT_CONTEXT cert, + FILETIME* time, + HCERTSTORE trustedStore, + HCERTSTORE untrustedStore, + HCERTSTORE certStore, + HCERTSTORE crlStore, + int checkRevocation); static FILETIME* xmlSecMSCngX509StoreGetVerificationTime(xmlSecKeyInfoCtxPtr keyInfoCtx, FILETIME* timeContainer) { @@ -190,7 +198,7 @@ xmlSecMSCngX509StoreAdoptKeyStore(xmlSecKeyDataStorePtr store, HCERTSTORE keySto /** * @brief Adds @p trustedStore to the trusted certs list. * @details Adds @p trustedStore to the list of trusted certs stores. - * @param store the pointer to the X509 key data store instance. + * @param store the pointer to the X509 key data store instance. * @param trustedStore the pointer to certs store. * @return 0 on success or a negative value if an error occurs. */ @@ -220,7 +228,7 @@ xmlSecMSCngX509StoreAdoptTrustedStore(xmlSecKeyDataStorePtr store, HCERTSTORE tr /** * @brief Adds @p untrustedStore to the untrusted certs list. * @details Adds @p untrustedStore to the list of untrusted certs stores. - * @param store the pointer to the X509 key data store instance. + * @param store the pointer to the X509 key data store instance. * @param untrustedStore the pointer to certs store. * @return 0 on success or a negative value if an error occurs. */ @@ -448,15 +456,125 @@ xmlSecMSCngX509StoreAdoptCrl(xmlSecKeyDataStorePtr store, PCCRL_CONTEXT crl) { return(0); } +/* Returns 1 if the CRL signature verifies against a trusted issuer + * certificate, 0 if it does not, or a negative value if an error occurs. + * Issuers found outside the trusted store must still chain to trust, but that + * chain validation skips revocation to avoid recursively consulting the CRL + * being verified. */ +static int +xmlSecMSCngX509StoreVerifyCrlSignature( + HCERTSTORE trustedStore, + HCERTSTORE untrustedStore, + HCERTSTORE certStore, + PCCRL_CONTEXT crl, + FILETIME* time +) { + PCCERT_CONTEXT issuerCert = NULL; + HCERTSTORE stores[2]; + int numStores = 0; + int ii; + BOOL verified = FALSE; + + xmlSecAssert2(trustedStore != NULL, -1); + xmlSecAssert2(crl != NULL, -1); + xmlSecAssert2(crl->pCrlInfo != NULL, -1); + + /* find the issuer certificate in the trusted store and verify the CRL signature */ + issuerCert = CertFindCertificateInStore(trustedStore, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_SUBJECT_NAME, + &(crl->pCrlInfo->Issuer), + NULL); + while (issuerCert != NULL) { + verified = CryptVerifyCertificateSignatureEx( + (HCRYPTPROV_LEGACY)NULL, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + CRYPT_VERIFY_CERT_SIGN_SUBJECT_CRL, (void*)crl, + CRYPT_VERIFY_CERT_SIGN_ISSUER_CERT, (void*)issuerCert, + 0, NULL); + if (verified == TRUE) { + CertFreeCertificateContext(issuerCert); + return(1); + } + /* try next matching cert; CertFindCertificateInStore frees issuerCert + * (see https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore) */ + issuerCert = CertFindCertificateInStore(trustedStore, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_SUBJECT_NAME, + &(crl->pCrlInfo->Issuer), + issuerCert); + } + + if ((certStore != NULL) && (certStore != trustedStore)) { + stores[numStores++] = certStore; + } + if ((untrustedStore != NULL) && (untrustedStore != trustedStore) && (untrustedStore != certStore)) { + stores[numStores++] = untrustedStore; + } + + for (ii = 0; ii < numStores; ++ii) { + issuerCert = CertFindCertificateInStore(stores[ii], + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_SUBJECT_NAME, + &(crl->pCrlInfo->Issuer), + NULL); + while (issuerCert != NULL) { + verified = CryptVerifyCertificateSignatureEx( + (HCRYPTPROV_LEGACY)NULL, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + CRYPT_VERIFY_CERT_SIGN_SUBJECT_CRL, (void*)crl, + CRYPT_VERIFY_CERT_SIGN_ISSUER_CERT, (void*)issuerCert, + 0, NULL); + if (verified == TRUE) { + int ret; + + ret = xmlSecMSCngX509StoreVerifyCertificateChain( + issuerCert, time, trustedStore, untrustedStore, certStore, + NULL, 0); /* do not check for revocation while verifying the CRL to avoid circular dependency */ + if (ret < 0) { + xmlSecInternalError("xmlSecMSCngX509StoreVerifyCertificateChain", NULL); + CertFreeCertificateContext(issuerCert); + return(-1); + } + if (ret == 1) { + CertFreeCertificateContext(issuerCert); + return(1); + } + } + issuerCert = CertFindCertificateInStore(stores[ii], + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_SUBJECT_NAME, + &(crl->pCrlInfo->Issuer), + issuerCert); + } + } + + /* CRL issuer certificate not found or signature does not verify, or the + * issuer certificate does not chain to a trusted root. */ + return(0); +} + /** * @brief Checks if @p cert is in the CRL of @p store. * @param store may contain a CRL + * @param trustedStore trusted certificates added via xmlSecMSCngX509StoreAdoptCert() * @param cert the certificate that is revoked (or not) * @param time the time for CRL validity check (can be NULL) * @return 1 if the certificate is NOT revoked, 0 if it is revoked, or a negative value if an error occurs. */ static int -xmlSecMSCngCheckRevocation(HCERTSTORE store, PCCERT_CONTEXT cert, LPFILETIME time) { +xmlSecMSCngCheckRevocation( + HCERTSTORE store, + HCERTSTORE trustedStore, + HCERTSTORE untrustedStore, + HCERTSTORE certStore, + PCCERT_CONTEXT cert, + LPFILETIME time +) { PCCRL_CONTEXT crlCtx = NULL; PCRL_ENTRY crlEntry = NULL; int isCrlTimeValid; @@ -468,12 +586,26 @@ xmlSecMSCngCheckRevocation(HCERTSTORE store, PCCERT_CONTEXT cert, LPFILETIME tim /* CertEnumCRLsInStore automatically frees the previous CRL context (see * https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certenumcrlsinstore) */ while((crlCtx = CertEnumCRLsInStore(store, crlCtx)) != NULL) { + /* only trust CRLs whose signature verifies against a trusted issuer; a CRL + * embedded in the document is controlled by the document author, so an + * unverified (forged) CRL must not be able to revoke a certificate */ + ret = xmlSecMSCngX509StoreVerifyCrlSignature(trustedStore, + untrustedStore, certStore, crlCtx, time); + if(ret < 0) { + xmlSecInternalError("xmlSecMSCngX509StoreVerifyCrlSignature", NULL); + CertFreeCRLContext(crlCtx); + return(-1); + } else if(ret == 0) { + continue; + } + isCrlTimeValid = xmlSecMSCngX509StoreIsCrlTimeValid(crlCtx, time); if(isCrlTimeValid < 0) { xmlSecInternalError("xmlSecMSCngX509StoreIsCrlTimeValid", NULL); CertFreeCRLContext(crlCtx); return(-1); } else if(isCrlTimeValid == 0) { + /* CRL is not valid at the given time, skip it */ continue; } @@ -490,14 +622,17 @@ xmlSecMSCngCheckRevocation(HCERTSTORE store, PCCERT_CONTEXT cert, LPFILETIME tim return(-1); } if(crlEntry == NULL) { + /* Certificate is not listed in the CRL, continue checking other CRLs */ continue; } + /* Certificate is listed in the CRL, verification failed */ xmlSecOtherError(XMLSEC_ERRORS_R_CERT_VERIFY_FAILED, NULL, "cert found in CRL"); CertFreeCRLContext(crlCtx); return(0); } + /* No CRL listed the certificate, verification succeeded */ return(1); } @@ -589,13 +724,18 @@ static int xmlSecMSCngX509StoreVerifyCertificateValidityAndRevocation( PCCERT_CONTEXT cert, FILETIME* time, + HCERTSTORE trustedStore, + HCERTSTORE untrustedStore, HCERTSTORE certStore, - HCERTSTORE crlStore + HCERTSTORE crlStore, + int checkRevocation ) { int ret; xmlSecAssert2(cert != NULL, -1); xmlSecAssert2(cert->pCertInfo != NULL, -1); + xmlSecAssert2(trustedStore != NULL, -1); + xmlSecAssert2(untrustedStore != NULL, -1); xmlSecAssert2(certStore != NULL, -1); /* if time is specified, check certificate notBefore/notAfter */ @@ -611,17 +751,10 @@ xmlSecMSCngX509StoreVerifyCertificateValidityAndRevocation( } } - /* check certificate revocation */ - ret = xmlSecMSCngCheckRevocation(certStore, cert, time); - if(ret < 0) { - xmlSecInternalError("xmlSecMSCngCheckRevocation", NULL); - return(-1); - } else if (ret != 1) { - /* certificate is revoked */ - return(0); - } - if(crlStore != NULL) { - ret = xmlSecMSCngCheckRevocation(crlStore, cert, time); + if(checkRevocation != 0) { + /* check certificate revocation */ + ret = xmlSecMSCngCheckRevocation(certStore, trustedStore, + untrustedStore, certStore, cert, time); if(ret < 0) { xmlSecInternalError("xmlSecMSCngCheckRevocation", NULL); return(-1); @@ -629,6 +762,17 @@ xmlSecMSCngX509StoreVerifyCertificateValidityAndRevocation( /* certificate is revoked */ return(0); } + if(crlStore != NULL) { + ret = xmlSecMSCngCheckRevocation(crlStore, trustedStore, + untrustedStore, certStore, cert, time); + if(ret < 0) { + xmlSecInternalError("xmlSecMSCngCheckRevocation", NULL); + return(-1); + } else if (ret != 1) { + /* certificate is revoked */ + return(0); + } + } } /* success */ @@ -737,11 +881,19 @@ xmlSecMSCngX509GetCertHash(PCCERT_CONTEXT pCert, BYTE* pHash, DWORD* hashSize) { * @param untrustedStore untrusted certificates stack. * @param certStore the certificates stack from the document. * @param crlStore the CRL store containing certificate revocation lists. + * @param checkRevocation if non-zero, perform revocation checks; otherwise + * skip revocation and verify only time validity and trust chain. * @return 1 on success (cert verified), 0 if cert can't be verified, or a negative value if an error occurs. */ static int -xmlSecMSCngX509StoreVerifyCertificateChain(PCCERT_CONTEXT cert, FILETIME* time, - HCERTSTORE trustedStore, HCERTSTORE untrustedStore, HCERTSTORE certStore, HCERTSTORE crlStore +xmlSecMSCngX509StoreVerifyCertificateChain( + PCCERT_CONTEXT cert, + FILETIME* time, + HCERTSTORE trustedStore, + HCERTSTORE untrustedStore, + HCERTSTORE certStore, + HCERTSTORE crlStore, + int checkRevocation ) { struct xmlSecMSCngX509StoreVerifyCertificateChainStep * queue = NULL; xmlSecSize queueSize = 0, queueMaxSize = 0; @@ -817,7 +969,8 @@ xmlSecMSCngX509StoreVerifyCertificateChain(PCCERT_CONTEXT cert, FILETIME* time, ++seenSize; /* check certificate itself */ - ret = xmlSecMSCngX509StoreVerifyCertificateValidityAndRevocation(currentCert, time, certStore, crlStore); + ret = xmlSecMSCngX509StoreVerifyCertificateValidityAndRevocation(currentCert, time, + trustedStore, untrustedStore, certStore, crlStore, checkRevocation); if(ret < 0) { xmlSecInternalError("xmlSecMSCngX509StoreVerifyCertificateValidityAndRevocation", NULL); goto done; @@ -1109,7 +1262,8 @@ xmlSecMSCngX509StoreVerifyCertificate(xmlSecMSCngX509StoreCtxPtr ctx, PCCERT_CON } /* verify based on the own trusted certificates */ - ret = xmlSecMSCngX509StoreVerifyCertificateChain(cert, time, ctx->trusted, ctx->untrusted, certStore, ctx->crlMemStore); + ret = xmlSecMSCngX509StoreVerifyCertificateChain(cert, time, ctx->trusted, + ctx->untrusted, certStore, ctx->crlMemStore, 1); /* check for revocation when verifying the certificate */ if(ret < 0) { xmlSecInternalError("xmlSecMSCngX509StoreVerifyCertificateChain", NULL); return(-1); @@ -1279,7 +1433,8 @@ xmlSecMSCngX509StoreVerifyCrl(xmlSecKeyDataStorePtr store, PCCRL_CONTEXT crl, CRYPT_VERIFY_CERT_SIGN_ISSUER_CERT, (void*)issuerCert, 0, NULL) == TRUE) { /* verify that the issuer cert itself chains to a trusted root */ - ret = xmlSecMSCngX509StoreVerifyCertificateChain(issuerCert, time, ctx->trusted, ctx->untrusted, ctx->untrusted, ctx->crlMemStore); + ret = xmlSecMSCngX509StoreVerifyCertificateChain(issuerCert, + time, ctx->trusted, ctx->untrusted, ctx->untrusted, NULL, 0); /* do not check for revocation while verifying the issuer cert to avoid circular dependency */ if (ret < 0) { xmlSecInternalError("xmlSecMSCngX509StoreVerifyCertificateChain", NULL); CertFreeCertificateContext(issuerCert); diff --git a/src/mscrypto/certkeys.c b/src/mscrypto/certkeys.c index 7b1ebf18d..8116ad5d8 100644 --- a/src/mscrypto/certkeys.c +++ b/src/mscrypto/certkeys.c @@ -874,8 +874,9 @@ PCCERT_CONTEXT xmlSecMSCryptoCertDup(PCCERT_CONTEXT pCert) { * @param pCert the pointer to cert. * @param type the expected key type. * - * The function takes ownership of the certificate context; the caller - * must not free it afterwards. + * @details On success, the function takes ownership of the certificate context; the caller + * must not free it afterwards. On failure the function does not take ownership and the + * caller must free @p pCert. * * @return pointer to newly created xmlsec key or NULL if an error occurs. */ diff --git a/src/mscrypto/ciphers.c b/src/mscrypto/ciphers.c index 08d694623..8237688ca 100644 --- a/src/mscrypto/ciphers.c +++ b/src/mscrypto/ciphers.c @@ -88,6 +88,11 @@ xmlSecMSCryptoBlockCipherCtxInit(xmlSecMSCryptoBlockCipherCtxPtr ctx, unsigned char* iv; /* allocate space for IV */ + if(outSize > XMLSEC_SIZE_MAX - blockSize) { + xmlSecInternalError3("xmlSecBufferSetSize", cipherName, + "outSize=" XMLSEC_SIZE_FMT "; blockSize=" XMLSEC_SIZE_FMT, outSize, blockSize); + return(-1); + } ret = xmlSecBufferSetSize(out, outSize + blockSize); if(ret < 0) { xmlSecInternalError2("xmlSecBufferSetSize", cipherName, @@ -178,6 +183,12 @@ xmlSecMSCryptoBlockCipherCtxUpdate(xmlSecMSCryptoBlockCipherCtxPtr ctx, inSize = inBlocks * blockSize; /* we write out the input size plus maybe one block */ + if(outSize > XMLSEC_SIZE_MAX - inSize - blockSize) { + xmlSecInternalError4("xmlSecBufferSetMaxSize", cipherName, + "outSize=" XMLSEC_SIZE_FMT "; inSize=" XMLSEC_SIZE_FMT "; blockSize=" XMLSEC_SIZE_FMT, + outSize, inSize, blockSize); + return(-1); + } ret = xmlSecBufferSetMaxSize(out, outSize + inSize + blockSize); if(ret < 0) { xmlSecInternalError2("xmlSecBufferSetMaxSize", cipherName, @@ -292,6 +303,11 @@ xmlSecMSCryptoBlockCipherCtxFinal(xmlSecMSCryptoBlockCipherCtxPtr ctx, } /* process last block */ + if(outSize > XMLSEC_SIZE_MAX - 2 * blockSize) { + xmlSecInternalError3("xmlSecBufferSetMaxSize", cipherName, + "outSize=" XMLSEC_SIZE_FMT "; blockSize=" XMLSEC_SIZE_FMT, outSize, blockSize); + return(-1); + } ret = xmlSecBufferSetMaxSize(out, outSize + 2 * blockSize); if(ret < 0) { xmlSecInternalError2("xmlSecBufferSetMaxSize", cipherName, diff --git a/src/mscrypto/keysstore.c b/src/mscrypto/keysstore.c index 848b13a79..7e1072400 100644 --- a/src/mscrypto/keysstore.c +++ b/src/mscrypto/keysstore.c @@ -231,7 +231,10 @@ xmlSecMSCryptoKeysStoreFindCert(xmlSecKeyStorePtr store, const xmlChar* name) { #endif /* XMLSEC_NO_X509 */ /* - * Try to find certificate with name="Friendly Name" + * Try to find certificate with name="Friendly Name". This is an O(N) + * enumeration over the store: a targeted CertFindCertificateInStore() match + * is not possible because CERT_FIND_PROPERTY is an existence check, not a + * value match on the friendly-name string. */ if (NULL == pCertContext) { DWORD dwPropSize; @@ -257,6 +260,10 @@ xmlSecMSCryptoKeysStoreFindCert(xmlSecKeyStorePtr store, const xmlChar* name) { break; } + /* CertGetCertificateContextProperty takes a generic LPVOID and has + * no _A/_W variant; the friendly-name property (CERT_FRIENDLY_NAME_PROP_ID) + * is always a NULL-terminated UTF-16 string, so it can be compared + * with lstrcmpW regardless of the library's TCHAR width */ if (TRUE != CertGetCertificateContextProperty(pCertCtxIter, CERT_FRIENDLY_NAME_PROP_ID, NULL, @@ -294,9 +301,8 @@ xmlSecMSCryptoKeysStoreFindCert(xmlSecKeyStorePtr store, const xmlChar* name) { xmlFree(lpwName); } - - /* We don't give up easily, now try to find cert with part of the name - */ + /* We don't give up easily, now try to find cert with part of the name. + * This is an indexed lookup. */ if (NULL == pCertContext) { pCertContext = CertFindCertificateInStore( hStoreHandle, @@ -307,7 +313,6 @@ xmlSecMSCryptoKeysStoreFindCert(xmlSecKeyStorePtr store, const xmlChar* name) { NULL); } - /* We could do the following here: * It would be nice if we could locate the cert with issuer name and * serial number, the given keyname can be something like this: @@ -450,8 +455,8 @@ xmlSecMSCryptoKeysStoreFindKey(xmlSecKeyStorePtr store, const xmlChar* name, goto done; } - /* now that we have a key, make sure it is valid and let the simple - * store adopt it */ + /* now that we have a key, make sure it is valid; the key is returned + * to the caller (it is not cached in the simple store) */ if (xmlSecKeyIsValid(key)) { res = key; key = NULL; diff --git a/src/mscrypto/kw_rfc_3394.c b/src/mscrypto/kw_rfc_3394.c index 6caef4eb3..95b5d96eb 100644 --- a/src/mscrypto/kw_rfc_3394.c +++ b/src/mscrypto/kw_rfc_3394.c @@ -298,7 +298,8 @@ xmlSecMSCryptoKWAesBlockEncrypt(xmlSecTransformPtr transform, const xmlSecByte * XMLSEC_SAFE_CAST_SIZE_TO_ULONG(keySize, dwKeySize, goto done, NULL); /* Import this key and get an HCRYPTKEY handle. We do it again and again - to obtain a fresh session key per call (AES KW runs in ECB mode) */ + * to obtain a fresh session key per call (AES KW runs in ECB mode). + * we do not want to cache the key */ if (!xmlSecMSCryptoImportPlainSessionBlob(ctx->cryptProvider, ctx->pubPrivKey, ctx->algorithmIdentifier, @@ -368,7 +369,8 @@ xmlSecMSCryptoKWAesBlockDecrypt(xmlSecTransformPtr transform, const xmlSecByte * XMLSEC_SAFE_CAST_SIZE_TO_ULONG(keySize, dwKeySize, goto done, NULL); /* Import this key and get an HCRYPTKEY handle. We do it again and again - to obtain a fresh session key per call (AES KW runs in ECB mode) */ + * to obtain a fresh session key per call (AES KW runs in ECB mode). + * we do not want to cache the key */ if (!xmlSecMSCryptoImportPlainSessionBlob(ctx->cryptProvider, ctx->pubPrivKey, ctx->algorithmIdentifier, diff --git a/src/mscrypto/x509.c b/src/mscrypto/x509.c index b6f44b84e..ec9e54125 100644 --- a/src/mscrypto/x509.c +++ b/src/mscrypto/x509.c @@ -207,6 +207,8 @@ xmlSecMSCryptoKeyDataX509AdoptKeyCert(xmlSecKeyDataPtr data, PCCERT_CONTEXT cert ctx = xmlSecMSCryptoX509DataGetCtx(data); xmlSecAssert2(ctx != NULL, -1); + /* PCERT_CONTEXT is reference-counted, even if this is same cert we need to free the old one + * https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certduplicatecertificatecontext */ if(ctx->keyCert != NULL) { CertFreeCertificateContext(ctx->keyCert); ctx->keyCert = NULL; diff --git a/src/mscrypto/x509vfy.c b/src/mscrypto/x509vfy.c index c7a5ee72b..2336d9b18 100644 --- a/src/mscrypto/x509vfy.c +++ b/src/mscrypto/x509vfy.c @@ -61,6 +61,13 @@ XMLSEC_KEY_DATA_STORE_DECLARE(MSCryptoX509Store, xmlSecMSCryptoX509StoreCtx) static int xmlSecMSCryptoX509StoreInitialize (xmlSecKeyDataStorePtr store); static void xmlSecMSCryptoX509StoreFinalize (xmlSecKeyDataStorePtr store); +static int xmlSecMSCryptoBuildCertChain (PCCERT_CONTEXT cert, + LPFILETIME pfTime, + HCERTSTORE trustedStore, + HCERTSTORE untrustedStore, + HCERTSTORE certStore, + xmlSecKeyDataStorePtr store, + int checkRevocation); static xmlSecKeyDataStoreKlass xmlSecMSCryptoX509StoreKlass = { sizeof(xmlSecKeyDataStoreKlass), @@ -189,27 +196,160 @@ xmlSecMSCryptoUnixTimeToFileTime(time_t t, LPFILETIME pft) { pft->dwHighDateTime = (DWORD)(ll >> 32); } +/* Returns TRUE if the CRL is time valid (NotBefore <= time <= NotAfter), + * FALSE otherwise. A NULL time skips the check (skip-time-checks flag). */ static BOOL -xmlSecMSCryptoVerifyCertTime(PCCERT_CONTEXT pCert, LPFILETIME pft) { +xmlSecMSCryptoVerifyCertTime(PCCERT_CONTEXT pCert, LPFILETIME pfTime) { xmlSecAssert2(pCert != NULL, FALSE); xmlSecAssert2(pCert->pCertInfo != NULL, FALSE); - xmlSecAssert2(pft != NULL, FALSE); - if(1 == CompareFileTime(&(pCert->pCertInfo->NotBefore), pft)) { + if (pfTime == NULL) { + return(TRUE); + } + + if(1 == CompareFileTime(&(pCert->pCertInfo->NotBefore), pfTime)) { return (FALSE); } - if(-1 == CompareFileTime(&(pCert->pCertInfo->NotAfter), pft)) { + if(-1 == CompareFileTime(&(pCert->pCertInfo->NotAfter), pfTime)) { return (FALSE); } return (TRUE); } +/* Returns TRUE if the CRL is time valid (thisUpdate <= time <= nextUpdate), + * FALSE otherwise. A NULL time skips the check (skip-time-checks flag). */ +static BOOL +xmlSecMSCryptoVerifyCrlTime(PCCRL_CONTEXT pCrl, LPFILETIME pfTime) { + xmlSecAssert2(pCrl != NULL, FALSE); + xmlSecAssert2(pCrl->pCrlInfo != NULL, FALSE); + + if (pfTime == NULL) { + return(TRUE); + } + + if (CompareFileTime(pfTime, &(pCrl->pCrlInfo->ThisUpdate)) < 0) { + return(FALSE); + } + + if ((pCrl->pCrlInfo->NextUpdate.dwLowDateTime != 0) || + (pCrl->pCrlInfo->NextUpdate.dwHighDateTime != 0)) { + if (CompareFileTime(pfTime, &(pCrl->pCrlInfo->NextUpdate)) > 0) { + return(FALSE); + } + } + + return(TRUE); +} + +/* Returns 1 if the CRL signature verifies against a trusted issuer + * certificate, 0 if it does not, or a negative value on error. Issuers found + * outside the trusted store must still chain to trust, but that chain + * validation skips revocation to avoid recursively consulting the CRL being + * verified. */ +static int +xmlSecMSCryptoVerifyCrlSignature( + HCERTSTORE trustedStore, + HCERTSTORE untrustedStore, + HCERTSTORE certStore, + PCCRL_CONTEXT pCrl, + LPFILETIME pfTime +) { + PCCERT_CONTEXT issuerCert = NULL; + HCERTSTORE stores[2]; + int numStores = 0; + int ii; + + xmlSecAssert2(trustedStore != NULL, -1); + xmlSecAssert2(pCrl != NULL, -1); + xmlSecAssert2(pCrl->pCrlInfo != NULL, -1); + + issuerCert = CertFindCertificateInStore(trustedStore, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_SUBJECT_NAME, + &(pCrl->pCrlInfo->Issuer), + NULL); + while (issuerCert != NULL) { + if (CryptVerifyCertificateSignatureEx( + (HCRYPTPROV_LEGACY)NULL, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + CRYPT_VERIFY_CERT_SIGN_SUBJECT_CRL, (void*)pCrl, + CRYPT_VERIFY_CERT_SIGN_ISSUER_CERT, (void*)issuerCert, + 0, NULL) == TRUE) { + CertFreeCertificateContext(issuerCert); + return(1); + } + + /* try next matching cert; CertFindCertificateInStore frees issuerCert + * (see https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore) */ + issuerCert = CertFindCertificateInStore(trustedStore, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_SUBJECT_NAME, + &(pCrl->pCrlInfo->Issuer), + issuerCert); + } + + if ((certStore != NULL) && (certStore != trustedStore)) { + stores[numStores++] = certStore; + } + if ((untrustedStore != NULL) && (untrustedStore != trustedStore) && (untrustedStore != certStore)) { + stores[numStores++] = untrustedStore; + } + + for (ii = 0; ii < numStores; ++ii) { + issuerCert = CertFindCertificateInStore(stores[ii], + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_SUBJECT_NAME, + &(pCrl->pCrlInfo->Issuer), + NULL); + while (issuerCert != NULL) { + if (CryptVerifyCertificateSignatureEx( + (HCRYPTPROV_LEGACY)NULL, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + CRYPT_VERIFY_CERT_SIGN_SUBJECT_CRL, (void*)pCrl, + CRYPT_VERIFY_CERT_SIGN_ISSUER_CERT, (void*)issuerCert, + 0, NULL) == TRUE) { + int ret; + + ret = xmlSecMSCryptoBuildCertChain( + issuerCert, pfTime, trustedStore, untrustedStore, certStore, + NULL, 0); /* do not check for revocation when verifying CRL to avoid infinite recursion */ + if(ret < 0) { + xmlSecInternalError("xmlSecMSCryptoBuildCertChain", NULL); + CertFreeCertificateContext(issuerCert); + return(-1); + } + if(ret == 1) { + CertFreeCertificateContext(issuerCert); + return(1); + } + } + + issuerCert = CertFindCertificateInStore(stores[ii], + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_SUBJECT_NAME, + &(pCrl->pCrlInfo->Issuer), + issuerCert); + } + } + + /* no matching issuer found, none of the matching issuers verified the CRL + * signature, or the issuer certificate does not chain to a trusted root */ + return(0); +} + static BOOL -xmlSecMSCryptoCheckRevocation(HCERTSTORE hStore, PCCERT_CONTEXT pCert) { +xmlSecMSCryptoCheckRevocation(HCERTSTORE hStore, PCCERT_CONTEXT pCert, + LPFILETIME pfTime, HCERTSTORE trustedStore, HCERTSTORE untrustedStore, + HCERTSTORE certStore) { PCCRL_CONTEXT pCrl = NULL; PCRL_ENTRY pCrlEntry = NULL; BOOL ret; + int sigRet; xmlSecAssert2(pCert != NULL, FALSE); xmlSecAssert2(hStore != NULL, FALSE); @@ -217,6 +357,25 @@ xmlSecMSCryptoCheckRevocation(HCERTSTORE hStore, PCCERT_CONTEXT pCert) { /* CertEnumCRLsInStore automatically frees the previous CRL context (see * https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certenumcrlsinstore) */ while((pCrl = CertEnumCRLsInStore(hStore, pCrl)) != NULL) { + /* check CRL time validity (thisUpdate <= time <= nextUpdate); skip a + * CRL that is not yet valid or has expired */ + if (!xmlSecMSCryptoVerifyCrlTime(pCrl, pfTime)) { + continue; + } + + /* verify the CRL signature against a trusted issuer; a CRL embedded in + * the document is controlled by the document author, so an unverified + * (forged) CRL must not be able to revoke a certificate */ + sigRet = xmlSecMSCryptoVerifyCrlSignature(trustedStore, + untrustedStore, certStore, pCrl, pfTime); + if (sigRet < 0) { + xmlSecInternalError("xmlSecMSCryptoVerifyCrlSignature", NULL); + return(FALSE); + } + if (sigRet == 0) { + continue; + } + /* pCrlEntry will point to the entry for the certificate in the CRL if it exists, it doesn't need * to be freed manually (see https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateincrl) */ ret = CertFindCertificateInCRL(pCert, pCrl, 0, NULL, &pCrlEntry); @@ -290,7 +449,10 @@ xmlSecBuildChainUsingWinapi (PCCERT_CONTEXT cert, LPFILETIME pfTime, xmlSecMSCryptoError("CertGetCertificateChain", NULL); goto end; } - if (pChainContext->TrustStatus.dwErrorStatus == CERT_TRUST_REVOCATION_STATUS_UNKNOWN) { + /* retry excluding the root if the revocation status is unknown; the + * unknown bit may be combined with other ignorable bits, so use a mask + * rather than an exact equality to avoid skipping the retry */ + if ((pChainContext->TrustStatus.dwErrorStatus & CERT_TRUST_REVOCATION_STATUS_UNKNOWN) != 0) { CertFreeCertificateChain(pChainContext); pChainContext = NULL; if(!CertGetCertificateChain(NULL, /* use the default chain engine */ cert, @@ -451,20 +613,20 @@ xmlSecMSCryptoX509GetCertHash(PCCERT_CONTEXT pCert, BYTE* pHash, DWORD* hashSize return(0); } -/** - * @brief Builds certificates chain manually. - * @param theCert the certificate we check - * @param pfTime pointer to FILETIME that we are interested in - * @param store_trusted trusted certificates added via API - * @param store_untrusted untrusted certificates added via API - * @param certs untrusted certificates/CRLs extracted from a document - * @param store pointer to store klass passed to error functions - * @return TRUE on success or FALSE otherwise. - */ -static BOOL -xmlSecMSCryptoBuildCertChainManually (PCCERT_CONTEXT theCert, LPFILETIME pfTime, - HCERTSTORE store_trusted, HCERTSTORE store_untrusted, HCERTSTORE certs, - xmlSecKeyDataStorePtr store) { +/* Returns 1 if @p cert chains to @p trustedStore without using revocation, + * 0 if no trusted chain is found, or a negative value on error. This is used + * only for CRL issuer certificates to avoid recursively consulting the CRL + * currently being verified. */ +static int +xmlSecMSCryptoBuildCertChain( + PCCERT_CONTEXT theCert, + LPFILETIME pfTime, + HCERTSTORE trustedStore, + HCERTSTORE untrustedStore, + HCERTSTORE certStore, + xmlSecKeyDataStorePtr store, + int checkRevocation +) { struct xmlSecMSCryptoBuildCertChainStep * queue = NULL; xmlSecSize queueSize = 0, queueMaxSize = 0; BYTE seenHashes[XMLSEC_MSCRYPTO_BUILD_CERT_CHAIN_MAX_DEPTH][XMLSEC_MSCRYPTO_X509_CERT_HASH_SIZE]; @@ -473,15 +635,15 @@ xmlSecMSCryptoBuildCertChainManually (PCCERT_CONTEXT theCert, LPFILETIME pfTime, DWORD hashSize; PCCERT_CONTEXT currentCert = NULL; BOOL freeCurrentCert = FALSE; - BOOL res = FALSE; + const xmlChar* storeName = (store != NULL) ? xmlSecKeyDataStoreGetName(store) : NULL; + int res = -1; int ret; - xmlSecAssert2(theCert != NULL, FALSE); - xmlSecAssert2(pfTime != NULL, FALSE); - xmlSecAssert2(store_trusted != NULL, FALSE); - xmlSecAssert2(store_untrusted != NULL, FALSE); - xmlSecAssert2(certs != NULL, FALSE); - xmlSecAssert2(store != NULL, FALSE); + xmlSecAssert2(theCert != NULL, -1); + xmlSecAssert2(trustedStore != NULL, -1); + xmlSecAssert2(untrustedStore != NULL, -1); + xmlSecAssert2(certStore != NULL, -1); + xmlSecAssert2((checkRevocation == 0) || (store != NULL), -1); /* setup queue */ queue = (struct xmlSecMSCryptoBuildCertChainStep*)xmlMalloc( @@ -489,7 +651,7 @@ xmlSecMSCryptoBuildCertChainManually (PCCERT_CONTEXT theCert, LPFILETIME pfTime, if(queue == NULL) { xmlSecMallocError( sizeof(struct xmlSecMSCryptoBuildCertChainStep) * XMLSEC_MSCRYPTO_BUILD_CERT_CHAIN_STEP_SIZE, NULL); - return(FALSE); + return(-1); } queueMaxSize = XMLSEC_MSCRYPTO_BUILD_CERT_CHAIN_STEP_SIZE; @@ -508,9 +670,9 @@ xmlSecMSCryptoBuildCertChainManually (PCCERT_CONTEXT theCert, LPFILETIME pfTime, /* limit the chain depth to avoid excessive work on crafted inputs */ if(seenSize >= XMLSEC_MSCRYPTO_BUILD_CERT_CHAIN_MAX_DEPTH) { - xmlSecOtherError(XMLSEC_ERRORS_R_CERT_VERIFY_FAILED, - xmlSecKeyDataStoreGetName(store), + xmlSecOtherError(XMLSEC_ERRORS_R_CERT_VERIFY_FAILED, storeName, "certificate chain is too deep"); + res = 0; goto done; } @@ -544,10 +706,10 @@ xmlSecMSCryptoBuildCertChainManually (PCCERT_CONTEXT theCert, LPFILETIME pfTime, /* check certificate validity and revocation; an expired/revoked cert * cannot be part of a valid chain, so skip this branch (and its issuer) - * and continue searching the other branches in the queue */ + * and continue searching the other branches in the queue. */ if (!xmlSecMSCryptoVerifyCertTime(currentCert, pfTime)) { xmlSecOtherError(XMLSEC_ERRORS_R_CERT_HAS_EXPIRED, - xmlSecKeyDataStoreGetName(store), + storeName, "certificate expired"); if(freeCurrentCert == TRUE) { CertFreeCertificateContext(currentCert); @@ -557,37 +719,40 @@ xmlSecMSCryptoBuildCertChainManually (PCCERT_CONTEXT theCert, LPFILETIME pfTime, continue; } - if (!xmlSecMSCryptoCheckRevocation(certs, currentCert)) { - xmlSecOtherError(XMLSEC_ERRORS_R_CRL_VERIFY_FAILED, - xmlSecKeyDataStoreGetName(store), - "certificate revoked"); - if(freeCurrentCert == TRUE) { - CertFreeCertificateContext(currentCert); + if(checkRevocation != 0) { + if (!xmlSecMSCryptoCheckRevocation(certStore, currentCert, pfTime, + trustedStore, untrustedStore, certStore)) { + xmlSecOtherError(XMLSEC_ERRORS_R_CRL_VERIFY_FAILED, + storeName, + "certificate revoked"); + if(freeCurrentCert == TRUE) { + CertFreeCertificateContext(currentCert); + } + currentCert = NULL; + freeCurrentCert = FALSE; + continue; } - currentCert = NULL; - freeCurrentCert = FALSE; - continue; } /* does trustedStore contain cert directly? */ - ret = xmlSecMSCryptoX509StoreContainsCert(store_trusted, &(currentCert->pCertInfo->Subject), currentCert); + ret = xmlSecMSCryptoX509StoreContainsCert(trustedStore, &(currentCert->pCertInfo->Subject), currentCert); if (ret < 0) { xmlSecInternalError("xmlSecMSCryptoX509StoreContainsCert", NULL); goto done; } else if (ret == 1) { /* success */ - res = TRUE; + res = 1; goto done; } /* does trustedStore contain the issuer cert? */ - ret = xmlSecMSCryptoX509StoreContainsCert(store_trusted, &(currentCert->pCertInfo->Issuer), currentCert); + ret = xmlSecMSCryptoX509StoreContainsCert(trustedStore, &(currentCert->pCertInfo->Issuer), currentCert); if (ret < 0) { xmlSecInternalError("xmlSecMSCryptoX509StoreContainsCert", NULL); goto done; } else if (ret == 1) { /* success */ - res = TRUE; + res = 1; goto done; } @@ -613,7 +778,7 @@ xmlSecMSCryptoBuildCertChainManually (PCCERT_CONTEXT theCert, LPFILETIME pfTime, } /* try the untrusted certs in the chain */ - issuerCert = xmlSecMSCryptoX509StoreFindIssuer(certs, currentCert); + issuerCert = xmlSecMSCryptoX509StoreFindIssuer(certStore, currentCert); if(issuerCert != NULL) { xmlSecAssert2(queueSize < queueMaxSize, FALSE); queue[queueSize].cert = issuerCert; @@ -622,7 +787,7 @@ xmlSecMSCryptoBuildCertChainManually (PCCERT_CONTEXT theCert, LPFILETIME pfTime, } /* try the untrusted certs in the store */ - issuerCert = xmlSecMSCryptoX509StoreFindIssuer(store_untrusted, currentCert); + issuerCert = xmlSecMSCryptoX509StoreFindIssuer(untrustedStore, currentCert); if(issuerCert != NULL) { xmlSecAssert2(queueSize < queueMaxSize, FALSE); queue[queueSize].cert = issuerCert; @@ -638,7 +803,9 @@ xmlSecMSCryptoBuildCertChainManually (PCCERT_CONTEXT theCert, LPFILETIME pfTime, freeCurrentCert = FALSE; } - /* not verified */ + /* not verified */ + res = 0; + done: if((currentCert != NULL) && (freeCurrentCert == TRUE)) { CertFreeCertificateContext(currentCert); @@ -655,12 +822,37 @@ xmlSecMSCryptoBuildCertChainManually (PCCERT_CONTEXT theCert, LPFILETIME pfTime, return(res); } +/** + * @brief Builds certificates chain manually. + * @param theCert the certificate we check + * @param pfTime pointer to FILETIME that we are interested in + * @param store_trusted trusted certificates added via API + * @param store_untrusted untrusted certificates added via API + * @param certs untrusted certificates/CRLs extracted from a document + * @param store pointer to store klass passed to error functions + * @return TRUE on success or FALSE otherwise. + */ +static BOOL +xmlSecMSCryptoBuildCertChainManually (PCCERT_CONTEXT theCert, LPFILETIME pfTime, + HCERTSTORE store_trusted, HCERTSTORE store_untrusted, HCERTSTORE certs, + xmlSecKeyDataStorePtr store) { + int ret; + + ret = xmlSecMSCryptoBuildCertChain(theCert, pfTime, + store_trusted, store_untrusted, certs, store, 1); /* check for revocation when building the certificate chain */ + if(ret < 0) { + return(FALSE); + } + return((ret == 1) ? TRUE : FALSE); +} + static BOOL xmlSecMSCryptoX509StoreConstructCertsChain(xmlSecKeyDataStorePtr store, PCCERT_CONTEXT cert, HCERTSTORE certs, xmlSecKeyInfoCtx* keyInfoCtx) { xmlSecMSCryptoX509StoreCtxPtr ctx; PCCERT_CONTEXT tempCert = NULL; FILETIME fTime; + FILETIME* pfTime; BOOL res = FALSE; xmlSecAssert2(xmlSecKeyDataStoreCheckId(store, xmlSecMSCryptoX509StoreId), FALSE); @@ -674,14 +866,23 @@ xmlSecMSCryptoX509StoreConstructCertsChain(xmlSecKeyDataStorePtr store, PCCERT_C xmlSecAssert2(ctx->trusted != NULL, FALSE); xmlSecAssert2(ctx->untrusted != NULL, FALSE); - if(keyInfoCtx->certsVerificationTime > 0) { - /* convert the time to FILETIME */ - xmlSecMSCryptoUnixTimeToFileTime(keyInfoCtx->certsVerificationTime, &fTime); + + + /* honor the skip-time-checks flag: pass NULL so the chain builders skip + * the certificate notBefore/notAfter checks */ + if ((keyInfoCtx->flags & XMLSEC_KEYINFO_FLAGS_X509DATA_SKIP_TIME_CHECKS) == 0) { + /* get time and convert to FILETIME*/ + if(keyInfoCtx->certsVerificationTime > 0) { + xmlSecMSCryptoUnixTimeToFileTime(keyInfoCtx->certsVerificationTime, &fTime); + } else { + /* Defaults to current time. GetSystemTimeAsFileTime effectively never + * fails, so its return value is not checked. + * https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimeasfiletime */ + GetSystemTimeAsFileTime(&fTime); + } + pfTime = &fTime; } else { - /* Defaults to current time. GetSystemTimeAsFileTime effectively never - * fails, so its return value is not checked. - * https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimeasfiletime */ - GetSystemTimeAsFileTime(&fTime); + pfTime = NULL; } /* try the certificates in the keys manager */ @@ -691,13 +892,13 @@ xmlSecMSCryptoX509StoreConstructCertsChain(xmlSecKeyDataStorePtr store, PCCERT_C tempCert = CertEnumCertificatesInStore(ctx->trusted, NULL); if(tempCert) { CertFreeCertificateContext(tempCert); - res = xmlSecMSCryptoBuildCertChainManually(cert, &fTime, ctx->trusted, ctx->untrusted, certs, store); + res = xmlSecMSCryptoBuildCertChainManually(cert, pfTime, ctx->trusted, ctx->untrusted, certs, store); } } /* try the certificates in the system */ if(!res && !ctx->dont_use_system_trusted_certs) { - res = xmlSecBuildChainUsingWinapi(cert, &fTime, ctx->untrusted, certs); + res = xmlSecBuildChainUsingWinapi(cert, pfTime, ctx->untrusted, certs); } /* done */ diff --git a/win32/Makefile.msvc b/win32/Makefile.msvc index 0a2e8997a..9565d79c3 100644 --- a/win32/Makefile.msvc +++ b/win32/Makefile.msvc @@ -64,6 +64,7 @@ AUTOCONF = .\configure.txt APP_NAME = xmlseca.exe UNIT_TESTS_APP_NAME = xmlsec_unit_testsa.exe XMLSEC_FUZZER_APP_NAME = xmlsec_fuzzera.exe +XMLSEC_RELATIONSHIP_FUZZER_APP_NAME = xmlsec_relationship_fuzzera.exe XMLSEC_DSIG_VERIFY_FUZZER_APP_NAME = xmlsec_dsig_verify_fuzzera.exe XMLSEC_KEYINFO_FUZZER_APP_NAME = xmlsec_keyinfo_fuzzera.exe XMLSEC_KEYLOAD_FUZZER_APP_NAME = xmlsec_keyload_fuzzera.exe @@ -71,6 +72,7 @@ XMLSEC_KEYLOAD_FUZZER_APP_NAME = xmlsec_keyload_fuzzera.exe APP_NAME = xmlsec.exe UNIT_TESTS_APP_NAME = xmlsec_unit_tests.exe XMLSEC_FUZZER_APP_NAME = xmlsec_fuzzer.exe +XMLSEC_RELATIONSHIP_FUZZER_APP_NAME = xmlsec_relationship_fuzzer.exe XMLSEC_DSIG_VERIFY_FUZZER_APP_NAME = xmlsec_dsig_verify_fuzzer.exe XMLSEC_KEYINFO_FUZZER_APP_NAME = xmlsec_keyinfo_fuzzer.exe XMLSEC_KEYLOAD_FUZZER_APP_NAME = xmlsec_keyload_fuzzer.exe @@ -194,6 +196,13 @@ XMLSEC_FUZZER_OBJS_A = \ $(XMLSEC_OSS_FUZZ_INTDIR_A)\xmlsec_target.obj \ $(XMLSEC_OSS_FUZZ_INTDIR_A)\standalone_fuzz_runner.obj +XMLSEC_RELATIONSHIP_FUZZER_OBJS = \ + $(XMLSEC_OSS_FUZZ_INTDIR)\xmlsec_relationship_target.obj \ + $(XMLSEC_OSS_FUZZ_INTDIR)\standalone_fuzz_runner.obj +XMLSEC_RELATIONSHIP_FUZZER_OBJS_A = \ + $(XMLSEC_OSS_FUZZ_INTDIR_A)\xmlsec_relationship_target.obj \ + $(XMLSEC_OSS_FUZZ_INTDIR_A)\standalone_fuzz_runner.obj + XMLSEC_DSIG_VERIFY_FUZZER_OBJS = \ $(XMLSEC_OSS_FUZZ_INTDIR)\xmlsec_dsig_verify_target.obj \ $(XMLSEC_OSS_FUZZ_INTDIR)\standalone_fuzz_runner.obj @@ -750,7 +759,7 @@ XMLSEC_OPENSSL_FUZZER_TARGETS = XMLSEC_OPENSSL_FUZZER_TARGETS = $(BINDIR)\$(XMLSEC_DSIG_VERIFY_FUZZER_APP_NAME) $(BINDIR)\$(XMLSEC_KEYINFO_FUZZER_APP_NAME) $(BINDIR)\$(XMLSEC_KEYLOAD_FUZZER_APP_NAME) !endif -apps: $(BINDIR)\$(APP_NAME) $(BINDIR)\$(UNIT_TESTS_APP_NAME) $(BINDIR)\$(XMLSEC_FUZZER_APP_NAME) $(XMLSEC_OPENSSL_FUZZER_TARGETS) +apps: $(BINDIR)\$(APP_NAME) $(BINDIR)\$(UNIT_TESTS_APP_NAME) $(BINDIR)\$(XMLSEC_FUZZER_APP_NAME) $(BINDIR)\$(XMLSEC_RELATIONSHIP_FUZZER_APP_NAME) $(XMLSEC_OPENSSL_FUZZER_TARGETS) xmlseca: $(BINDIR)\$(XMLSEC_A) $(XMLSEC_DEFAULT_CRYPTO_A) xmlsec: $(BINDIR)\$(XMLSEC_SO) $(XMLSEC_DEFAULT_CRYPTO) @@ -769,10 +778,11 @@ mscng: $(BINDIR)\$(XMLSEC_MSCNG_SO) check : check-unit-tests check-keys check-dsig check-enc check-fuzz -check-fuzz : $(BINDIR)\$(XMLSEC_FUZZER_APP_NAME) +check-fuzz : $(BINDIR)\$(XMLSEC_FUZZER_APP_NAME) $(BINDIR)\$(XMLSEC_RELATIONSHIP_FUZZER_APP_NAME) set ASAN_OPTIONS=continue_on_error=0 cd .. win32\$(BINDIR)\$(XMLSEC_FUZZER_APP_NAME) + win32\$(BINDIR)\$(XMLSEC_RELATIONSHIP_FUZZER_APP_NAME) cd win32 set ASAN_OPTIONS= @@ -1112,6 +1122,12 @@ $(XMLSEC_FUZZER_RES) : version.rc $(BINDIR) /d "RC_DESCRIPTION=\"XML Security Library - fuzzer\"" \ /d "RC_FILETYPE=VFT_APP" /fo $@ version.rc +XMLSEC_RELATIONSHIP_FUZZER_RES = $(BINDIR)\xmlsec-relationship-fuzzer.res +$(XMLSEC_RELATIONSHIP_FUZZER_RES) : version.rc $(BINDIR) + $(RC) $(RCFLAGS) /d "RC_FILENAME=\"$(XMLSEC_RELATIONSHIP_FUZZER_APP_NAME)\"" \ + /d "RC_DESCRIPTION=\"XML Security Library - OPC relationship fuzzer\"" \ + /d "RC_FILETYPE=VFT_APP" /fo $@ version.rc + XMLSEC_DSIG_VERIFY_FUZZER_RES = $(BINDIR)\xmlsec-dsig-verify-fuzzer.res $(XMLSEC_DSIG_VERIFY_FUZZER_RES) : version.rc $(BINDIR) $(RC) $(RCFLAGS) /d "RC_FILENAME=\"$(XMLSEC_DSIG_VERIFY_FUZZER_APP_NAME)\"" \ @@ -1186,6 +1202,11 @@ $(BINDIR)\xmlsec_fuzzera.exe: xmlseca $(XMLSEC_OSS_FUZZ_INTDIR_A) $(BINDIR) $(XM $(BINDIR)\xmlsec_fuzzer.exe: xmlsec $(XMLSEC_OSS_FUZZ_INTDIR) $(BINDIR) $(XMLSEC_FUZZER_OBJS) $(XMLSEC_FUZZER_RES) $(LD) $(LDFLAGS) /OUT:$@ $(XMLSEC_IMP) $(SOLIBS) $(XMLSEC_FUZZER_OBJS) $(XMLSEC_FUZZER_RES) +$(BINDIR)\xmlsec_relationship_fuzzera.exe: xmlseca $(XMLSEC_OSS_FUZZ_INTDIR_A) $(BINDIR) $(XMLSEC_RELATIONSHIP_FUZZER_OBJS_A) $(XMLSEC_RELATIONSHIP_FUZZER_RES) + $(LD) $(LDFLAGS) /OUT:$@ $(XMLSEC_A) $(ALIBS) $(XMLSEC_RELATIONSHIP_FUZZER_OBJS_A) $(XMLSEC_RELATIONSHIP_FUZZER_RES) +$(BINDIR)\xmlsec_relationship_fuzzer.exe: xmlsec $(XMLSEC_OSS_FUZZ_INTDIR) $(BINDIR) $(XMLSEC_RELATIONSHIP_FUZZER_OBJS) $(XMLSEC_RELATIONSHIP_FUZZER_RES) + $(LD) $(LDFLAGS) /OUT:$@ $(XMLSEC_IMP) $(SOLIBS) $(XMLSEC_RELATIONSHIP_FUZZER_OBJS) $(XMLSEC_RELATIONSHIP_FUZZER_RES) + $(BINDIR)\xmlsec_dsig_verify_fuzzera.exe: xmlseca $(XMLSEC_OSS_FUZZ_INTDIR_A) $(BINDIR) $(XMLSEC_DSIG_VERIFY_FUZZER_OBJS_A) $(XMLSEC_DSIG_VERIFY_FUZZER_RES) $(LD) $(LDFLAGS) /OUT:$@ $(XMLSEC_A) $(XMLSEC_OPENSSL_A) $(ALIBS) $(XMLSEC_OPENSSL_ALIBS) $(XMLSEC_DSIG_VERIFY_FUZZER_OBJS_A) $(XMLSEC_DSIG_VERIFY_FUZZER_RES) $(BINDIR)\xmlsec_dsig_verify_fuzzer.exe: xmlsec $(XMLSEC_OSS_FUZZ_INTDIR) $(BINDIR) $(XMLSEC_DSIG_VERIFY_FUZZER_OBJS) $(XMLSEC_DSIG_VERIFY_FUZZER_RES) diff --git a/win32/configure.ps1 b/win32/configure.ps1 index 6d324d649..5b6a6ea36 100755 --- a/win32/configure.ps1 +++ b/win32/configure.ps1 @@ -124,6 +124,7 @@ function Show-Usage { Write-Host " hardening: Build with security hardening flags: /guard:cf, /DYNAMICBASE, and /NXCOMPAT (default: '$(if ($script:buildHardening) { 'yes' } else { 'no' })')" Write-Host " cc: Build with the specified compiler (default: '$($script:buildCc)')" Write-Host " cflags: Build with the specified compiler flags (default: '$($script:buildCflags)')" + Write-Host " cruntime: Compiler runtime library to link (default: '$($script:cruntime)')" Write-Host " static: Build static xmlsec libraries (default: '$(if ($script:buildStatic) { 'yes' } else { 'no' })')" Write-Host " apps: Build binaries from the 'apps/' folder (default: '$(if ($script:buildApps) { 'yes' } else { 'no' })')" Write-Host " prefix: Base directory for the installation (default: '$($script:buildPrefix)')" @@ -449,7 +450,12 @@ if (-not (Test-Path $makefileMsvc)) { Write-Host "ERROR: Cannot find '$makefileMsvc'." exit 1 } -Copy-Item $makefileMsvc (Join-Path $scriptRoot "Makefile") -Force +try { + Copy-Item $makefileMsvc (Join-Path $scriptRoot "Makefile") -Force -ErrorAction Stop +} catch { + Write-Host "ERROR: Failed to create 'Makefile' from '$makefileMsvc': $($_.Exception.Message)" + exit 1 +} Write-Host "Created Makefile." # Display the final configuration. diff --git a/win32/mycfg.bat b/win32/mycfg.bat index 98c88b919..966018fc8 100755 --- a/win32/mycfg.bat +++ b/win32/mycfg.bat @@ -30,10 +30,12 @@ SET XMLSEC_LIB=%LIBXML2_PREFIX%\lib;%LIBXSLT_PREFIX%\lib;%OPENSSL_PREFIX%\lib IF DEFINED MSSDK_LIB SET XMLSEC_LIB=%XMLSEC_LIB%;%MSSDK_LIB% SET XMLSEC_OPTIONS=crypto=%XMLSEC_CRYPTO% legacy-features=no static=no pedantic=yes %XMLSEC_OPTIONS% -nmake clean +REM Makefile is generated by configure.ps1, so on a fresh checkout there is +REM nothing to clean; only run "nmake clean" when a Makefile is present. +if exist Makefile nmake clean if errorlevel 1 exit /b %ERRORLEVEL% del /F /Q Makefile configure.txt -powershell -ExecutionPolicy Bypass -File configure.ps1 prefix=%XMLSEC_PREFIX% %XMLSEC_OPTIONS% include=%XMLSEC_INCLUDE% lib=%XMLSEC_LIB% +powershell -ExecutionPolicy Bypass -File configure.ps1 prefix="%XMLSEC_PREFIX%" %XMLSEC_OPTIONS% include="%XMLSEC_INCLUDE%" lib="%XMLSEC_LIB%" if errorlevel 1 exit /b %ERRORLEVEL% @ECHO OFF From 742721dbe1b92a3a367653a8576f43cda1102776 Mon Sep 17 00:00:00 2001 From: Aleksey Sanin Date: Sat, 3 Oct 2026 13:53:00 -0400 Subject: [PATCH 2/4] Fix MinGW and adjust parameters parsing in config script --- include/xmlsec/mscng/x509.h | 3 --- win32/configure.ps1 | 4 ---- 2 files changed, 7 deletions(-) diff --git a/include/xmlsec/mscng/x509.h b/include/xmlsec/mscng/x509.h index 079a7e6c2..7f1cf8d05 100644 --- a/include/xmlsec/mscng/x509.h +++ b/include/xmlsec/mscng/x509.h @@ -74,9 +74,6 @@ XMLSEC_CRYPTO_EXPORT int xmlSecMSCngX509StoreAdoptUntrustedStore XMLSEC_CRYPTO_EXPORT PCCERT_CONTEXT xmlSecMSCngX509StoreVerify (xmlSecKeyDataStorePtr store, HCERTSTORE certs, xmlSecKeyInfoCtxPtr keyInfoCtx); -XMLSEC_CRYPTO_EXPORT int xmlSecMSCngX509StoreVerifyKey (xmlSecKeyDataStorePtr store, - xmlSecKeyPtr key, - xmlSecKeyInfoCtxPtr keyInfoCtx); /****************************************************************************** * diff --git a/win32/configure.ps1 b/win32/configure.ps1 index 5b6a6ea36..1f9596fd2 100755 --- a/win32/configure.ps1 +++ b/win32/configure.ps1 @@ -303,16 +303,12 @@ $cruntimeSet = 0 for ($i = 0; ($i -lt $args.Count) -and ($script:errorFlag -eq 0); $i++) { $arg = $args[$i] $eqIdx = $arg.IndexOf("=") - $colIdx = $arg.IndexOf(":") $opt = "" $sepIdx = -1 if ($eqIdx -ge 0) { $opt = $arg.Substring(0, $eqIdx) $sepIdx = $eqIdx - } elseif ($colIdx -ge 0) { - $opt = $arg.Substring(0, $colIdx) - $sepIdx = $colIdx } if ($opt.Length -gt 0) { From be17e633c7be3396a3b14f3b2e9f9f1def1d51eb Mon Sep 17 00:00:00 2001 From: Aleksey Sanin Date: Sat, 3 Oct 2026 14:16:20 -0400 Subject: [PATCH 3/4] Fix MinGW --- include/xmlsec/mscrypto/x509.h | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/include/xmlsec/mscrypto/x509.h b/include/xmlsec/mscrypto/x509.h index fd5a87d97..a2dcab0e4 100644 --- a/include/xmlsec/mscrypto/x509.h +++ b/include/xmlsec/mscrypto/x509.h @@ -85,28 +85,6 @@ XMLSEC_CRYPTO_EXPORT PCCERT_CONTEXT xmlSecMSCryptoX509StoreVerify HCERTSTORE certs, xmlSecKeyInfoCtxPtr keyInfoCtx); -/****************************************************************************** - * - * DEPRECATED - * - *****************************************************************************/ -XMLSEC_CRYPTO_EXPORT XMLSEC_DEPRECATED PCCERT_CONTEXT xmlSecMSCryptoX509StoreFindCert (xmlSecKeyDataStorePtr store, - xmlChar *subjectName, - xmlChar *issuerName, - xmlChar *issuerSerial, - xmlChar *ski, - xmlSecKeyInfoCtxPtr keyInfoCtx); -XMLSEC_CRYPTO_EXPORT XMLSEC_DEPRECATED PCCERT_CONTEXT xmlSecMSCryptoX509StoreFindCert_ex (xmlSecKeyDataStorePtr store, - xmlChar* subjectName, - xmlChar* issuerName, - xmlChar* issuerSerial, - xmlSecByte* ski, - xmlSecSize skiSize, - xmlSecKeyInfoCtxPtr keyInfoCtx); -XMLSEC_CRYPTO_EXPORT XMLSEC_DEPRECATED PCCERT_CONTEXT xmlSecMSCryptoX509FindCertBySubject (HCERTSTORE store, - LPCTSTR wcSubject, - DWORD dwCertEncodingType); - #ifdef __cplusplus } From 99c5602a258318b102322c7762065216ec286d78 Mon Sep 17 00:00:00 2001 From: Aleksey Sanin Date: Sat, 3 Oct 2026 14:25:21 -0400 Subject: [PATCH 4/4] Fix MinGW --- include/xmlsec/mscrypto/x509.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/xmlsec/mscrypto/x509.h b/include/xmlsec/mscrypto/x509.h index a2dcab0e4..99e84a849 100644 --- a/include/xmlsec/mscrypto/x509.h +++ b/include/xmlsec/mscrypto/x509.h @@ -81,10 +81,6 @@ XMLSEC_CRYPTO_EXPORT int xmlSecMSCryptoX509StoreAdoptUntrustedSto XMLSEC_CRYPTO_EXPORT void xmlSecMSCryptoX509StoreEnableSystemTrustedCerts(xmlSecKeyDataStorePtr store, int val); -XMLSEC_CRYPTO_EXPORT PCCERT_CONTEXT xmlSecMSCryptoX509StoreVerify (xmlSecKeyDataStorePtr store, - HCERTSTORE certs, - xmlSecKeyInfoCtxPtr keyInfoCtx); - #ifdef __cplusplus }