static SECStatus PR_CALLBACK GetOCSPResponders (CERTCertificate *aCert, SECItem *aDBKey, void *aArg) { nsIMutableArray *array = static_cast<nsIMutableArray*>(aArg); PRUnichar* nn = nsnull; PRUnichar* url = nsnull; char *serviceURL = nsnull; char *nickname = nsnull; PRUint32 i, count; nsresult rv; // Are we interested in this cert // if (!nsOCSPResponder::IncludeCert(aCert)) { return SECSuccess; } // Get the AIA and nickname // serviceURL = CERT_GetOCSPAuthorityInfoAccessLocation(aCert); if (serviceURL) { url = ToNewUnicode(NS_ConvertUTF8toUTF16(serviceURL)); PORT_Free(serviceURL); } nickname = aCert->nickname; nn = ToNewUnicode(NS_ConvertUTF8toUTF16(nickname)); nsCOMPtr<nsIOCSPResponder> new_entry = new nsOCSPResponder(nn, url); nsMemory::Free(nn); nsMemory::Free(url); // Sort the items according to nickname // rv = array->GetLength(&count); for (i=0; i < count; ++i) { nsCOMPtr<nsIOCSPResponder> entry = do_QueryElementAt(array, i); if (nsOCSPResponder::CompareEntries(new_entry, entry) < 0) { array->InsertElementAt(new_entry, i, PR_FALSE); break; } } if (i == count) { array->AppendElement(new_entry, PR_FALSE); } return SECSuccess; }
/* * Create a DER-encoded OCSP request (for the certificate whose nickname is * "cert_name"), then get and dump a corresponding response. The responder * location is either specified explicitly (as "responder_url") or found * via the AuthorityInfoAccess URL in the cert. */ static SECStatus dump_response (FILE *out_file, CERTCertDBHandle *handle, CERTCertificate *cert, const char *responder_url) { CERTCertList *certs = NULL; CERTCertificate *myCert = NULL; char *loc = NULL; PRTime now = PR_Now(); SECItem *response = NULL; SECStatus rv = SECFailure; PRBool includeServiceLocator; if (handle == NULL || cert == NULL) return rv; myCert = CERT_DupCertificate(cert); if (myCert == NULL) goto loser; if (responder_url != NULL) { loc = (char *) responder_url; includeServiceLocator = PR_TRUE; } else { loc = CERT_GetOCSPAuthorityInfoAccessLocation (cert); if (loc == NULL) goto loser; includeServiceLocator = PR_FALSE; } /* * We need to create a list of one. */ certs = CERT_NewCertList(); if (certs == NULL) goto loser; if (CERT_AddCertToListTail (certs, myCert) != SECSuccess) goto loser; /* * Now that cert is included in the list, we need to be careful * that we do not try to destroy it twice. This will prevent that. */ myCert = NULL; response = CERT_GetEncodedOCSPResponse (NULL, certs, loc, now, includeServiceLocator, NULL, NULL, NULL); if (response == NULL) goto loser; MAKE_FILE_BINARY(out_file); if (fwrite (response->data, response->len, 1, out_file) != 1) goto loser; rv = SECSuccess; loser: if (response != NULL) SECITEM_FreeItem (response, PR_TRUE); if (certs != NULL) CERT_DestroyCertList (certs); if (myCert != NULL) CERT_DestroyCertificate(myCert); if (loc != NULL && loc != responder_url) PORT_Free (loc); return rv; }
SECStatus NSSCertDBTrustDomain::CheckRevocation( insanity::pkix::EndEntityOrCA endEntityOrCA, const CERTCertificate* cert, /*const*/ CERTCertificate* issuerCert, PRTime time, /*optional*/ const SECItem* stapledOCSPResponse) { // Actively distrusted certificates will have already been blocked by // GetCertTrust. // TODO: need to verify that IsRevoked isn't called for trust anchors AND // that that fact is documented in insanity. PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: Top of CheckRevocation\n")); PORT_Assert(cert); PORT_Assert(issuerCert); if (!cert || !issuerCert) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } // If we have a stapled OCSP response then the verification of that response // determines the result unless the OCSP response is expired. We make an // exception for expired responses because some servers, nginx in particular, // are known to serve expired responses due to bugs. if (stapledOCSPResponse) { PR_ASSERT(endEntityOrCA == MustBeEndEntity); SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time, stapledOCSPResponse); if (rv == SECSuccess) { return rv; } if (PR_GetError() != SEC_ERROR_OCSP_OLD_RESPONSE) { return rv; } } // TODO(bug 921885): We need to change this when we add EV support. // TODO: when !mOCSPDownloadEnabled, we still need to handle the fallback for // expired responses. But, if/when we disable OCSP fetching by default, it // would be ambiguous whether !mOCSPDownloadEnabled means "I want the default" // or "I really never want you to ever fetch OCSP." if (mOCSPDownloadEnabled) { // We don't do OCSP fetching for intermediates. if (endEntityOrCA == MustBeCA) { PR_ASSERT(!stapledOCSPResponse); return SECSuccess; } ScopedPtr<char, PORT_Free_string> url(CERT_GetOCSPAuthorityInfoAccessLocation(cert)); // Nothing to do if we don't have an OCSP responder URI for the cert; just // assume it is good. Note that this is the confusing, but intended, // interpretation of "strict" revocation checking in the face of a // certificate that lacks an OCSP responder URI. if (!url) { if (stapledOCSPResponse) { PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0); return SECFailure; } return SECSuccess; } ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); if (!arena) { return SECFailure; } const SECItem* request = CreateEncodedOCSPRequest(arena.get(), cert, issuerCert); if (!request) { return SECFailure; } const SECItem* response(CERT_PostOCSPRequest(arena.get(), url.get(), request)); if (!response) { if (mOCSPStrict) { return SECFailure; } // Soft fail -> success :( } else { SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time, response); if (rv == SECSuccess) { return SECSuccess; } PRErrorCode error = PR_GetError(); switch (error) { case SEC_ERROR_OCSP_UNKNOWN_CERT: case SEC_ERROR_REVOKED_CERTIFICATE: return SECFailure; default: if (mOCSPStrict) { return SECFailure; } break; // Soft fail -> success :( } } } PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: end of CheckRevocation")); return SECSuccess; }
SECStatus NSSCertDBTrustDomain::CheckRevocation( mozilla::pkix::EndEntityOrCA endEntityOrCA, const CERTCertificate* cert, /*const*/ CERTCertificate* issuerCert, PRTime time, /*optional*/ const SECItem* stapledOCSPResponse) { // Actively distrusted certificates will have already been blocked by // GetCertTrust. // TODO: need to verify that IsRevoked isn't called for trust anchors AND // that that fact is documented in mozillapkix. PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: Top of CheckRevocation\n")); PORT_Assert(cert); PORT_Assert(issuerCert); if (!cert || !issuerCert) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } // Bug 991815: The BR allow OCSP for intermediates to be up to one year old. // Since this affects EV there is no reason why DV should be more strict // so all intermediatates are allowed to have OCSP responses up to one year // old. uint16_t maxOCSPLifetimeInDays = 10; if (endEntityOrCA == EndEntityOrCA::MustBeCA) { maxOCSPLifetimeInDays = 365; } // If we have a stapled OCSP response then the verification of that response // determines the result unless the OCSP response is expired. We make an // exception for expired responses because some servers, nginx in particular, // are known to serve expired responses due to bugs. // We keep track of the result of verifying the stapled response but don't // immediately return failure if the response has expired. PRErrorCode stapledOCSPResponseErrorCode = 0; if (stapledOCSPResponse) { PR_ASSERT(endEntityOrCA == MustBeEndEntity); bool expired; SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, time, maxOCSPLifetimeInDays, stapledOCSPResponse, ResponseWasStapled, expired); if (rv == SECSuccess) { // stapled OCSP response present and good Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 1); PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: stapled OCSP response: good")); return rv; } stapledOCSPResponseErrorCode = PR_GetError(); if (stapledOCSPResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE || expired) { // stapled OCSP response present but expired Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 3); PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: expired stapled OCSP response")); } else { // stapled OCSP response present but invalid for some reason Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 4); PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: stapled OCSP response: failure")); return rv; } } else { // no stapled OCSP response Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 2); PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: no stapled OCSP response")); } PRErrorCode cachedResponseErrorCode = 0; PRTime cachedResponseValidThrough = 0; bool cachedResponsePresent = mOCSPCache.Get(cert, issuerCert, cachedResponseErrorCode, cachedResponseValidThrough); if (cachedResponsePresent) { if (cachedResponseErrorCode == 0 && cachedResponseValidThrough >= time) { PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: cached OCSP response: good")); return SECSuccess; } // If we have a cached revoked response, use it. if (cachedResponseErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) { PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: cached OCSP response: revoked")); PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0); return SECFailure; } // The cached response may indicate an unknown certificate or it may be // expired. Don't return with either of these statuses yet - we may be // able to fetch a more recent one. PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: cached OCSP response: error %ld valid " "until %lld", cachedResponseErrorCode, cachedResponseValidThrough)); // When a good cached response has expired, it is more convenient // to convert that to an error code and just deal with // cachedResponseErrorCode from here on out. if (cachedResponseErrorCode == 0 && cachedResponseValidThrough < time) { cachedResponseErrorCode = SEC_ERROR_OCSP_OLD_RESPONSE; } // We may have a cached indication of server failure. Ignore it if // it has expired. if (cachedResponseErrorCode != 0 && cachedResponseErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT && cachedResponseErrorCode != SEC_ERROR_OCSP_OLD_RESPONSE && cachedResponseValidThrough < time) { cachedResponseErrorCode = 0; cachedResponsePresent = false; } } else { PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: no cached OCSP response")); } // At this point, if and only if cachedErrorResponseCode is 0, there was no // cached response. PR_ASSERT((!cachedResponsePresent && cachedResponseErrorCode == 0) || (cachedResponsePresent && cachedResponseErrorCode != 0)); // TODO: We still need to handle the fallback for expired responses. But, // if/when we disable OCSP fetching by default, it would be ambiguous whether // security.OCSP.enable==0 means "I want the default" or "I really never want // you to ever fetch OCSP." if ((mOCSPFetching == NeverFetchOCSP) || (endEntityOrCA == MustBeCA && (mOCSPFetching == FetchOCSPForDVHardFail || mOCSPFetching == FetchOCSPForDVSoftFail))) { // We're not going to be doing any fetching, so if there was a cached // "unknown" response, say so. if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) { PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0); return SECFailure; } // If we're doing hard-fail, we want to know if we have a cached response // that has expired. if (mOCSPFetching == FetchOCSPForDVHardFail && cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) { PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0); return SECFailure; } return SECSuccess; } if (mOCSPFetching == LocalOnlyOCSPForEV) { PR_SetError(cachedResponseErrorCode != 0 ? cachedResponseErrorCode : SEC_ERROR_OCSP_UNKNOWN_CERT, 0); return SECFailure; } ScopedPtr<char, PORT_Free_string> url(CERT_GetOCSPAuthorityInfoAccessLocation(cert)); if (!url) { if (mOCSPFetching == FetchOCSPForEV || cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) { PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0); return SECFailure; } if (cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) { PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0); return SECFailure; } if (stapledOCSPResponseErrorCode != 0) { PR_SetError(stapledOCSPResponseErrorCode, 0); return SECFailure; } // Nothing to do if we don't have an OCSP responder URI for the cert; just // assume it is good. Note that this is the confusing, but intended, // interpretation of "strict" revocation checking in the face of a // certificate that lacks an OCSP responder URI. return SECSuccess; } ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); if (!arena) { return SECFailure; } // Only request a response if we didn't have a cached indication of failure // (don't keep requesting responses from a failing server). const SECItem* response = nullptr; if (cachedResponseErrorCode == 0 || cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT || cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) { const SECItem* request(CreateEncodedOCSPRequest(arena.get(), cert, issuerCert)); if (!request) { return SECFailure; } response = DoOCSPRequest(arena.get(), url.get(), request, OCSPFetchingTypeToTimeoutTime(mOCSPFetching)); } if (!response) { PRErrorCode error = PR_GetError(); if (error == 0) { error = cachedResponseErrorCode; } PRTime timeout = time + ServerFailureDelay; if (mOCSPCache.Put(cert, issuerCert, error, time, timeout) != SECSuccess) { return SECFailure; } PR_SetError(error, 0); if (mOCSPFetching != FetchOCSPForDVSoftFail) { PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: returning SECFailure after " "OCSP request failure")); return SECFailure; } if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) { PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: returning SECFailure from cached " "response after OCSP request failure")); PR_SetError(cachedResponseErrorCode, 0); return SECFailure; } if (stapledOCSPResponseErrorCode != 0) { PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: returning SECFailure from expired " "stapled response after OCSP request failure")); PR_SetError(stapledOCSPResponseErrorCode, 0); return SECFailure; } PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: returning SECSuccess after " "OCSP request failure")); return SECSuccess; // Soft fail -> success :( } // If the response from the network has expired but indicates a revoked // or unknown certificate, PR_GetError() will return the appropriate error. // We actually ignore expired here. bool expired; SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, time, maxOCSPLifetimeInDays, response, ResponseIsFromNetwork, expired); if (rv == SECSuccess || mOCSPFetching != FetchOCSPForDVSoftFail) { PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: returning after VerifyEncodedOCSPResponse")); return rv; } PRErrorCode error = PR_GetError(); if (error == SEC_ERROR_OCSP_UNKNOWN_CERT || error == SEC_ERROR_REVOKED_CERTIFICATE) { return rv; } if (stapledOCSPResponseErrorCode != 0) { PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: returning SECFailure from expired stapled " "response after OCSP request verification failure")); PR_SetError(stapledOCSPResponseErrorCode, 0); return SECFailure; } PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: end of CheckRevocation")); return SECSuccess; // Soft fail -> success :( }