security/certverifier/NSSCertDBTrustDomain.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/security/certverifier/NSSCertDBTrustDomain.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,751 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set ts=8 sts=2 et sw=2 tw=80: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "NSSCertDBTrustDomain.h"
    1.11 +
    1.12 +#include <stdint.h>
    1.13 +
    1.14 +#include "ExtendedValidation.h"
    1.15 +#include "OCSPRequestor.h"
    1.16 +#include "certdb.h"
    1.17 +#include "mozilla/Telemetry.h"
    1.18 +#include "nss.h"
    1.19 +#include "ocsp.h"
    1.20 +#include "pk11pub.h"
    1.21 +#include "pkix/pkix.h"
    1.22 +#include "prerror.h"
    1.23 +#include "prmem.h"
    1.24 +#include "prprf.h"
    1.25 +#include "secerr.h"
    1.26 +#include "secmod.h"
    1.27 +
    1.28 +using namespace mozilla::pkix;
    1.29 +
    1.30 +#ifdef PR_LOGGING
    1.31 +extern PRLogModuleInfo* gCertVerifierLog;
    1.32 +#endif
    1.33 +
    1.34 +namespace mozilla { namespace psm {
    1.35 +
    1.36 +const char BUILTIN_ROOTS_MODULE_DEFAULT_NAME[] = "Builtin Roots Module";
    1.37 +
    1.38 +void PORT_Free_string(char* str) { PORT_Free(str); }
    1.39 +
    1.40 +namespace {
    1.41 +
    1.42 +typedef ScopedPtr<SECMODModule, SECMOD_DestroyModule> ScopedSECMODModule;
    1.43 +
    1.44 +} // unnamed namespace
    1.45 +
    1.46 +NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType,
    1.47 +                                           OCSPFetching ocspFetching,
    1.48 +                                           OCSPCache& ocspCache,
    1.49 +                                           void* pinArg,
    1.50 +                                           CERTChainVerifyCallback* checkChainCallback)
    1.51 +  : mCertDBTrustType(certDBTrustType)
    1.52 +  , mOCSPFetching(ocspFetching)
    1.53 +  , mOCSPCache(ocspCache)
    1.54 +  , mPinArg(pinArg)
    1.55 +  , mCheckChainCallback(checkChainCallback)
    1.56 +{
    1.57 +}
    1.58 +
    1.59 +SECStatus
    1.60 +NSSCertDBTrustDomain::FindPotentialIssuers(
    1.61 +  const SECItem* encodedIssuerName, PRTime time,
    1.62 +  /*out*/ mozilla::pkix::ScopedCERTCertList& results)
    1.63 +{
    1.64 +  // TODO: normalize encodedIssuerName
    1.65 +  // TODO: NSS seems to be ambiguous between "no potential issuers found" and
    1.66 +  // "there was an error trying to retrieve the potential issuers."
    1.67 +  results = CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(),
    1.68 +                                       encodedIssuerName, time, true);
    1.69 +  return SECSuccess;
    1.70 +}
    1.71 +
    1.72 +SECStatus
    1.73 +NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
    1.74 +                                   SECOidTag policy,
    1.75 +                                   const CERTCertificate* candidateCert,
    1.76 +                                   /*out*/ TrustLevel* trustLevel)
    1.77 +{
    1.78 +  PR_ASSERT(candidateCert);
    1.79 +  PR_ASSERT(trustLevel);
    1.80 +
    1.81 +  if (!candidateCert || !trustLevel) {
    1.82 +    PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
    1.83 +    return SECFailure;
    1.84 +  }
    1.85 +
    1.86 +#ifdef MOZ_NO_EV_CERTS
    1.87 +  if (policy != SEC_OID_X509_ANY_POLICY) {
    1.88 +    PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0);
    1.89 +    return SECFailure;
    1.90 +  }
    1.91 +#endif
    1.92 +
    1.93 +  // XXX: CERT_GetCertTrust seems to be abusing SECStatus as a boolean, where
    1.94 +  // SECSuccess means that there is a trust record and SECFailure means there
    1.95 +  // is not a trust record. I looked at NSS's internal uses of
    1.96 +  // CERT_GetCertTrust, and all that code uses the result as a boolean meaning
    1.97 +  // "We have a trust record."
    1.98 +  CERTCertTrust trust;
    1.99 +  if (CERT_GetCertTrust(candidateCert, &trust) == SECSuccess) {
   1.100 +    PRUint32 flags = SEC_GET_TRUST_FLAGS(&trust, mCertDBTrustType);
   1.101 +
   1.102 +    // For DISTRUST, we use the CERTDB_TRUSTED or CERTDB_TRUSTED_CA bit,
   1.103 +    // because we can have active distrust for either type of cert. Note that
   1.104 +    // CERTDB_TERMINAL_RECORD means "stop trying to inherit trust" so if the
   1.105 +    // relevant trust bit isn't set then that means the cert must be considered
   1.106 +    // distrusted.
   1.107 +    PRUint32 relevantTrustBit = endEntityOrCA == MustBeCA ? CERTDB_TRUSTED_CA
   1.108 +                                                          : CERTDB_TRUSTED;
   1.109 +    if (((flags & (relevantTrustBit|CERTDB_TERMINAL_RECORD)))
   1.110 +            == CERTDB_TERMINAL_RECORD) {
   1.111 +      *trustLevel = ActivelyDistrusted;
   1.112 +      return SECSuccess;
   1.113 +    }
   1.114 +
   1.115 +    // For TRUST, we only use the CERTDB_TRUSTED_CA bit, because Gecko hasn't
   1.116 +    // needed to consider end-entity certs to be their own trust anchors since
   1.117 +    // Gecko implemented nsICertOverrideService.
   1.118 +    if (flags & CERTDB_TRUSTED_CA) {
   1.119 +      if (policy == SEC_OID_X509_ANY_POLICY) {
   1.120 +        *trustLevel = TrustAnchor;
   1.121 +        return SECSuccess;
   1.122 +      }
   1.123 +#ifndef MOZ_NO_EV_CERTS
   1.124 +      if (CertIsAuthoritativeForEVPolicy(candidateCert, policy)) {
   1.125 +        *trustLevel = TrustAnchor;
   1.126 +        return SECSuccess;
   1.127 +      }
   1.128 +#endif
   1.129 +    }
   1.130 +  }
   1.131 +
   1.132 +  *trustLevel = InheritsTrust;
   1.133 +  return SECSuccess;
   1.134 +}
   1.135 +
   1.136 +SECStatus
   1.137 +NSSCertDBTrustDomain::VerifySignedData(const CERTSignedData* signedData,
   1.138 +                                       const CERTCertificate* cert)
   1.139 +{
   1.140 +  return ::mozilla::pkix::VerifySignedData(signedData, cert, mPinArg);
   1.141 +}
   1.142 +
   1.143 +static PRIntervalTime
   1.144 +OCSPFetchingTypeToTimeoutTime(NSSCertDBTrustDomain::OCSPFetching ocspFetching)
   1.145 +{
   1.146 +  switch (ocspFetching) {
   1.147 +    case NSSCertDBTrustDomain::FetchOCSPForDVSoftFail:
   1.148 +      return PR_SecondsToInterval(2);
   1.149 +    case NSSCertDBTrustDomain::FetchOCSPForEV:
   1.150 +    case NSSCertDBTrustDomain::FetchOCSPForDVHardFail:
   1.151 +      return PR_SecondsToInterval(10);
   1.152 +    // The rest of these are error cases. Assert in debug builds, but return
   1.153 +    // the default value corresponding to 2 seconds in release builds.
   1.154 +    case NSSCertDBTrustDomain::NeverFetchOCSP:
   1.155 +    case NSSCertDBTrustDomain::LocalOnlyOCSPForEV:
   1.156 +      PR_NOT_REACHED("we should never see this OCSPFetching type here");
   1.157 +    default:
   1.158 +      PR_NOT_REACHED("we're not handling every OCSPFetching type");
   1.159 +  }
   1.160 +  return PR_SecondsToInterval(2);
   1.161 +}
   1.162 +
   1.163 +SECStatus
   1.164 +NSSCertDBTrustDomain::CheckRevocation(
   1.165 +  mozilla::pkix::EndEntityOrCA endEntityOrCA,
   1.166 +  const CERTCertificate* cert,
   1.167 +  /*const*/ CERTCertificate* issuerCert,
   1.168 +  PRTime time,
   1.169 +  /*optional*/ const SECItem* stapledOCSPResponse)
   1.170 +{
   1.171 +  // Actively distrusted certificates will have already been blocked by
   1.172 +  // GetCertTrust.
   1.173 +
   1.174 +  // TODO: need to verify that IsRevoked isn't called for trust anchors AND
   1.175 +  // that that fact is documented in mozillapkix.
   1.176 +
   1.177 +  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.178 +         ("NSSCertDBTrustDomain: Top of CheckRevocation\n"));
   1.179 +
   1.180 +  PORT_Assert(cert);
   1.181 +  PORT_Assert(issuerCert);
   1.182 +  if (!cert || !issuerCert) {
   1.183 +    PORT_SetError(SEC_ERROR_INVALID_ARGS);
   1.184 +    return SECFailure;
   1.185 +  }
   1.186 +
   1.187 +  // Bug 991815: The BR allow OCSP for intermediates to be up to one year old.
   1.188 +  // Since this affects EV there is no reason why DV should be more strict
   1.189 +  // so all intermediatates are allowed to have OCSP responses up to one year
   1.190 +  // old.
   1.191 +  uint16_t maxOCSPLifetimeInDays = 10;
   1.192 +  if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
   1.193 +    maxOCSPLifetimeInDays = 365;
   1.194 +  }
   1.195 +
   1.196 +  // If we have a stapled OCSP response then the verification of that response
   1.197 +  // determines the result unless the OCSP response is expired. We make an
   1.198 +  // exception for expired responses because some servers, nginx in particular,
   1.199 +  // are known to serve expired responses due to bugs.
   1.200 +  // We keep track of the result of verifying the stapled response but don't
   1.201 +  // immediately return failure if the response has expired.
   1.202 +  PRErrorCode stapledOCSPResponseErrorCode = 0;
   1.203 +  if (stapledOCSPResponse) {
   1.204 +    PR_ASSERT(endEntityOrCA == MustBeEndEntity);
   1.205 +    bool expired;
   1.206 +    SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert,
   1.207 +                                                          time,
   1.208 +                                                          maxOCSPLifetimeInDays,
   1.209 +                                                          stapledOCSPResponse,
   1.210 +                                                          ResponseWasStapled,
   1.211 +                                                          expired);
   1.212 +    if (rv == SECSuccess) {
   1.213 +      // stapled OCSP response present and good
   1.214 +      Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 1);
   1.215 +      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.216 +             ("NSSCertDBTrustDomain: stapled OCSP response: good"));
   1.217 +      return rv;
   1.218 +    }
   1.219 +    stapledOCSPResponseErrorCode = PR_GetError();
   1.220 +    if (stapledOCSPResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE ||
   1.221 +        expired) {
   1.222 +      // stapled OCSP response present but expired
   1.223 +      Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 3);
   1.224 +      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.225 +             ("NSSCertDBTrustDomain: expired stapled OCSP response"));
   1.226 +    } else {
   1.227 +      // stapled OCSP response present but invalid for some reason
   1.228 +      Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 4);
   1.229 +      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.230 +             ("NSSCertDBTrustDomain: stapled OCSP response: failure"));
   1.231 +      return rv;
   1.232 +    }
   1.233 +  } else {
   1.234 +    // no stapled OCSP response
   1.235 +    Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 2);
   1.236 +    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.237 +           ("NSSCertDBTrustDomain: no stapled OCSP response"));
   1.238 +  }
   1.239 +
   1.240 +  PRErrorCode cachedResponseErrorCode = 0;
   1.241 +  PRTime cachedResponseValidThrough = 0;
   1.242 +  bool cachedResponsePresent = mOCSPCache.Get(cert, issuerCert,
   1.243 +                                              cachedResponseErrorCode,
   1.244 +                                              cachedResponseValidThrough);
   1.245 +  if (cachedResponsePresent) {
   1.246 +    if (cachedResponseErrorCode == 0 && cachedResponseValidThrough >= time) {
   1.247 +      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.248 +             ("NSSCertDBTrustDomain: cached OCSP response: good"));
   1.249 +      return SECSuccess;
   1.250 +    }
   1.251 +    // If we have a cached revoked response, use it.
   1.252 +    if (cachedResponseErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) {
   1.253 +      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.254 +             ("NSSCertDBTrustDomain: cached OCSP response: revoked"));
   1.255 +      PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0);
   1.256 +      return SECFailure;
   1.257 +    }
   1.258 +    // The cached response may indicate an unknown certificate or it may be
   1.259 +    // expired. Don't return with either of these statuses yet - we may be
   1.260 +    // able to fetch a more recent one.
   1.261 +    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.262 +           ("NSSCertDBTrustDomain: cached OCSP response: error %ld valid "
   1.263 +           "until %lld", cachedResponseErrorCode, cachedResponseValidThrough));
   1.264 +    // When a good cached response has expired, it is more convenient
   1.265 +    // to convert that to an error code and just deal with
   1.266 +    // cachedResponseErrorCode from here on out.
   1.267 +    if (cachedResponseErrorCode == 0 && cachedResponseValidThrough < time) {
   1.268 +      cachedResponseErrorCode = SEC_ERROR_OCSP_OLD_RESPONSE;
   1.269 +    }
   1.270 +    // We may have a cached indication of server failure. Ignore it if
   1.271 +    // it has expired.
   1.272 +    if (cachedResponseErrorCode != 0 &&
   1.273 +        cachedResponseErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT &&
   1.274 +        cachedResponseErrorCode != SEC_ERROR_OCSP_OLD_RESPONSE &&
   1.275 +        cachedResponseValidThrough < time) {
   1.276 +      cachedResponseErrorCode = 0;
   1.277 +      cachedResponsePresent = false;
   1.278 +    }
   1.279 +  } else {
   1.280 +    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.281 +           ("NSSCertDBTrustDomain: no cached OCSP response"));
   1.282 +  }
   1.283 +  // At this point, if and only if cachedErrorResponseCode is 0, there was no
   1.284 +  // cached response.
   1.285 +  PR_ASSERT((!cachedResponsePresent && cachedResponseErrorCode == 0) ||
   1.286 +            (cachedResponsePresent && cachedResponseErrorCode != 0));
   1.287 +
   1.288 +  // TODO: We still need to handle the fallback for expired responses. But,
   1.289 +  // if/when we disable OCSP fetching by default, it would be ambiguous whether
   1.290 +  // security.OCSP.enable==0 means "I want the default" or "I really never want
   1.291 +  // you to ever fetch OCSP."
   1.292 +
   1.293 +  if ((mOCSPFetching == NeverFetchOCSP) ||
   1.294 +      (endEntityOrCA == MustBeCA && (mOCSPFetching == FetchOCSPForDVHardFail ||
   1.295 +                                     mOCSPFetching == FetchOCSPForDVSoftFail))) {
   1.296 +    // We're not going to be doing any fetching, so if there was a cached
   1.297 +    // "unknown" response, say so.
   1.298 +    if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) {
   1.299 +      PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
   1.300 +      return SECFailure;
   1.301 +    }
   1.302 +    // If we're doing hard-fail, we want to know if we have a cached response
   1.303 +    // that has expired.
   1.304 +    if (mOCSPFetching == FetchOCSPForDVHardFail &&
   1.305 +        cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
   1.306 +      PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
   1.307 +      return SECFailure;
   1.308 +    }
   1.309 +
   1.310 +    return SECSuccess;
   1.311 +  }
   1.312 +
   1.313 +  if (mOCSPFetching == LocalOnlyOCSPForEV) {
   1.314 +    PR_SetError(cachedResponseErrorCode != 0 ? cachedResponseErrorCode
   1.315 +                                             : SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
   1.316 +    return SECFailure;
   1.317 +  }
   1.318 +
   1.319 +  ScopedPtr<char, PORT_Free_string>
   1.320 +    url(CERT_GetOCSPAuthorityInfoAccessLocation(cert));
   1.321 +
   1.322 +  if (!url) {
   1.323 +    if (mOCSPFetching == FetchOCSPForEV ||
   1.324 +        cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) {
   1.325 +      PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
   1.326 +      return SECFailure;
   1.327 +    }
   1.328 +    if (cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
   1.329 +      PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
   1.330 +      return SECFailure;
   1.331 +    }
   1.332 +    if (stapledOCSPResponseErrorCode != 0) {
   1.333 +      PR_SetError(stapledOCSPResponseErrorCode, 0);
   1.334 +      return SECFailure;
   1.335 +    }
   1.336 +
   1.337 +    // Nothing to do if we don't have an OCSP responder URI for the cert; just
   1.338 +    // assume it is good. Note that this is the confusing, but intended,
   1.339 +    // interpretation of "strict" revocation checking in the face of a
   1.340 +    // certificate that lacks an OCSP responder URI.
   1.341 +    return SECSuccess;
   1.342 +  }
   1.343 +
   1.344 +  ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
   1.345 +  if (!arena) {
   1.346 +    return SECFailure;
   1.347 +  }
   1.348 +
   1.349 +  // Only request a response if we didn't have a cached indication of failure
   1.350 +  // (don't keep requesting responses from a failing server).
   1.351 +  const SECItem* response = nullptr;
   1.352 +  if (cachedResponseErrorCode == 0 ||
   1.353 +      cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT ||
   1.354 +      cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
   1.355 +    const SECItem* request(CreateEncodedOCSPRequest(arena.get(), cert,
   1.356 +                                                    issuerCert));
   1.357 +    if (!request) {
   1.358 +      return SECFailure;
   1.359 +    }
   1.360 +
   1.361 +    response = DoOCSPRequest(arena.get(), url.get(), request,
   1.362 +                             OCSPFetchingTypeToTimeoutTime(mOCSPFetching));
   1.363 +  }
   1.364 +
   1.365 +  if (!response) {
   1.366 +    PRErrorCode error = PR_GetError();
   1.367 +    if (error == 0) {
   1.368 +      error = cachedResponseErrorCode;
   1.369 +    }
   1.370 +    PRTime timeout = time + ServerFailureDelay;
   1.371 +    if (mOCSPCache.Put(cert, issuerCert, error, time, timeout) != SECSuccess) {
   1.372 +      return SECFailure;
   1.373 +    }
   1.374 +    PR_SetError(error, 0);
   1.375 +    if (mOCSPFetching != FetchOCSPForDVSoftFail) {
   1.376 +      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.377 +             ("NSSCertDBTrustDomain: returning SECFailure after "
   1.378 +              "OCSP request failure"));
   1.379 +      return SECFailure;
   1.380 +    }
   1.381 +    if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) {
   1.382 +      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.383 +             ("NSSCertDBTrustDomain: returning SECFailure from cached "
   1.384 +              "response after OCSP request failure"));
   1.385 +      PR_SetError(cachedResponseErrorCode, 0);
   1.386 +      return SECFailure;
   1.387 +    }
   1.388 +    if (stapledOCSPResponseErrorCode != 0) {
   1.389 +      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.390 +             ("NSSCertDBTrustDomain: returning SECFailure from expired "
   1.391 +              "stapled response after OCSP request failure"));
   1.392 +      PR_SetError(stapledOCSPResponseErrorCode, 0);
   1.393 +      return SECFailure;
   1.394 +    }
   1.395 +
   1.396 +    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.397 +           ("NSSCertDBTrustDomain: returning SECSuccess after "
   1.398 +            "OCSP request failure"));
   1.399 +    return SECSuccess; // Soft fail -> success :(
   1.400 +  }
   1.401 +
   1.402 +  // If the response from the network has expired but indicates a revoked
   1.403 +  // or unknown certificate, PR_GetError() will return the appropriate error.
   1.404 +  // We actually ignore expired here.
   1.405 +  bool expired;
   1.406 +  SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, time,
   1.407 +                                                        maxOCSPLifetimeInDays,
   1.408 +                                                        response,
   1.409 +                                                        ResponseIsFromNetwork,
   1.410 +                                                        expired);
   1.411 +  if (rv == SECSuccess || mOCSPFetching != FetchOCSPForDVSoftFail) {
   1.412 +    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.413 +      ("NSSCertDBTrustDomain: returning after VerifyEncodedOCSPResponse"));
   1.414 +    return rv;
   1.415 +  }
   1.416 +
   1.417 +  PRErrorCode error = PR_GetError();
   1.418 +  if (error == SEC_ERROR_OCSP_UNKNOWN_CERT ||
   1.419 +      error == SEC_ERROR_REVOKED_CERTIFICATE) {
   1.420 +    return rv;
   1.421 +  }
   1.422 +
   1.423 +  if (stapledOCSPResponseErrorCode != 0) {
   1.424 +    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.425 +           ("NSSCertDBTrustDomain: returning SECFailure from expired stapled "
   1.426 +            "response after OCSP request verification failure"));
   1.427 +    PR_SetError(stapledOCSPResponseErrorCode, 0);
   1.428 +    return SECFailure;
   1.429 +  }
   1.430 +
   1.431 +  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.432 +         ("NSSCertDBTrustDomain: end of CheckRevocation"));
   1.433 +
   1.434 +  return SECSuccess; // Soft fail -> success :(
   1.435 +}
   1.436 +
   1.437 +SECStatus
   1.438 +NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse(
   1.439 +  const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time,
   1.440 +  uint16_t maxLifetimeInDays, const SECItem* encodedResponse,
   1.441 +  EncodedResponseSource responseSource, /*out*/ bool& expired)
   1.442 +{
   1.443 +  PRTime thisUpdate = 0;
   1.444 +  PRTime validThrough = 0;
   1.445 +  SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
   1.446 +                                           maxLifetimeInDays, encodedResponse,
   1.447 +                                           expired, &thisUpdate, &validThrough);
   1.448 +  PRErrorCode error = (rv == SECSuccess ? 0 : PR_GetError());
   1.449 +  // If a response was stapled and expired, we don't want to cache it. Return
   1.450 +  // early to simplify the logic here.
   1.451 +  if (responseSource == ResponseWasStapled && expired) {
   1.452 +    PR_ASSERT(rv != SECSuccess);
   1.453 +    return rv;
   1.454 +  }
   1.455 +  // validThrough is only trustworthy if the response successfully verifies
   1.456 +  // or it indicates a revoked or unknown certificate.
   1.457 +  // If this isn't the case, store an indication of failure (to prevent
   1.458 +  // repeatedly requesting a response from a failing server).
   1.459 +  if (rv != SECSuccess && error != SEC_ERROR_REVOKED_CERTIFICATE &&
   1.460 +      error != SEC_ERROR_OCSP_UNKNOWN_CERT) {
   1.461 +    validThrough = time + ServerFailureDelay;
   1.462 +  }
   1.463 +  if (responseSource == ResponseIsFromNetwork ||
   1.464 +      rv == SECSuccess ||
   1.465 +      error == SEC_ERROR_REVOKED_CERTIFICATE ||
   1.466 +      error == SEC_ERROR_OCSP_UNKNOWN_CERT) {
   1.467 +    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.468 +           ("NSSCertDBTrustDomain: caching OCSP response"));
   1.469 +    if (mOCSPCache.Put(cert, issuerCert, error, thisUpdate, validThrough)
   1.470 +          != SECSuccess) {
   1.471 +      return SECFailure;
   1.472 +    }
   1.473 +  }
   1.474 +
   1.475 +  // If the verification failed, re-set to that original error
   1.476 +  // (the call to Put may have un-set it).
   1.477 +  if (rv != SECSuccess) {
   1.478 +    PR_SetError(error, 0);
   1.479 +  }
   1.480 +  return rv;
   1.481 +}
   1.482 +
   1.483 +SECStatus
   1.484 +NSSCertDBTrustDomain::IsChainValid(const CERTCertList* certChain) {
   1.485 +  SECStatus rv = SECFailure;
   1.486 +
   1.487 +  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
   1.488 +      ("NSSCertDBTrustDomain: Top of IsChainValid mCheckCallback=%p",
   1.489 +       mCheckChainCallback));
   1.490 +
   1.491 +  if (!mCheckChainCallback) {
   1.492 +    return SECSuccess;
   1.493 +  }
   1.494 +  if (!mCheckChainCallback->isChainValid) {
   1.495 +    PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
   1.496 +    return SECFailure;
   1.497 +  }
   1.498 +  PRBool chainOK;
   1.499 +  rv = (mCheckChainCallback->isChainValid)(mCheckChainCallback->isChainValidArg,
   1.500 +                                           certChain, &chainOK);
   1.501 +  if (rv != SECSuccess) {
   1.502 +    return rv;
   1.503 +  }
   1.504 +  // rv = SECSuccess only implies successful call, now is time
   1.505 +  // to check the chain check status
   1.506 +  // we should only return success if the chain is valid
   1.507 +  if (chainOK) {
   1.508 +    return SECSuccess;
   1.509 +  }
   1.510 +  PR_SetError(SEC_ERROR_APPLICATION_CALLBACK_ERROR, 0);
   1.511 +  return SECFailure;
   1.512 +}
   1.513 +
   1.514 +namespace {
   1.515 +
   1.516 +static char*
   1.517 +nss_addEscape(const char* string, char quote)
   1.518 +{
   1.519 +  char* newString = 0;
   1.520 +  int escapes = 0, size = 0;
   1.521 +  const char* src;
   1.522 +  char* dest;
   1.523 +
   1.524 +  for (src = string; *src; src++) {
   1.525 +  if ((*src == quote) || (*src == '\\')) {
   1.526 +    escapes++;
   1.527 +  }
   1.528 +  size++;
   1.529 +  }
   1.530 +
   1.531 +  newString = (char*) PORT_ZAlloc(escapes + size + 1);
   1.532 +  if (!newString) {
   1.533 +    return nullptr;
   1.534 +  }
   1.535 +
   1.536 +  for (src = string, dest = newString; *src; src++, dest++) {
   1.537 +    if ((*src == quote) || (*src == '\\')) {
   1.538 +      *dest++ = '\\';
   1.539 +    }
   1.540 +    *dest = *src;
   1.541 +  }
   1.542 +
   1.543 +  return newString;
   1.544 +}
   1.545 +
   1.546 +} // unnamed namespace
   1.547 +
   1.548 +SECStatus
   1.549 +InitializeNSS(const char* dir, bool readOnly)
   1.550 +{
   1.551 +  // The NSS_INIT_NOROOTINIT flag turns off the loading of the root certs
   1.552 +  // module by NSS_Initialize because we will load it in InstallLoadableRoots
   1.553 +  // later.  It also allows us to work around a bug in the system NSS in
   1.554 +  // Ubuntu 8.04, which loads any nonexistent "<configdir>/libnssckbi.so" as
   1.555 +  // "/usr/lib/nss/libnssckbi.so".
   1.556 +  uint32_t flags = NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE;
   1.557 +  if (readOnly) {
   1.558 +    flags |= NSS_INIT_READONLY;
   1.559 +  }
   1.560 +  return ::NSS_Initialize(dir, "", "", SECMOD_DB, flags);
   1.561 +}
   1.562 +
   1.563 +void
   1.564 +DisableMD5()
   1.565 +{
   1.566 +  NSS_SetAlgorithmPolicy(SEC_OID_MD5,
   1.567 +    0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE);
   1.568 +  NSS_SetAlgorithmPolicy(SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION,
   1.569 +    0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE);
   1.570 +  NSS_SetAlgorithmPolicy(SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC,
   1.571 +    0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE);
   1.572 +}
   1.573 +
   1.574 +SECStatus
   1.575 +LoadLoadableRoots(/*optional*/ const char* dir, const char* modNameUTF8)
   1.576 +{
   1.577 +  PR_ASSERT(modNameUTF8);
   1.578 +
   1.579 +  if (!modNameUTF8) {
   1.580 +    PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
   1.581 +    return SECFailure;
   1.582 +  }
   1.583 +
   1.584 +  ScopedPtr<char, PR_FreeLibraryName> fullLibraryPath(
   1.585 +    PR_GetLibraryName(dir, "nssckbi"));
   1.586 +  if (!fullLibraryPath) {
   1.587 +    return SECFailure;
   1.588 +  }
   1.589 +
   1.590 +  ScopedPtr<char, PORT_Free_string> escaped_fullLibraryPath(
   1.591 +    nss_addEscape(fullLibraryPath.get(), '\"'));
   1.592 +  if (!escaped_fullLibraryPath) {
   1.593 +    return SECFailure;
   1.594 +  }
   1.595 +
   1.596 +  // If a module exists with the same name, delete it.
   1.597 +  int modType;
   1.598 +  SECMOD_DeleteModule(modNameUTF8, &modType);
   1.599 +
   1.600 +  ScopedPtr<char, PR_smprintf_free> pkcs11ModuleSpec(
   1.601 +    PR_smprintf("name=\"%s\" library=\"%s\"", modNameUTF8,
   1.602 +                escaped_fullLibraryPath.get()));
   1.603 +  if (!pkcs11ModuleSpec) {
   1.604 +    return SECFailure;
   1.605 +  }
   1.606 +
   1.607 +  ScopedSECMODModule rootsModule(SECMOD_LoadUserModule(pkcs11ModuleSpec.get(),
   1.608 +                                                       nullptr, false));
   1.609 +  if (!rootsModule) {
   1.610 +    return SECFailure;
   1.611 +  }
   1.612 +
   1.613 +  if (!rootsModule->loaded) {
   1.614 +    PR_SetError(PR_INVALID_STATE_ERROR, 0);
   1.615 +    return SECFailure;
   1.616 +  }
   1.617 +
   1.618 +  return SECSuccess;
   1.619 +}
   1.620 +
   1.621 +void
   1.622 +UnloadLoadableRoots(const char* modNameUTF8)
   1.623 +{
   1.624 +  PR_ASSERT(modNameUTF8);
   1.625 +  ScopedSECMODModule rootsModule(SECMOD_FindModule(modNameUTF8));
   1.626 +
   1.627 +  if (rootsModule) {
   1.628 +    SECMOD_UnloadUserModule(rootsModule.get());
   1.629 +  }
   1.630 +}
   1.631 +
   1.632 +void
   1.633 +SetClassicOCSPBehavior(CertVerifier::ocsp_download_config enabled,
   1.634 +                       CertVerifier::ocsp_strict_config strict,
   1.635 +                       CertVerifier::ocsp_get_config get)
   1.636 +{
   1.637 +  CERT_DisableOCSPDefaultResponder(CERT_GetDefaultCertDB());
   1.638 +  if (enabled == CertVerifier::ocsp_off) {
   1.639 +    CERT_DisableOCSPChecking(CERT_GetDefaultCertDB());
   1.640 +  } else {
   1.641 +    CERT_EnableOCSPChecking(CERT_GetDefaultCertDB());
   1.642 +  }
   1.643 +
   1.644 +  SEC_OcspFailureMode failureMode = strict == CertVerifier::ocsp_strict
   1.645 +                                  ? ocspMode_FailureIsVerificationFailure
   1.646 +                                  : ocspMode_FailureIsNotAVerificationFailure;
   1.647 +  (void) CERT_SetOCSPFailureMode(failureMode);
   1.648 +
   1.649 +  CERT_ForcePostMethodForOCSP(get != CertVerifier::ocsp_get_enabled);
   1.650 +
   1.651 +  int OCSPTimeoutSeconds = 3;
   1.652 +  if (strict == CertVerifier::ocsp_strict) {
   1.653 +    OCSPTimeoutSeconds = 10;
   1.654 +  }
   1.655 +  CERT_SetOCSPTimeout(OCSPTimeoutSeconds);
   1.656 +}
   1.657 +
   1.658 +char*
   1.659 +DefaultServerNicknameForCert(CERTCertificate* cert)
   1.660 +{
   1.661 +  char* nickname = nullptr;
   1.662 +  int count;
   1.663 +  bool conflict;
   1.664 +  char* servername = nullptr;
   1.665 +
   1.666 +  servername = CERT_GetCommonName(&cert->subject);
   1.667 +  if (!servername) {
   1.668 +    // Certs without common names are strange, but they do exist...
   1.669 +    // Let's try to use another string for the nickname
   1.670 +    servername = CERT_GetOrgUnitName(&cert->subject);
   1.671 +    if (!servername) {
   1.672 +      servername = CERT_GetOrgName(&cert->subject);
   1.673 +      if (!servername) {
   1.674 +        servername = CERT_GetLocalityName(&cert->subject);
   1.675 +        if (!servername) {
   1.676 +          servername = CERT_GetStateName(&cert->subject);
   1.677 +          if (!servername) {
   1.678 +            servername = CERT_GetCountryName(&cert->subject);
   1.679 +            if (!servername) {
   1.680 +              // We tried hard, there is nothing more we can do.
   1.681 +              // A cert without any names doesn't really make sense.
   1.682 +              return nullptr;
   1.683 +            }
   1.684 +          }
   1.685 +        }
   1.686 +      }
   1.687 +    }
   1.688 +  }
   1.689 +
   1.690 +  count = 1;
   1.691 +  while (1) {
   1.692 +    if (count == 1) {
   1.693 +      nickname = PR_smprintf("%s", servername);
   1.694 +    }
   1.695 +    else {
   1.696 +      nickname = PR_smprintf("%s #%d", servername, count);
   1.697 +    }
   1.698 +    if (!nickname) {
   1.699 +      break;
   1.700 +    }
   1.701 +
   1.702 +    conflict = SEC_CertNicknameConflict(nickname, &cert->derSubject,
   1.703 +                                        cert->dbhandle);
   1.704 +    if (!conflict) {
   1.705 +      break;
   1.706 +    }
   1.707 +    PR_Free(nickname);
   1.708 +    count++;
   1.709 +  }
   1.710 +  PR_FREEIF(servername);
   1.711 +  return nickname;
   1.712 +}
   1.713 +
   1.714 +void
   1.715 +SaveIntermediateCerts(const ScopedCERTCertList& certList)
   1.716 +{
   1.717 +  if (!certList) {
   1.718 +    return;
   1.719 +  }
   1.720 +
   1.721 +  bool isEndEntity = true;
   1.722 +  for (CERTCertListNode* node = CERT_LIST_HEAD(certList);
   1.723 +        !CERT_LIST_END(node, certList);
   1.724 +        node = CERT_LIST_NEXT(node)) {
   1.725 +    if (isEndEntity) {
   1.726 +      // Skip the end-entity; we only want to store intermediates
   1.727 +      isEndEntity = false;
   1.728 +      continue;
   1.729 +    }
   1.730 +
   1.731 +    if (node->cert->slot) {
   1.732 +      // This cert was found on a token, no need to remember it in the temp db.
   1.733 +      continue;
   1.734 +    }
   1.735 +
   1.736 +    if (node->cert->isperm) {
   1.737 +      // We don't need to remember certs already stored in perm db.
   1.738 +      continue;
   1.739 +    }
   1.740 +
   1.741 +    // We have found a signer cert that we want to remember.
   1.742 +    char* nickname = DefaultServerNicknameForCert(node->cert);
   1.743 +    if (nickname && *nickname) {
   1.744 +      ScopedPtr<PK11SlotInfo, PK11_FreeSlot> slot(PK11_GetInternalKeySlot());
   1.745 +      if (slot) {
   1.746 +        PK11_ImportCert(slot.get(), node->cert, CK_INVALID_HANDLE,
   1.747 +                        nickname, false);
   1.748 +      }
   1.749 +    }
   1.750 +    PR_FREEIF(nickname);
   1.751 +  }
   1.752 +}
   1.753 +
   1.754 +} } // namespace mozilla::psm

mercurial