Bug 878932, Part 1: add insanity::pkix as an option for certificate verification, r=keeler, r=cviecco

--HG--
extra : rebase_source : c1f75dff6ac7f32e082517af701654abebaee250
This commit is contained in:
Brian Smith 2014-02-10 11:41:12 -08:00
parent 2658e5f7ad
commit ff6bc14650
9 changed files with 374 additions and 35 deletions

View File

@ -8,7 +8,7 @@
#include <stdint.h>
#include "insanity/pkixtypes.h"
#include "insanity/pkix.h"
#include "ExtendedValidation.h"
#include "NSSCertDBTrustDomain.h"
#include "cert.h"
@ -32,14 +32,18 @@ const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1;
const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2;
CertVerifier::CertVerifier(implementation_config ic,
#ifndef NSS_NO_LIBPKIX
missing_cert_download_config mcdc,
crl_download_config cdc,
#endif
ocsp_download_config odc,
ocsp_strict_config osc,
ocsp_get_config ogc)
: mImplementation(ic)
#ifndef NSS_NO_LIBPKIX
, mMissingCertDownloadEnabled(mcdc == missing_cert_download_on)
, mCRLDownloadEnabled(cdc == crl_download_allowed)
#endif
, mOCSPDownloadEnabled(odc == ocsp_on)
, mOCSPStrict(osc == ocsp_strict)
, mOCSPGETEnabled(ogc == ocsp_get_enabled)
@ -129,9 +133,6 @@ ClassicVerifyCert(CERTCertificate* cert,
case certificateUsageSSLServer:
enumUsage = certUsageSSLServer;
break;
case certificateUsageSSLServerWithStepUp:
enumUsage = certUsageSSLServerWithStepUp;
break;
case certificateUsageSSLCA:
enumUsage = certUsageSSLCA;
break;
@ -144,22 +145,12 @@ ClassicVerifyCert(CERTCertificate* cert,
case certificateUsageObjectSigner:
enumUsage = certUsageObjectSigner;
break;
case certificateUsageUserCertImport:
enumUsage = certUsageUserCertImport;
break;
case certificateUsageVerifyCA:
enumUsage = certUsageVerifyCA;
break;
case certificateUsageProtectedObjectSigner:
enumUsage = certUsageProtectedObjectSigner;
break;
case certificateUsageStatusResponder:
enumUsage = certUsageStatusResponder;
break;
case certificateUsageAnyCA:
enumUsage = certUsageAnyCA;
break;
default:
default:
PR_NOT_REACHED("unexpected usage");
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
}
@ -199,6 +190,181 @@ destroyCertListThatShouldNotExist(CERTCertList** certChain)
}
#endif
static SECStatus
BuildCertChainForOneKeyUsage(TrustDomain& trustDomain, CERTCertificate* cert,
PRTime time, KeyUsages ku1, KeyUsages ku2,
KeyUsages ku3, SECOidTag eku,
ScopedCERTCertList& builtChain)
{
PR_ASSERT(ku1);
PR_ASSERT(ku2);
SECStatus rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
ku1, eku, builtChain);
if (rv != SECSuccess && ku2 &&
PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
ku2, eku, builtChain);
if (rv != SECSuccess && ku3 &&
PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
ku3, eku, builtChain);
if (rv != SECSuccess) {
PR_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE, 0);
}
}
}
return rv;
}
SECStatus
CertVerifier::InsanityVerifyCert(
CERTCertificate* cert,
/*optional*/ const SECItem* /*TODO: stapledOCSPResponse*/,
const SECCertificateUsage usage,
const PRTime time,
void* pinArg,
const Flags flags,
/*optional out*/ insanity::pkix::ScopedCERTCertList* validationChain)
{
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("Top of InsanityVerifyCert\n"));
SECStatus rv;
// TODO(bug 970750): anyExtendedKeyUsage
// TODO: encipherOnly/decipherOnly
// S/MIME Key Usage: http://tools.ietf.org/html/rfc3850#section-4.4.2
// S/MIME EKU: http://tools.ietf.org/html/rfc3850#section-4.4.4
// TODO(bug 915931): Pass in stapled OCSP response in all calls to
// BuildCertChain.
insanity::pkix::ScopedCERTCertList builtChain;
switch (usage) {
case certificateUsageSSLClient: {
// XXX: We don't really have a trust bit for SSL client authentication so
// just use trustEmail as it is the closest alternative.
NSSCertDBTrustDomain trustDomain(trustEmail, mOCSPDownloadEnabled,
mOCSPStrict, pinArg);
rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
KU_DIGITAL_SIGNATURE,
SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH,
builtChain);
break;
}
case certificateUsageSSLServer: {
// TODO: When verifying a certificate in an SSL handshake, we should
// restrict the acceptable key usage based on the key exchange method
// chosen by the server.
NSSCertDBTrustDomain trustDomain(trustSSL, mOCSPDownloadEnabled,
mOCSPStrict, pinArg);
rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
KU_DIGITAL_SIGNATURE, // ECDHE/DHE
KU_KEY_ENCIPHERMENT, // RSA
KU_KEY_AGREEMENT, // ECDH/DH
SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
builtChain);
break;
}
case certificateUsageSSLCA: {
NSSCertDBTrustDomain trustDomain(trustSSL, mOCSPDownloadEnabled,
mOCSPStrict, pinArg);
rv = BuildCertChain(trustDomain, cert, time, MustBeCA,
KU_KEY_CERT_SIGN,
SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
builtChain);
break;
}
case certificateUsageEmailSigner: {
NSSCertDBTrustDomain trustDomain(trustEmail, mOCSPDownloadEnabled,
mOCSPStrict, pinArg);
rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
KU_DIGITAL_SIGNATURE,
SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
builtChain);
break;
}
case certificateUsageEmailRecipient: {
// TODO: The higher level S/MIME processing should pass in which key
// usage it is trying to verify for, and base its algorithm choices
// based on the result of the verification(s).
NSSCertDBTrustDomain trustDomain(trustEmail, mOCSPDownloadEnabled,
mOCSPStrict, pinArg);
rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
KU_KEY_ENCIPHERMENT, // RSA
KU_KEY_AGREEMENT, // ECDH/DH
0,
SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
builtChain);
break;
}
case certificateUsageObjectSigner: {
NSSCertDBTrustDomain trustDomain(trustObjectSigning,
mOCSPDownloadEnabled, mOCSPStrict,
pinArg);
rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
KU_DIGITAL_SIGNATURE,
SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
builtChain);
break;
}
case certificateUsageVerifyCA:
case certificateUsageStatusResponder: {
// XXX This is a pretty useless way to verify a certificate. It is used
// by the implementation of window.crypto.importCertificates and in the
// certificate viewer UI. Because we don't know what trust bit is
// interesting, we just try them all.
insanity::pkix::EndEntityOrCA endEntityOrCA;
insanity::pkix::KeyUsages keyUsage;
SECOidTag eku;
if (usage == certificateUsageVerifyCA) {
endEntityOrCA = MustBeCA;
keyUsage = KU_KEY_CERT_SIGN;
eku = SEC_OID_UNKNOWN;
} else {
endEntityOrCA = MustBeEndEntity;
keyUsage = KU_DIGITAL_SIGNATURE;
eku = SEC_OID_OCSP_RESPONDER;
}
NSSCertDBTrustDomain sslTrust(trustSSL,
mOCSPDownloadEnabled, mOCSPStrict, pinArg);
rv = BuildCertChain(sslTrust, cert, time, endEntityOrCA,
keyUsage, eku, builtChain);
if (rv == SECFailure && PR_GetError() == SEC_ERROR_UNKNOWN_ISSUER) {
NSSCertDBTrustDomain emailTrust(trustEmail, mOCSPDownloadEnabled,
mOCSPStrict, pinArg);
rv = BuildCertChain(emailTrust, cert, time, endEntityOrCA, keyUsage,
eku, builtChain);
if (rv == SECFailure && SEC_ERROR_UNKNOWN_ISSUER) {
NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
mOCSPDownloadEnabled,
mOCSPStrict, pinArg);
rv = BuildCertChain(objectSigningTrust, cert, time, endEntityOrCA,
keyUsage, eku, builtChain);
}
}
break;
}
default:
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return SECFailure;
}
if (validationChain && rv == SECSuccess) {
*validationChain = builtChain.release();
}
return rv;
}
SECStatus
CertVerifier::VerifyCert(CERTCertificate* cert,
/*optional*/ const SECItem* stapledOCSPResponse,
@ -433,6 +599,11 @@ CertVerifier::VerifyCert(CERTCertificate* cert,
return SECFailure;
}
if (mImplementation == insanity) {
return InsanityVerifyCert(cert, stapledOCSPResponse, usage, time,
pinArg, flags, validationChain);
}
if (mImplementation == classic) {
// XXX: we do not care about the localOnly flag (currently) as the
// caller that wants localOnly should disable and reenable the fetching.

View File

@ -7,7 +7,6 @@
#ifndef mozilla_psm__CertVerifier_h
#define mozilla_psm__CertVerifier_h
#include "certt.h"
#include "insanity/pkixtypes.h"
namespace mozilla { namespace psm {
@ -49,6 +48,7 @@ public:
#ifndef NSS_NO_LIBPKIX
libpkix = 1,
#endif
insanity = 2
};
enum missing_cert_download_config { missing_cert_download_off = 0, missing_cert_download_on };
@ -59,18 +59,31 @@ public:
bool IsOCSPDownloadEnabled() const { return mOCSPDownloadEnabled; }
CertVerifier(implementation_config ic, missing_cert_download_config ac,
crl_download_config cdc, ocsp_download_config odc,
ocsp_strict_config osc, ocsp_get_config ogc);
CertVerifier(implementation_config ic,
#ifndef NSS_NO_LIBPKIX
missing_cert_download_config ac, crl_download_config cdc,
#endif
ocsp_download_config odc, ocsp_strict_config osc,
ocsp_get_config ogc);
~CertVerifier();
public:
const implementation_config mImplementation;
#ifndef NSS_NO_LIBPKIX
const bool mMissingCertDownloadEnabled;
const bool mCRLDownloadEnabled;
#endif
const bool mOCSPDownloadEnabled;
const bool mOCSPStrict;
const bool mOCSPGETEnabled;
private:
SECStatus InsanityVerifyCert(CERTCertificate* cert,
/*optional*/ const SECItem* stapledOCSPResponse,
const SECCertificateUsage usage,
const PRTime time,
void* pinArg,
const Flags flags,
/*optional out*/ insanity::pkix::ScopedCERTCertList* validationChain);
};
void InitCertVerifierLog();

View File

@ -8,7 +8,7 @@
#include <stdint.h>
#include "insanity/ScopedPtr.h"
#include "insanity/pkix.h"
#include "certdb.h"
#include "nss.h"
#include "ocsp.h"
@ -21,6 +21,10 @@
using namespace insanity::pkix;
#ifdef PR_LOGGING
extern PRLogModuleInfo* gCertVerifierLog;
#endif
namespace mozilla { namespace psm {
const char BUILTIN_ROOTS_MODULE_DEFAULT_NAME[] = "Builtin Roots Module";
@ -31,6 +35,92 @@ inline void PORT_Free_string(char* str) { PORT_Free(str); }
typedef ScopedPtr<SECMODModule, SECMOD_DestroyModule> ScopedSECMODModule;
} // unnamed namespace
NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType,
bool /*ocspDownloadEnabled*/,
bool /*ocspStrict*/,
void* pinArg)
: mCertDBTrustType(certDBTrustType)
// , mOCSPDownloadEnabled(ocspDownloadEnabled)
// , mOCSPStrict(ocspStrict)
, mPinArg(pinArg)
{
}
SECStatus
NSSCertDBTrustDomain::FindPotentialIssuers(
const SECItem* encodedIssuerName, PRTime time,
/*out*/ insanity::pkix::ScopedCERTCertList& results)
{
// TODO: normalize encodedIssuerName
// TODO: NSS seems to be ambiguous between "no potential issuers found" and
// "there was an error trying to retrieve the potential issuers."
results = CERT_CreateSubjectCertList(nullptr, CERT_GetDefaultCertDB(),
encodedIssuerName, time, true);
if (!results) {
return SECFailure;
}
return SECSuccess;
}
SECStatus
NSSCertDBTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
const CERTCertificate* candidateCert,
/*out*/ TrustLevel* trustLevel)
{
PORT_Assert(candidateCert);
PORT_Assert(trustLevel);
if (!candidateCert || !trustLevel) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
// XXX: CERT_GetCertTrust seems to be abusing SECStatus as a boolean, where
// SECSuccess means that there is a trust record and SECFailure means there
// is not a trust record. I looked at NSS's internal uses of
// CERT_GetCertTrust, and all that code uses the result as a boolean meaning
// "We have a trust record."
CERTCertTrust trust;
if (CERT_GetCertTrust(candidateCert, &trust) == SECSuccess) {
PRUint32 flags = SEC_GET_TRUST_FLAGS(&trust, mCertDBTrustType);
// For DISTRUST, we use the CERTDB_TRUSTED or CERTDB_TRUSTED_CA bit,
// because we can have active distrust for either type of cert. Note that
// CERTDB_TERMINAL_RECORD means "stop trying to inherit trust" so if the
// relevant trust bit isn't set then that means the cert must be considered
// distrusted.
PRUint32 relevantTrustBit = endEntityOrCA == MustBeCA ? CERTDB_TRUSTED_CA
: CERTDB_TRUSTED;
if (((flags & (relevantTrustBit|CERTDB_TERMINAL_RECORD)))
== CERTDB_TERMINAL_RECORD) {
*trustLevel = ActivelyDistrusted;
return SECSuccess;
}
// For TRUST, we only use the CERTDB_TRUSTED_CA bit, because Gecko hasn't
// needed to consider end-entity certs to be their own trust anchors since
// Gecko implemented nsICertOverrideService.
if (flags & CERTDB_TRUSTED_CA) {
*trustLevel = TrustAnchor;
return SECSuccess;
}
}
*trustLevel = InheritsTrust;
return SECSuccess;
}
SECStatus
NSSCertDBTrustDomain::VerifySignedData(const CERTSignedData* signedData,
const CERTCertificate* cert)
{
return ::insanity::pkix::VerifySignedData(signedData, cert, mPinArg);
}
namespace {
static char*
nss_addEscape(const char* string, char quote)
{

View File

@ -43,6 +43,33 @@ char* DefaultServerNicknameForCert(CERTCertificate* cert);
void SaveIntermediateCerts(const insanity::pkix::ScopedCERTCertList& certList);
class NSSCertDBTrustDomain : public insanity::pkix::TrustDomain
{
public:
NSSCertDBTrustDomain(SECTrustType certDBTrustType,
bool ocspDownloadEnabled, bool ocspStrict,
void* pinArg);
virtual SECStatus FindPotentialIssuers(
const SECItem* encodedIssuerName,
PRTime time,
/*out*/ insanity::pkix::ScopedCERTCertList& results);
virtual SECStatus GetCertTrust(insanity::pkix::EndEntityOrCA endEntityOrCA,
const CERTCertificate* candidateCert,
/*out*/ TrustLevel* trustLevel);
virtual SECStatus VerifySignedData(const CERTSignedData* signedData,
const CERTCertificate* cert);
private:
const SECTrustType mCertDBTrustType;
// const bool mOCSPDownloadEnabled;
// const bool mOCSPStrict;
void* mPinArg; // non-owning!
};
} } // namespace mozilla::psm
#endif // mozilla_psm__NSSCertDBTrustDomain_h

View File

@ -744,6 +744,8 @@ AuthCertificate(CertVerifier& certVerifier, TransportSecurityInfo* infoObject,
SECStatus rv;
// TODO: Remove this after we switch to insanity::pkix as the
// only option
if (stapledOCSPResponse) {
CERTCertDBHandle* handle = CERT_GetDefaultCertDB();
rv = CERT_CacheOCSPResponseFromSideChannel(handle, cert, PR_Now(),
@ -921,6 +923,12 @@ SSLServerCertVerificationJob::Run()
failureTelemetry
= Telemetry::SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_CLASSIC;
break;
case CertVerifier::insanity:
successTelemetry
= Telemetry::SSL_SUCCESFUL_CERT_VALIDATION_TIME_INSANITY;
failureTelemetry
= Telemetry::SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_INSANITY;
break;
#ifndef NSS_NO_LIBPKIX
case CertVerifier::libpkix:
successTelemetry

View File

@ -15,10 +15,17 @@ class SharedCertVerifier : public mozilla::psm::CertVerifier,
public mozilla::AtomicRefCounted<SharedCertVerifier>
{
public:
SharedCertVerifier(implementation_config ic, missing_cert_download_config ac,
crl_download_config cdc, ocsp_download_config odc,
ocsp_strict_config osc, ocsp_get_config ogc)
: mozilla::psm::CertVerifier(ic, ac, cdc, odc, osc, ogc)
SharedCertVerifier(implementation_config ic,
#ifndef NSS_NO_LIBPKIX
missing_cert_download_config ac, crl_download_config cdc,
#endif
ocsp_download_config odc, ocsp_strict_config osc,
ocsp_get_config ogc)
: mozilla::psm::CertVerifier(ic,
#ifndef NSS_NO_LIBPKIX
ac, cdc,
#endif
odc, osc, ogc)
{
}
~SharedCertVerifier();

View File

@ -1738,8 +1738,7 @@ nsNSSCertificateDB::VerifyCertNow(nsIX509Cert* aCert,
nullptr, // Assume no context
aFlags,
&resultChain,
&evOidPolicy,
nullptr);
&evOidPolicy);
PRErrorCode error = PR_GetError();

View File

@ -940,9 +940,6 @@ CipherSuiteChangeObserver::Observe(nsISupports* aSubject,
void nsNSSComponent::setValidationOptions(bool isInitialSetting,
const MutexAutoLock& lock)
{
bool crlDownloading = Preferences::GetBool("security.CRL_download.enabled",
false);
// This preference controls whether we do OCSP fetching and does not affect
// OCSP stapling.
// 0 = disabled, 1 = enabled
@ -959,9 +956,13 @@ void nsNSSComponent::setValidationOptions(bool isInitialSetting,
Telemetry::Accumulate(Telemetry::CERT_OCSP_REQUIRED, ocspRequired);
}
bool aiaDownloadEnabled = Preferences::GetBool("security.missing_cert_download.enabled",
false);
#ifndef NSS_NO_LIBPKIX
bool crlDownloading = Preferences::GetBool("security.CRL_download.enabled",
false);
bool aiaDownloadEnabled =
Preferences::GetBool("security.missing_cert_download.enabled", false);
#endif
bool ocspStaplingEnabled = Preferences::GetBool("security.ssl.enable_ocsp_stapling",
true);
PublicSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled);
@ -970,11 +971,16 @@ void nsNSSComponent::setValidationOptions(bool isInitialSetting,
CertVerifier::implementation_config certVerifierImplementation
= CertVerifier::classic;
// The insanity::pkix pref overrides the libpkix pref
if (Preferences::GetBool("security.use_insanity_verification", false)) {
certVerifierImplementation = CertVerifier::insanity;
} else {
#ifndef NSS_NO_LIBPKIX
if (Preferences::GetBool("security.use_libpkix_verification", false)) {
certVerifierImplementation = CertVerifier::libpkix;
}
#endif
}
CertVerifier::ocsp_download_config odc;
CertVerifier::ocsp_strict_config osc;
@ -983,10 +989,12 @@ void nsNSSComponent::setValidationOptions(bool isInitialSetting,
SetClassicOCSPBehaviorFromPrefs(&odc, &osc, &ogc, lock);
mDefaultCertVerifier = new SharedCertVerifier(
certVerifierImplementation,
#ifndef NSS_NO_LIBPKIX
aiaDownloadEnabled ?
CertVerifier::missing_cert_download_on : CertVerifier::missing_cert_download_off,
crlDownloading ?
CertVerifier::crl_download_allowed : CertVerifier::crl_local_only,
#endif
odc, osc, ogc);
}

View File

@ -4242,7 +4242,15 @@
"extended_statistics_ok": true,
"description": "Time spent on a successful cert verification in classic mode (ms)"
},
"SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_LIBPKIX": {
"SSL_SUCCESFUL_CERT_VALIDATION_TIME_INSANITY" : {
"expires_in_version": "never",
"kind": "exponential",
"high": "60000",
"n_buckets": 50,
"extended_statistics_ok": true,
"description": "Time spent on a successful cert verification in insanity mode (ms)"
},
"SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_LIBPKIX" : {
"expires_in_version": "never",
"kind": "exponential",
"high": "60000",
@ -4258,6 +4266,14 @@
"extended_statistics_ok": true,
"description": "Time spent on an initially failed cert verification in classic mode (ms)"
},
"SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_INSANITY" : {
"expires_in_version": "never",
"kind": "exponential",
"high": "60000",
"n_buckets": 50,
"extended_statistics_ok": true,
"description": "Time spent on an initially failed cert verification in insanity mode (ms)"
},
"HEALTHREPORT_DB_OPEN_FIRSTRUN_MS": {
"expires_in_version": "never",
"kind": "exponential",