/
githubmirror
/
xmlsec
Обзор
Документация
Войти
/
githubmirror
/
xmlsec
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/nss/x509vfy.c
1 312 строк
43 KB
Javid Khan
fix cert leak in xmlSecNssX509StoreRemoveRevokedCerts (#1174)
26 май 2026, 15:49
Не верифицирован
26 май 2026, 15:49
a3cbdc8
Код
Авторство
О чём код?
/** * XML Security Library (http://www.aleksey.com/xmlsec). * * This is free software; see the Copyright file in the source distribution for precise wording. * * Copyright (C) 2003-2026 Aleksey Sanin <aleksey@aleksey.com>. All Rights Reserved. * Copyright (c) 2003 America Online, Inc. All rights reserved. */ /** * @addtogroup xmlsec_nss_x509 * @brief X509 certificates verification support functions for NSS. */ #include "globals.h" #include <stdlib.h> #include <stdio.h> #include <string.h> #include <ctype.h> #include <errno.h> #include <cert.h> #include <secerr.h> #include <secder.h> #include <sechash.h> #include <keyhi.h> #include <xmlsec/xmlsec.h> #include <xmlsec/keys.h> #include <xmlsec/keyinfo.h> #include <xmlsec/keysmngr.h> #include <xmlsec/base64.h> #include <xmlsec/errors.h> #include <xmlsec/private.h> #include <xmlsec/xmltree.h> #include <xmlsec/nss/crypto.h> #include <xmlsec/nss/x509.h> #include "../cast_helpers.h" #include "../x509_helpers.h" #include "private.h" #ifndef XMLSEC_NO_X509 /****************************************************************************** * * Internal NSS X509 store CTX * *****************************************************************************/ typedef struct _xmlSecNssX509StoreCtx xmlSecNssX509StoreCtx, *xmlSecNssX509StoreCtxPtr; struct _xmlSecNssX509StoreCtx { /* Two uses: * * 1) Just keeping a reference to destroy later. * * 2) NSS doesn't update it's cache correctly when new certs are added * https://bugzilla.mozilla.org/show_bug.cgi?id=211051 * we use this list to perform search ourselves. */ CERTCertList* certsList; /* just keeping a reference to destroy later */ xmlSecNssX509CrlNodePtr crlsList; unsigned int numCrls; }; /****************************************************************************** * * xmlSecNssKeyDataStoreX509Id: * *****************************************************************************/ XMLSEC_KEY_DATA_STORE_DECLARE(NssX509Store, xmlSecNssX509StoreCtx) #define xmlSecNssX509StoreSize XMLSEC_KEY_DATA_STORE_SIZE(NssX509Store) static int xmlSecNssX509StoreInitialize (xmlSecKeyDataStorePtr store); static void xmlSecNssX509StoreFinalize (xmlSecKeyDataStorePtr store); static xmlSecKeyDataStoreKlass xmlSecNssX509StoreKlass = { sizeof(xmlSecKeyDataStoreKlass), xmlSecNssX509StoreSize, /* data */ xmlSecNameX509Store, /* const xmlChar* name; */ /* constructors/destructor */ xmlSecNssX509StoreInitialize, /* xmlSecKeyDataStoreInitializeMethod initialize; */ xmlSecNssX509StoreFinalize, /* xmlSecKeyDataStoreFinalizeMethod finalize; */ /* reserved for the future */ NULL, /* void* reserved0; */ NULL, /* void* reserved1; */ }; static CERTCertificate* xmlSecNssX509FindCert(CERTCertList* certsList, xmlSecNssX509FindCertCtxPtr findCertCtx); static int xmlSecNssX509VerifyCRLTimeValidity(CERTSignedCrl* crl, xmlSecKeyInfoCtxPtr keyInfoCtx); /** * @brief The NSS X509 certificates key data store klass. * @return pointer to NSS X509 certificates key data store klass. */ xmlSecKeyDataStoreId xmlSecNssX509StoreGetKlass(void) { return(&xmlSecNssX509StoreKlass); } /** * @brief Searches @p store for a certificate that matches given criteria. * @param store the pointer to X509 key data store klass. * @param subjectName the desired certificate name. * @param issuerName the desired certificate issuer name. * @param issuerSerial the desired certificate issuer serial number. * @param ski the desired certificate SKI. * @param keyInfoCtx the pointer to <dsig:KeyInfo/> element processing context. * * * @return pointer to found certificate or NULL if certificate is not found * or an error occurs. */ CERTCertificate * xmlSecNssX509StoreFindCert(xmlSecKeyDataStorePtr store, xmlChar *subjectName, xmlChar *issuerName, xmlChar *issuerSerial, xmlChar *ski, xmlSecKeyInfoCtx* keyInfoCtx) { if(ski != NULL) { xmlSecSize skiDecodedSize = 0; int ret; /* our usual trick with base64 decode */ ret = xmlSecBase64DecodeInPlace(ski, &skiDecodedSize); if(ret < 0) { xmlSecInternalError2("xmlSecBase64DecodeInPlace", NULL, "ski=%s", xmlSecErrorsSafeString(ski)); return(NULL); } return(xmlSecNssX509StoreFindCert_ex(store, subjectName, issuerName, issuerSerial, (xmlSecByte*)ski, skiDecodedSize, keyInfoCtx)); } else { return(xmlSecNssX509StoreFindCert_ex(store, subjectName, issuerName, issuerSerial, NULL, 0, keyInfoCtx)); } } /** * @brief Deprecated. Searches @p store for a certificate that matches given criteria. * @param store the pointer to X509 key data store klass. * @param subjectName the desired certificate name. * @param issuerName the desired certificate issuer name. * @param issuerSerial the desired certificate issuer serial number. * @param ski the desired certificate SKI. * @param skiSize the desired certificate SKI size. * @param keyInfoCtx the pointer to <dsig:KeyInfo/> element processing context. * * * @return pointer to found certificate or NULL if certificate is not found * or an error occurs. */ CERTCertificate * xmlSecNssX509StoreFindCert_ex(xmlSecKeyDataStorePtr store, xmlChar *subjectName, xmlChar *issuerName, xmlChar *issuerSerial, xmlSecByte * ski, xmlSecSize skiSize, xmlSecKeyInfoCtx* keyInfoCtx XMLSEC_ATTRIBUTE_UNUSED) { xmlSecNssX509StoreCtxPtr ctx; xmlSecNssX509FindCertCtx findCertCtx; CERTCertificate * cert; int ret; xmlSecAssert2(store != NULL, NULL); xmlSecAssert2(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId), NULL); UNREFERENCED_PARAMETER(keyInfoCtx); ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert2(ctx != NULL, NULL); /* ctx->certsList CAN be NULL since we are searching NSSDB as well */ ret = xmlSecNssX509FindCertCtxInitialize(&findCertCtx, subjectName, issuerName, issuerSerial, ski, skiSize); if(ret < 0) { xmlSecInternalError("xmlSecNssX509FindCertCtxInitialize", NULL); xmlSecNssX509FindCertCtxFinalize(&findCertCtx); return(NULL); } cert = xmlSecNssX509FindCert(ctx->certsList, &findCertCtx); /* done */ xmlSecNssX509FindCertCtxFinalize(&findCertCtx); return(cert); } CERTCertificate * xmlSecNssX509StoreFindCertByValue(xmlSecKeyDataStorePtr store, xmlSecKeyX509DataValuePtr x509Value) { xmlSecNssX509StoreCtxPtr ctx; xmlSecNssX509FindCertCtx findCertCtx; CERTCertificate * cert; int ret; xmlSecAssert2(store != NULL, NULL); xmlSecAssert2(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId), NULL); xmlSecAssert2(x509Value != NULL, NULL); ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert2(ctx != NULL, NULL); /* ctx->certsList CAN be NULL since we are searching NSSDB as well */ ret = xmlSecNssX509FindCertCtxInitializeFromValue(&findCertCtx, x509Value); if(ret < 0) { xmlSecInternalError("xmlSecNssX509FindCertCtxInitializeFromValue", NULL); xmlSecNssX509FindCertCtxFinalize(&findCertCtx); return(NULL); } cert = xmlSecNssX509FindCert(ctx->certsList, &findCertCtx); /* done */ xmlSecNssX509FindCertCtxFinalize(&findCertCtx); return(cert); } /* returns 1 if cert was revoked, 0 if not, and a negative value if an error occurs */ static int xmlSecNssX509StoreCheckIfCertIsRevoked(CERTCertificate* cert, CERTSignedCrl* crl, xmlSecKeyInfoCtx* keyInfoCtx) { CERTCrlEntry *entry; SECStatus rv; int ret; int ii; xmlSecAssert2(cert != NULL, -1); xmlSecAssert2(crl != NULL, -1); xmlSecAssert2(keyInfoCtx != NULL, -1); /* do we have any revocation entries? */ if (crl->crl.entries == NULL) { return(0); } for(ii = 0; ((entry = crl->crl.entries[ii]) != NULL); ++ii) { if (SECITEM_CompareItem(&(cert->serialNumber), &(entry->serialNumber)) != SECEqual) { continue; } /* check revocation date: if we are checking against current time, we assume * that CRL and revocation do NOT come from the future and we don't need to check * the timestamps */ if(keyInfoCtx->certsVerificationTime > 0) { PRTime revocationDate = 0; time_t revocationTs = 0; rv = DER_DecodeTimeChoice(&revocationDate, &(entry->revocationDate)); if((rv != SECSuccess) || (revocationDate == 0)) { xmlSecNssError("DER_DecodeTimeChoice(revocationDate)", NULL); return(-1); } ret = xmlSecNssX509CertGetTime(&revocationDate, &revocationTs); if((ret < 0) || (revocationTs == 0)) { xmlSecInternalError("xmlSecNssX509CertGetTime(revocationDate)", NULL); return(-1); } if(keyInfoCtx->certsVerificationTime < revocationTs) { /* verification time before revocation ts, this doesn't apply */ continue; } } /* we found a valid revocation entry for this cert */ return(1); } /* not found */ return(0); } static int xmlSecNssX509StoreFindBestCrl(xmlSecNssX509StoreCtxPtr x509StoreCtx, CERTCertificate* cert, CERTSignedCrl ** res, xmlSecKeyInfoCtx* keyInfoCtx) { xmlSecNssX509CrlNodePtr cur; PRTime lastUpdate = 0; PRTime resLastUpdate = 0; int timeRet; SECStatus rv; xmlSecAssert2(x509StoreCtx != NULL, -1); xmlSecAssert2(cert != NULL, -1); xmlSecAssert2(res != NULL, -1); xmlSecAssert2((*res) == NULL, -1); xmlSecAssert2(keyInfoCtx != NULL, -1); /* find best matching CRL */ for(cur = x509StoreCtx->crlsList; cur != NULL; cur = cur->next) { if(cur->crl == NULL) { continue; } if (SECITEM_CompareItem(&(cert->derIssuer), &(cur->crl->crl.derName)) != SECEqual) { continue; } /* skip CRLs that are not yet valid or have expired */ timeRet = xmlSecNssX509VerifyCRLTimeValidity(cur->crl, keyInfoCtx); if(timeRet < 0) { xmlSecInternalError("xmlSecNssX509VerifyCRLTimeValidity", NULL); return(-1); } else if(timeRet != 1) { continue; } /* get lastUpdate time */ rv = DER_DecodeTimeChoice(&lastUpdate, &(cur->crl->crl.lastUpdate)); if((rv != SECSuccess) || (lastUpdate == 0)) { xmlSecNssError("DER_DecodeTimeChoice(lastUpdate)", NULL); return(-1); } /* Use latest CRL by the last update time */ if(((*res) == NULL) || (resLastUpdate < lastUpdate)) { (*res) = cur->crl; resLastUpdate = lastUpdate; } } /* done! */ return(0); } static int xmlSecNssX509StoreRemoveRevokedCerts(xmlSecNssX509StoreCtxPtr x509StoreCtx, CERTCertList* certs, CERTCertList** res, xmlSecKeyInfoCtx* keyInfoCtx ) { CERTCertListNode* cur; CERTCertificate* cert; SECStatus rv; int ret; xmlSecAssert2(x509StoreCtx != NULL, -1); xmlSecAssert2(certs != NULL, -1); xmlSecAssert2(res != NULL, -1); xmlSecAssert2((*res) == NULL, -1); xmlSecAssert2(keyInfoCtx != NULL, -1); (*res) = CERT_NewCertList(); if((*res) == NULL) { xmlSecNssError("CERT_NewCertList", NULL); return(-1); } for (cur = CERT_LIST_HEAD(certs); !CERT_LIST_END(cur, certs); cur = CERT_LIST_NEXT(cur)) { CERTSignedCrl* crl = NULL; if(cur->cert == NULL) { continue; } ret = xmlSecNssX509StoreFindBestCrl(x509StoreCtx, cur->cert, &crl, keyInfoCtx); if(ret < 0) { xmlSecInternalError("xmlSecNssX509StoreFindBestCrl", NULL); return(-1); } if(crl != NULL) { ret = xmlSecNssX509StoreCheckIfCertIsRevoked(cur->cert, crl, keyInfoCtx); if(ret < 0) { xmlSecInternalError("xmlSecNssX509StoreCheckIfCertIsRevoked", NULL); return(-1); } else if(ret != 0) { /* cert was revoked */ continue; } } /* add to the output */ cert = CERT_DupCertificate(cur->cert); if(cert == NULL) { xmlSecNssError("CERT_DupCertificate", NULL); return(-1); } rv = CERT_AddCertToListTail((*res), cert); if(rv != SECSuccess) { xmlSecNssError("CERT_AddCertToListTail", NULL); CERT_DestroyCertificate(cert); return(-1); } } /* done */ return(0); } static CERTCertificate * xmlSecNssX509StoreFindChildCert(CERTCertificate* cert, CERTCertList* certs) { CERTCertListNode* cur; xmlSecAssert2(cert != NULL, NULL); xmlSecAssert2(certs != NULL, NULL); for (cur = CERT_LIST_HEAD(certs); !CERT_LIST_END(cur, certs); cur = CERT_LIST_NEXT(cur)) { /* allow self signed certs */ if(cur->cert == cert) { continue; } if (SECITEM_CompareItem(&(cur->cert->derIssuer), &(cert->derSubject)) == SECEqual) { return(cur->cert); } } return(NULL); } static int64 xmlSecNssX509SGetVerificationTime(xmlSecKeyInfoCtx* keyInfoCtx) { xmlSecAssert2(keyInfoCtx != NULL, 0); if(keyInfoCtx->certsVerificationTime > 0) { /* convert the time since epoch in seconds to microseconds */ return (int64)keyInfoCtx->certsVerificationTime * PR_USEC_PER_SEC; } else { return PR_Now(); } } /* returns 1 if verified, 0 if not, an < 0 if an error occurs */ static int xmlSecNssX509StoreVerifyCert(CERTCertDBHandle *handle, CERTCertificate* cert, xmlSecKeyInfoCtxPtr keyInfoCtx) { int64 verificationTime; SECStatus status; PRErrorCode err; xmlSecAssert2(handle != NULL, -1); xmlSecAssert2(cert != NULL, -1); xmlSecAssert2(keyInfoCtx != NULL, -1); /* do we need to verify anything at all? */ if((keyInfoCtx->flags & XMLSEC_KEYINFO_FLAGS_X509DATA_DONT_VERIFY_CERTS) != 0) { return(1); } /* get verification time */ verificationTime = xmlSecNssX509SGetVerificationTime(keyInfoCtx); /* it's important to set the usage here, otherwise no real verification * is performed. */ status = CERT_VerifyCertificate(handle, cert, PR_FALSE, certificateUsageEmailSigner, verificationTime , NULL, NULL, NULL); if(status == SECSuccess) { return(1); } /* not verified, print an error and bail out */ err = PORT_GetError(); switch(err) { case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: case SEC_ERROR_CA_CERT_INVALID: case SEC_ERROR_UNKNOWN_SIGNER: xmlSecOtherError2(XMLSEC_ERRORS_R_CERT_ISSUER_FAILED, NULL, "subject=\"%s\"; reason=the issuer's cert is expired/invalid or not found", xmlSecErrorsSafeString(cert != NULL ? cert->subjectName : NULL)); break; case SEC_ERROR_EXPIRED_CERTIFICATE: xmlSecOtherError2(XMLSEC_ERRORS_R_CERT_HAS_EXPIRED, NULL, "subject=\"%s\"; reason=expired", xmlSecErrorsSafeString(cert != NULL ? cert->subjectName : NULL)); break; case SEC_ERROR_REVOKED_CERTIFICATE: xmlSecOtherError2(XMLSEC_ERRORS_R_CERT_REVOKED, NULL, "subject=\"%s\"; reason=revoked", xmlSecErrorsSafeString(cert != NULL ? cert->subjectName : NULL)); break; default: xmlSecOtherError3(XMLSEC_ERRORS_R_CERT_VERIFY_FAILED, NULL, "subject=\"%s\"; reason=%d", xmlSecErrorsSafeString(cert != NULL ? cert->subjectName : NULL), err); break; } return(0); } /** * @brief Verifies @p key with the keys manager @p mngr created with #xmlSecCryptoAppDefaultKeysMngrInit * @param store the pointer to X509 key data store klass. * @param key the pointer to key. * @param keyInfoCtx the key info context for verification. * * function: * - Checks that key certificate is present * - Checks that key certificate is valid * * Adds @p key to the keys manager @p mngr created with #xmlSecCryptoAppDefaultKeysMngrInit * function. * * @return 1 if key is verified, 0 otherwise, or a negative value if an error occurs. */ int xmlSecNssX509StoreVerifyKey(xmlSecKeyDataStorePtr store, xmlSecKeyPtr key, xmlSecKeyInfoCtxPtr keyInfoCtx) { xmlSecNssX509StoreCtxPtr ctx; xmlSecKeyDataPtr x509Data; CERTCertificate* key_cert; int ret; xmlSecAssert2(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId), -1); xmlSecAssert2(key != NULL, -1); xmlSecAssert2(keyInfoCtx != NULL, -1); ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert2(ctx != NULL, -1); /* retrieve X509 data and get key cert, other certs and crls */ x509Data = xmlSecKeyGetData(key, xmlSecNssKeyDataX509Id); if(x509Data == NULL) { xmlSecInternalError("xmlSecKeyGetData(xmlSecNssKeyDataX509Id)", xmlSecKeyDataStoreGetName(store)); return(0); /* key cannot be verified w/o key cert */ } key_cert = xmlSecNssKeyDataX509GetKeyCert(x509Data); if(key_cert == NULL) { xmlSecInternalError("xmlSecNssKeyDataX509GetKeyCert", xmlSecKeyDataStoreGetName(store)); return(0); /* key cannot be verified w/o key cert */ } ret = xmlSecNssX509StoreVerifyCert(CERT_GetDefaultCertDB(), key_cert, keyInfoCtx); if(ret < 0) { xmlSecInternalError("xmlSecNssX509StoreVerifyCert", xmlSecKeyDataStoreGetName(store)); return(-1); } else if(ret != 1) { return(0); /* cert verification failed*/ } /* success */ return(1); } /** * @brief Verifies @p certs list. * @param store the pointer to X509 key data store klass. * @param certs the untrusted certificates stack. * @param keyInfoCtx the pointer to <dsig:KeyInfo/> element processing context. * @return pointer to the first verified certificate from @p certs. */ CERTCertificate * xmlSecNssX509StoreVerify(xmlSecKeyDataStorePtr store, CERTCertList* certs, xmlSecKeyInfoCtx* keyInfoCtx) { xmlSecNssX509StoreCtxPtr ctx; CERTCertListNode* cur; CERTCertList* good_certs = NULL; CERTCertificate* res = NULL; int ret; xmlSecAssert2(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId), NULL); xmlSecAssert2(certs != NULL, NULL); xmlSecAssert2(keyInfoCtx != NULL, NULL); ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert2(ctx != NULL, NULL); /* do we need to verify anything at all? */ if((keyInfoCtx->flags & XMLSEC_KEYINFO_FLAGS_X509DATA_DONT_VERIFY_CERTS) != 0) { good_certs = certs; } else { /* look through the certs and remove all revoked certs */ ret = xmlSecNssX509StoreRemoveRevokedCerts(ctx, certs, &good_certs, keyInfoCtx); if((ret < 0) || (good_certs == NULL)) { xmlSecInternalError("xmlSecNssX509StoreRemoveRevokedCerts", xmlSecKeyDataStoreGetName(store)); goto done; } } /* now go through all good certs we have and try to verify them */ for (cur = CERT_LIST_HEAD(good_certs); (!CERT_LIST_END(cur, good_certs)) && (res == NULL); cur = CERT_LIST_NEXT(cur)) { CERTCertificate* cert = cur->cert; /* if cert is the issuer of any other cert in the list, then it is * to be skipped (note that we are using the bigger "certs" list instead of good_certs!) */ if(xmlSecNssX509StoreFindChildCert(cert, certs) != NULL) { /* found a child, skip */ continue; } ret = xmlSecNssX509StoreVerifyCert(CERT_GetDefaultCertDB(), cert, keyInfoCtx); if(ret < 0) { xmlSecInternalError("xmlSecNssX509StoreVerifyCert", xmlSecKeyDataStoreGetName(store)); continue; /* ignore all errors and try other certs */ } else if(ret != 1) { continue; /* ignore all errors and try other certs */ } /* DONE! */ res = cert; } done: /* SMALL HACK: we are using the fact that NSS implements certs as ref counted objects * and CERT_DupCertificate() simply bumps the counter. Otherwise, the "cert" * might belong to good_certs and will be destroyed here. But exactly the * same pointer is in certs as well so we are good. Otherwise we will need to find * a certificates in "certs" that matches "cert" and return that pointer instead. */ if((good_certs != certs) && (good_certs != NULL)) { CERT_DestroyCertList(good_certs); } return(res); } /** * @brief Adds cert to the trusted or untrusted store. * @details Adds trusted (root) or untrusted certificate to the store. * @param store the pointer to X509 key data store klass. * @param cert the pointer to NSS X509 certificate. * @param type the certificate type (trusted/untrusted). * @return 0 on success or a negative value if an error occurs. */ int xmlSecNssX509StoreAdoptCert(xmlSecKeyDataStorePtr store, CERTCertificate* cert, xmlSecKeyDataType type) { xmlSecNssX509StoreCtxPtr ctx; int ret; xmlSecAssert2(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId), -1); xmlSecAssert2(cert != NULL, -1); ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert2(ctx != NULL, -1); if(ctx->certsList == NULL) { ctx->certsList = CERT_NewCertList(); if(ctx->certsList == NULL) { xmlSecNssError("CERT_NewCertList", xmlSecKeyDataStoreGetName(store)); return(-1); } } ret = CERT_AddCertToListTail(ctx->certsList, cert); if(ret != SECSuccess) { xmlSecNssError("CERT_AddCertToListTail", xmlSecKeyDataStoreGetName(store)); return(-1); } if(type == xmlSecKeyDataTypeTrusted) { SECStatus status; /* if requested, mark the certificate as trusted */ CERTCertTrust trust; status = CERT_DecodeTrustString(&trust, "TCu,Cu,Tu"); if(status != SECSuccess) { xmlSecNssError("CERT_DecodeTrustString", xmlSecKeyDataStoreGetName(store)); return(-1); } status = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), cert, &trust); if(status != SECSuccess) { xmlSecNssError("CERT_ChangeCertTrust", xmlSecKeyDataStoreGetName(store)); return(-1); } } return(0); } /** * @brief Adds CRL to the store. * @param store the pointer to X509 key data store klass. * @param crl the pointer to NSS X509 CRL. * @return 0 on success or a negative value if an error occurs. */ int xmlSecNssX509StoreAdoptCrl(xmlSecKeyDataStorePtr store, CERTSignedCrl * crl) { xmlSecNssX509StoreCtxPtr ctx; int ret; xmlSecAssert2(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId), -1); xmlSecAssert2(crl != NULL, -1); ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert2(ctx != NULL, -1); ret = xmlSecNssX509CrlListAdoptCrl(&(ctx->crlsList), crl); if(ret < 0) { xmlSecInternalError("xmlSecNssX509CrlListAdoptCrl", xmlSecKeyDataStoreGetName(store)); return(-1); } ++ctx->numCrls; return(0); } /* Helper function to verify CRL time validity */ static int xmlSecNssX509VerifyCRLTimeValidity(CERTSignedCrl* crl, xmlSecKeyInfoCtxPtr keyInfoCtx) { PRTime verification_time; PRTime thisUpdate = 0; PRTime nextUpdate = 0; time_t verification_ts; xmlSecAssert2(crl != NULL, -1); xmlSecAssert2(keyInfoCtx != NULL, -1); /* Get verification time */ if(keyInfoCtx->certsVerificationTime > 0) { verification_ts = keyInfoCtx->certsVerificationTime; } else { verification_ts = time(NULL); } /* Convert time_t to PRTime (microseconds since epoch) */ verification_time = ((PRTime)verification_ts) * PR_USEC_PER_SEC; /* Get thisUpdate */ if(crl->crl.lastUpdate.data != NULL) { SECStatus rv = DER_DecodeTimeChoice(&thisUpdate, &(crl->crl.lastUpdate)); if(rv != SECSuccess) { xmlSecNssError("DER_DecodeTimeChoice(thisUpdate)", NULL); return(-1); } /* Verify thisUpdate <= verification_time */ if(thisUpdate > verification_time) { /* CRL not yet valid */ xmlSecOtherError(XMLSEC_ERRORS_R_CRL_NOT_YET_VALID, NULL, NULL); return(0); } } /* Get nextUpdate */ if(crl->crl.nextUpdate.data != NULL) { SECStatus rv = DER_DecodeTimeChoice(&nextUpdate, &(crl->crl.nextUpdate)); if(rv != SECSuccess) { xmlSecNssError("DER_DecodeTimeChoice(nextUpdate)", NULL); return(-1); } /* Verify verification_time <= nextUpdate */ if(verification_time > nextUpdate) { /* CRL expired */ xmlSecOtherError(XMLSEC_ERRORS_R_CRL_HAS_EXPIRED, NULL, NULL); return(0); } } /* Success */ return(1); } /* Helper function to verify CRL signature */ static int xmlSecNssX509VerifyCRLSignature(xmlSecNssX509StoreCtxPtr ctx, CERTSignedCrl* crl, xmlSecKeyInfoCtxPtr keyInfoCtx) { CERTCertificate* issuer_cert = NULL; SECKEYPublicKey* pubkey = NULL; SECStatus rv; int64 verificationTime; int res = -1; xmlSecAssert2(ctx != NULL, -1); xmlSecAssert2(crl != NULL, -1); xmlSecAssert2(keyInfoCtx != NULL, -1); /* Find the CRL issuer certificate in the store */ if((crl->crl.derName.data != NULL) && (crl->crl.derName.len > 0)) { /* Search in the certs list */ if(ctx->certsList != NULL) { CERTCertListNode* node; for(node = CERT_LIST_HEAD(ctx->certsList); !CERT_LIST_END(node, ctx->certsList); node = CERT_LIST_NEXT(node)) { if(node->cert != NULL) { /* Compare CRL issuer (derName) with certificate subject (derSubject) */ if(SECITEM_CompareItem(&(crl->crl.derName), &(node->cert->derSubject)) == SECEqual) { issuer_cert = CERT_DupCertificate(node->cert); break; } } } } /* If not found in store, try to find in NSS database */ if(issuer_cert == NULL) { issuer_cert = CERT_FindCertByName(CERT_GetDefaultCertDB(), &(crl->crl.derName)); } } if(issuer_cert == NULL) { xmlSecOtherError(XMLSEC_ERRORS_R_CERT_NOT_FOUND, NULL, "CRL issuer certificate not found"); goto done; } /* Get the public key from the issuer certificate */ pubkey = CERT_ExtractPublicKey(issuer_cert); if(pubkey == NULL) { xmlSecNssError("CERT_ExtractPublicKey", NULL); goto done; } /* get verification time */ verificationTime = xmlSecNssX509SGetVerificationTime(keyInfoCtx); /* Verify the CRL signature */ rv = CERT_VerifySignedData(&(crl->signatureWrap), issuer_cert, verificationTime, NULL); if(rv != SECSuccess) { xmlSecNssError("CERT_VerifySignedData", NULL); /* Verification failed */ res = 0; goto done; } /* Success: verified */ res = 1; done: if(pubkey != NULL) { SECKEY_DestroyPublicKey(pubkey); } if(issuer_cert != NULL) { CERT_DestroyCertificate(issuer_cert); } return(res); } /** * @brief Verifies @p crl by checking: * @param store the pointer to X509 key data store klass. * @param crl the CRL to verify. * @param keyInfoCtx the key info context for verification parameters. * * 1. Signature is valid (signed by issuer cert in store) * 2. thisUpdate <= verification_time <= nextUpdate * * @return 1 if verified, 0 if not verified, or a negative value on error. */ int xmlSecNssX509StoreVerifyCrl(xmlSecKeyDataStorePtr store, CERTSignedCrl* crl, xmlSecKeyInfoCtxPtr keyInfoCtx ) { xmlSecNssX509StoreCtxPtr ctx; int ret; xmlSecAssert2(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId), -1); xmlSecAssert2(crl != NULL, -1); xmlSecAssert2(keyInfoCtx != NULL, -1); /* do we even need to verify the CRL? */ if((keyInfoCtx->flags & XMLSEC_KEYINFO_FLAGS_X509DATA_DONT_VERIFY_CERTS) != 0) { return(1); } ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert2(ctx != NULL, -1); /* Verify time validity first (fast check) */ ret = xmlSecNssX509VerifyCRLTimeValidity(crl, keyInfoCtx); if(ret < 0) { xmlSecInternalError("xmlSecNssX509VerifyCRLTimeValidity", xmlSecKeyDataStoreGetName(store)); return(-1); } else if(ret != 1) { /* Time validity check failed */ return(0); } /* Verify CRL signature (slower check) */ ret = xmlSecNssX509VerifyCRLSignature(ctx, crl, keyInfoCtx); if(ret < 0) { xmlSecInternalError("xmlSecNssX509VerifyCRLSignature", xmlSecKeyDataStoreGetName(store)); return(-1); } else if(ret != 1) { /* Signature verification failed */ return(0); } /* Success */ return(1); } static int xmlSecNssX509StoreInitialize(xmlSecKeyDataStorePtr store) { xmlSecNssX509StoreCtxPtr ctx; xmlSecAssert2(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId), -1); ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert2(ctx != NULL, -1); memset(ctx, 0, sizeof(xmlSecNssX509StoreCtx)); return(0); } static void xmlSecNssX509StoreFinalize(xmlSecKeyDataStorePtr store) { xmlSecNssX509StoreCtxPtr ctx; xmlSecAssert(xmlSecKeyDataStoreCheckId(store, xmlSecNssX509StoreId)); ctx = xmlSecNssX509StoreGetCtx(store); xmlSecAssert(ctx != NULL); if (ctx->certsList) { CERT_DestroyCertList(ctx->certsList); ctx->certsList = NULL; } if (ctx->crlsList != NULL) { xmlSecNssX509CrlListDestroy(ctx->crlsList); ctx->crlsList = NULL; } memset(ctx, 0, sizeof(xmlSecNssX509StoreCtx)); } /****************************************************************************** * * Low-level x509 functions * *****************************************************************************/ static CERTName * xmlSecNssGetCertName(const xmlChar * name) { xmlChar *name2; xmlChar *p; CERTName *res; xmlSecAssert2(name != NULL, NULL); /* nss doesn't support emailAddress (see https://bugzilla.mozilla.org/show_bug.cgi?id=561689) * This code is not bullet proof and may produce incorrect results if someone has * "emailAddress=" string in one of the fields, but it is best I can suggest to fix * this problem. */ name2 = xmlStrdup(name); if(name2 == NULL) { xmlSecStrdupError(name, NULL); return(NULL); } while( (p = (xmlChar*)xmlStrstr(name2, BAD_CAST "emailAddress=")) != NULL) { memcpy(p, " E=", 13); } res = CERT_AsciiToName((char*)name2); if (res == NULL) { xmlSecNssError2("CERT_AsciiToName", NULL, "name2=\"%s\"", xmlSecErrorsSafeString((char*)name2)); xmlFree(name2); return(NULL); } xmlFree(name2); return(res); } static CERTCertificate* xmlSecNssX509FindCert(CERTCertList* certsList, xmlSecNssX509FindCertCtxPtr findCertCtx) { CERTCertDBHandle * certDb; CERTCertificate * cert = NULL; int ret; /* certsList can be NULL */ xmlSecAssert2(findCertCtx != NULL, NULL); /* try to search in our list - NSS doesn't update it's cache correctly * when new certs are added https://bugzilla.mozilla.org/show_bug.cgi?id=211051 */ if(certsList != NULL) { CERTCertListNode* curCertNode; for(curCertNode = CERT_LIST_HEAD(certsList); (cert == NULL) && !CERT_LIST_END(curCertNode, certsList) && (curCertNode != NULL) && (curCertNode->cert != NULL); curCertNode = CERT_LIST_NEXT(curCertNode) ) { ret = xmlSecNssX509FindCertCtxMatch(findCertCtx, curCertNode->cert); if(ret < 0) { xmlSecInternalError("xmlSecNssX509FindCertCtxMatch", NULL); return(NULL); } else if(ret == 1) { cert = CERT_DupCertificate(curCertNode->cert); if(cert == NULL) { xmlSecNssError("CERT_DupCertificate", NULL); return(NULL); } return(cert); } } } /* search in the NSS DB */ certDb = CERT_GetDefaultCertDB(); if(certDb == NULL) { xmlSecNssError("CERT_GetDefaultCertDB(ski)", NULL); return(NULL); } /* search by subject name if available */ if ((cert == NULL) && (findCertCtx->subjectNameItem != NULL)) { cert = CERT_FindCertByName(certDb, findCertCtx->subjectNameItem); } /* search by issuer name+serial if available */ if((cert == NULL) && (findCertCtx->issuerAndSNInitialized == 1)) { cert = CERT_FindCertByIssuerAndSN(certDb, &(findCertCtx->issuerAndSN)); } /* search by SKI if available */ if((cert == NULL) && (findCertCtx->skiItem.data != NULL) && (findCertCtx->skiItem.len > 0)) { cert = CERT_FindCertBySubjectKeyID(certDb, &(findCertCtx->skiItem)); } /* done */ return(cert); } xmlSecKeyPtr xmlSecNssX509FindKeyByValue(xmlSecPtrListPtr keysList, xmlSecKeyX509DataValuePtr x509Value) { xmlSecNssX509FindCertCtx findCertCtx; xmlSecSize keysListSize, ii; xmlSecKeyPtr res = NULL; int ret; xmlSecAssert2(keysList != NULL, NULL); xmlSecAssert2(x509Value != NULL, NULL); ret = xmlSecNssX509FindCertCtxInitializeFromValue(&findCertCtx, x509Value); if(ret < 0) { xmlSecInternalError("xmlSecNssX509FindCertCtxInitializeFromValue", NULL); xmlSecNssX509FindCertCtxFinalize(&findCertCtx); return(NULL); } keysListSize = xmlSecPtrListGetSize(keysList); for(ii = 0; ii < keysListSize; ++ii) { xmlSecKeyPtr key; xmlSecKeyDataPtr keyData; CERTCertificate* keyCert; /* get key's cert from x509 key data */ key = (xmlSecKeyPtr)xmlSecPtrListGetItem(keysList, ii); if(key == NULL) { continue; } keyData = xmlSecKeyGetData(key, xmlSecNssKeyDataX509Id); if(keyData == NULL) { continue; } keyCert = xmlSecNssKeyDataX509GetKeyCert(keyData); if(keyCert == NULL) { continue; } /* does it match? */ ret = xmlSecNssX509FindCertCtxMatch(&findCertCtx, keyCert); if(ret < 0) { xmlSecInternalError("xmlSecNssX509FindCertCtxMatch", NULL); xmlSecNssX509FindCertCtxFinalize(&findCertCtx); return(NULL); } else if(ret == 1) { res = key; break; } } /* done */ xmlSecNssX509FindCertCtxFinalize(&findCertCtx); return(res); } /****************************************************************************** * * xmlSecNssX509FindCertCtx * *****************************************************************************/ static int xmlSecNssX509SerialNumberRead(const xmlChar *str, SECItem *res, PLArenaPool *arena) { xmlSecByte buf[XMLSEC_X509_MAX_SERIAL_NUMBER_BYTES]; xmlSecSize written = 0; unsigned int uwritten; int ret; xmlSecAssert2(str != NULL, -1); xmlSecAssert2(res != NULL, -1); xmlSecAssert2(arena != NULL, -1); ret = xmlSecX509SerialNumberRead(str, buf, sizeof(buf), &written); if(ret < 0) { xmlSecInternalError("xmlSecX509SerialNumberRead", NULL); return(-1); } xmlSecAssert2(written > 0, -1); XMLSEC_SAFE_CAST_SIZE_TO_UINT(written, uwritten, return(-1), NULL); res->data = (unsigned char *)PORT_ArenaAlloc(arena, uwritten); if(res->data == NULL) { xmlSecNssError("PORT_ArenaAlloc", NULL); return(-1); } memcpy(res->data, buf, written); res->len = uwritten; res->type = siBuffer; return(0); } int xmlSecNssX509FindCertCtxInitialize(xmlSecNssX509FindCertCtxPtr ctx, const xmlChar *subjectName, const xmlChar *issuerName, const xmlChar *issuerSerial, xmlSecByte * ski, xmlSecSize skiSize ) { int ret; xmlSecAssert2(ctx != NULL, -1); memset(ctx, 0, sizeof(*ctx)); /* ski (easy first) */ if((ski != NULL) && (skiSize > 0)) { ctx->skiItem.type = siBuffer; ctx->skiItem.data = ski; XMLSEC_SAFE_CAST_SIZE_TO_UINT(skiSize, ctx->skiItem.len, return(-1), NULL); } ctx->arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if (ctx->arena == NULL) { xmlSecNssError("PORT_NewArena", NULL); xmlSecNssX509FindCertCtxFinalize(ctx); return(-1); } /* subject name */ if(subjectName != NULL) { ctx->subjectName = xmlSecNssGetCertName(subjectName); if(ctx->subjectName == NULL) { xmlSecInternalError2("xmlSecNssGetCertName", NULL, "subjectName=%s", xmlSecErrorsSafeString(subjectName)); xmlSecNssX509FindCertCtxFinalize(ctx); return(-1); } ctx->subjectNameItem = SEC_ASN1EncodeItem(ctx->arena, NULL, (void *)ctx->subjectName , SEC_ASN1_GET(CERT_NameTemplate)); if (ctx->subjectNameItem == NULL) { xmlSecNssError2("SEC_ASN1EncodeItem(subjectName)", NULL, "subjectName=%s", xmlSecErrorsSafeString(subjectName)); xmlSecNssX509FindCertCtxFinalize(ctx); return(-1); } } /* issuer name + serial */ if((issuerName != NULL) && (issuerSerial != NULL)) { memset(&ctx->issuerAndSN, 0, sizeof(ctx->issuerAndSN)); ctx->issuerName = xmlSecNssGetCertName(issuerName); if(ctx->issuerName == NULL) { xmlSecInternalError2("xmlSecNssGetCertName", NULL, "issuerName=%s", xmlSecErrorsSafeString(issuerName)); xmlSecNssX509FindCertCtxFinalize(ctx); return(-1); } ctx->issuerNameItem = SEC_ASN1EncodeItem(ctx->arena, NULL, (void *)ctx->issuerName , SEC_ASN1_GET(CERT_NameTemplate)); if (ctx->issuerNameItem == NULL) { xmlSecNssError2("SEC_ASN1EncodeItem(issuerName)", NULL, "issuerName=%s", xmlSecErrorsSafeString(issuerName)); xmlSecNssX509FindCertCtxFinalize(ctx); return(-1); } ctx->issuerAndSN.derIssuer.type = ctx->issuerNameItem->type; ctx->issuerAndSN.derIssuer.data = ctx->issuerNameItem->data; ctx->issuerAndSN.derIssuer.len = ctx->issuerNameItem->len; ret = xmlSecNssX509SerialNumberRead(issuerSerial, &(ctx->issuerAndSN.serialNumber), ctx->arena); if(ret < 0) { xmlSecInternalError("xmlSecNssX509SerialNumberRead", NULL); xmlSecNssX509FindCertCtxFinalize(ctx); return(-1); } ctx->issuerAndSNInitialized = 1; } /* done! */ return(0); } int xmlSecNssX509FindCertCtxInitializeFromValue(xmlSecNssX509FindCertCtxPtr ctx, xmlSecKeyX509DataValuePtr x509Value) { int ret; xmlSecAssert2(ctx != NULL, -1); xmlSecAssert2(x509Value != NULL, -1); ret = xmlSecNssX509FindCertCtxInitialize(ctx, x509Value->subject, x509Value->issuerName, x509Value->issuerSerial, xmlSecBufferGetData(&(x509Value->ski)), xmlSecBufferGetSize(&(x509Value->ski)) ); if(ret < 0) { xmlSecInternalError("xmlSecNssX509FindCertCtxInitialize", NULL); xmlSecNssX509FindCertCtxFinalize(ctx); return(-1); } if((!xmlSecBufferIsEmpty(&(x509Value->digest))) && (x509Value->digestAlgorithm != NULL)) { xmlSecSize digestSize; ctx->digestValue = xmlSecBufferGetData(&(x509Value->digest)); digestSize = xmlSecBufferGetSize(&(x509Value->digest)); XMLSEC_SAFE_CAST_SIZE_TO_UINT(digestSize, ctx->digestLen, return(-1), NULL); ctx->digestAlg = xmlSecNssGetDigestFromHref(x509Value->digestAlgorithm); if(ctx->digestAlg == SEC_OID_UNKNOWN) { xmlSecInternalError("xmlSecNssGetDigestFromHref", NULL); xmlSecNssX509FindCertCtxFinalize(ctx); return(-1); } } return(0); } void xmlSecNssX509FindCertCtxFinalize(xmlSecNssX509FindCertCtxPtr ctx) { xmlSecAssert(ctx != NULL); if(ctx->subjectName != NULL) { CERT_DestroyName(ctx->subjectName); } if(ctx->issuerName != NULL) { CERT_DestroyName(ctx->issuerName); } if (ctx->arena != NULL) { PORT_FreeArena(ctx->arena, PR_FALSE); } memset(ctx, 0, sizeof(*ctx)); } /* returns 1 for match, 0 for no match, and a negative value if an error occurs */ int xmlSecNssX509FindCertCtxMatch(xmlSecNssX509FindCertCtxPtr ctx, CERTCertificate* cert) { SECStatus status; xmlSecAssert2(ctx != NULL, -1); xmlSecAssert2(cert != NULL, -1); /* subject name */ if(ctx->subjectNameItem != NULL) { if (SECITEM_ItemsAreEqual(&(cert->derSubject), ctx->subjectNameItem)) { /* found a match */ return(1); } else { /* no match */ return(0); } } /* issuer name + serial */ if(ctx->issuerAndSNInitialized != 0) { if ( SECITEM_ItemsAreEqual(&(cert->derIssuer), &(ctx->issuerAndSN.derIssuer)) && SECITEM_ItemsAreEqual(&(cert->serialNumber), &(ctx->issuerAndSN.serialNumber)) ) { /* found a match */ return(1); } else { /* no match */ return(0); } } /* ski */ if( (ctx->skiItem.data != NULL) && (ctx->skiItem.len > 0)) { SECItem tmpitem = { siBuffer, NULL, 0 }; memset(&tmpitem, 0, sizeof(tmpitem)); status = CERT_FindSubjectKeyIDExtension(cert, &tmpitem); if (status != SECSuccess) { xmlSecNssError("CERT_FindSubjectKeyIDExtension(ski)", NULL); return(-1); } if((tmpitem.len != ctx->skiItem.len) || (memcmp(tmpitem.data, ctx->skiItem.data, ctx->skiItem.len) != 0)) { /* no match */ SECITEM_FreeItem(&tmpitem, PR_FALSE); return(0); } SECITEM_FreeItem(&tmpitem, PR_FALSE); /* found a match */ return(1); } /* cert digest */ if( (ctx->digestAlg != SEC_OID_UNKNOWN) && (ctx->digestValue != NULL) && (ctx->digestLen > 0) && (cert->derCert.type == siBuffer) && (cert->derCert.data != NULL) && (cert->derCert.len > 0) ) { xmlSecByte digest[XMLSEC_NSS_MAX_DIGEST_SIZE]; unsigned int digestLen; digestLen = HASH_ResultLenByOidTag(ctx->digestAlg); if((digestLen == 0) || (digestLen > sizeof(digest))) { xmlSecNssError3("HASH_ResultLenByOidTag", NULL, "digestAlgOid=%d; len=%u", (int)ctx->digestAlg, digestLen); return(-1); } status = PK11_HashBuf(ctx->digestAlg, digest, cert->derCert.data, (PRInt32)cert->derCert.len); if (status != SECSuccess) { xmlSecNssError2("PK11_HashBuf(cert->derCert)", NULL, "digestAlgOid=%d", (int)ctx->digestAlg); return(-1); } if((digestLen != ctx->digestLen) || (memcmp(digest, ctx->digestValue, ctx->digestLen) != 0)) { /* no match */ return(0); } /* found a match */ return(1); } return(0); } #endif /* XMLSEC_NO_X509 */