diff --git a/config/external/nss/nss.def b/config/external/nss/nss.def index 3f432d163eb..991b7f423f1 100644 --- a/config/external/nss/nss.def +++ b/config/external/nss/nss.def @@ -647,6 +647,7 @@ SSL_NumImplementedCiphers DATA SSL_OptionSet SSL_OptionSetDefault SSL_PeerCertificate +SSL_PeerCertificateChain SSL_PeerStapledOCSPResponses SSL_ResetHandshake SSL_SetCanFalseStartCallback diff --git a/netwerk/socket/nsITransportSecurityInfo.idl b/netwerk/socket/nsITransportSecurityInfo.idl index 96a16233b7f..1e5c9d0a02d 100644 --- a/netwerk/socket/nsITransportSecurityInfo.idl +++ b/netwerk/socket/nsITransportSecurityInfo.idl @@ -6,9 +6,18 @@ #include "nsISupports.idl" -[scriptable, uuid(8813d03b-e76c-4240-9691-d327d9b91e88)] +interface nsIX509CertList; + +[scriptable, uuid(154898ce-e14f-4981-bfc5-00a41722f44b)] interface nsITransportSecurityInfo : nsISupports { readonly attribute unsigned long securityState; readonly attribute wstring errorMessage; + + /** + * If certificate verification failed, this will be the peer certificate + * chain provided in the handshake, so it can be used for error reporting. + * If verification succeeded, this will be null. + */ + readonly attribute nsIX509CertList failedCertChain; }; diff --git a/security/manager/ssl/src/SSLServerCertVerification.cpp b/security/manager/ssl/src/SSLServerCertVerification.cpp index 2e76b14c5b3..9f1fab7af7c 100644 --- a/security/manager/ssl/src/SSLServerCertVerification.cpp +++ b/security/manager/ssl/src/SSLServerCertVerification.cpp @@ -621,6 +621,7 @@ public: const void* fdForLogging, TransportSecurityInfo* infoObject, CERTCertificate* serverCert, + ScopedCERTCertList& peerCertChain, SECItem* stapledOCSPResponse, uint32_t providerFlags, Time time, @@ -633,6 +634,7 @@ private: const void* fdForLogging, TransportSecurityInfo* infoObject, CERTCertificate* cert, + CERTCertList* peerCertChain, SECItem* stapledOCSPResponse, uint32_t providerFlags, Time time, @@ -641,6 +643,7 @@ private: const void* const mFdForLogging; const RefPtr mInfoObject; const ScopedCERTCertificate mCert; + ScopedCERTCertList mPeerCertChain; const uint32_t mProviderFlags; const Time mTime; const PRTime mPRTime; @@ -651,12 +654,13 @@ private: SSLServerCertVerificationJob::SSLServerCertVerificationJob( const RefPtr& certVerifier, const void* fdForLogging, TransportSecurityInfo* infoObject, CERTCertificate* cert, - SECItem* stapledOCSPResponse, uint32_t providerFlags, - Time time, PRTime prtime) + CERTCertList* peerCertChain, SECItem* stapledOCSPResponse, + uint32_t providerFlags, Time time, PRTime prtime) : mCertVerifier(certVerifier) , mFdForLogging(fdForLogging) , mInfoObject(infoObject) , mCert(CERT_DupCertificate(cert)) + , mPeerCertChain(peerCertChain) , mProviderFlags(providerFlags) , mTime(time) , mPRTime(prtime) @@ -733,9 +737,13 @@ BlockServerCertChangeForSpdy(nsNSSSocketInfo* infoObject, } SECStatus -AuthCertificate(CertVerifier& certVerifier, TransportSecurityInfo* infoObject, - CERTCertificate* cert, SECItem* stapledOCSPResponse, - uint32_t providerFlags, Time time) +AuthCertificate(CertVerifier& certVerifier, + TransportSecurityInfo* infoObject, + CERTCertificate* cert, + ScopedCERTCertList& peerCertChain, + SECItem* stapledOCSPResponse, + uint32_t providerFlags, + Time time) { MOZ_ASSERT(infoObject); MOZ_ASSERT(cert); @@ -747,7 +755,6 @@ AuthCertificate(CertVerifier& certVerifier, TransportSecurityInfo* infoObject, bool saveIntermediates = !(providerFlags & nsISocketProvider::NO_PERMANENT_STORAGE); - ScopedCERTCertList certList; SECOidTag evOidPolicy; rv = certVerifier.VerifySSLServerCert(cert, stapledOCSPResponse, time, infoObject, @@ -799,6 +806,13 @@ AuthCertificate(CertVerifier& certVerifier, TransportSecurityInfo* infoObject, } } + if (rv != SECSuccess) { + // Certificate validation failed; store the peer certificate chain on + // infoObject so it can be used for error reporting. Note: infoObject + // indirectly takes ownership of peerCertChain. + infoObject->SetFailedCertChain(peerCertChain); + } + return rv; } @@ -808,6 +822,7 @@ SSLServerCertVerificationJob::Dispatch( const void* fdForLogging, TransportSecurityInfo* infoObject, CERTCertificate* serverCert, + ScopedCERTCertList& peerCertChain, SECItem* stapledOCSPResponse, uint32_t providerFlags, Time time, @@ -820,10 +835,18 @@ SSLServerCertVerificationJob::Dispatch( return SECFailure; } + // Copy the certificate list so the runnable can take ownership of it in the + // constructor. + // We can safely skip checking if NSS has already shut down here since we're + // in the middle of verifying a certificate. + nsNSSShutDownPreventionLock lock; + CERTCertList* peerCertChainCopy = nsNSSCertList::DupCertList(peerCertChain, lock); + RefPtr job( new SSLServerCertVerificationJob(certVerifier, fdForLogging, infoObject, - serverCert, stapledOCSPResponse, - providerFlags, time, prtime)); + serverCert, peerCertChainCopy, + stapledOCSPResponse, providerFlags, + time, prtime)); nsresult nrv; if (!gCertVerificationThreadPool) { @@ -873,8 +896,8 @@ SSLServerCertVerificationJob::Run() // set the error code if/when it fails. PR_SetError(0, 0); SECStatus rv = AuthCertificate(*mCertVerifier, mInfoObject, mCert.get(), - mStapledOCSPResponse, mProviderFlags, - mTime); + mPeerCertChain, mStapledOCSPResponse, + mProviderFlags, mTime); if (rv == SECSuccess) { uint32_t interval = (uint32_t) ((TimeStamp::Now() - mJobStartTime).ToMilliseconds()); RefPtr restart( @@ -975,6 +998,9 @@ AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig, PRBool isServer) return SECFailure; } + // Get the peer certificate chain for error reporting + ScopedCERTCertList peerCertChain(SSL_PeerCertificateChain(fd)); + socketInfo->SetFullHandshake(); Time now(Now()); @@ -1020,8 +1046,8 @@ AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig, PRBool isServer) socketInfo->SetCertVerificationWaiting(); SECStatus rv = SSLServerCertVerificationJob::Dispatch( certVerifier, static_cast(fd), socketInfo, - serverCert, stapledOCSPResponse, providerFlags, now, - prnow); + serverCert, peerCertChain, stapledOCSPResponse, + providerFlags, now, prnow); return rv; } @@ -1031,7 +1057,8 @@ AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig, PRBool isServer) // a non-blocking socket. SECStatus rv = AuthCertificate(*certVerifier, socketInfo, serverCert, - stapledOCSPResponse, providerFlags, now); + peerCertChain, stapledOCSPResponse, + providerFlags, now); if (rv == SECSuccess) { Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1); return SECSuccess; diff --git a/security/manager/ssl/src/TransportSecurityInfo.cpp b/security/manager/ssl/src/TransportSecurityInfo.cpp index 89be5468534..cc9cfbfc9bb 100644 --- a/security/manager/ssl/src/TransportSecurityInfo.cpp +++ b/security/manager/ssl/src/TransportSecurityInfo.cpp @@ -289,8 +289,8 @@ TransportSecurityInfo::GetInterface(const nsIID & uuid, void * *result) // of the previous value. This is so when older versions attempt to // read a newer serialized TransportSecurityInfo, they will actually // fail and return NS_ERROR_FAILURE instead of silently failing. -#define TRANSPORTSECURITYINFOMAGIC { 0xa9863a23, 0x28ea, 0x45d2, \ - { 0xa2, 0x5a, 0x35, 0x7c, 0xae, 0xfa, 0x7f, 0x82 } } +#define TRANSPORTSECURITYINFOMAGIC { 0xa9863a23, 0xf40a, 0x4060, \ + { 0xb2, 0xe1, 0x62, 0xab, 0x2b, 0x85, 0x26, 0xa9 } } static NS_DEFINE_CID(kTransportSecurityInfoMagic, TRANSPORTSECURITYINFOMAGIC); NS_IMETHODIMP @@ -331,6 +331,15 @@ TransportSecurityInfo::Write(nsIObjectOutputStream* stream) if (NS_FAILED(rv)) { return rv; } + + rv = NS_WriteOptionalCompoundObject(stream, + mFailedCertChain, + NS_GET_IID(nsIX509CertList), + true); + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; } @@ -386,6 +395,14 @@ TransportSecurityInfo::Read(nsIObjectInputStream* stream) if (!mSSLStatus) { return NS_ERROR_FAILURE; } + + nsCOMPtr failedCertChainSupports; + rv = NS_ReadOptionalObject(stream, true, getter_AddRefs(failedCertChainSupports)); + if (NS_FAILED(rv)) { + return rv; + } + mFailedCertChain = do_QueryInterface(failedCertChainSupports); + return NS_OK; } @@ -1072,4 +1089,30 @@ TransportSecurityInfo::SetStatusErrorBits(nsIX509Cert & cert, SECFailure); } +NS_IMETHODIMP +TransportSecurityInfo::GetFailedCertChain(nsIX509CertList** _result) +{ + NS_ASSERTION(_result, "non-NULL destination required"); + + *_result = mFailedCertChain; + NS_IF_ADDREF(*_result); + + return NS_OK; +} + +nsresult +TransportSecurityInfo::SetFailedCertChain(ScopedCERTCertList& certList) +{ + nsNSSShutDownPreventionLock lock; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr comCertList; + // nsNSSCertList takes ownership of certList + mFailedCertChain = new nsNSSCertList(certList, lock); + + return NS_OK; +} + } } // namespace mozilla::psm diff --git a/security/manager/ssl/src/TransportSecurityInfo.h b/security/manager/ssl/src/TransportSecurityInfo.h index 342408c8270..d32889924f3 100644 --- a/security/manager/ssl/src/TransportSecurityInfo.h +++ b/security/manager/ssl/src/TransportSecurityInfo.h @@ -7,16 +7,18 @@ #ifndef _MOZILLA_PSM_TRANSPORTSECURITYINFO_H #define _MOZILLA_PSM_TRANSPORTSECURITYINFO_H +#include "ScopedNSSTypes.h" #include "certt.h" #include "mozilla/Mutex.h" #include "mozilla/RefPtr.h" -#include "nsIInterfaceRequestor.h" -#include "nsITransportSecurityInfo.h" -#include "nsSSLStatus.h" -#include "nsISSLStatusProvider.h" -#include "nsIAssociatedContentSecurity.h" -#include "nsNSSShutDown.h" #include "nsDataHashtable.h" +#include "nsIAssociatedContentSecurity.h" +#include "nsIInterfaceRequestor.h" +#include "nsISSLStatusProvider.h" +#include "nsITransportSecurityInfo.h" +#include "nsNSSShutDown.h" +#include "nsSSLStatus.h" +#include "pkix/pkixtypes.h" namespace mozilla { namespace psm { @@ -74,6 +76,8 @@ public: nsSSLStatus* SSLStatus() { return mSSLStatus; } void SetStatusErrorBits(nsIX509Cert & cert, uint32_t collected_errors); + nsresult SetFailedCertChain(ScopedCERTCertList& certList); + private: mutable ::mozilla::Mutex mMutex; @@ -100,6 +104,9 @@ private: /* SSL Status */ mozilla::RefPtr mSSLStatus; + /* Peer cert chain for failed connections (for error reporting) */ + nsCOMPtr mFailedCertChain; + virtual void virtualDestroyNSSReference(); void destructorSafeDestroyNSSReference(); }; diff --git a/security/manager/ssl/src/nsNSSCertificate.cpp b/security/manager/ssl/src/nsNSSCertificate.cpp index 94b93f6458a..b886bfe8d0e 100644 --- a/security/manager/ssl/src/nsNSSCertificate.cpp +++ b/security/manager/ssl/src/nsNSSCertificate.cpp @@ -1545,7 +1545,9 @@ ConstructCERTCertListFromReversedDERArray( } // namespace mozilla -NS_IMPL_ISUPPORTS(nsNSSCertList, nsIX509CertList) +NS_IMPL_ISUPPORTS(nsNSSCertList, + nsIX509CertList, + nsISerializable) nsNSSCertList::nsNSSCertList(ScopedCERTCertList& certList, const nsNSSShutDownPreventionLock& proofOfLock) @@ -1667,6 +1669,84 @@ nsNSSCertList::GetRawCertList() return mCertList.get(); } +NS_IMETHODIMP +nsNSSCertList::Write(nsIObjectOutputStream* aStream) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + NS_ENSURE_STATE(mCertList); + nsresult rv = NS_OK; + + // First, enumerate the certs to get the length of the list + uint32_t certListLen = 0; + CERTCertListNode* node = nullptr; + for (node = CERT_LIST_HEAD(mCertList); + !CERT_LIST_END(node, mCertList); + node = CERT_LIST_NEXT(node), ++certListLen) { + } + + // Write the length of the list + rv = aStream->Write32(certListLen); + + // Repeat the loop, and serialize each certificate + node = nullptr; + for (node = CERT_LIST_HEAD(mCertList); + !CERT_LIST_END(node, mCertList); + node = CERT_LIST_NEXT(node)) + { + nsCOMPtr cert = nsNSSCertificate::Create(node->cert); + if (!cert) { + rv = NS_ERROR_OUT_OF_MEMORY; + break; + } + + nsCOMPtr serializableCert = do_QueryInterface(cert); + rv = aStream->WriteCompoundObject(serializableCert, NS_GET_IID(nsIX509Cert), true); + if (NS_FAILED(rv)) { + break; + } + } + + return rv; +} + +NS_IMETHODIMP +nsNSSCertList::Read(nsIObjectInputStream* aStream) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + NS_ENSURE_STATE(mCertList); + nsresult rv = NS_OK; + + uint32_t certListLen; + rv = aStream->Read32(&certListLen); + if (NS_FAILED(rv)) { + return rv; + } + + for(uint32_t i = 0; i < certListLen; ++i) { + nsCOMPtr certSupports; + rv = aStream->ReadObject(true, getter_AddRefs(certSupports)); + if (NS_FAILED(rv)) { + break; + } + + nsCOMPtr cert = do_QueryInterface(certSupports); + rv = AddCert(cert); + if (NS_FAILED(rv)) { + break; + } + } + + return rv; +} + NS_IMETHODIMP nsNSSCertList::GetEnumerator(nsISimpleEnumerator** _retval) { diff --git a/security/manager/ssl/src/nsNSSCertificate.h b/security/manager/ssl/src/nsNSSCertificate.h index 468dec06b8e..4245b1547f4 100644 --- a/security/manager/ssl/src/nsNSSCertificate.h +++ b/security/manager/ssl/src/nsNSSCertificate.h @@ -82,11 +82,13 @@ SECStatus ConstructCERTCertListFromReversedDERArray( } // namespcae mozilla class nsNSSCertList: public nsIX509CertList, + public nsISerializable, public nsNSSShutDownObject { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIX509CERTLIST + NS_DECL_NSISERIALIZABLE // certList is adopted nsNSSCertList(mozilla::ScopedCERTCertList& certList,