/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * 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 "CommonSocketControl.h" #include "PublicKeyPinningService.h" #include "SharedCertVerifier.h" #include "SharedSSLState.h" #include "mozilla/ErrorResult.h" #include "mozilla/StaticPrefs_network.h" #include "mozilla/dom/Promise.h" #include "nsICertOverrideService.h" #include "nsISocketProvider.h" #include "nsITlsHandshakeListener.h" #include "nsNSSComponent.h" #include "nsNSSHelper.h" #include "ssl.h" #include "sslt.h" using namespace mozilla; extern LazyLogModule gPIPNSSLog; NS_IMPL_ISUPPORTS(CommonSocketControl, nsITLSSocketControl) CommonSocketControl::CommonSocketControl(const nsCString& aHostName, int32_t aPort, uint32_t aProviderFlags) : mHostName(aHostName), mPort(aPort), mOriginAttributes(), mCanceled(false), mSessionCacheInfo(), mHandshakeCompleted(false), mJoined(false), mSentClientCert(false), mFailedVerification(false), mSSLVersionUsed(nsITLSSocketControl::SSL_VERSION_UNKNOWN), mProviderFlags(aProviderFlags), mSecurityState(0), mErrorCode(0), mFailedCertChain(), mServerCert(nullptr), mSucceededCertChain(), mCipherSuite(), mKeaGroupName(), mSignatureSchemeName(), mProtocolVersion(), mCertificateTransparencyStatus(0), mIsAcceptedEch(), mIsDelegatedCredential(), mOverridableErrorCategory(), mMadeOCSPRequests(false), mUsedPrivateDNS(false), mIsEV(), mNPNCompleted(false), mNegotiatedNPN(), mResumed(false), mIsBuiltCertChainRootBuiltInRoot(false), mPeerId() { #ifdef DEBUG mOwningThread = PR_GetCurrentThread(); #endif } void CommonSocketControl::SetStatusErrorBits( const nsCOMPtr& cert, nsITransportSecurityInfo::OverridableErrorCategory overridableErrorCategory) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); SetServerCert(cert, mozilla::psm::EVStatus::NotEV); mOverridableErrorCategory = Some(overridableErrorCategory); } static void CreateCertChain(nsTArray>& aOutput, nsTArray>&& aCertList) { nsTArray> certList = std::move(aCertList); aOutput.Clear(); for (auto& certBytes : certList) { RefPtr cert = new nsNSSCertificate(std::move(certBytes)); aOutput.AppendElement(cert); } } void CommonSocketControl::SetServerCert( const nsCOMPtr& aServerCert, mozilla::psm::EVStatus aEVStatus) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); mServerCert = aServerCert; mIsEV = Some(aEVStatus == mozilla::psm::EVStatus::EV); } void CommonSocketControl::SetSucceededCertChain( nsTArray>&& aCertList) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); return CreateCertChain(mSucceededCertChain, std::move(aCertList)); } void CommonSocketControl::SetFailedCertChain( nsTArray>&& aCertList) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); return CreateCertChain(mFailedCertChain, std::move(aCertList)); } void CommonSocketControl::SetCanceled(PRErrorCode errorCode) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); MOZ_ASSERT(errorCode != 0); if (errorCode == 0) { errorCode = SEC_ERROR_LIBRARY_FAILURE; } mErrorCode = errorCode; mCanceled = true; } // NB: GetErrorCode may be called before an error code is set (if ever). In that // case, this returns 0, which is treated as a successful value. int32_t CommonSocketControl::GetErrorCode() { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); // We're in an inconsistent state if we think we've been canceled but no error // code was set or we haven't been canceled but an error code was set. MOZ_ASSERT( !((mCanceled && mErrorCode == 0) || (!mCanceled && mErrorCode != 0))); if ((mCanceled && mErrorCode == 0) || (!mCanceled && mErrorCode != 0)) { mCanceled = true; mErrorCode = SEC_ERROR_LIBRARY_FAILURE; } return mErrorCode; } NS_IMETHODIMP CommonSocketControl::ProxyStartSSL(void) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::StartTLS(void) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::SetNPNList(nsTArray& aNPNList) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::GetAlpnEarlySelection(nsACString& _retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::GetEarlyDataAccepted(bool* aEarlyDataAccepted) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::DriveHandshake(void) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::JoinConnection(const nsACString& npnProtocol, const nsACString& hostname, int32_t port, bool* _retval) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); nsresult rv = TestJoinConnection(npnProtocol, hostname, port, _retval); if (NS_SUCCEEDED(rv) && *_retval) { // All tests pass - this is joinable mJoined = true; } return rv; } NS_IMETHODIMP CommonSocketControl::TestJoinConnection(const nsACString& npnProtocol, const nsACString& hostname, int32_t port, bool* _retval) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); *_retval = false; // Different ports may not be joined together if (port != GetPort()) return NS_OK; // Make sure NPN has been completed and matches requested npnProtocol if (!mNPNCompleted || !mNegotiatedNPN.Equals(npnProtocol)) { return NS_OK; } IsAcceptableForHost(hostname, _retval); // sets _retval return NS_OK; } NS_IMETHODIMP CommonSocketControl::IsAcceptableForHost(const nsACString& hostname, bool* _retval) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); NS_ENSURE_ARG(_retval); *_retval = false; // If this is the same hostname then the certicate status does not // need to be considered. They are joinable. if (hostname.Equals(GetHostName())) { *_retval = true; return NS_OK; } // Before checking the server certificate we need to make sure the // handshake has completed. if (!mHandshakeCompleted || !HasServerCert()) { return NS_OK; } // Security checks can only be skipped when running xpcshell tests. if (PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) { nsCOMPtr overrideService = do_GetService(NS_CERTOVERRIDE_CONTRACTID); if (overrideService) { bool securityCheckDisabled = false; overrideService->GetSecurityCheckDisabled(&securityCheckDisabled); if (securityCheckDisabled) { *_retval = true; return NS_OK; } } } // If the cert has error bits (e.g. it is untrusted) then do not join. if (mOverridableErrorCategory.isSome()) { return NS_OK; } // If the connection is using client certificates then do not join // because the user decides on whether to send client certs to hosts on a // per-domain basis. if (mSentClientCert) return NS_OK; // Ensure that the server certificate covers the hostname that would // like to join this connection nsCOMPtr cert(GetServerCert()); if (!cert) { return NS_OK; } nsTArray certDER; if (NS_FAILED(cert->GetRawDER(certDER))) { return NS_OK; } // An empty mSucceededCertChain means the server certificate verification // failed before, so don't join in this case. if (mSucceededCertChain.IsEmpty()) { return NS_OK; } // See where CheckCertHostname() is called in // CertVerifier::VerifySSLServerCert. We are doing the same hostname-specific // checks here. If any hostname-specific checks are added to // CertVerifier::VerifySSLServerCert we need to add them here too. Input serverCertInput; mozilla::pkix::Result rv = serverCertInput.Init(certDER.Elements(), certDER.Length()); if (rv != Success) { return NS_OK; } Input hostnameInput; rv = hostnameInput.Init( BitwiseCast(hostname.BeginReading()), hostname.Length()); if (rv != Success) { return NS_OK; } rv = CheckCertHostname(serverCertInput, hostnameInput); if (rv != Success) { return NS_OK; } nsTArray> rawDerCertList; nsTArray> derCertSpanList; for (const auto& cert : mSucceededCertChain) { rawDerCertList.EmplaceBack(); nsresult nsrv = cert->GetRawDER(rawDerCertList.LastElement()); if (NS_FAILED(nsrv)) { return nsrv; } derCertSpanList.EmplaceBack(rawDerCertList.LastElement()); } bool chainHasValidPins; nsresult nsrv = mozilla::psm::PublicKeyPinningService::ChainHasValidPins( derCertSpanList, PromiseFlatCString(hostname).BeginReading(), Now(), mIsBuiltCertChainRootBuiltInRoot, chainHasValidPins, nullptr); if (NS_FAILED(nsrv)) { return NS_OK; } if (!chainHasValidPins) { return NS_OK; } // All tests pass *_retval = true; return NS_OK; } void CommonSocketControl::RebuildCertificateInfoFromSSLTokenCache() { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); if (!mSessionCacheInfo) { MOZ_LOG( gPIPNSSLog, LogLevel::Debug, ("CommonSocketControl::RebuildCertificateInfoFromSSLTokenCache cannot " "find cached info.")); return; } mozilla::net::SessionCacheInfo& info = *mSessionCacheInfo; nsCOMPtr cert( new nsNSSCertificate(std::move(info.mServerCertBytes))); if (info.mOverridableErrorCategory == nsITransportSecurityInfo::OverridableErrorCategory::ERROR_UNSET) { SetServerCert(cert, info.mEVStatus); } else { SetStatusErrorBits(cert, info.mOverridableErrorCategory); } SetCertificateTransparencyStatus(info.mCertificateTransparencyStatus); if (info.mSucceededCertChainBytes) { SetSucceededCertChain(std::move(*info.mSucceededCertChainBytes)); } if (info.mIsBuiltCertChainRootBuiltInRoot) { SetIsBuiltCertChainRootBuiltInRoot(*info.mIsBuiltCertChainRootBuiltInRoot); } if (info.mFailedCertChainBytes) { SetFailedCertChain(std::move(*info.mFailedCertChainBytes)); } } NS_IMETHODIMP CommonSocketControl::GetKEAUsed(int16_t* aKEAUsed) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::GetKEAKeyBits(uint32_t* aKEAKeyBits) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::GetProviderFlags(uint32_t* aProviderFlags) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); *aProviderFlags = mProviderFlags; return NS_OK; } NS_IMETHODIMP CommonSocketControl::GetSSLVersionUsed(int16_t* aSSLVersionUsed) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); *aSSLVersionUsed = mSSLVersionUsed; return NS_OK; } NS_IMETHODIMP CommonSocketControl::GetSSLVersionOffered(int16_t* aSSLVersionOffered) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::GetMACAlgorithmUsed(int16_t* aMACAlgorithmUsed) { return NS_ERROR_NOT_IMPLEMENTED; } bool CommonSocketControl::GetDenyClientCert() { return true; } void CommonSocketControl::SetDenyClientCert(bool aDenyClientCert) {} NS_IMETHODIMP CommonSocketControl::GetClientCertSent(bool* arg) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); *arg = mSentClientCert; return NS_OK; } NS_IMETHODIMP CommonSocketControl::GetFailedVerification(bool* arg) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); *arg = mFailedVerification; return NS_OK; } NS_IMETHODIMP CommonSocketControl::GetEsniTxt(nsACString& aEsniTxt) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::SetEsniTxt(const nsACString& aEsniTxt) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::GetEchConfig(nsACString& aEchConfig) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::SetEchConfig(const nsACString& aEchConfig) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::GetRetryEchConfig(nsACString& aEchConfig) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::SetHandshakeCallbackListener( nsITlsHandshakeCallbackListener* callback) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::DisableEarlyData(void) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP CommonSocketControl::GetPeerId(nsACString& aResult) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); if (!mPeerId.IsEmpty()) { aResult.Assign(mPeerId); return NS_OK; } if (mProviderFlags & nsISocketProvider::ANONYMOUS_CONNECT) { // See bug 466080 mPeerId.AppendLiteral("anon:"); } if (mProviderFlags & nsISocketProvider::NO_PERMANENT_STORAGE) { mPeerId.AppendLiteral("private:"); } if (mProviderFlags & nsISocketProvider::BE_CONSERVATIVE) { mPeerId.AppendLiteral("beConservative:"); } mPeerId.Append(mHostName); mPeerId.Append(':'); mPeerId.AppendInt(GetPort()); nsAutoCString suffix; mOriginAttributes.CreateSuffix(suffix); mPeerId.Append(suffix); aResult.Assign(mPeerId); return NS_OK; } NS_IMETHODIMP CommonSocketControl::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) { COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); // Make sure peerId is set. nsAutoCString unused; nsresult rv = GetPeerId(unused); if (NS_FAILED(rv)) { return rv; } nsCOMPtr securityInfo(new TransportSecurityInfo( mSecurityState, mErrorCode, mFailedCertChain.Clone(), mServerCert, mSucceededCertChain.Clone(), mCipherSuite, mKeaGroupName, mSignatureSchemeName, mProtocolVersion, mCertificateTransparencyStatus, mIsAcceptedEch, mIsDelegatedCredential, mOverridableErrorCategory, mMadeOCSPRequests, mUsedPrivateDNS, mIsEV, mNPNCompleted, mNegotiatedNPN, mResumed, mIsBuiltCertChainRootBuiltInRoot, mPeerId)); securityInfo.forget(aSecurityInfo); return NS_OK; } NS_IMETHODIMP CommonSocketControl::AsyncGetSecurityInfo(JSContext* aCx, mozilla::dom::Promise** aPromise) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); NS_ENSURE_ARG_POINTER(aCx); NS_ENSURE_ARG_POINTER(aPromise); nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx); if (!globalObject) { return NS_ERROR_UNEXPECTED; } ErrorResult result; RefPtr promise = mozilla::dom::Promise::Create(globalObject, result); if (result.Failed()) { return result.StealNSResult(); } nsCOMPtr runnable(NS_NewRunnableFunction( "CommonSocketControl::AsyncGetSecurityInfo", [promise, self = RefPtr{this}]() mutable { nsCOMPtr securityInfo; nsresult rv = self->GetSecurityInfo(getter_AddRefs(securityInfo)); nsCOMPtr runnable(NS_NewRunnableFunction( "CommonSocketControl::AsyncGetSecurityInfoResolve", [rv, promise = std::move(promise), securityInfo = std::move(securityInfo)]() { if (NS_FAILED(rv)) { promise->MaybeReject(rv); } else { promise->MaybeResolve(securityInfo); } })); NS_DispatchToMainThread(runnable.forget()); })); nsCOMPtr target( do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID)); if (!target) { return NS_ERROR_FAILURE; } nsresult rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL); if (NS_FAILED(rv)) { return rv; } promise.forget(aPromise); return NS_OK; }