diff options
Diffstat (limited to '')
-rw-r--r-- | security/manager/ssl/CommonSocketControl.cpp | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/security/manager/ssl/CommonSocketControl.cpp b/security/manager/ssl/CommonSocketControl.cpp new file mode 100644 index 0000000000..ef4ed33ee4 --- /dev/null +++ b/security/manager/ssl/CommonSocketControl.cpp @@ -0,0 +1,520 @@ +/* -*- 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<nsIX509Cert>& cert, + nsITransportSecurityInfo::OverridableErrorCategory + overridableErrorCategory) { + COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); + SetServerCert(cert, mozilla::psm::EVStatus::NotEV); + mOverridableErrorCategory = Some(overridableErrorCategory); +} + +static void CreateCertChain(nsTArray<RefPtr<nsIX509Cert>>& aOutput, + nsTArray<nsTArray<uint8_t>>&& aCertList) { + nsTArray<nsTArray<uint8_t>> certList = std::move(aCertList); + aOutput.Clear(); + for (auto& certBytes : certList) { + RefPtr<nsIX509Cert> cert = new nsNSSCertificate(std::move(certBytes)); + aOutput.AppendElement(cert); + } +} + +void CommonSocketControl::SetServerCert( + const nsCOMPtr<nsIX509Cert>& 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<nsTArray<uint8_t>>&& aCertList) { + COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD(); + return CreateCertChain(mSucceededCertChain, std::move(aCertList)); +} + +void CommonSocketControl::SetFailedCertChain( + nsTArray<nsTArray<uint8_t>>&& 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<nsCString>& 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<nsICertOverrideService> 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<nsIX509Cert> cert(GetServerCert()); + if (!cert) { + return NS_OK; + } + nsTArray<uint8_t> 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<const uint8_t*, const char*>(hostname.BeginReading()), + hostname.Length()); + if (rv != Success) { + return NS_OK; + } + + rv = CheckCertHostname(serverCertInput, hostnameInput); + if (rv != Success) { + return NS_OK; + } + + nsTArray<nsTArray<uint8_t>> rawDerCertList; + nsTArray<Span<const uint8_t>> 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<nsIX509Cert> 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<nsITransportSecurityInfo> 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<mozilla::dom::Promise> promise = + mozilla::dom::Promise::Create(globalObject, result); + if (result.Failed()) { + return result.StealNSResult(); + } + nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction( + "CommonSocketControl::AsyncGetSecurityInfo", + [promise, self = RefPtr{this}]() mutable { + nsCOMPtr<nsITransportSecurityInfo> securityInfo; + nsresult rv = self->GetSecurityInfo(getter_AddRefs(securityInfo)); + nsCOMPtr<nsIRunnable> 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<nsIEventTarget> 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; +} |