michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "NSSCertDBTrustDomain.h" michael@0: michael@0: #include michael@0: michael@0: #include "ExtendedValidation.h" michael@0: #include "OCSPRequestor.h" michael@0: #include "certdb.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "nss.h" michael@0: #include "ocsp.h" michael@0: #include "pk11pub.h" michael@0: #include "pkix/pkix.h" michael@0: #include "prerror.h" michael@0: #include "prmem.h" michael@0: #include "prprf.h" michael@0: #include "secerr.h" michael@0: #include "secmod.h" michael@0: michael@0: using namespace mozilla::pkix; michael@0: michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* gCertVerifierLog; michael@0: #endif michael@0: michael@0: namespace mozilla { namespace psm { michael@0: michael@0: const char BUILTIN_ROOTS_MODULE_DEFAULT_NAME[] = "Builtin Roots Module"; michael@0: michael@0: void PORT_Free_string(char* str) { PORT_Free(str); } michael@0: michael@0: namespace { michael@0: michael@0: typedef ScopedPtr ScopedSECMODModule; michael@0: michael@0: } // unnamed namespace michael@0: michael@0: NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType, michael@0: OCSPFetching ocspFetching, michael@0: OCSPCache& ocspCache, michael@0: void* pinArg, michael@0: CERTChainVerifyCallback* checkChainCallback) michael@0: : mCertDBTrustType(certDBTrustType) michael@0: , mOCSPFetching(ocspFetching) michael@0: , mOCSPCache(ocspCache) michael@0: , mPinArg(pinArg) michael@0: , mCheckChainCallback(checkChainCallback) michael@0: { michael@0: } michael@0: michael@0: SECStatus michael@0: NSSCertDBTrustDomain::FindPotentialIssuers( michael@0: const SECItem* encodedIssuerName, PRTime time, michael@0: /*out*/ mozilla::pkix::ScopedCERTCertList& results) michael@0: { michael@0: // TODO: normalize encodedIssuerName michael@0: // TODO: NSS seems to be ambiguous between "no potential issuers found" and michael@0: // "there was an error trying to retrieve the potential issuers." michael@0: results = CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(), michael@0: encodedIssuerName, time, true); michael@0: return SECSuccess; michael@0: } michael@0: michael@0: SECStatus michael@0: NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA, michael@0: SECOidTag policy, michael@0: const CERTCertificate* candidateCert, michael@0: /*out*/ TrustLevel* trustLevel) michael@0: { michael@0: PR_ASSERT(candidateCert); michael@0: PR_ASSERT(trustLevel); michael@0: michael@0: if (!candidateCert || !trustLevel) { michael@0: PR_SetError(SEC_ERROR_INVALID_ARGS, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: #ifdef MOZ_NO_EV_CERTS michael@0: if (policy != SEC_OID_X509_ANY_POLICY) { michael@0: PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0); michael@0: return SECFailure; michael@0: } michael@0: #endif michael@0: michael@0: // XXX: CERT_GetCertTrust seems to be abusing SECStatus as a boolean, where michael@0: // SECSuccess means that there is a trust record and SECFailure means there michael@0: // is not a trust record. I looked at NSS's internal uses of michael@0: // CERT_GetCertTrust, and all that code uses the result as a boolean meaning michael@0: // "We have a trust record." michael@0: CERTCertTrust trust; michael@0: if (CERT_GetCertTrust(candidateCert, &trust) == SECSuccess) { michael@0: PRUint32 flags = SEC_GET_TRUST_FLAGS(&trust, mCertDBTrustType); michael@0: michael@0: // For DISTRUST, we use the CERTDB_TRUSTED or CERTDB_TRUSTED_CA bit, michael@0: // because we can have active distrust for either type of cert. Note that michael@0: // CERTDB_TERMINAL_RECORD means "stop trying to inherit trust" so if the michael@0: // relevant trust bit isn't set then that means the cert must be considered michael@0: // distrusted. michael@0: PRUint32 relevantTrustBit = endEntityOrCA == MustBeCA ? CERTDB_TRUSTED_CA michael@0: : CERTDB_TRUSTED; michael@0: if (((flags & (relevantTrustBit|CERTDB_TERMINAL_RECORD))) michael@0: == CERTDB_TERMINAL_RECORD) { michael@0: *trustLevel = ActivelyDistrusted; michael@0: return SECSuccess; michael@0: } michael@0: michael@0: // For TRUST, we only use the CERTDB_TRUSTED_CA bit, because Gecko hasn't michael@0: // needed to consider end-entity certs to be their own trust anchors since michael@0: // Gecko implemented nsICertOverrideService. michael@0: if (flags & CERTDB_TRUSTED_CA) { michael@0: if (policy == SEC_OID_X509_ANY_POLICY) { michael@0: *trustLevel = TrustAnchor; michael@0: return SECSuccess; michael@0: } michael@0: #ifndef MOZ_NO_EV_CERTS michael@0: if (CertIsAuthoritativeForEVPolicy(candidateCert, policy)) { michael@0: *trustLevel = TrustAnchor; michael@0: return SECSuccess; michael@0: } michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: *trustLevel = InheritsTrust; michael@0: return SECSuccess; michael@0: } michael@0: michael@0: SECStatus michael@0: NSSCertDBTrustDomain::VerifySignedData(const CERTSignedData* signedData, michael@0: const CERTCertificate* cert) michael@0: { michael@0: return ::mozilla::pkix::VerifySignedData(signedData, cert, mPinArg); michael@0: } michael@0: michael@0: static PRIntervalTime michael@0: OCSPFetchingTypeToTimeoutTime(NSSCertDBTrustDomain::OCSPFetching ocspFetching) michael@0: { michael@0: switch (ocspFetching) { michael@0: case NSSCertDBTrustDomain::FetchOCSPForDVSoftFail: michael@0: return PR_SecondsToInterval(2); michael@0: case NSSCertDBTrustDomain::FetchOCSPForEV: michael@0: case NSSCertDBTrustDomain::FetchOCSPForDVHardFail: michael@0: return PR_SecondsToInterval(10); michael@0: // The rest of these are error cases. Assert in debug builds, but return michael@0: // the default value corresponding to 2 seconds in release builds. michael@0: case NSSCertDBTrustDomain::NeverFetchOCSP: michael@0: case NSSCertDBTrustDomain::LocalOnlyOCSPForEV: michael@0: PR_NOT_REACHED("we should never see this OCSPFetching type here"); michael@0: default: michael@0: PR_NOT_REACHED("we're not handling every OCSPFetching type"); michael@0: } michael@0: return PR_SecondsToInterval(2); michael@0: } michael@0: michael@0: SECStatus michael@0: NSSCertDBTrustDomain::CheckRevocation( michael@0: mozilla::pkix::EndEntityOrCA endEntityOrCA, michael@0: const CERTCertificate* cert, michael@0: /*const*/ CERTCertificate* issuerCert, michael@0: PRTime time, michael@0: /*optional*/ const SECItem* stapledOCSPResponse) michael@0: { michael@0: // Actively distrusted certificates will have already been blocked by michael@0: // GetCertTrust. michael@0: michael@0: // TODO: need to verify that IsRevoked isn't called for trust anchors AND michael@0: // that that fact is documented in mozillapkix. michael@0: michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: Top of CheckRevocation\n")); michael@0: michael@0: PORT_Assert(cert); michael@0: PORT_Assert(issuerCert); michael@0: if (!cert || !issuerCert) { michael@0: PORT_SetError(SEC_ERROR_INVALID_ARGS); michael@0: return SECFailure; michael@0: } michael@0: michael@0: // Bug 991815: The BR allow OCSP for intermediates to be up to one year old. michael@0: // Since this affects EV there is no reason why DV should be more strict michael@0: // so all intermediatates are allowed to have OCSP responses up to one year michael@0: // old. michael@0: uint16_t maxOCSPLifetimeInDays = 10; michael@0: if (endEntityOrCA == EndEntityOrCA::MustBeCA) { michael@0: maxOCSPLifetimeInDays = 365; michael@0: } michael@0: michael@0: // If we have a stapled OCSP response then the verification of that response michael@0: // determines the result unless the OCSP response is expired. We make an michael@0: // exception for expired responses because some servers, nginx in particular, michael@0: // are known to serve expired responses due to bugs. michael@0: // We keep track of the result of verifying the stapled response but don't michael@0: // immediately return failure if the response has expired. michael@0: PRErrorCode stapledOCSPResponseErrorCode = 0; michael@0: if (stapledOCSPResponse) { michael@0: PR_ASSERT(endEntityOrCA == MustBeEndEntity); michael@0: bool expired; michael@0: SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, michael@0: time, michael@0: maxOCSPLifetimeInDays, michael@0: stapledOCSPResponse, michael@0: ResponseWasStapled, michael@0: expired); michael@0: if (rv == SECSuccess) { michael@0: // stapled OCSP response present and good michael@0: Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 1); michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: stapled OCSP response: good")); michael@0: return rv; michael@0: } michael@0: stapledOCSPResponseErrorCode = PR_GetError(); michael@0: if (stapledOCSPResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE || michael@0: expired) { michael@0: // stapled OCSP response present but expired michael@0: Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 3); michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: expired stapled OCSP response")); michael@0: } else { michael@0: // stapled OCSP response present but invalid for some reason michael@0: Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 4); michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: stapled OCSP response: failure")); michael@0: return rv; michael@0: } michael@0: } else { michael@0: // no stapled OCSP response michael@0: Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 2); michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: no stapled OCSP response")); michael@0: } michael@0: michael@0: PRErrorCode cachedResponseErrorCode = 0; michael@0: PRTime cachedResponseValidThrough = 0; michael@0: bool cachedResponsePresent = mOCSPCache.Get(cert, issuerCert, michael@0: cachedResponseErrorCode, michael@0: cachedResponseValidThrough); michael@0: if (cachedResponsePresent) { michael@0: if (cachedResponseErrorCode == 0 && cachedResponseValidThrough >= time) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: cached OCSP response: good")); michael@0: return SECSuccess; michael@0: } michael@0: // If we have a cached revoked response, use it. michael@0: if (cachedResponseErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: cached OCSP response: revoked")); michael@0: PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0); michael@0: return SECFailure; michael@0: } michael@0: // The cached response may indicate an unknown certificate or it may be michael@0: // expired. Don't return with either of these statuses yet - we may be michael@0: // able to fetch a more recent one. michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: cached OCSP response: error %ld valid " michael@0: "until %lld", cachedResponseErrorCode, cachedResponseValidThrough)); michael@0: // When a good cached response has expired, it is more convenient michael@0: // to convert that to an error code and just deal with michael@0: // cachedResponseErrorCode from here on out. michael@0: if (cachedResponseErrorCode == 0 && cachedResponseValidThrough < time) { michael@0: cachedResponseErrorCode = SEC_ERROR_OCSP_OLD_RESPONSE; michael@0: } michael@0: // We may have a cached indication of server failure. Ignore it if michael@0: // it has expired. michael@0: if (cachedResponseErrorCode != 0 && michael@0: cachedResponseErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT && michael@0: cachedResponseErrorCode != SEC_ERROR_OCSP_OLD_RESPONSE && michael@0: cachedResponseValidThrough < time) { michael@0: cachedResponseErrorCode = 0; michael@0: cachedResponsePresent = false; michael@0: } michael@0: } else { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: no cached OCSP response")); michael@0: } michael@0: // At this point, if and only if cachedErrorResponseCode is 0, there was no michael@0: // cached response. michael@0: PR_ASSERT((!cachedResponsePresent && cachedResponseErrorCode == 0) || michael@0: (cachedResponsePresent && cachedResponseErrorCode != 0)); michael@0: michael@0: // TODO: We still need to handle the fallback for expired responses. But, michael@0: // if/when we disable OCSP fetching by default, it would be ambiguous whether michael@0: // security.OCSP.enable==0 means "I want the default" or "I really never want michael@0: // you to ever fetch OCSP." michael@0: michael@0: if ((mOCSPFetching == NeverFetchOCSP) || michael@0: (endEntityOrCA == MustBeCA && (mOCSPFetching == FetchOCSPForDVHardFail || michael@0: mOCSPFetching == FetchOCSPForDVSoftFail))) { michael@0: // We're not going to be doing any fetching, so if there was a cached michael@0: // "unknown" response, say so. michael@0: if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) { michael@0: PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0); michael@0: return SECFailure; michael@0: } michael@0: // If we're doing hard-fail, we want to know if we have a cached response michael@0: // that has expired. michael@0: if (mOCSPFetching == FetchOCSPForDVHardFail && michael@0: cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) { michael@0: PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: if (mOCSPFetching == LocalOnlyOCSPForEV) { michael@0: PR_SetError(cachedResponseErrorCode != 0 ? cachedResponseErrorCode michael@0: : SEC_ERROR_OCSP_UNKNOWN_CERT, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: ScopedPtr michael@0: url(CERT_GetOCSPAuthorityInfoAccessLocation(cert)); michael@0: michael@0: if (!url) { michael@0: if (mOCSPFetching == FetchOCSPForEV || michael@0: cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) { michael@0: PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0); michael@0: return SECFailure; michael@0: } michael@0: if (cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) { michael@0: PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0); michael@0: return SECFailure; michael@0: } michael@0: if (stapledOCSPResponseErrorCode != 0) { michael@0: PR_SetError(stapledOCSPResponseErrorCode, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: // Nothing to do if we don't have an OCSP responder URI for the cert; just michael@0: // assume it is good. Note that this is the confusing, but intended, michael@0: // interpretation of "strict" revocation checking in the face of a michael@0: // certificate that lacks an OCSP responder URI. michael@0: return SECSuccess; michael@0: } michael@0: michael@0: ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); michael@0: if (!arena) { michael@0: return SECFailure; michael@0: } michael@0: michael@0: // Only request a response if we didn't have a cached indication of failure michael@0: // (don't keep requesting responses from a failing server). michael@0: const SECItem* response = nullptr; michael@0: if (cachedResponseErrorCode == 0 || michael@0: cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT || michael@0: cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) { michael@0: const SECItem* request(CreateEncodedOCSPRequest(arena.get(), cert, michael@0: issuerCert)); michael@0: if (!request) { michael@0: return SECFailure; michael@0: } michael@0: michael@0: response = DoOCSPRequest(arena.get(), url.get(), request, michael@0: OCSPFetchingTypeToTimeoutTime(mOCSPFetching)); michael@0: } michael@0: michael@0: if (!response) { michael@0: PRErrorCode error = PR_GetError(); michael@0: if (error == 0) { michael@0: error = cachedResponseErrorCode; michael@0: } michael@0: PRTime timeout = time + ServerFailureDelay; michael@0: if (mOCSPCache.Put(cert, issuerCert, error, time, timeout) != SECSuccess) { michael@0: return SECFailure; michael@0: } michael@0: PR_SetError(error, 0); michael@0: if (mOCSPFetching != FetchOCSPForDVSoftFail) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: returning SECFailure after " michael@0: "OCSP request failure")); michael@0: return SECFailure; michael@0: } michael@0: if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: returning SECFailure from cached " michael@0: "response after OCSP request failure")); michael@0: PR_SetError(cachedResponseErrorCode, 0); michael@0: return SECFailure; michael@0: } michael@0: if (stapledOCSPResponseErrorCode != 0) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: returning SECFailure from expired " michael@0: "stapled response after OCSP request failure")); michael@0: PR_SetError(stapledOCSPResponseErrorCode, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: returning SECSuccess after " michael@0: "OCSP request failure")); michael@0: return SECSuccess; // Soft fail -> success :( michael@0: } michael@0: michael@0: // If the response from the network has expired but indicates a revoked michael@0: // or unknown certificate, PR_GetError() will return the appropriate error. michael@0: // We actually ignore expired here. michael@0: bool expired; michael@0: SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, time, michael@0: maxOCSPLifetimeInDays, michael@0: response, michael@0: ResponseIsFromNetwork, michael@0: expired); michael@0: if (rv == SECSuccess || mOCSPFetching != FetchOCSPForDVSoftFail) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: returning after VerifyEncodedOCSPResponse")); michael@0: return rv; michael@0: } michael@0: michael@0: PRErrorCode error = PR_GetError(); michael@0: if (error == SEC_ERROR_OCSP_UNKNOWN_CERT || michael@0: error == SEC_ERROR_REVOKED_CERTIFICATE) { michael@0: return rv; michael@0: } michael@0: michael@0: if (stapledOCSPResponseErrorCode != 0) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: returning SECFailure from expired stapled " michael@0: "response after OCSP request verification failure")); michael@0: PR_SetError(stapledOCSPResponseErrorCode, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: end of CheckRevocation")); michael@0: michael@0: return SECSuccess; // Soft fail -> success :( michael@0: } michael@0: michael@0: SECStatus michael@0: NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse( michael@0: const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time, michael@0: uint16_t maxLifetimeInDays, const SECItem* encodedResponse, michael@0: EncodedResponseSource responseSource, /*out*/ bool& expired) michael@0: { michael@0: PRTime thisUpdate = 0; michael@0: PRTime validThrough = 0; michael@0: SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time, michael@0: maxLifetimeInDays, encodedResponse, michael@0: expired, &thisUpdate, &validThrough); michael@0: PRErrorCode error = (rv == SECSuccess ? 0 : PR_GetError()); michael@0: // If a response was stapled and expired, we don't want to cache it. Return michael@0: // early to simplify the logic here. michael@0: if (responseSource == ResponseWasStapled && expired) { michael@0: PR_ASSERT(rv != SECSuccess); michael@0: return rv; michael@0: } michael@0: // validThrough is only trustworthy if the response successfully verifies michael@0: // or it indicates a revoked or unknown certificate. michael@0: // If this isn't the case, store an indication of failure (to prevent michael@0: // repeatedly requesting a response from a failing server). michael@0: if (rv != SECSuccess && error != SEC_ERROR_REVOKED_CERTIFICATE && michael@0: error != SEC_ERROR_OCSP_UNKNOWN_CERT) { michael@0: validThrough = time + ServerFailureDelay; michael@0: } michael@0: if (responseSource == ResponseIsFromNetwork || michael@0: rv == SECSuccess || michael@0: error == SEC_ERROR_REVOKED_CERTIFICATE || michael@0: error == SEC_ERROR_OCSP_UNKNOWN_CERT) { michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: caching OCSP response")); michael@0: if (mOCSPCache.Put(cert, issuerCert, error, thisUpdate, validThrough) michael@0: != SECSuccess) { michael@0: return SECFailure; michael@0: } michael@0: } michael@0: michael@0: // If the verification failed, re-set to that original error michael@0: // (the call to Put may have un-set it). michael@0: if (rv != SECSuccess) { michael@0: PR_SetError(error, 0); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: SECStatus michael@0: NSSCertDBTrustDomain::IsChainValid(const CERTCertList* certChain) { michael@0: SECStatus rv = SECFailure; michael@0: michael@0: PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, michael@0: ("NSSCertDBTrustDomain: Top of IsChainValid mCheckCallback=%p", michael@0: mCheckChainCallback)); michael@0: michael@0: if (!mCheckChainCallback) { michael@0: return SECSuccess; michael@0: } michael@0: if (!mCheckChainCallback->isChainValid) { michael@0: PR_SetError(SEC_ERROR_INVALID_ARGS, 0); michael@0: return SECFailure; michael@0: } michael@0: PRBool chainOK; michael@0: rv = (mCheckChainCallback->isChainValid)(mCheckChainCallback->isChainValidArg, michael@0: certChain, &chainOK); michael@0: if (rv != SECSuccess) { michael@0: return rv; michael@0: } michael@0: // rv = SECSuccess only implies successful call, now is time michael@0: // to check the chain check status michael@0: // we should only return success if the chain is valid michael@0: if (chainOK) { michael@0: return SECSuccess; michael@0: } michael@0: PR_SetError(SEC_ERROR_APPLICATION_CALLBACK_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: static char* michael@0: nss_addEscape(const char* string, char quote) michael@0: { michael@0: char* newString = 0; michael@0: int escapes = 0, size = 0; michael@0: const char* src; michael@0: char* dest; michael@0: michael@0: for (src = string; *src; src++) { michael@0: if ((*src == quote) || (*src == '\\')) { michael@0: escapes++; michael@0: } michael@0: size++; michael@0: } michael@0: michael@0: newString = (char*) PORT_ZAlloc(escapes + size + 1); michael@0: if (!newString) { michael@0: return nullptr; michael@0: } michael@0: michael@0: for (src = string, dest = newString; *src; src++, dest++) { michael@0: if ((*src == quote) || (*src == '\\')) { michael@0: *dest++ = '\\'; michael@0: } michael@0: *dest = *src; michael@0: } michael@0: michael@0: return newString; michael@0: } michael@0: michael@0: } // unnamed namespace michael@0: michael@0: SECStatus michael@0: InitializeNSS(const char* dir, bool readOnly) michael@0: { michael@0: // The NSS_INIT_NOROOTINIT flag turns off the loading of the root certs michael@0: // module by NSS_Initialize because we will load it in InstallLoadableRoots michael@0: // later. It also allows us to work around a bug in the system NSS in michael@0: // Ubuntu 8.04, which loads any nonexistent "/libnssckbi.so" as michael@0: // "/usr/lib/nss/libnssckbi.so". michael@0: uint32_t flags = NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE; michael@0: if (readOnly) { michael@0: flags |= NSS_INIT_READONLY; michael@0: } michael@0: return ::NSS_Initialize(dir, "", "", SECMOD_DB, flags); michael@0: } michael@0: michael@0: void michael@0: DisableMD5() michael@0: { michael@0: NSS_SetAlgorithmPolicy(SEC_OID_MD5, michael@0: 0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE); michael@0: NSS_SetAlgorithmPolicy(SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, michael@0: 0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE); michael@0: NSS_SetAlgorithmPolicy(SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, michael@0: 0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE); michael@0: } michael@0: michael@0: SECStatus michael@0: LoadLoadableRoots(/*optional*/ const char* dir, const char* modNameUTF8) michael@0: { michael@0: PR_ASSERT(modNameUTF8); michael@0: michael@0: if (!modNameUTF8) { michael@0: PR_SetError(SEC_ERROR_INVALID_ARGS, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: ScopedPtr fullLibraryPath( michael@0: PR_GetLibraryName(dir, "nssckbi")); michael@0: if (!fullLibraryPath) { michael@0: return SECFailure; michael@0: } michael@0: michael@0: ScopedPtr escaped_fullLibraryPath( michael@0: nss_addEscape(fullLibraryPath.get(), '\"')); michael@0: if (!escaped_fullLibraryPath) { michael@0: return SECFailure; michael@0: } michael@0: michael@0: // If a module exists with the same name, delete it. michael@0: int modType; michael@0: SECMOD_DeleteModule(modNameUTF8, &modType); michael@0: michael@0: ScopedPtr pkcs11ModuleSpec( michael@0: PR_smprintf("name=\"%s\" library=\"%s\"", modNameUTF8, michael@0: escaped_fullLibraryPath.get())); michael@0: if (!pkcs11ModuleSpec) { michael@0: return SECFailure; michael@0: } michael@0: michael@0: ScopedSECMODModule rootsModule(SECMOD_LoadUserModule(pkcs11ModuleSpec.get(), michael@0: nullptr, false)); michael@0: if (!rootsModule) { michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (!rootsModule->loaded) { michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: void michael@0: UnloadLoadableRoots(const char* modNameUTF8) michael@0: { michael@0: PR_ASSERT(modNameUTF8); michael@0: ScopedSECMODModule rootsModule(SECMOD_FindModule(modNameUTF8)); michael@0: michael@0: if (rootsModule) { michael@0: SECMOD_UnloadUserModule(rootsModule.get()); michael@0: } michael@0: } michael@0: michael@0: void michael@0: SetClassicOCSPBehavior(CertVerifier::ocsp_download_config enabled, michael@0: CertVerifier::ocsp_strict_config strict, michael@0: CertVerifier::ocsp_get_config get) michael@0: { michael@0: CERT_DisableOCSPDefaultResponder(CERT_GetDefaultCertDB()); michael@0: if (enabled == CertVerifier::ocsp_off) { michael@0: CERT_DisableOCSPChecking(CERT_GetDefaultCertDB()); michael@0: } else { michael@0: CERT_EnableOCSPChecking(CERT_GetDefaultCertDB()); michael@0: } michael@0: michael@0: SEC_OcspFailureMode failureMode = strict == CertVerifier::ocsp_strict michael@0: ? ocspMode_FailureIsVerificationFailure michael@0: : ocspMode_FailureIsNotAVerificationFailure; michael@0: (void) CERT_SetOCSPFailureMode(failureMode); michael@0: michael@0: CERT_ForcePostMethodForOCSP(get != CertVerifier::ocsp_get_enabled); michael@0: michael@0: int OCSPTimeoutSeconds = 3; michael@0: if (strict == CertVerifier::ocsp_strict) { michael@0: OCSPTimeoutSeconds = 10; michael@0: } michael@0: CERT_SetOCSPTimeout(OCSPTimeoutSeconds); michael@0: } michael@0: michael@0: char* michael@0: DefaultServerNicknameForCert(CERTCertificate* cert) michael@0: { michael@0: char* nickname = nullptr; michael@0: int count; michael@0: bool conflict; michael@0: char* servername = nullptr; michael@0: michael@0: servername = CERT_GetCommonName(&cert->subject); michael@0: if (!servername) { michael@0: // Certs without common names are strange, but they do exist... michael@0: // Let's try to use another string for the nickname michael@0: servername = CERT_GetOrgUnitName(&cert->subject); michael@0: if (!servername) { michael@0: servername = CERT_GetOrgName(&cert->subject); michael@0: if (!servername) { michael@0: servername = CERT_GetLocalityName(&cert->subject); michael@0: if (!servername) { michael@0: servername = CERT_GetStateName(&cert->subject); michael@0: if (!servername) { michael@0: servername = CERT_GetCountryName(&cert->subject); michael@0: if (!servername) { michael@0: // We tried hard, there is nothing more we can do. michael@0: // A cert without any names doesn't really make sense. michael@0: return nullptr; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: count = 1; michael@0: while (1) { michael@0: if (count == 1) { michael@0: nickname = PR_smprintf("%s", servername); michael@0: } michael@0: else { michael@0: nickname = PR_smprintf("%s #%d", servername, count); michael@0: } michael@0: if (!nickname) { michael@0: break; michael@0: } michael@0: michael@0: conflict = SEC_CertNicknameConflict(nickname, &cert->derSubject, michael@0: cert->dbhandle); michael@0: if (!conflict) { michael@0: break; michael@0: } michael@0: PR_Free(nickname); michael@0: count++; michael@0: } michael@0: PR_FREEIF(servername); michael@0: return nickname; michael@0: } michael@0: michael@0: void michael@0: SaveIntermediateCerts(const ScopedCERTCertList& certList) michael@0: { michael@0: if (!certList) { michael@0: return; michael@0: } michael@0: michael@0: bool isEndEntity = true; michael@0: for (CERTCertListNode* node = CERT_LIST_HEAD(certList); michael@0: !CERT_LIST_END(node, certList); michael@0: node = CERT_LIST_NEXT(node)) { michael@0: if (isEndEntity) { michael@0: // Skip the end-entity; we only want to store intermediates michael@0: isEndEntity = false; michael@0: continue; michael@0: } michael@0: michael@0: if (node->cert->slot) { michael@0: // This cert was found on a token, no need to remember it in the temp db. michael@0: continue; michael@0: } michael@0: michael@0: if (node->cert->isperm) { michael@0: // We don't need to remember certs already stored in perm db. michael@0: continue; michael@0: } michael@0: michael@0: // We have found a signer cert that we want to remember. michael@0: char* nickname = DefaultServerNicknameForCert(node->cert); michael@0: if (nickname && *nickname) { michael@0: ScopedPtr slot(PK11_GetInternalKeySlot()); michael@0: if (slot) { michael@0: PK11_ImportCert(slot.get(), node->cert, CK_INVALID_HANDLE, michael@0: nickname, false); michael@0: } michael@0: } michael@0: PR_FREEIF(nickname); michael@0: } michael@0: } michael@0: michael@0: } } // namespace mozilla::psm