summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/CommonSocketControl.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--security/manager/ssl/CommonSocketControl.cpp520
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;
+}