Bug 1577643 - Make AuthCertificateHook work without PRFileDesc and also make code work with TransportSecurityInfo. r=keeler

This patch makes the certificate authentication work with TransportSecurityInfo, so that it can be used for nsNSSSocketInfo and a quic's version of the security info class.
Also it adds a new AuthCertificateHookWithInfo function that will be called by Http3Session to authenticate certificates.

Differential Revision: https://phabricator.services.mozilla.com/D44064

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Dragana Damjanovic 2019-09-26 10:14:53 +00:00
Родитель 37c23f431d
Коммит 5f2cea9512
4 изменённых файлов: 190 добавлений и 71 удалений

Просмотреть файл

@ -198,26 +198,27 @@ class SSLServerCertVerificationResult : public Runnable {
public:
NS_DECL_NSIRUNNABLE
SSLServerCertVerificationResult(nsNSSSocketInfo* infoObject,
SSLServerCertVerificationResult(TransportSecurityInfo* infoObject,
PRErrorCode errorCode);
void Dispatch();
private:
const RefPtr<nsNSSSocketInfo> mInfoObject;
const RefPtr<TransportSecurityInfo> mInfoObject;
const PRErrorCode mErrorCode;
};
class NotifyCertProblemRunnable : public SyncRunnableBase {
public:
NotifyCertProblemRunnable(uint64_t fdForLogging, nsNSSSocketInfo* infoObject)
NotifyCertProblemRunnable(uint64_t fdForLogging,
TransportSecurityInfo* infoObject)
: mFdForLogging(fdForLogging), mInfoObject(infoObject) {}
virtual void RunOnTargetThread() override;
private:
uint64_t mFdForLogging;
const RefPtr<nsNSSSocketInfo> mInfoObject;
const RefPtr<TransportSecurityInfo> mInfoObject;
};
// A probe value of 1 means "no error".
@ -546,12 +547,13 @@ class SSLServerCertVerificationJob : public Runnable {
// Must be called only on the socket transport thread
static SECStatus Dispatch(const RefPtr<SharedCertVerifier>& certVerifier,
const void* fdForLogging,
nsNSSSocketInfo* infoObject,
TransportSecurityInfo* infoObject,
const UniqueCERTCertificate& serverCert,
const UniqueCERTCertList& peerCertChain,
Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
uint32_t providerFlags, Time time, PRTime prtime);
uint32_t providerFlags, Time time, PRTime prtime,
uint32_t certVerifierFlags);
private:
NS_DECL_NSIRUNNABLE
@ -559,19 +561,20 @@ class SSLServerCertVerificationJob : public Runnable {
// Must be called only on the socket transport thread
SSLServerCertVerificationJob(const RefPtr<SharedCertVerifier>& certVerifier,
const void* fdForLogging,
nsNSSSocketInfo* infoObject,
TransportSecurityInfo* infoObject,
const UniqueCERTCertificate& cert,
UniqueCERTCertList peerCertChain,
Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
uint32_t providerFlags, Time time,
PRTime prtime);
uint32_t providerFlags, Time time, PRTime prtime,
uint32_t certVerifierFlags);
const RefPtr<SharedCertVerifier> mCertVerifier;
const void* const mFdForLogging;
const RefPtr<nsNSSSocketInfo> mInfoObject;
const RefPtr<TransportSecurityInfo> mInfoObject;
const UniqueCERTCertificate mCert;
UniqueCERTCertList mPeerCertChain;
const uint32_t mProviderFlags;
const uint32_t mCertVerifierFlags;
const Time mTime;
const PRTime mPRTime;
Maybe<nsTArray<uint8_t>> mStapledOCSPResponse;
@ -580,11 +583,11 @@ class SSLServerCertVerificationJob : public Runnable {
SSLServerCertVerificationJob::SSLServerCertVerificationJob(
const RefPtr<SharedCertVerifier>& certVerifier, const void* fdForLogging,
nsNSSSocketInfo* infoObject, const UniqueCERTCertificate& cert,
TransportSecurityInfo* infoObject, const UniqueCERTCertificate& cert,
UniqueCERTCertList peerCertChain,
Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension, uint32_t providerFlags,
Time time, PRTime prtime)
Time time, PRTime prtime, uint32_t certVerifierFlags)
: Runnable("psm::SSLServerCertVerificationJob"),
mCertVerifier(certVerifier),
mFdForLogging(fdForLogging),
@ -592,6 +595,7 @@ SSLServerCertVerificationJob::SSLServerCertVerificationJob(
mCert(CERT_DupCertificate(cert.get())),
mPeerCertChain(std::move(peerCertChain)),
mProviderFlags(providerFlags),
mCertVerifierFlags(certVerifierFlags),
mTime(time),
mPRTime(prtime),
mStapledOCSPResponse(std::move(stapledOCSPResponse)),
@ -1137,7 +1141,7 @@ static void CollectCertTelemetry(
}
static void AuthCertificateSetResults(
nsNSSSocketInfo* aInfoObject, const UniqueCERTCertificate& aCert,
TransportSecurityInfo* aInfoObject, const UniqueCERTCertificate& aCert,
UniqueCERTCertList& aBuiltCertChain, UniqueCERTCertList& aPeerCertChain,
const CertificateTransparencyInfo& aCertificateTransparencyInfo,
SECOidTag aEvOidPolicy, bool aSucceeded) {
@ -1172,12 +1176,14 @@ static void AuthCertificateSetResults(
}
// Note: Takes ownership of |peerCertChain| if SECSuccess is not returned.
Result AuthCertificate(CertVerifier& certVerifier, nsNSSSocketInfo* infoObject,
Result AuthCertificate(CertVerifier& certVerifier,
TransportSecurityInfo* infoObject,
const UniqueCERTCertificate& cert,
UniqueCERTCertList& peerCertChain,
const Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
const Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
uint32_t providerFlags, Time time) {
uint32_t providerFlags, Time time,
uint32_t certVerifierFlags) {
MOZ_ASSERT(infoObject);
MOZ_ASSERT(cert);
@ -1195,18 +1201,12 @@ Result AuthCertificate(CertVerifier& certVerifier, nsNSSSocketInfo* infoObject,
PinningTelemetryInfo pinningTelemetryInfo;
CertificateTransparencyInfo certificateTransparencyInfo;
int flags = 0;
if (!infoObject->SharedState().IsOCSPStaplingEnabled() ||
!infoObject->SharedState().IsOCSPMustStapleEnabled()) {
flags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
}
Result rv = certVerifier.VerifySSLServerCert(
cert, stapledOCSPResponse, sctsFromTLSExtension, time, infoObject,
infoObject->GetHostName(), builtCertChain, saveIntermediates, flags,
infoObject->GetOriginAttributes(), &evOidPolicy, &ocspStaplingStatus,
&keySizeStatus, &sha1ModeResult, &pinningTelemetryInfo,
&certificateTransparencyInfo);
infoObject->GetHostName(), builtCertChain, saveIntermediates,
certVerifierFlags, infoObject->GetOriginAttributes(), &evOidPolicy,
&ocspStaplingStatus, &keySizeStatus, &sha1ModeResult,
&pinningTelemetryInfo, &certificateTransparencyInfo);
CollectCertTelemetry(rv, evOidPolicy, ocspStaplingStatus, keySizeStatus,
sha1ModeResult, pinningTelemetryInfo, builtCertChain,
@ -1221,11 +1221,11 @@ Result AuthCertificate(CertVerifier& certVerifier, nsNSSSocketInfo* infoObject,
/*static*/
SECStatus SSLServerCertVerificationJob::Dispatch(
const RefPtr<SharedCertVerifier>& certVerifier, const void* fdForLogging,
nsNSSSocketInfo* infoObject, const UniqueCERTCertificate& serverCert,
TransportSecurityInfo* infoObject, const UniqueCERTCertificate& serverCert,
const UniqueCERTCertList& peerCertChain,
Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension, uint32_t providerFlags,
Time time, PRTime prtime) {
Time time, PRTime prtime, uint32_t certVerifierFlags) {
// Runs on the socket transport thread
if (!certVerifier || !infoObject || !serverCert) {
NS_ERROR("Invalid parameters for SSL server cert validation");
@ -1250,7 +1250,7 @@ SECStatus SSLServerCertVerificationJob::Dispatch(
RefPtr<SSLServerCertVerificationJob> job(new SSLServerCertVerificationJob(
certVerifier, fdForLogging, infoObject, serverCert,
std::move(peerCertChainCopy), stapledOCSPResponse, sctsFromTLSExtension,
providerFlags, time, prtime));
providerFlags, time, prtime, certVerifierFlags));
nsresult nrv = gCertVerificationThreadPool->Dispatch(job, NS_DISPATCH_NORMAL);
if (NS_FAILED(nrv)) {
@ -1394,17 +1394,17 @@ SSLServerCertVerificationJob::Run() {
("[%p] SSLServerCertVerificationJob::Run\n", mInfoObject.get()));
TimeStamp jobStartTime = TimeStamp::Now();
Result rv = AuthCertificate(*mCertVerifier, mInfoObject, mCert,
mPeerCertChain, mStapledOCSPResponse,
mSCTsFromTLSExtension, mProviderFlags, mTime);
Result rv = AuthCertificate(
*mCertVerifier, mInfoObject, mCert, mPeerCertChain, mStapledOCSPResponse,
mSCTsFromTLSExtension, mProviderFlags, mTime, mCertVerifierFlags);
MOZ_ASSERT(
(mPeerCertChain && rv == Success) || (!mPeerCertChain && rv != Success),
"AuthCertificate() should take ownership of chain on failure");
if (rv == Success) {
Telemetry::AccumulateTimeDelta(
Telemetry::SSL_SUCCESFUL_CERT_VALIDATION_TIME_MOZILLAPKIX,
jobStartTime, TimeStamp::Now());
Telemetry::SSL_SUCCESFUL_CERT_VALIDATION_TIME_MOZILLAPKIX, jobStartTime,
TimeStamp::Now());
Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1);
RefPtr<SSLServerCertVerificationResult> runnable(
new SSLServerCertVerificationResult(mInfoObject, 0));
@ -1472,11 +1472,14 @@ SSLServerCertVerificationJob::Run() {
} // unnamed namespace
// Extracts whatever information we need out of fd (using SSL_*) and passes it
// to SSLServerCertVerificationJob::Dispatch. SSLServerCertVerificationJob
// should never do anything with fd except logging.
SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig,
PRBool isServer) {
// Takes information needed for cert verification, does some consistency
// checks and calls SSLServerCertVerificationJob::Dispatch.
SECStatus AuthCertificateHookInternal(
TransportSecurityInfo* infoObject, const void* aPtrForLogging,
const UniqueCERTCertificate& serverCert, UniqueCERTCertList& peerCertChain,
Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension, uint32_t providerFlags,
uint32_t certVerifierFlags) {
RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
if (!certVerifier) {
PR_SetError(SEC_ERROR_NOT_INITIALIZED, 0);
@ -1486,28 +1489,9 @@ SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig,
// Runs on the socket transport thread
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[%p] starting AuthCertificateHook\n", fd));
("[%p] starting AuthCertificateHookInternal\n", aPtrForLogging));
// Modern libssl always passes PR_TRUE for checkSig, and we have no means of
// doing verification without checking signatures.
MOZ_ASSERT(checkSig, "AuthCertificateHook: checkSig unexpectedly false");
// PSM never causes libssl to call this function with PR_TRUE for isServer,
// and many things in PSM assume that we are a client.
MOZ_ASSERT(!isServer, "AuthCertificateHook: isServer unexpectedly true");
nsNSSSocketInfo* socketInfo = static_cast<nsNSSSocketInfo*>(arg);
UniqueCERTCertificate serverCert(SSL_PeerCertificate(fd));
if (!checkSig || isServer || !socketInfo || !serverCert) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
// Get the peer certificate chain for error reporting
UniqueCERTCertList peerCertChain(SSL_PeerCertificateChain(fd));
if (!peerCertChain) {
if (!infoObject || !serverCert) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
@ -1533,12 +1517,54 @@ SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig,
return SECFailure;
}
// We *must* do certificate verification on a background thread because
// we need the socket transport thread to be free for our OCSP requests,
// and we *want* to do certificate verification on a background thread
// because of the performance benefits of doing so.
return SSLServerCertVerificationJob::Dispatch(
certVerifier, aPtrForLogging, infoObject, serverCert, peerCertChain,
stapledOCSPResponse, sctsFromTLSExtension, providerFlags, Now(), PR_Now(),
certVerifierFlags);
}
// Extracts whatever information we need out of fd (using SSL_*) and passes it
// to AuthCertificateHookInternal. AuthCertificateHookInternal will call
// SSLServerCertVerificationJob::Dispatch. SSLServerCertVerificationJob
// should never do anything with fd except logging.
SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig,
PRBool isServer) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[%p] starting AuthCertificateHook\n", fd));
// Modern libssl always passes PR_TRUE for checkSig, and we have no means of
// doing verification without checking signatures.
MOZ_ASSERT(checkSig, "AuthCertificateHook: checkSig unexpectedly false");
// PSM never causes libssl to call this function with PR_TRUE for isServer,
// and many things in PSM assume that we are a client.
MOZ_ASSERT(!isServer, "AuthCertificateHook: isServer unexpectedly true");
nsNSSSocketInfo* socketInfo = static_cast<nsNSSSocketInfo*>(arg);
UniqueCERTCertificate serverCert(SSL_PeerCertificate(fd));
if (!checkSig || isServer || !socketInfo || !serverCert) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
socketInfo->SetFullHandshake();
if (BlockServerCertChangeForSpdy(socketInfo, serverCert) != SECSuccess) {
return SECFailure;
}
// Get the peer certificate chain for error reporting
UniqueCERTCertList peerCertChain(SSL_PeerCertificateChain(fd));
if (!peerCertChain) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
// SSL_PeerStapledOCSPResponses will never return a non-empty response if
// OCSP stapling wasn't enabled because libssl wouldn't have let the server
// return a stapled OCSP response.
@ -1564,20 +1590,98 @@ SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig,
uint32_t providerFlags = 0;
socketInfo->GetProviderFlags(&providerFlags);
// We *must* do certificate verification on a background thread because
// we need the socket transport thread to be free for our OCSP requests,
// and we *want* to do certificate verification on a background thread
// because of the performance benefits of doing so.
uint32_t certVerifierFlags = 0;
if (!socketInfo->SharedState().IsOCSPStaplingEnabled() ||
!socketInfo->SharedState().IsOCSPMustStapleEnabled()) {
certVerifierFlags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
}
socketInfo->SetCertVerificationWaiting();
SECStatus rv = SSLServerCertVerificationJob::Dispatch(
certVerifier, static_cast<const void*>(fd), socketInfo, serverCert,
peerCertChain, stapledOCSPResponse, sctsFromTLSExtension, providerFlags,
Now(), PR_Now());
return rv;
return AuthCertificateHookInternal(socketInfo, static_cast<const void*>(fd),
serverCert, peerCertChain,
stapledOCSPResponse, sctsFromTLSExtension,
providerFlags, certVerifierFlags);
}
// Make a cert chain from an array of ders.
SECStatus MakeCertChain(nsTArray<nsTArray<uint8_t>>& inPeerCertChain,
UniqueCERTCertificate &outCert,
UniqueCERTCertList &outCertChain) {
for (auto& certDer : inPeerCertChain) {
if (certDer.Length() > UINT32_MAX) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("MakeCertChain: certDer too long."));
return SECFailure;
}
SECItem der = {SECItemType::siBuffer, certDer.Elements(),
(uint32_t)certDer.Length()
};
if (!outCert) {
outCert.reset(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der,
nullptr, false, true));
if (!outCert) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("MakeCertChain: cert failed"));
return SECFailure;
}
}
UniqueCERTCertificate tmpCert(CERT_NewTempCertificate(
CERT_GetDefaultCertDB(), &der, nullptr, false, true));
if (!tmpCert) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("MakeCertChain: cert failed"));
return SECFailure;
}
if (CERT_AddCertToListTail(outCertChain.get(), tmpCert.get()) !=
SECSuccess) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("MakeCertChain: getting cert chain failed"));
return SECFailure;
}
Unused << tmpCert.release();
}
return SECSuccess;
}
// Takes information needed for cert verification, does some consistency
// checks and calls SSLServerCertVerificationJob::Dispatch.
// This function is used for Quic.
SECStatus AuthCertificateHookWithInfo(
TransportSecurityInfo* infoObject, const void* aPtrForLogging,
nsTArray<nsTArray<uint8_t>>& peerCertChain,
Maybe<nsTArray<nsTArray<uint8_t>>>& stapledOCSPResponses,
Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension, uint32_t providerFlags) {
UniqueCERTCertificate cert;
UniqueCERTCertList certChain(CERT_NewCertList());
if (!certChain) {
return SECFailure;
}
if (MakeCertChain(peerCertChain, cert, certChain) != SECSuccess) {
return SECFailure;
}
// we currently only support single stapled responses
Maybe<nsTArray<uint8_t>> stapledOCSPResponse;
if (stapledOCSPResponses && (stapledOCSPResponses->Length() == 1)) {
stapledOCSPResponse.emplace(stapledOCSPResponses->ElementAt(0));
}
uint32_t certVerifierFlags = 0;
// QuicTransportSecInfo does not have a SharedState as nsNSSSocketInfo.
// Here we need prefs for ocsp. This are prefs they are the same for
// PublicSSLState and PrivateSSLState, just take them from one of them.
if (!PublicSSLState()->IsOCSPStaplingEnabled() ||
!PublicSSLState()->IsOCSPMustStapleEnabled()) {
certVerifierFlags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
}
return AuthCertificateHookInternal(
infoObject, aPtrForLogging, cert, certChain, stapledOCSPResponse,
sctsFromTLSExtension, providerFlags, certVerifierFlags);
}
SSLServerCertVerificationResult::SSLServerCertVerificationResult(
nsNSSSocketInfo* infoObject, PRErrorCode errorCode)
TransportSecurityInfo* infoObject, PRErrorCode errorCode)
: Runnable("psm::SSLServerCertVerificationResult"),
mInfoObject(infoObject),
mErrorCode(errorCode) {}

