gecko-dev/security/certverifier/CertVerifier.cpp
Brian Smith 2d9e74e8ee Bug 975229: Remove NSS-based certificate verification, r=keeler
--HG--
extra : rebase_source : 49cb20f1b51e2d9993a35decd820764e20ad9be9
2014-06-16 23:13:29 -07:00

470 lines
17 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "CertVerifier.h"
#include <stdint.h>
#include "ExtendedValidation.h"
#include "NSSCertDBTrustDomain.h"
#include "NSSErrorsService.h"
#include "PublicKeyPinningService.h"
#include "cert.h"
#include "pk11pub.h"
#include "pkix/pkix.h"
#include "prerror.h"
#include "secerr.h"
#include "sslerr.h"
// ScopedXXX in this file are mozilla::pkix::ScopedXXX, not
// mozilla::ScopedXXX.
using namespace mozilla::pkix;
using namespace mozilla::psm;
#ifdef PR_LOGGING
PRLogModuleInfo* gCertVerifierLog = nullptr;
#endif
namespace mozilla { namespace psm {
const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1;
const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2;
CertVerifier::CertVerifier(ocsp_download_config odc,
ocsp_strict_config osc,
ocsp_get_config ogc,
pinning_enforcement_config pel)
: mOCSPDownloadEnabled(odc == ocsp_on)
, mOCSPStrict(osc == ocsp_strict)
, mOCSPGETEnabled(ogc == ocsp_get_enabled)
, mPinningEnforcementLevel(pel)
{
}
CertVerifier::~CertVerifier()
{
}
void
InitCertVerifierLog()
{
#ifdef PR_LOGGING
if (!gCertVerifierLog) {
gCertVerifierLog = PR_NewLogModule("certverifier");
}
#endif
}
SECStatus
IsCertBuiltInRoot(CERTCertificate* cert, bool& result) {
result = false;
ScopedPtr<PK11SlotList, PK11_FreeSlotList> slots;
slots = PK11_GetAllSlotsForCert(cert, nullptr);
if (!slots) {
if (PORT_GetError() == SEC_ERROR_NO_TOKEN) {
// no list
return SECSuccess;
}
return SECFailure;
}
for (PK11SlotListElement* le = slots->head; le; le = le->next) {
char* token = PK11_GetTokenName(le->slot);
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("BuiltInRoot? subject=%s token=%s",cert->subjectName, token));
if (strcmp("Builtin Object Token", token) == 0) {
result = true;
return SECSuccess;
}
}
return SECSuccess;
}
struct ChainValidationCallbackState
{
const char* hostname;
const CertVerifier::pinning_enforcement_config pinningEnforcementLevel;
const SECCertificateUsage usage;
const PRTime time;
};
SECStatus chainValidationCallback(void* state, const CERTCertList* certList,
PRBool* chainOK)
{
ChainValidationCallbackState* callbackState =
reinterpret_cast<ChainValidationCallbackState*>(state);
*chainOK = PR_FALSE;
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("verifycert: Inside the Callback \n"));
// On sanity failure we fail closed.
if (!certList) {
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("verifycert: Short circuit, callback, sanity check failed \n"));
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
if (!callbackState) {
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("verifycert: Short circuit, callback, no state! \n"));
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
if (callbackState->usage != certificateUsageSSLServer ||
callbackState->pinningEnforcementLevel == CertVerifier::pinningDisabled) {
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("verifycert: Callback shortcut pel=%d \n",
callbackState->pinningEnforcementLevel));
*chainOK = PR_TRUE;
return SECSuccess;
}
for (CERTCertListNode* node = CERT_LIST_HEAD(certList);
!CERT_LIST_END(node, certList);
node = CERT_LIST_NEXT(node)) {
CERTCertificate* currentCert = node->cert;
if (CERT_LIST_END(CERT_LIST_NEXT(node), certList)) {
bool isBuiltInRoot = false;
SECStatus srv = IsCertBuiltInRoot(currentCert, isBuiltInRoot);
if (srv != SECSuccess) {
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("Is BuiltInRoot failure"));
return srv;
}
// If desired, the user can enable "allow user CA MITM mode", in which
// case key pinning is not enforced for certificates that chain to trust
// anchors that are not in Mozilla's root program
if (!isBuiltInRoot &&
(callbackState->pinningEnforcementLevel ==
CertVerifier::pinningAllowUserCAMITM)) {
*chainOK = PR_TRUE;
return SECSuccess;
}
}
}
bool enforceTestMode = (callbackState->pinningEnforcementLevel ==
CertVerifier::pinningEnforceTestMode);
*chainOK = PublicKeyPinningService::
ChainHasValidPins(certList, callbackState->hostname, callbackState->time,
enforceTestMode);
return SECSuccess;
}
static SECStatus
BuildCertChainForOneKeyUsage(TrustDomain& trustDomain, CERTCertificate* cert,
PRTime time, KeyUsage ku1, KeyUsage ku2,
KeyUsage ku3, KeyPurposeId eku,
const CertPolicyId& requiredPolicy,
const SECItem* stapledOCSPResponse,
ScopedCERTCertList& builtChain)
{
SECStatus rv = BuildCertChain(trustDomain, cert, time,
EndEntityOrCA::MustBeEndEntity, ku1,
eku, requiredPolicy,
stapledOCSPResponse, builtChain);
if (rv != SECSuccess && PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
rv = BuildCertChain(trustDomain, cert, time,
EndEntityOrCA::MustBeEndEntity, ku2,
eku, requiredPolicy,
stapledOCSPResponse, builtChain);
if (rv != SECSuccess && PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
rv = BuildCertChain(trustDomain, cert, time,
EndEntityOrCA::MustBeEndEntity, ku3,
eku, requiredPolicy,
stapledOCSPResponse, builtChain);
if (rv != SECSuccess) {
PR_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE, 0);
}
}
}
return rv;
}
SECStatus
CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
PRTime time, void* pinArg, const char* hostname,
const Flags flags,
/*optional*/ const SECItem* stapledOCSPResponse,
/*optional out*/ mozilla::pkix::ScopedCERTCertList* validationChain,
/*optional out*/ SECOidTag* evOidPolicy)
{
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("Top of VerifyCert\n"));
PR_ASSERT(cert);
PR_ASSERT(usage == certificateUsageSSLServer || !(flags & FLAG_MUST_BE_EV));
if (validationChain) {
*validationChain = nullptr;
}
if (evOidPolicy) {
*evOidPolicy = SEC_OID_UNKNOWN;
}
if (!cert ||
(usage != certificateUsageSSLServer && (flags & FLAG_MUST_BE_EV))) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return SECFailure;
}
ChainValidationCallbackState callbackState = {
hostname, mPinningEnforcementLevel, usage, time
};
CERTChainVerifyCallback callbackContainer;
callbackContainer.isChainValid = chainValidationCallback;
callbackContainer.isChainValidArg = &callbackState;
NSSCertDBTrustDomain::OCSPFetching ocspFetching
= !mOCSPDownloadEnabled ||
(flags & FLAG_LOCAL_ONLY) ? NSSCertDBTrustDomain::NeverFetchOCSP
: !mOCSPStrict ? NSSCertDBTrustDomain::FetchOCSPForDVSoftFail
: NSSCertDBTrustDomain::FetchOCSPForDVHardFail;
ocsp_get_config ocspGETConfig = mOCSPGETEnabled ? ocsp_get_enabled
: ocsp_get_disabled;
SECStatus rv;
mozilla::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, ocspFetching, mOCSPCache,
pinArg, ocspGETConfig);
rv = BuildCertChain(trustDomain, cert, time,
EndEntityOrCA::MustBeEndEntity,
KeyUsage::digitalSignature,
KeyPurposeId::id_kp_clientAuth,
CertPolicyId::anyPolicy, stapledOCSPResponse,
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.
#ifndef MOZ_NO_EV_CERTS
// Try to validate for EV first.
CertPolicyId evPolicy;
SECOidTag evPolicyOidTag;
rv = GetFirstEVPolicy(cert, evPolicy, evPolicyOidTag);
if (rv == SECSuccess) {
NSSCertDBTrustDomain
trustDomain(trustSSL,
ocspFetching == NSSCertDBTrustDomain::NeverFetchOCSP
? NSSCertDBTrustDomain::LocalOnlyOCSPForEV
: NSSCertDBTrustDomain::FetchOCSPForEV,
mOCSPCache, pinArg, ocspGETConfig, &callbackContainer);
rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
KeyUsage::digitalSignature,// (EC)DHE
KeyUsage::keyEncipherment, // RSA
KeyUsage::keyAgreement, // (EC)DH
KeyPurposeId::id_kp_serverAuth,
evPolicy, stapledOCSPResponse,
builtChain);
if (rv == SECSuccess) {
if (evOidPolicy) {
*evOidPolicy = evPolicyOidTag;
}
break;
}
builtChain = nullptr; // clear built chain, just in case.
}
#endif
if (flags & FLAG_MUST_BE_EV) {
PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0);
rv = SECFailure;
break;
}
// Now try non-EV.
NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
pinArg, ocspGETConfig,
&callbackContainer);
rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
KeyUsage::digitalSignature, // (EC)DHE
KeyUsage::keyEncipherment, // RSA
KeyUsage::keyAgreement, // (EC)DH
KeyPurposeId::id_kp_serverAuth,
CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
break;
}
case certificateUsageSSLCA: {
NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
pinArg, ocspGETConfig);
rv = BuildCertChain(trustDomain, cert, time, EndEntityOrCA::MustBeCA,
KeyUsage::keyCertSign,
KeyPurposeId::id_kp_serverAuth,
CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
break;
}
case certificateUsageEmailSigner: {
NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
pinArg, ocspGETConfig);
rv = BuildCertChain(trustDomain, cert, time,
EndEntityOrCA::MustBeEndEntity,
KeyUsage::digitalSignature,
KeyPurposeId::id_kp_emailProtection,
CertPolicyId::anyPolicy,
stapledOCSPResponse, 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, ocspFetching, mOCSPCache,
pinArg, ocspGETConfig);
rv = BuildCertChain(trustDomain, cert, time,
EndEntityOrCA::MustBeEndEntity,
KeyUsage::keyEncipherment, // RSA
KeyPurposeId::id_kp_emailProtection,
CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
if (rv != SECSuccess && PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
rv = BuildCertChain(trustDomain, cert, time,
EndEntityOrCA::MustBeEndEntity,
KeyUsage::keyAgreement, // ECDH/DH
KeyPurposeId::id_kp_emailProtection,
CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
}
break;
}
case certificateUsageObjectSigner: {
NSSCertDBTrustDomain trustDomain(trustObjectSigning, ocspFetching,
mOCSPCache, pinArg, ocspGETConfig);
rv = BuildCertChain(trustDomain, cert, time,
EndEntityOrCA::MustBeEndEntity,
KeyUsage::digitalSignature,
KeyPurposeId::id_kp_codeSigning,
CertPolicyId::anyPolicy,
stapledOCSPResponse, 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.
mozilla::pkix::EndEntityOrCA endEntityOrCA;
mozilla::pkix::KeyUsage keyUsage;
KeyPurposeId eku;
if (usage == certificateUsageVerifyCA) {
endEntityOrCA = EndEntityOrCA::MustBeCA;
keyUsage = KeyUsage::keyCertSign;
eku = KeyPurposeId::anyExtendedKeyUsage;
} else {
endEntityOrCA = EndEntityOrCA::MustBeEndEntity;
keyUsage = KeyUsage::digitalSignature;
eku = KeyPurposeId::id_kp_OCSPSigning;
}
NSSCertDBTrustDomain sslTrust(trustSSL, ocspFetching, mOCSPCache,
pinArg, ocspGETConfig);
rv = BuildCertChain(sslTrust, cert, time, endEntityOrCA,
keyUsage, eku, CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
if (rv == SECFailure && PR_GetError() == SEC_ERROR_UNKNOWN_ISSUER) {
NSSCertDBTrustDomain emailTrust(trustEmail, ocspFetching, mOCSPCache,
pinArg, ocspGETConfig);
rv = BuildCertChain(emailTrust, cert, time, endEntityOrCA, keyUsage,
eku, CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
if (rv == SECFailure && SEC_ERROR_UNKNOWN_ISSUER) {
NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
ocspFetching, mOCSPCache,
pinArg, ocspGETConfig);
rv = BuildCertChain(objectSigningTrust, cert, time, endEntityOrCA,
keyUsage, eku, CertPolicyId::anyPolicy,
stapledOCSPResponse, builtChain);
}
}
break;
}
default:
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return SECFailure;
}
if (validationChain && rv == SECSuccess) {
*validationChain = builtChain.release();
}
return rv;
}
SECStatus
CertVerifier::VerifySSLServerCert(CERTCertificate* peerCert,
/*optional*/ const SECItem* stapledOCSPResponse,
PRTime time,
/*optional*/ void* pinarg,
const char* hostname,
bool saveIntermediatesInPermanentDatabase,
/*optional out*/ mozilla::pkix::ScopedCERTCertList* certChainOut,
/*optional out*/ SECOidTag* evOidPolicy)
{
PR_ASSERT(peerCert);
// XXX: PR_ASSERT(pinarg)
PR_ASSERT(hostname);
PR_ASSERT(hostname[0]);
if (certChainOut) {
*certChainOut = nullptr;
}
if (evOidPolicy) {
*evOidPolicy = SEC_OID_UNKNOWN;
}
if (!hostname || !hostname[0]) {
PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0);
return SECFailure;
}
// CreateCertErrorRunnable assumes that CERT_VerifyCertName is only called
// if VerifyCert succeeded.
ScopedCERTCertList validationChain;
SECStatus rv = VerifyCert(peerCert, certificateUsageSSLServer, time, pinarg,
hostname, 0, stapledOCSPResponse, &validationChain,
evOidPolicy);
if (rv != SECSuccess) {
return rv;
}
rv = CERT_VerifyCertName(peerCert, hostname);
if (rv != SECSuccess) {
return rv;
}
if (saveIntermediatesInPermanentDatabase) {
SaveIntermediateCerts(validationChain);
}
if (certChainOut) {
*certChainOut = validationChain.release();
}
return SECSuccess;
}
} } // namespace mozilla::psm