Bug 1560353 - Extend SSLTokensCache to store the result of VerifySSLServerCert r=dragana,keeler

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Kershaw Chang 2019-10-01 12:10:58 +00:00
parent 6d782a5e1e
commit 3f5bb45b8e
7 changed files with 230 additions and 9 deletions

View File

@ -5,6 +5,7 @@
#include "SSLTokensCache.h"
#include "mozilla/Preferences.h"
#include "mozilla/Logging.h"
#include "nsNSSIOLayer.h"
#include "ssl.h"
#include "sslexp.h"
@ -36,6 +37,28 @@ class ExpirationComparator {
StaticRefPtr<SSLTokensCache> SSLTokensCache::gInstance;
StaticMutex SSLTokensCache::sLock;
uint32_t SSLTokensCache::TokenCacheRecord::Size() const {
uint32_t size = mToken.Length() + sizeof(mSessionCacheInfo.mEVStatus) +
sizeof(mSessionCacheInfo.mCertificateTransparencyStatus) +
mSessionCacheInfo.mServerCertBytes.Length();
if (mSessionCacheInfo.mSucceededCertChainBytes) {
for (const auto& cert : mSessionCacheInfo.mSucceededCertChainBytes.ref()) {
size += cert.Length();
}
}
return size;
}
void SSLTokensCache::TokenCacheRecord::Reset() {
mToken.Clear();
mExpirationTime = 0;
mSessionCacheInfo.mEVStatus = psm::EVStatus::NotEV;
mSessionCacheInfo.mCertificateTransparencyStatus =
nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
mSessionCacheInfo.mServerCertBytes.Clear();
mSessionCacheInfo.mSucceededCertChainBytes.reset();
}
NS_IMPL_ISUPPORTS(SSLTokensCache, nsIMemoryReporter)
// static
@ -79,7 +102,8 @@ SSLTokensCache::~SSLTokensCache() { LOG(("SSLTokensCache::~SSLTokensCache")); }
// static
nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
uint32_t aTokenLen) {
uint32_t aTokenLen,
nsITransportSecurityInfo* aSecInfo) {
StaticMutexAutoLock lock(sLock);
LOG(("SSLTokensCache::Put [key=%s, tokenLen=%u]",
@ -90,6 +114,10 @@ nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
return NS_ERROR_NOT_INITIALIZED;
}
if (!aSecInfo) {
return NS_ERROR_FAILURE;
}
PRUint32 expirationTime;
SSLResumptionTokenInfo tokenInfo;
if (SSL_GetResumptionTokenInfo(aToken, aTokenLen, &tokenInfo,
@ -101,6 +129,53 @@ nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
expirationTime = tokenInfo.expirationTime;
SSL_DestroyResumptionTokenInfo(&tokenInfo);
nsCOMPtr<nsIX509Cert> cert;
aSecInfo->GetServerCert(getter_AddRefs(cert));
if (!cert) {
return NS_ERROR_FAILURE;
}
nsTArray<uint8_t> certBytes;
nsresult rv = cert->GetRawDER(certBytes);
if (NS_FAILED(rv)) {
return rv;
}
Maybe<nsTArray<nsTArray<uint8_t>>> succeededCertChainBytes;
nsCOMPtr<nsIX509CertList> succeededCertChain;
Unused << aSecInfo->GetSucceededCertChain(getter_AddRefs(succeededCertChain));
if (succeededCertChain) {
succeededCertChainBytes.emplace();
rv = succeededCertChain->GetCertList()->ForEachCertificateInChain(
[&succeededCertChainBytes](nsCOMPtr<nsIX509Cert> aCert, bool /*unused*/,
/*out*/ bool& /*unused*/) {
nsTArray<uint8_t> cert;
nsresult rv = aCert->GetRawDER(cert);
if (NS_FAILED(rv)) {
return rv;
}
succeededCertChainBytes->AppendElement(std::move(cert));
return NS_OK;
});
if (NS_FAILED(rv)) {
return rv;
}
}
bool isEV;
rv = aSecInfo->GetIsExtendedValidation(&isEV);
if (NS_FAILED(rv)) {
return rv;
}
uint16_t certificateTransparencyStatus;
rv = aSecInfo->GetCertificateTransparencyStatus(
&certificateTransparencyStatus);
if (NS_FAILED(rv)) {
return rv;
}
TokenCacheRecord* rec = nullptr;
if (!gInstance->mTokenCacheRecords.Get(aKey, &rec)) {
@ -109,14 +184,27 @@ nsresult SSLTokensCache::Put(const nsACString& aKey, const uint8_t* aToken,
gInstance->mTokenCacheRecords.Put(aKey, rec);
gInstance->mExpirationArray.AppendElement(rec);
} else {
gInstance->mCacheSize -= rec->mToken.Length();
rec->mToken.Clear();
gInstance->mCacheSize -= rec->Size();
rec->Reset();
}
rec->mExpirationTime = expirationTime;
MOZ_ASSERT(rec->mToken.IsEmpty());
rec->mToken.AppendElements(aToken, aTokenLen);
gInstance->mCacheSize += rec->mToken.Length();
rec->mSessionCacheInfo.mServerCertBytes = std::move(certBytes);
rec->mSessionCacheInfo.mSucceededCertChainBytes =
std::move(succeededCertChainBytes);
if (isEV) {
rec->mSessionCacheInfo.mEVStatus = psm::EVStatus::EV;
}
rec->mSessionCacheInfo.mCertificateTransparencyStatus =
certificateTransparencyStatus;
gInstance->mCacheSize += rec->Size();
gInstance->LogStats();
@ -150,6 +238,30 @@ nsresult SSLTokensCache::Get(const nsACString& aKey,
return NS_ERROR_NOT_AVAILABLE;
}
// static
bool SSLTokensCache::GetSessionCacheInfo(const nsACString& aKey,
SessionCacheInfo& aResult) {
StaticMutexAutoLock lock(sLock);
LOG(("SSLTokensCache::GetSessionCacheInfo [key=%s]",
PromiseFlatCString(aKey).get()));
if (!gInstance) {
LOG((" service not initialized"));
return false;
}
TokenCacheRecord* rec = nullptr;
if (gInstance->mTokenCacheRecords.Get(aKey, &rec)) {
aResult = rec->mSessionCacheInfo;
return true;
}
LOG((" token not found"));
return false;
}
// static
nsresult SSLTokensCache::Remove(const nsACString& aKey) {
StaticMutexAutoLock lock(sLock);
@ -177,7 +289,7 @@ nsresult SSLTokensCache::RemoveLocked(const nsACString& aKey) {
return NS_ERROR_NOT_AVAILABLE;
}
mCacheSize -= rec->mToken.Length();
mCacheSize -= rec->Size();
if (!mExpirationArray.RemoveElement(rec)) {
MOZ_ASSERT(false, "token not found in mExpirationArray");

View File

@ -8,13 +8,23 @@
#include "nsIMemoryReporter.h"
#include "nsClassHashtable.h"
#include "nsTArray.h"
#include "mozilla/Maybe.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPtr.h"
#include "nsXULAppAPI.h"
#include "TransportSecurityInfo.h" // For EVStatus
namespace mozilla {
namespace net {
struct SessionCacheInfo {
psm::EVStatus mEVStatus = psm::EVStatus::NotEV;
uint16_t mCertificateTransparencyStatus =
nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
nsTArray<uint8_t> mServerCertBytes;
Maybe<nsTArray<nsTArray<uint8_t>>> mSucceededCertChainBytes;
};
class SSLTokensCache : public nsIMemoryReporter {
public:
NS_DECL_THREADSAFE_ISUPPORTS
@ -28,8 +38,10 @@ class SSLTokensCache : public nsIMemoryReporter {
static bool IsEnabled() { return sEnabled; }
static nsresult Put(const nsACString& aKey, const uint8_t* aToken,
uint32_t aTokenLen);
uint32_t aTokenLen, nsITransportSecurityInfo* aSecInfo);
static nsresult Get(const nsACString& aKey, nsTArray<uint8_t>& aToken);
static bool GetSessionCacheInfo(const nsACString& aKey,
SessionCacheInfo& aResult);
static nsresult Remove(const nsACString& aKey);
private:
@ -55,9 +67,13 @@ class SSLTokensCache : public nsIMemoryReporter {
class TokenCacheRecord {
public:
uint32_t Size() const;
void Reset();
nsCString mKey;
PRUint32 mExpirationTime;
nsTArray<uint8_t> mToken;
SessionCacheInfo mSessionCacheInfo;
};
nsClassHashtable<nsCStringHashKey, TokenCacheRecord> mTokenCacheRecords;

View File

@ -322,6 +322,7 @@ LOCAL_INCLUDES += [
'/netwerk/protocol/http',
'/netwerk/socket',
'/netwerk/url-classifier',
'/security/manager/ssl',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':

View File

@ -1243,10 +1243,17 @@ SECStatus nsSocketTransport::StoreResumptionToken(
if (!secCtrl) {
return SECFailure;
}
nsAutoCString peerId;
secCtrl->GetPeerId(peerId);
SSLTokensCache::Put(peerId, resumptionToken, len);
nsCOMPtr<nsITransportSecurityInfo> secInfo = do_QueryInterface(secCtrl);
if (!secInfo) {
return SECFailure;
}
if (NS_FAILED(SSLTokensCache::Put(peerId, resumptionToken, len, secInfo))) {
return SECFailure;
}
return SECSuccess;
}

View File

@ -87,6 +87,11 @@ class TransportSecurityInfo : public nsITransportSecurityInfo,
// Use errorCode == 0 to indicate success;
virtual void SetCertVerificationResult(PRErrorCode errorCode){};
void SetCertificateTransparencyStatus(
uint16_t aCertificateTransparencyStatus) {
mCertificateTransparencyStatus = aCertificateTransparencyStatus;
}
uint16_t mCipherSuite;
uint16_t mProtocolVersion;
uint16_t mCertificateTransparencyStatus;

View File

@ -211,6 +211,7 @@ FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/dom/base',
'/dom/crypto',
'/netwerk/base',
'/security/certverifier',
]

View File

@ -39,6 +39,7 @@
#include "mozpkix/pkixtypes.h"
#include "ssl.h"
#include "sslproto.h"
#include "SSLTokensCache.h"
#include "TrustOverrideUtils.h"
#include "TrustOverride-SymantecData.inc"
@ -1159,6 +1160,80 @@ nsresult IsCertificateDistrustImminent(nsIX509CertList* aCertList,
return NS_OK;
}
static bool ConstructCERTCertListFromBytesArray(
nsTArray<nsTArray<uint8_t>>& aCertArray,
/*out*/ UniqueCERTCertList& aCertList) {
aCertList = UniqueCERTCertList(CERT_NewCertList());
if (!aCertList) {
return false;
}
CERTCertDBHandle* certDB(CERT_GetDefaultCertDB()); // non-owning
for (auto& cert : aCertArray) {
SECItem certDER = {siBuffer, cert.Elements(),
static_cast<unsigned int>(cert.Length())};
UniqueCERTCertificate tmpCert(
CERT_NewTempCertificate(certDB, &certDER, nullptr, false, true));
if (!tmpCert) {
return false;
}
if (CERT_AddCertToListTail(aCertList.get(), tmpCert.get()) != SECSuccess) {
return false;
}
Unused << tmpCert.release(); // tmpCert is now owned by aCertList.
}
return true;
}
static void RebuildCertificateInfoFromSSLTokenCache(
nsNSSSocketInfo* aInfoObject) {
MOZ_ASSERT(aInfoObject);
if (!aInfoObject) {
return;
}
nsAutoCString key;
aInfoObject->GetPeerId(key);
mozilla::net::SessionCacheInfo info;
if (!mozilla::net::SSLTokensCache::GetSessionCacheInfo(key, info)) {
MOZ_LOG(
gPIPNSSLog, LogLevel::Debug,
("RebuildCertificateInfoFromSSLTokenCache cannot find cached info."));
return;
}
RefPtr<nsNSSCertificate> nssc = nsNSSCertificate::ConstructFromDER(
BitwiseCast<char*, uint8_t*>(info.mServerCertBytes.Elements()),
info.mServerCertBytes.Length());
if (!nssc) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("RebuildCertificateInfoFromSSLTokenCache failed to construct "
"server cert"));
return;
}
UniqueCERTCertList builtCertChain;
if (info.mSucceededCertChainBytes) {
if (!ConstructCERTCertListFromBytesArray(
info.mSucceededCertChainBytes.ref(), builtCertChain)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("RebuildCertificateInfoFromSSLTokenCache failed to construct "
"cert list"));
return;
}
}
aInfoObject->SetServerCert(nssc, info.mEVStatus);
aInfoObject->SetCertificateTransparencyStatus(
info.mCertificateTransparencyStatus);
if (builtCertChain) {
aInfoObject->SetSucceededCertChain(std::move(builtCertChain));
}
}
void HandshakeCallback(PRFileDesc* fd, void* client_data) {
SECStatus rv;
@ -1296,7 +1371,11 @@ void HandshakeCallback(PRFileDesc* fd, void* client_data) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("HandshakeCallback KEEPING existing cert\n"));
} else {
RebuildVerifiedCertificateInformation(fd, infoObject);
if (mozilla::net::SSLTokensCache::IsEnabled()) {
RebuildCertificateInfoFromSSLTokenCache(infoObject);
} else {
RebuildVerifiedCertificateInformation(fd, infoObject);
}
}
nsCOMPtr<nsIX509CertList> succeededCertChain;