security/certverifier/NSSCertDBTrustDomain.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "NSSCertDBTrustDomain.h"
michael@0 8
michael@0 9 #include <stdint.h>
michael@0 10
michael@0 11 #include "ExtendedValidation.h"
michael@0 12 #include "OCSPRequestor.h"
michael@0 13 #include "certdb.h"
michael@0 14 #include "mozilla/Telemetry.h"
michael@0 15 #include "nss.h"
michael@0 16 #include "ocsp.h"
michael@0 17 #include "pk11pub.h"
michael@0 18 #include "pkix/pkix.h"
michael@0 19 #include "prerror.h"
michael@0 20 #include "prmem.h"
michael@0 21 #include "prprf.h"
michael@0 22 #include "secerr.h"
michael@0 23 #include "secmod.h"
michael@0 24
michael@0 25 using namespace mozilla::pkix;
michael@0 26
michael@0 27 #ifdef PR_LOGGING
michael@0 28 extern PRLogModuleInfo* gCertVerifierLog;
michael@0 29 #endif
michael@0 30
michael@0 31 namespace mozilla { namespace psm {
michael@0 32
michael@0 33 const char BUILTIN_ROOTS_MODULE_DEFAULT_NAME[] = "Builtin Roots Module";
michael@0 34
michael@0 35 void PORT_Free_string(char* str) { PORT_Free(str); }
michael@0 36
michael@0 37 namespace {
michael@0 38
michael@0 39 typedef ScopedPtr<SECMODModule, SECMOD_DestroyModule> ScopedSECMODModule;
michael@0 40
michael@0 41 } // unnamed namespace
michael@0 42
michael@0 43 NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType,
michael@0 44 OCSPFetching ocspFetching,
michael@0 45 OCSPCache& ocspCache,
michael@0 46 void* pinArg,
michael@0 47 CERTChainVerifyCallback* checkChainCallback)
michael@0 48 : mCertDBTrustType(certDBTrustType)
michael@0 49 , mOCSPFetching(ocspFetching)
michael@0 50 , mOCSPCache(ocspCache)
michael@0 51 , mPinArg(pinArg)
michael@0 52 , mCheckChainCallback(checkChainCallback)
michael@0 53 {
michael@0 54 }
michael@0 55
michael@0 56 SECStatus
michael@0 57 NSSCertDBTrustDomain::FindPotentialIssuers(
michael@0 58 const SECItem* encodedIssuerName, PRTime time,
michael@0 59 /*out*/ mozilla::pkix::ScopedCERTCertList& results)
michael@0 60 {
michael@0 61 // TODO: normalize encodedIssuerName
michael@0 62 // TODO: NSS seems to be ambiguous between "no potential issuers found" and
michael@0 63 // "there was an error trying to retrieve the potential issuers."
michael@0 64 results = CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(),
michael@0 65 encodedIssuerName, time, true);
michael@0 66 return SECSuccess;
michael@0 67 }
michael@0 68
michael@0 69 SECStatus
michael@0 70 NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
michael@0 71 SECOidTag policy,
michael@0 72 const CERTCertificate* candidateCert,
michael@0 73 /*out*/ TrustLevel* trustLevel)
michael@0 74 {
michael@0 75 PR_ASSERT(candidateCert);
michael@0 76 PR_ASSERT(trustLevel);
michael@0 77
michael@0 78 if (!candidateCert || !trustLevel) {
michael@0 79 PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
michael@0 80 return SECFailure;
michael@0 81 }
michael@0 82
michael@0 83 #ifdef MOZ_NO_EV_CERTS
michael@0 84 if (policy != SEC_OID_X509_ANY_POLICY) {
michael@0 85 PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0);
michael@0 86 return SECFailure;
michael@0 87 }
michael@0 88 #endif
michael@0 89
michael@0 90 // XXX: CERT_GetCertTrust seems to be abusing SECStatus as a boolean, where
michael@0 91 // SECSuccess means that there is a trust record and SECFailure means there
michael@0 92 // is not a trust record. I looked at NSS's internal uses of
michael@0 93 // CERT_GetCertTrust, and all that code uses the result as a boolean meaning
michael@0 94 // "We have a trust record."
michael@0 95 CERTCertTrust trust;
michael@0 96 if (CERT_GetCertTrust(candidateCert, &trust) == SECSuccess) {
michael@0 97 PRUint32 flags = SEC_GET_TRUST_FLAGS(&trust, mCertDBTrustType);
michael@0 98
michael@0 99 // For DISTRUST, we use the CERTDB_TRUSTED or CERTDB_TRUSTED_CA bit,
michael@0 100 // because we can have active distrust for either type of cert. Note that
michael@0 101 // CERTDB_TERMINAL_RECORD means "stop trying to inherit trust" so if the
michael@0 102 // relevant trust bit isn't set then that means the cert must be considered
michael@0 103 // distrusted.
michael@0 104 PRUint32 relevantTrustBit = endEntityOrCA == MustBeCA ? CERTDB_TRUSTED_CA
michael@0 105 : CERTDB_TRUSTED;
michael@0 106 if (((flags & (relevantTrustBit|CERTDB_TERMINAL_RECORD)))
michael@0 107 == CERTDB_TERMINAL_RECORD) {
michael@0 108 *trustLevel = ActivelyDistrusted;
michael@0 109 return SECSuccess;
michael@0 110 }
michael@0 111
michael@0 112 // For TRUST, we only use the CERTDB_TRUSTED_CA bit, because Gecko hasn't
michael@0 113 // needed to consider end-entity certs to be their own trust anchors since
michael@0 114 // Gecko implemented nsICertOverrideService.
michael@0 115 if (flags & CERTDB_TRUSTED_CA) {
michael@0 116 if (policy == SEC_OID_X509_ANY_POLICY) {
michael@0 117 *trustLevel = TrustAnchor;
michael@0 118 return SECSuccess;
michael@0 119 }
michael@0 120 #ifndef MOZ_NO_EV_CERTS
michael@0 121 if (CertIsAuthoritativeForEVPolicy(candidateCert, policy)) {
michael@0 122 *trustLevel = TrustAnchor;
michael@0 123 return SECSuccess;
michael@0 124 }
michael@0 125 #endif
michael@0 126 }
michael@0 127 }
michael@0 128
michael@0 129 *trustLevel = InheritsTrust;
michael@0 130 return SECSuccess;
michael@0 131 }
michael@0 132
michael@0 133 SECStatus
michael@0 134 NSSCertDBTrustDomain::VerifySignedData(const CERTSignedData* signedData,
michael@0 135 const CERTCertificate* cert)
michael@0 136 {
michael@0 137 return ::mozilla::pkix::VerifySignedData(signedData, cert, mPinArg);
michael@0 138 }
michael@0 139
michael@0 140 static PRIntervalTime
michael@0 141 OCSPFetchingTypeToTimeoutTime(NSSCertDBTrustDomain::OCSPFetching ocspFetching)
michael@0 142 {
michael@0 143 switch (ocspFetching) {
michael@0 144 case NSSCertDBTrustDomain::FetchOCSPForDVSoftFail:
michael@0 145 return PR_SecondsToInterval(2);
michael@0 146 case NSSCertDBTrustDomain::FetchOCSPForEV:
michael@0 147 case NSSCertDBTrustDomain::FetchOCSPForDVHardFail:
michael@0 148 return PR_SecondsToInterval(10);
michael@0 149 // The rest of these are error cases. Assert in debug builds, but return
michael@0 150 // the default value corresponding to 2 seconds in release builds.
michael@0 151 case NSSCertDBTrustDomain::NeverFetchOCSP:
michael@0 152 case NSSCertDBTrustDomain::LocalOnlyOCSPForEV:
michael@0 153 PR_NOT_REACHED("we should never see this OCSPFetching type here");
michael@0 154 default:
michael@0 155 PR_NOT_REACHED("we're not handling every OCSPFetching type");
michael@0 156 }
michael@0 157 return PR_SecondsToInterval(2);
michael@0 158 }
michael@0 159
michael@0 160 SECStatus
michael@0 161 NSSCertDBTrustDomain::CheckRevocation(
michael@0 162 mozilla::pkix::EndEntityOrCA endEntityOrCA,
michael@0 163 const CERTCertificate* cert,
michael@0 164 /*const*/ CERTCertificate* issuerCert,
michael@0 165 PRTime time,
michael@0 166 /*optional*/ const SECItem* stapledOCSPResponse)
michael@0 167 {
michael@0 168 // Actively distrusted certificates will have already been blocked by
michael@0 169 // GetCertTrust.
michael@0 170
michael@0 171 // TODO: need to verify that IsRevoked isn't called for trust anchors AND
michael@0 172 // that that fact is documented in mozillapkix.
michael@0 173
michael@0 174 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 175 ("NSSCertDBTrustDomain: Top of CheckRevocation\n"));
michael@0 176
michael@0 177 PORT_Assert(cert);
michael@0 178 PORT_Assert(issuerCert);
michael@0 179 if (!cert || !issuerCert) {
michael@0 180 PORT_SetError(SEC_ERROR_INVALID_ARGS);
michael@0 181 return SECFailure;
michael@0 182 }
michael@0 183
michael@0 184 // Bug 991815: The BR allow OCSP for intermediates to be up to one year old.
michael@0 185 // Since this affects EV there is no reason why DV should be more strict
michael@0 186 // so all intermediatates are allowed to have OCSP responses up to one year
michael@0 187 // old.
michael@0 188 uint16_t maxOCSPLifetimeInDays = 10;
michael@0 189 if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
michael@0 190 maxOCSPLifetimeInDays = 365;
michael@0 191 }
michael@0 192
michael@0 193 // If we have a stapled OCSP response then the verification of that response
michael@0 194 // determines the result unless the OCSP response is expired. We make an
michael@0 195 // exception for expired responses because some servers, nginx in particular,
michael@0 196 // are known to serve expired responses due to bugs.
michael@0 197 // We keep track of the result of verifying the stapled response but don't
michael@0 198 // immediately return failure if the response has expired.
michael@0 199 PRErrorCode stapledOCSPResponseErrorCode = 0;
michael@0 200 if (stapledOCSPResponse) {
michael@0 201 PR_ASSERT(endEntityOrCA == MustBeEndEntity);
michael@0 202 bool expired;
michael@0 203 SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert,
michael@0 204 time,
michael@0 205 maxOCSPLifetimeInDays,
michael@0 206 stapledOCSPResponse,
michael@0 207 ResponseWasStapled,
michael@0 208 expired);
michael@0 209 if (rv == SECSuccess) {
michael@0 210 // stapled OCSP response present and good
michael@0 211 Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 1);
michael@0 212 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 213 ("NSSCertDBTrustDomain: stapled OCSP response: good"));
michael@0 214 return rv;
michael@0 215 }
michael@0 216 stapledOCSPResponseErrorCode = PR_GetError();
michael@0 217 if (stapledOCSPResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE ||
michael@0 218 expired) {
michael@0 219 // stapled OCSP response present but expired
michael@0 220 Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 3);
michael@0 221 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 222 ("NSSCertDBTrustDomain: expired stapled OCSP response"));
michael@0 223 } else {
michael@0 224 // stapled OCSP response present but invalid for some reason
michael@0 225 Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 4);
michael@0 226 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 227 ("NSSCertDBTrustDomain: stapled OCSP response: failure"));
michael@0 228 return rv;
michael@0 229 }
michael@0 230 } else {
michael@0 231 // no stapled OCSP response
michael@0 232 Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 2);
michael@0 233 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 234 ("NSSCertDBTrustDomain: no stapled OCSP response"));
michael@0 235 }
michael@0 236
michael@0 237 PRErrorCode cachedResponseErrorCode = 0;
michael@0 238 PRTime cachedResponseValidThrough = 0;
michael@0 239 bool cachedResponsePresent = mOCSPCache.Get(cert, issuerCert,
michael@0 240 cachedResponseErrorCode,
michael@0 241 cachedResponseValidThrough);
michael@0 242 if (cachedResponsePresent) {
michael@0 243 if (cachedResponseErrorCode == 0 && cachedResponseValidThrough >= time) {
michael@0 244 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 245 ("NSSCertDBTrustDomain: cached OCSP response: good"));
michael@0 246 return SECSuccess;
michael@0 247 }
michael@0 248 // If we have a cached revoked response, use it.
michael@0 249 if (cachedResponseErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) {
michael@0 250 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 251 ("NSSCertDBTrustDomain: cached OCSP response: revoked"));
michael@0 252 PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0);
michael@0 253 return SECFailure;
michael@0 254 }
michael@0 255 // The cached response may indicate an unknown certificate or it may be
michael@0 256 // expired. Don't return with either of these statuses yet - we may be
michael@0 257 // able to fetch a more recent one.
michael@0 258 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 259 ("NSSCertDBTrustDomain: cached OCSP response: error %ld valid "
michael@0 260 "until %lld", cachedResponseErrorCode, cachedResponseValidThrough));
michael@0 261 // When a good cached response has expired, it is more convenient
michael@0 262 // to convert that to an error code and just deal with
michael@0 263 // cachedResponseErrorCode from here on out.
michael@0 264 if (cachedResponseErrorCode == 0 && cachedResponseValidThrough < time) {
michael@0 265 cachedResponseErrorCode = SEC_ERROR_OCSP_OLD_RESPONSE;
michael@0 266 }
michael@0 267 // We may have a cached indication of server failure. Ignore it if
michael@0 268 // it has expired.
michael@0 269 if (cachedResponseErrorCode != 0 &&
michael@0 270 cachedResponseErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT &&
michael@0 271 cachedResponseErrorCode != SEC_ERROR_OCSP_OLD_RESPONSE &&
michael@0 272 cachedResponseValidThrough < time) {
michael@0 273 cachedResponseErrorCode = 0;
michael@0 274 cachedResponsePresent = false;
michael@0 275 }
michael@0 276 } else {
michael@0 277 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 278 ("NSSCertDBTrustDomain: no cached OCSP response"));
michael@0 279 }
michael@0 280 // At this point, if and only if cachedErrorResponseCode is 0, there was no
michael@0 281 // cached response.
michael@0 282 PR_ASSERT((!cachedResponsePresent && cachedResponseErrorCode == 0) ||
michael@0 283 (cachedResponsePresent && cachedResponseErrorCode != 0));
michael@0 284
michael@0 285 // TODO: We still need to handle the fallback for expired responses. But,
michael@0 286 // if/when we disable OCSP fetching by default, it would be ambiguous whether
michael@0 287 // security.OCSP.enable==0 means "I want the default" or "I really never want
michael@0 288 // you to ever fetch OCSP."
michael@0 289
michael@0 290 if ((mOCSPFetching == NeverFetchOCSP) ||
michael@0 291 (endEntityOrCA == MustBeCA && (mOCSPFetching == FetchOCSPForDVHardFail ||
michael@0 292 mOCSPFetching == FetchOCSPForDVSoftFail))) {
michael@0 293 // We're not going to be doing any fetching, so if there was a cached
michael@0 294 // "unknown" response, say so.
michael@0 295 if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) {
michael@0 296 PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
michael@0 297 return SECFailure;
michael@0 298 }
michael@0 299 // If we're doing hard-fail, we want to know if we have a cached response
michael@0 300 // that has expired.
michael@0 301 if (mOCSPFetching == FetchOCSPForDVHardFail &&
michael@0 302 cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
michael@0 303 PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
michael@0 304 return SECFailure;
michael@0 305 }
michael@0 306
michael@0 307 return SECSuccess;
michael@0 308 }
michael@0 309
michael@0 310 if (mOCSPFetching == LocalOnlyOCSPForEV) {
michael@0 311 PR_SetError(cachedResponseErrorCode != 0 ? cachedResponseErrorCode
michael@0 312 : SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
michael@0 313 return SECFailure;
michael@0 314 }
michael@0 315
michael@0 316 ScopedPtr<char, PORT_Free_string>
michael@0 317 url(CERT_GetOCSPAuthorityInfoAccessLocation(cert));
michael@0 318
michael@0 319 if (!url) {
michael@0 320 if (mOCSPFetching == FetchOCSPForEV ||
michael@0 321 cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) {
michael@0 322 PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
michael@0 323 return SECFailure;
michael@0 324 }
michael@0 325 if (cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
michael@0 326 PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
michael@0 327 return SECFailure;
michael@0 328 }
michael@0 329 if (stapledOCSPResponseErrorCode != 0) {
michael@0 330 PR_SetError(stapledOCSPResponseErrorCode, 0);
michael@0 331 return SECFailure;
michael@0 332 }
michael@0 333
michael@0 334 // Nothing to do if we don't have an OCSP responder URI for the cert; just
michael@0 335 // assume it is good. Note that this is the confusing, but intended,
michael@0 336 // interpretation of "strict" revocation checking in the face of a
michael@0 337 // certificate that lacks an OCSP responder URI.
michael@0 338 return SECSuccess;
michael@0 339 }
michael@0 340
michael@0 341 ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
michael@0 342 if (!arena) {
michael@0 343 return SECFailure;
michael@0 344 }
michael@0 345
michael@0 346 // Only request a response if we didn't have a cached indication of failure
michael@0 347 // (don't keep requesting responses from a failing server).
michael@0 348 const SECItem* response = nullptr;
michael@0 349 if (cachedResponseErrorCode == 0 ||
michael@0 350 cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT ||
michael@0 351 cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
michael@0 352 const SECItem* request(CreateEncodedOCSPRequest(arena.get(), cert,
michael@0 353 issuerCert));
michael@0 354 if (!request) {
michael@0 355 return SECFailure;
michael@0 356 }
michael@0 357
michael@0 358 response = DoOCSPRequest(arena.get(), url.get(), request,
michael@0 359 OCSPFetchingTypeToTimeoutTime(mOCSPFetching));
michael@0 360 }
michael@0 361
michael@0 362 if (!response) {
michael@0 363 PRErrorCode error = PR_GetError();
michael@0 364 if (error == 0) {
michael@0 365 error = cachedResponseErrorCode;
michael@0 366 }
michael@0 367 PRTime timeout = time + ServerFailureDelay;
michael@0 368 if (mOCSPCache.Put(cert, issuerCert, error, time, timeout) != SECSuccess) {
michael@0 369 return SECFailure;
michael@0 370 }
michael@0 371 PR_SetError(error, 0);
michael@0 372 if (mOCSPFetching != FetchOCSPForDVSoftFail) {
michael@0 373 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 374 ("NSSCertDBTrustDomain: returning SECFailure after "
michael@0 375 "OCSP request failure"));
michael@0 376 return SECFailure;
michael@0 377 }
michael@0 378 if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) {
michael@0 379 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 380 ("NSSCertDBTrustDomain: returning SECFailure from cached "
michael@0 381 "response after OCSP request failure"));
michael@0 382 PR_SetError(cachedResponseErrorCode, 0);
michael@0 383 return SECFailure;
michael@0 384 }
michael@0 385 if (stapledOCSPResponseErrorCode != 0) {
michael@0 386 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 387 ("NSSCertDBTrustDomain: returning SECFailure from expired "
michael@0 388 "stapled response after OCSP request failure"));
michael@0 389 PR_SetError(stapledOCSPResponseErrorCode, 0);
michael@0 390 return SECFailure;
michael@0 391 }
michael@0 392
michael@0 393 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 394 ("NSSCertDBTrustDomain: returning SECSuccess after "
michael@0 395 "OCSP request failure"));
michael@0 396 return SECSuccess; // Soft fail -> success :(
michael@0 397 }
michael@0 398
michael@0 399 // If the response from the network has expired but indicates a revoked
michael@0 400 // or unknown certificate, PR_GetError() will return the appropriate error.
michael@0 401 // We actually ignore expired here.
michael@0 402 bool expired;
michael@0 403 SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, time,
michael@0 404 maxOCSPLifetimeInDays,
michael@0 405 response,
michael@0 406 ResponseIsFromNetwork,
michael@0 407 expired);
michael@0 408 if (rv == SECSuccess || mOCSPFetching != FetchOCSPForDVSoftFail) {
michael@0 409 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 410 ("NSSCertDBTrustDomain: returning after VerifyEncodedOCSPResponse"));
michael@0 411 return rv;
michael@0 412 }
michael@0 413
michael@0 414 PRErrorCode error = PR_GetError();
michael@0 415 if (error == SEC_ERROR_OCSP_UNKNOWN_CERT ||
michael@0 416 error == SEC_ERROR_REVOKED_CERTIFICATE) {
michael@0 417 return rv;
michael@0 418 }
michael@0 419
michael@0 420 if (stapledOCSPResponseErrorCode != 0) {
michael@0 421 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 422 ("NSSCertDBTrustDomain: returning SECFailure from expired stapled "
michael@0 423 "response after OCSP request verification failure"));
michael@0 424 PR_SetError(stapledOCSPResponseErrorCode, 0);
michael@0 425 return SECFailure;
michael@0 426 }
michael@0 427
michael@0 428 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 429 ("NSSCertDBTrustDomain: end of CheckRevocation"));
michael@0 430
michael@0 431 return SECSuccess; // Soft fail -> success :(
michael@0 432 }
michael@0 433
michael@0 434 SECStatus
michael@0 435 NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse(
michael@0 436 const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time,
michael@0 437 uint16_t maxLifetimeInDays, const SECItem* encodedResponse,
michael@0 438 EncodedResponseSource responseSource, /*out*/ bool& expired)
michael@0 439 {
michael@0 440 PRTime thisUpdate = 0;
michael@0 441 PRTime validThrough = 0;
michael@0 442 SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
michael@0 443 maxLifetimeInDays, encodedResponse,
michael@0 444 expired, &thisUpdate, &validThrough);
michael@0 445 PRErrorCode error = (rv == SECSuccess ? 0 : PR_GetError());
michael@0 446 // If a response was stapled and expired, we don't want to cache it. Return
michael@0 447 // early to simplify the logic here.
michael@0 448 if (responseSource == ResponseWasStapled && expired) {
michael@0 449 PR_ASSERT(rv != SECSuccess);
michael@0 450 return rv;
michael@0 451 }
michael@0 452 // validThrough is only trustworthy if the response successfully verifies
michael@0 453 // or it indicates a revoked or unknown certificate.
michael@0 454 // If this isn't the case, store an indication of failure (to prevent
michael@0 455 // repeatedly requesting a response from a failing server).
michael@0 456 if (rv != SECSuccess && error != SEC_ERROR_REVOKED_CERTIFICATE &&
michael@0 457 error != SEC_ERROR_OCSP_UNKNOWN_CERT) {
michael@0 458 validThrough = time + ServerFailureDelay;
michael@0 459 }
michael@0 460 if (responseSource == ResponseIsFromNetwork ||
michael@0 461 rv == SECSuccess ||
michael@0 462 error == SEC_ERROR_REVOKED_CERTIFICATE ||
michael@0 463 error == SEC_ERROR_OCSP_UNKNOWN_CERT) {
michael@0 464 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 465 ("NSSCertDBTrustDomain: caching OCSP response"));
michael@0 466 if (mOCSPCache.Put(cert, issuerCert, error, thisUpdate, validThrough)
michael@0 467 != SECSuccess) {
michael@0 468 return SECFailure;
michael@0 469 }
michael@0 470 }
michael@0 471
michael@0 472 // If the verification failed, re-set to that original error
michael@0 473 // (the call to Put may have un-set it).
michael@0 474 if (rv != SECSuccess) {
michael@0 475 PR_SetError(error, 0);
michael@0 476 }
michael@0 477 return rv;
michael@0 478 }
michael@0 479
michael@0 480 SECStatus
michael@0 481 NSSCertDBTrustDomain::IsChainValid(const CERTCertList* certChain) {
michael@0 482 SECStatus rv = SECFailure;
michael@0 483
michael@0 484 PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
michael@0 485 ("NSSCertDBTrustDomain: Top of IsChainValid mCheckCallback=%p",
michael@0 486 mCheckChainCallback));
michael@0 487
michael@0 488 if (!mCheckChainCallback) {
michael@0 489 return SECSuccess;
michael@0 490 }
michael@0 491 if (!mCheckChainCallback->isChainValid) {
michael@0 492 PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
michael@0 493 return SECFailure;
michael@0 494 }
michael@0 495 PRBool chainOK;
michael@0 496 rv = (mCheckChainCallback->isChainValid)(mCheckChainCallback->isChainValidArg,
michael@0 497 certChain, &chainOK);
michael@0 498 if (rv != SECSuccess) {
michael@0 499 return rv;
michael@0 500 }
michael@0 501 // rv = SECSuccess only implies successful call, now is time
michael@0 502 // to check the chain check status
michael@0 503 // we should only return success if the chain is valid
michael@0 504 if (chainOK) {
michael@0 505 return SECSuccess;
michael@0 506 }
michael@0 507 PR_SetError(SEC_ERROR_APPLICATION_CALLBACK_ERROR, 0);
michael@0 508 return SECFailure;
michael@0 509 }
michael@0 510
michael@0 511 namespace {
michael@0 512
michael@0 513 static char*
michael@0 514 nss_addEscape(const char* string, char quote)
michael@0 515 {
michael@0 516 char* newString = 0;
michael@0 517 int escapes = 0, size = 0;
michael@0 518 const char* src;
michael@0 519 char* dest;
michael@0 520
michael@0 521 for (src = string; *src; src++) {
michael@0 522 if ((*src == quote) || (*src == '\\')) {
michael@0 523 escapes++;
michael@0 524 }
michael@0 525 size++;
michael@0 526 }
michael@0 527
michael@0 528 newString = (char*) PORT_ZAlloc(escapes + size + 1);
michael@0 529 if (!newString) {
michael@0 530 return nullptr;
michael@0 531 }
michael@0 532
michael@0 533 for (src = string, dest = newString; *src; src++, dest++) {
michael@0 534 if ((*src == quote) || (*src == '\\')) {
michael@0 535 *dest++ = '\\';
michael@0 536 }
michael@0 537 *dest = *src;
michael@0 538 }
michael@0 539
michael@0 540 return newString;
michael@0 541 }
michael@0 542
michael@0 543 } // unnamed namespace
michael@0 544
michael@0 545 SECStatus
michael@0 546 InitializeNSS(const char* dir, bool readOnly)
michael@0 547 {
michael@0 548 // The NSS_INIT_NOROOTINIT flag turns off the loading of the root certs
michael@0 549 // module by NSS_Initialize because we will load it in InstallLoadableRoots
michael@0 550 // later. It also allows us to work around a bug in the system NSS in
michael@0 551 // Ubuntu 8.04, which loads any nonexistent "<configdir>/libnssckbi.so" as
michael@0 552 // "/usr/lib/nss/libnssckbi.so".
michael@0 553 uint32_t flags = NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE;
michael@0 554 if (readOnly) {
michael@0 555 flags |= NSS_INIT_READONLY;
michael@0 556 }
michael@0 557 return ::NSS_Initialize(dir, "", "", SECMOD_DB, flags);
michael@0 558 }
michael@0 559
michael@0 560 void
michael@0 561 DisableMD5()
michael@0 562 {
michael@0 563 NSS_SetAlgorithmPolicy(SEC_OID_MD5,
michael@0 564 0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE);
michael@0 565 NSS_SetAlgorithmPolicy(SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION,
michael@0 566 0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE);
michael@0 567 NSS_SetAlgorithmPolicy(SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC,
michael@0 568 0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE);
michael@0 569 }
michael@0 570
michael@0 571 SECStatus
michael@0 572 LoadLoadableRoots(/*optional*/ const char* dir, const char* modNameUTF8)
michael@0 573 {
michael@0 574 PR_ASSERT(modNameUTF8);
michael@0 575
michael@0 576 if (!modNameUTF8) {
michael@0 577 PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
michael@0 578 return SECFailure;
michael@0 579 }
michael@0 580
michael@0 581 ScopedPtr<char, PR_FreeLibraryName> fullLibraryPath(
michael@0 582 PR_GetLibraryName(dir, "nssckbi"));
michael@0 583 if (!fullLibraryPath) {
michael@0 584 return SECFailure;
michael@0 585 }
michael@0 586
michael@0 587 ScopedPtr<char, PORT_Free_string> escaped_fullLibraryPath(
michael@0 588 nss_addEscape(fullLibraryPath.get(), '\"'));
michael@0 589 if (!escaped_fullLibraryPath) {
michael@0 590 return SECFailure;
michael@0 591 }
michael@0 592
michael@0 593 // If a module exists with the same name, delete it.
michael@0 594 int modType;
michael@0 595 SECMOD_DeleteModule(modNameUTF8, &modType);
michael@0 596
michael@0 597 ScopedPtr<char, PR_smprintf_free> pkcs11ModuleSpec(
michael@0 598 PR_smprintf("name=\"%s\" library=\"%s\"", modNameUTF8,
michael@0 599 escaped_fullLibraryPath.get()));
michael@0 600 if (!pkcs11ModuleSpec) {
michael@0 601 return SECFailure;
michael@0 602 }
michael@0 603
michael@0 604 ScopedSECMODModule rootsModule(SECMOD_LoadUserModule(pkcs11ModuleSpec.get(),
michael@0 605 nullptr, false));
michael@0 606 if (!rootsModule) {
michael@0 607 return SECFailure;
michael@0 608 }
michael@0 609
michael@0 610 if (!rootsModule->loaded) {
michael@0 611 PR_SetError(PR_INVALID_STATE_ERROR, 0);
michael@0 612 return SECFailure;
michael@0 613 }
michael@0 614
michael@0 615 return SECSuccess;
michael@0 616 }
michael@0 617
michael@0 618 void
michael@0 619 UnloadLoadableRoots(const char* modNameUTF8)
michael@0 620 {
michael@0 621 PR_ASSERT(modNameUTF8);
michael@0 622 ScopedSECMODModule rootsModule(SECMOD_FindModule(modNameUTF8));
michael@0 623
michael@0 624 if (rootsModule) {
michael@0 625 SECMOD_UnloadUserModule(rootsModule.get());
michael@0 626 }
michael@0 627 }
michael@0 628
michael@0 629 void
michael@0 630 SetClassicOCSPBehavior(CertVerifier::ocsp_download_config enabled,
michael@0 631 CertVerifier::ocsp_strict_config strict,
michael@0 632 CertVerifier::ocsp_get_config get)
michael@0 633 {
michael@0 634 CERT_DisableOCSPDefaultResponder(CERT_GetDefaultCertDB());
michael@0 635 if (enabled == CertVerifier::ocsp_off) {
michael@0 636 CERT_DisableOCSPChecking(CERT_GetDefaultCertDB());
michael@0 637 } else {
michael@0 638 CERT_EnableOCSPChecking(CERT_GetDefaultCertDB());
michael@0 639 }
michael@0 640
michael@0 641 SEC_OcspFailureMode failureMode = strict == CertVerifier::ocsp_strict
michael@0 642 ? ocspMode_FailureIsVerificationFailure
michael@0 643 : ocspMode_FailureIsNotAVerificationFailure;
michael@0 644 (void) CERT_SetOCSPFailureMode(failureMode);
michael@0 645
michael@0 646 CERT_ForcePostMethodForOCSP(get != CertVerifier::ocsp_get_enabled);
michael@0 647
michael@0 648 int OCSPTimeoutSeconds = 3;
michael@0 649 if (strict == CertVerifier::ocsp_strict) {
michael@0 650 OCSPTimeoutSeconds = 10;
michael@0 651 }
michael@0 652 CERT_SetOCSPTimeout(OCSPTimeoutSeconds);
michael@0 653 }
michael@0 654
michael@0 655 char*
michael@0 656 DefaultServerNicknameForCert(CERTCertificate* cert)
michael@0 657 {
michael@0 658 char* nickname = nullptr;
michael@0 659 int count;
michael@0 660 bool conflict;
michael@0 661 char* servername = nullptr;
michael@0 662
michael@0 663 servername = CERT_GetCommonName(&cert->subject);
michael@0 664 if (!servername) {
michael@0 665 // Certs without common names are strange, but they do exist...
michael@0 666 // Let's try to use another string for the nickname
michael@0 667 servername = CERT_GetOrgUnitName(&cert->subject);
michael@0 668 if (!servername) {
michael@0 669 servername = CERT_GetOrgName(&cert->subject);
michael@0 670 if (!servername) {
michael@0 671 servername = CERT_GetLocalityName(&cert->subject);
michael@0 672 if (!servername) {
michael@0 673 servername = CERT_GetStateName(&cert->subject);
michael@0 674 if (!servername) {
michael@0 675 servername = CERT_GetCountryName(&cert->subject);
michael@0 676 if (!servername) {
michael@0 677 // We tried hard, there is nothing more we can do.
michael@0 678 // A cert without any names doesn't really make sense.
michael@0 679 return nullptr;
michael@0 680 }
michael@0 681 }
michael@0 682 }
michael@0 683 }
michael@0 684 }
michael@0 685 }
michael@0 686
michael@0 687 count = 1;
michael@0 688 while (1) {
michael@0 689 if (count == 1) {
michael@0 690 nickname = PR_smprintf("%s", servername);
michael@0 691 }
michael@0 692 else {
michael@0 693 nickname = PR_smprintf("%s #%d", servername, count);
michael@0 694 }
michael@0 695 if (!nickname) {
michael@0 696 break;
michael@0 697 }
michael@0 698
michael@0 699 conflict = SEC_CertNicknameConflict(nickname, &cert->derSubject,
michael@0 700 cert->dbhandle);
michael@0 701 if (!conflict) {
michael@0 702 break;
michael@0 703 }
michael@0 704 PR_Free(nickname);
michael@0 705 count++;
michael@0 706 }
michael@0 707 PR_FREEIF(servername);
michael@0 708 return nickname;
michael@0 709 }
michael@0 710
michael@0 711 void
michael@0 712 SaveIntermediateCerts(const ScopedCERTCertList& certList)
michael@0 713 {
michael@0 714 if (!certList) {
michael@0 715 return;
michael@0 716 }
michael@0 717
michael@0 718 bool isEndEntity = true;
michael@0 719 for (CERTCertListNode* node = CERT_LIST_HEAD(certList);
michael@0 720 !CERT_LIST_END(node, certList);
michael@0 721 node = CERT_LIST_NEXT(node)) {
michael@0 722 if (isEndEntity) {
michael@0 723 // Skip the end-entity; we only want to store intermediates
michael@0 724 isEndEntity = false;
michael@0 725 continue;
michael@0 726 }
michael@0 727
michael@0 728 if (node->cert->slot) {
michael@0 729 // This cert was found on a token, no need to remember it in the temp db.
michael@0 730 continue;
michael@0 731 }
michael@0 732
michael@0 733 if (node->cert->isperm) {
michael@0 734 // We don't need to remember certs already stored in perm db.
michael@0 735 continue;
michael@0 736 }
michael@0 737
michael@0 738 // We have found a signer cert that we want to remember.
michael@0 739 char* nickname = DefaultServerNicknameForCert(node->cert);
michael@0 740 if (nickname && *nickname) {
michael@0 741 ScopedPtr<PK11SlotInfo, PK11_FreeSlot> slot(PK11_GetInternalKeySlot());
michael@0 742 if (slot) {
michael@0 743 PK11_ImportCert(slot.get(), node->cert, CK_INVALID_HANDLE,
michael@0 744 nickname, false);
michael@0 745 }
michael@0 746 }
michael@0 747 PR_FREEIF(nickname);
michael@0 748 }
michael@0 749 }
michael@0 750
michael@0 751 } } // namespace mozilla::psm

mercurial