Просмотреть файл

@ -6,16 +6,28 @@
#ifndef _SSLSERVERCERTVERIFICATION_H
#define _SSLSERVERCERTVERIFICATION_H
#include "mozilla/Maybe.h"
#include "nsTArray.h"
#include "seccomon.h"
#include "prio.h"
namespace mozilla {
namespace psm {
class TransportSecurityInfo;
SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig,
PRBool isServer);
}
// This function triggers the certificate verification. The verification is
// asynchronous and the info object will be notified when the verification has
// completed via SetCertVerificationResult.
SECStatus AuthCertificateHookWithInfo(
TransportSecurityInfo* infoObject, const void* aPtrForLogging,
nsTArray<nsTArray<uint8_t>>& peerCertChain,
Maybe<nsTArray<nsTArray<uint8_t>>>& stapledOCSPResponses,
Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension, uint32_t providerFlags);
} // namespace psm
} // namespace mozilla
#endif

Просмотреть файл

@ -84,6 +84,9 @@ class TransportSecurityInfo : public nsITransportSecurityInfo,
void SetCertificateTransparencyInfo(
const mozilla::psm::CertificateTransparencyInfo& info);
// Use errorCode == 0 to indicate success;
virtual void SetCertVerificationResult(PRErrorCode errorCode){};
uint16_t mCipherSuite;
uint16_t mProtocolVersion;
uint16_t mCertificateTransparencyStatus;

Просмотреть файл

@ -87,7 +87,7 @@ class nsNSSSocketInfo final : public mozilla::psm::TransportSecurityInfo,
};
void SetCertVerificationWaiting();
// Use errorCode == 0 to indicate success;
void SetCertVerificationResult(PRErrorCode errorCode);
void SetCertVerificationResult(PRErrorCode errorCode) override;
// for logging only
PRBool IsWaitingForCertVerification() const {