summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/nsNSSCallbacks.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--security/manager/ssl/nsNSSCallbacks.cpp1382
1 files changed, 1382 insertions, 0 deletions
diff --git a/security/manager/ssl/nsNSSCallbacks.cpp b/security/manager/ssl/nsNSSCallbacks.cpp
new file mode 100644
index 0000000000..54e290aa91
--- /dev/null
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -0,0 +1,1382 @@
+/* -*- 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 "nsNSSCallbacks.h"
+
+#include "PSMRunnable.h"
+#include "ScopedNSSTypes.h"
+#include "SharedCertVerifier.h"
+#include "SharedSSLState.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Casting.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Span.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIPrompt.h"
+#include "nsIProtocolProxyService.h"
+#include "nsISupportsPriority.h"
+#include "nsIStreamLoader.h"
+#include "nsITokenDialogs.h"
+#include "nsIUploadChannel.h"
+#include "nsIWebProgressListener.h"
+#include "nsNSSCertHelper.h"
+#include "nsNSSCertificate.h"
+#include "nsNSSComponent.h"
+#include "nsNSSHelper.h"
+#include "nsNSSIOLayer.h"
+#include "nsNetUtil.h"
+#include "nsProtectedAuthThread.h"
+#include "nsProxyRelease.h"
+#include "nsStringStream.h"
+#include "mozpkix/pkixtypes.h"
+#include "ssl.h"
+#include "sslproto.h"
+#include "SSLTokensCache.h"
+
+#include "TrustOverrideUtils.h"
+#include "TrustOverride-SymantecData.inc"
+#include "TrustOverride-AppleGoogleDigiCertData.inc"
+#include "TrustOverride-TestImminentDistrustData.inc"
+
+using namespace mozilla;
+using namespace mozilla::pkix;
+using namespace mozilla::psm;
+
+extern LazyLogModule gPIPNSSLog;
+
+static void AccumulateCipherSuite(Telemetry::HistogramID probe,
+ const SSLChannelInfo& channelInfo);
+
+namespace {
+
+// Bits in bit mask for SSL_REASONS_FOR_NOT_FALSE_STARTING telemetry probe
+// These bits are numbered so that the least subtle issues have higher values.
+// This should make it easier for us to interpret the results.
+const uint32_t POSSIBLE_VERSION_DOWNGRADE = 4;
+const uint32_t POSSIBLE_CIPHER_SUITE_DOWNGRADE = 2;
+const uint32_t KEA_NOT_SUPPORTED = 1;
+
+} // namespace
+
+class OCSPRequest final : public nsIStreamLoaderObserver, public nsIRunnable {
+ public:
+ OCSPRequest(const nsACString& aiaLocation,
+ const OriginAttributes& originAttributes,
+ const uint8_t (&ocspRequest)[OCSP_REQUEST_MAX_LENGTH],
+ size_t ocspRequestLength, TimeDuration timeout);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISTREAMLOADEROBSERVER
+ NS_DECL_NSIRUNNABLE
+
+ nsresult DispatchToMainThreadAndWait();
+ nsresult GetResponse(/*out*/ Vector<uint8_t>& response);
+
+ private:
+ ~OCSPRequest() = default;
+
+ static void OnTimeout(nsITimer* timer, void* closure);
+ nsresult NotifyDone(nsresult rv, MonitorAutoLock& proofOfLock);
+
+ // mMonitor provides the memory barrier protecting these member variables.
+ // What happens is the originating thread creates an OCSPRequest object with
+ // the information necessary to perform an OCSP request. It sends the object
+ // to the main thread and waits on the monitor for the operation to complete.
+ // On the main thread, a channel is set up to perform the request. This gets
+ // dispatched to necko. At the same time, a timeout timer is initialized. If
+ // the necko request completes, the response data is filled out, mNotifiedDone
+ // is set to true, and the monitor is notified. The original thread then wakes
+ // up and continues with the results that have been filled out. If the request
+ // times out, again the response data is filled out, mNotifiedDone is set to
+ // true, and the monitor is notified. The first of these two events wins. That
+ // is, if the timeout timer fires but the request completes shortly after, the
+ // caller will see the request as having timed out.
+ // When the request completes (i.e. OnStreamComplete runs), the timer will be
+ // cancelled. This is how we know the closure in OnTimeout is valid. If the
+ // timer fires before OnStreamComplete runs, it should be safe to not cancel
+ // the request because necko has a strong reference to it.
+ Monitor mMonitor;
+ bool mNotifiedDone;
+ nsCOMPtr<nsIStreamLoader> mLoader;
+ const nsCString mAIALocation;
+ const OriginAttributes mOriginAttributes;
+ const mozilla::Span<const char> mPOSTData;
+ const TimeDuration mTimeout;
+ nsCOMPtr<nsITimer> mTimeoutTimer;
+ TimeStamp mStartTime;
+ nsresult mResponseResult;
+ Vector<uint8_t> mResponseBytes;
+};
+
+NS_IMPL_ISUPPORTS(OCSPRequest, nsIStreamLoaderObserver, nsIRunnable)
+
+OCSPRequest::OCSPRequest(const nsACString& aiaLocation,
+ const OriginAttributes& originAttributes,
+ const uint8_t (&ocspRequest)[OCSP_REQUEST_MAX_LENGTH],
+ size_t ocspRequestLength, TimeDuration timeout)
+ : mMonitor("OCSPRequest.mMonitor"),
+ mNotifiedDone(false),
+ mLoader(nullptr),
+ mAIALocation(aiaLocation),
+ mOriginAttributes(originAttributes),
+ mPOSTData(reinterpret_cast<const char*>(ocspRequest), ocspRequestLength),
+ mTimeout(timeout),
+ mTimeoutTimer(nullptr),
+ mStartTime(),
+ mResponseResult(NS_ERROR_FAILURE),
+ mResponseBytes() {
+ MOZ_ASSERT(ocspRequestLength <= OCSP_REQUEST_MAX_LENGTH);
+}
+
+nsresult OCSPRequest::DispatchToMainThreadAndWait() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ if (NS_IsMainThread()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MonitorAutoLock lock(mMonitor);
+ nsresult rv = NS_DispatchToMainThread(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ while (!mNotifiedDone) {
+ lock.Wait();
+ }
+
+ TimeStamp endTime = TimeStamp::Now();
+ // CERT_VALIDATION_HTTP_REQUEST_RESULT:
+ // 0: request timed out
+ // 1: request succeeded
+ // 2: request failed
+ // 3: internal error
+ // If mStartTime was never set, we consider this an internal error.
+ // Otherwise, we managed to at least send the request.
+ if (mStartTime.IsNull()) {
+ Telemetry::Accumulate(Telemetry::CERT_VALIDATION_HTTP_REQUEST_RESULT, 3);
+ } else if (mResponseResult == NS_ERROR_NET_TIMEOUT) {
+ Telemetry::Accumulate(Telemetry::CERT_VALIDATION_HTTP_REQUEST_RESULT, 0);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::CERT_VALIDATION_HTTP_REQUEST_CANCELED_TIME, mStartTime,
+ endTime);
+ } else if (NS_SUCCEEDED(mResponseResult)) {
+ Telemetry::Accumulate(Telemetry::CERT_VALIDATION_HTTP_REQUEST_RESULT, 1);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::CERT_VALIDATION_HTTP_REQUEST_SUCCEEDED_TIME, mStartTime,
+ endTime);
+ } else {
+ Telemetry::Accumulate(Telemetry::CERT_VALIDATION_HTTP_REQUEST_RESULT, 2);
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::CERT_VALIDATION_HTTP_REQUEST_FAILED_TIME, mStartTime,
+ endTime);
+ }
+ return rv;
+}
+
+nsresult OCSPRequest::GetResponse(/*out*/ Vector<uint8_t>& response) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ if (NS_IsMainThread()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MonitorAutoLock lock(mMonitor);
+ if (!mNotifiedDone) {
+ return NS_ERROR_IN_PROGRESS;
+ }
+ if (NS_FAILED(mResponseResult)) {
+ return mResponseResult;
+ }
+ response.clear();
+ if (!response.append(mResponseBytes.begin(), mResponseBytes.length())) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+static constexpr auto OCSP_REQUEST_MIME_TYPE = "application/ocsp-request"_ns;
+static constexpr auto OCSP_REQUEST_METHOD = "POST"_ns;
+
+NS_IMETHODIMP
+OCSPRequest::Run() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MonitorAutoLock lock(mMonitor);
+
+ nsCOMPtr<nsIIOService> ios = do_GetIOService();
+ if (!ios) {
+ return NotifyDone(NS_ERROR_FAILURE, lock);
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), mAIALocation);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(NS_ERROR_MALFORMED_URI, lock);
+ }
+ nsAutoCString scheme;
+ rv = uri->GetScheme(scheme);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+ if (!scheme.LowerCaseEqualsLiteral("http")) {
+ return NotifyDone(NS_ERROR_MALFORMED_URI, lock);
+ }
+
+ // See bug 1219935.
+ // We should not send OCSP request if the PAC is still loading.
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+
+ if (pps->GetIsPACLoading()) {
+ return NotifyDone(NS_ERROR_FAILURE, lock);
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = ios->NewChannel(mAIALocation, nullptr, nullptr,
+ nullptr, // aLoadingNode
+ nsContentUtils::GetSystemPrincipal(),
+ nullptr, // aTriggeringPrincipal
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER, getter_AddRefs(channel));
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+
+ // Security operations scheduled through normal HTTP channels are given
+ // high priority to accommodate real time OCSP transactions.
+ nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(channel);
+ if (priorityChannel) {
+ priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
+ }
+
+ channel->SetLoadFlags(nsIRequest::LOAD_ANONYMOUS |
+ nsIChannel::LOAD_BYPASS_SERVICE_WORKER |
+ nsIChannel::LOAD_BYPASS_URL_CLASSIFIER);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+
+ // Prevent HTTPS-Only Mode from upgrading the OCSP request.
+ uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
+ httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT;
+ loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
+
+ // allow deprecated HTTP request from SystemPrincipal
+ loadInfo->SetAllowDeprecatedSystemRequests(true);
+
+ // For OCSP requests, only the first party domain and private browsing id
+ // aspects of origin attributes are used. This means that:
+ // a) if first party isolation is enabled, OCSP requests will be isolated
+ // according to the first party domain of the original https request
+ // b) OCSP requests are shared across different containers as long as first
+ // party isolation is not enabled and none of the containers are in private
+ // browsing mode.
+ if (mOriginAttributes != OriginAttributes()) {
+ OriginAttributes attrs;
+ attrs.mFirstPartyDomain = mOriginAttributes.mFirstPartyDomain;
+ attrs.mPrivateBrowsingId = mOriginAttributes.mPrivateBrowsingId;
+
+ rv = loadInfo->SetOriginAttributes(attrs);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+ }
+
+ nsCOMPtr<nsIInputStream> uploadStream;
+ rv = NS_NewByteInputStream(getter_AddRefs(uploadStream), mPOSTData,
+ NS_ASSIGNMENT_COPY);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(channel));
+ if (!uploadChannel) {
+ return NotifyDone(NS_ERROR_FAILURE, lock);
+ }
+ rv = uploadChannel->SetUploadStream(uploadStream, OCSP_REQUEST_MIME_TYPE, -1);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+ // Do not use SPDY or HTTP3 for internal security operations. It could result
+ // in the silent upgrade to ssl, which in turn could require an SSL
+ // operation to fulfill something like an OCSP fetch, which is an
+ // endless loop.
+ nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(channel);
+ if (!internalChannel) {
+ return NotifyDone(rv, lock);
+ }
+ rv = internalChannel->SetAllowSpdy(false);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+ rv = internalChannel->SetAllowHttp3(false);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+ nsCOMPtr<nsIHttpChannel> hchan = do_QueryInterface(channel);
+ if (!hchan) {
+ return NotifyDone(NS_ERROR_FAILURE, lock);
+ }
+ rv = hchan->SetAllowSTS(false);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+ rv = hchan->SetRequestMethod(OCSP_REQUEST_METHOD);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+
+ rv = NS_NewStreamLoader(getter_AddRefs(mLoader), this);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+
+ rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTimeoutTimer), OCSPRequest::OnTimeout, this,
+ mTimeout.ToMilliseconds(), nsITimer::TYPE_ONE_SHOT, "OCSPRequest::Run");
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+ rv = hchan->AsyncOpen(this->mLoader);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+ mStartTime = TimeStamp::Now();
+ return NS_OK;
+}
+
+nsresult OCSPRequest::NotifyDone(nsresult rv, MonitorAutoLock& lock) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mNotifiedDone) {
+ return mResponseResult;
+ }
+ mLoader = nullptr;
+ mResponseResult = rv;
+ if (mTimeoutTimer) {
+ Unused << mTimeoutTimer->Cancel();
+ }
+ mNotifiedDone = true;
+ lock.Notify();
+ return rv;
+}
+
+NS_IMETHODIMP
+OCSPRequest::OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
+ nsresult aStatus, uint32_t responseLen,
+ const uint8_t* responseBytes) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MonitorAutoLock lock(mMonitor);
+
+ nsCOMPtr<nsIRequest> req;
+ nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+
+ if (NS_FAILED(aStatus)) {
+ return NotifyDone(aStatus, lock);
+ }
+
+ nsCOMPtr<nsIHttpChannel> hchan = do_QueryInterface(req);
+ if (!hchan) {
+ return NotifyDone(NS_ERROR_FAILURE, lock);
+ }
+
+ bool requestSucceeded;
+ rv = hchan->GetRequestSucceeded(&requestSucceeded);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+ if (!requestSucceeded) {
+ return NotifyDone(NS_ERROR_FAILURE, lock);
+ }
+
+ unsigned int rcode;
+ rv = hchan->GetResponseStatus(&rcode);
+ if (NS_FAILED(rv)) {
+ return NotifyDone(rv, lock);
+ }
+ if (rcode != 200) {
+ return NotifyDone(NS_ERROR_FAILURE, lock);
+ }
+
+ mResponseBytes.clear();
+ if (!mResponseBytes.append(responseBytes, responseLen)) {
+ return NotifyDone(NS_ERROR_OUT_OF_MEMORY, lock);
+ }
+ mResponseResult = aStatus;
+
+ return NotifyDone(NS_OK, lock);
+}
+
+void OCSPRequest::OnTimeout(nsITimer* timer, void* closure) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ // We know the OCSPRequest is still alive because if the request had completed
+ // (i.e. OnStreamComplete ran), the timer would have been cancelled in
+ // NotifyDone.
+ OCSPRequest* self = static_cast<OCSPRequest*>(closure);
+ MonitorAutoLock lock(self->mMonitor);
+ self->mTimeoutTimer = nullptr;
+ self->NotifyDone(NS_ERROR_NET_TIMEOUT, lock);
+}
+
+mozilla::pkix::Result DoOCSPRequest(
+ const nsCString& aiaLocation, const OriginAttributes& originAttributes,
+ uint8_t (&ocspRequest)[OCSP_REQUEST_MAX_LENGTH], size_t ocspRequestLength,
+ TimeDuration timeout, /*out*/ Vector<uint8_t>& result) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ if (NS_IsMainThread()) {
+ return mozilla::pkix::Result::ERROR_OCSP_UNKNOWN_CERT;
+ }
+
+ if (ocspRequestLength > OCSP_REQUEST_MAX_LENGTH) {
+ return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
+ result.clear();
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("DoOCSPRequest to '%s'", aiaLocation.get()));
+
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(sts);
+ if (!sts) {
+ return mozilla::pkix::Result::FATAL_ERROR_INVALID_STATE;
+ }
+ bool onSTSThread;
+ nsresult rv = sts->IsOnCurrentThread(&onSTSThread);
+ if (NS_FAILED(rv)) {
+ return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+ MOZ_ASSERT(!onSTSThread);
+ if (onSTSThread) {
+ return mozilla::pkix::Result::FATAL_ERROR_INVALID_STATE;
+ }
+
+ RefPtr<OCSPRequest> request(new OCSPRequest(
+ aiaLocation, originAttributes, ocspRequest, ocspRequestLength, timeout));
+ rv = request->DispatchToMainThreadAndWait();
+ if (NS_FAILED(rv)) {
+ return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+ rv = request->GetResponse(result);
+ if (NS_FAILED(rv)) {
+ if (rv == NS_ERROR_MALFORMED_URI) {
+ return mozilla::pkix::Result::ERROR_CERT_BAD_ACCESS_LOCATION;
+ }
+ return mozilla::pkix::Result::ERROR_OCSP_SERVER_ERROR;
+ }
+ return Success;
+}
+
+static char* ShowProtectedAuthPrompt(PK11SlotInfo* slot,
+ nsIInterfaceRequestor* ir) {
+ if (!NS_IsMainThread()) {
+ NS_ERROR("ShowProtectedAuthPrompt called off the main thread");
+ return nullptr;
+ }
+
+ char* protAuthRetVal = nullptr;
+
+ // Get protected auth dialogs
+ nsCOMPtr<nsITokenDialogs> dialogs;
+ nsresult nsrv =
+ getNSSDialogs(getter_AddRefs(dialogs), NS_GET_IID(nsITokenDialogs),
+ NS_TOKENDIALOGS_CONTRACTID);
+ if (NS_SUCCEEDED(nsrv)) {
+ RefPtr<nsProtectedAuthThread> protectedAuthRunnable =
+ new nsProtectedAuthThread();
+ protectedAuthRunnable->SetParams(slot);
+
+ nsrv = dialogs->DisplayProtectedAuth(ir, protectedAuthRunnable);
+
+ // We call join on the thread,
+ // so we can be sure that no simultaneous access will happen.
+ protectedAuthRunnable->Join();
+
+ if (NS_SUCCEEDED(nsrv)) {
+ SECStatus rv = protectedAuthRunnable->GetResult();
+ switch (rv) {
+ case SECSuccess:
+ protAuthRetVal =
+ ToNewCString(nsDependentCString(PK11_PW_AUTHENTICATED));
+ break;
+ case SECWouldBlock:
+ protAuthRetVal = ToNewCString(nsDependentCString(PK11_PW_RETRY));
+ break;
+ default:
+ protAuthRetVal = nullptr;
+ break;
+ }
+ }
+ }
+
+ return protAuthRetVal;
+}
+
+class PK11PasswordPromptRunnable : public SyncRunnableBase {
+ public:
+ PK11PasswordPromptRunnable(PK11SlotInfo* slot, nsIInterfaceRequestor* ir)
+ : mResult(nullptr), mSlot(slot), mIR(ir) {}
+ virtual ~PK11PasswordPromptRunnable() = default;
+
+ char* mResult; // out
+ virtual void RunOnTargetThread() override;
+
+ private:
+ PK11SlotInfo* const mSlot; // in
+ nsIInterfaceRequestor* const mIR; // in
+};
+
+void PK11PasswordPromptRunnable::RunOnTargetThread() {
+ nsresult rv;
+ nsCOMPtr<nsIPrompt> prompt;
+ if (!mIR) {
+ rv = nsNSSComponent::GetNewPrompter(getter_AddRefs(prompt));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ } else {
+ prompt = do_GetInterface(mIR);
+ MOZ_ASSERT(prompt, "Interface requestor should implement nsIPrompt");
+ }
+
+ if (!prompt) {
+ return;
+ }
+
+ if (PK11_ProtectedAuthenticationPath(mSlot)) {
+ mResult = ShowProtectedAuthPrompt(mSlot, mIR);
+ return;
+ }
+
+ nsAutoString promptString;
+ if (PK11_IsInternal(mSlot)) {
+ rv = GetPIPNSSBundleString("CertPasswordPromptDefault", promptString);
+ } else {
+ AutoTArray<nsString, 1> formatStrings = {
+ NS_ConvertUTF8toUTF16(PK11_GetTokenName(mSlot))};
+ rv = PIPBundleFormatStringFromName("CertPasswordPrompt", formatStrings,
+ promptString);
+ }
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsString password;
+ // |checkState| is unused because |checkMsg| (the argument just before it) is
+ // null, but XPConnect requires it to point to a valid bool nonetheless.
+ bool checkState = false;
+ bool userClickedOK = false;
+ rv = prompt->PromptPassword(nullptr, promptString.get(),
+ getter_Copies(password), nullptr, &checkState,
+ &userClickedOK);
+ if (NS_FAILED(rv) || !userClickedOK) {
+ return;
+ }
+
+ mResult = ToNewUTF8String(password);
+}
+
+char* PK11PasswordPrompt(PK11SlotInfo* slot, PRBool /*retry*/, void* arg) {
+ RefPtr<PK11PasswordPromptRunnable> runnable(new PK11PasswordPromptRunnable(
+ slot, static_cast<nsIInterfaceRequestor*>(arg)));
+ runnable->DispatchToMainThreadAndWait();
+ return runnable->mResult;
+}
+
+nsCString getKeaGroupName(uint32_t aKeaGroup) {
+ nsCString groupName;
+ switch (aKeaGroup) {
+ case ssl_grp_ec_secp256r1:
+ groupName = "P256"_ns;
+ break;
+ case ssl_grp_ec_secp384r1:
+ groupName = "P384"_ns;
+ break;
+ case ssl_grp_ec_secp521r1:
+ groupName = "P521"_ns;
+ break;
+ case ssl_grp_ec_curve25519:
+ groupName = "x25519"_ns;
+ break;
+ case ssl_grp_ffdhe_2048:
+ groupName = "FF 2048"_ns;
+ break;
+ case ssl_grp_ffdhe_3072:
+ groupName = "FF 3072"_ns;
+ break;
+ case ssl_grp_none:
+ groupName = "none"_ns;
+ break;
+ case ssl_grp_ffdhe_custom:
+ groupName = "custom"_ns;
+ break;
+ // All other groups are not enabled in Firefox. See namedGroups in
+ // nsNSSIOLayer.cpp.
+ default:
+ // This really shouldn't happen!
+ MOZ_ASSERT_UNREACHABLE("Invalid key exchange group.");
+ groupName = "unknown group"_ns;
+ }
+ return groupName;
+}
+
+nsCString getSignatureName(uint32_t aSignatureScheme) {
+ nsCString signatureName;
+ switch (aSignatureScheme) {
+ case ssl_sig_none:
+ signatureName = "none"_ns;
+ break;
+ case ssl_sig_rsa_pkcs1_sha1:
+ signatureName = "RSA-PKCS1-SHA1"_ns;
+ break;
+ case ssl_sig_rsa_pkcs1_sha256:
+ signatureName = "RSA-PKCS1-SHA256"_ns;
+ break;
+ case ssl_sig_rsa_pkcs1_sha384:
+ signatureName = "RSA-PKCS1-SHA384"_ns;
+ break;
+ case ssl_sig_rsa_pkcs1_sha512:
+ signatureName = "RSA-PKCS1-SHA512"_ns;
+ break;
+ case ssl_sig_ecdsa_secp256r1_sha256:
+ signatureName = "ECDSA-P256-SHA256"_ns;
+ break;
+ case ssl_sig_ecdsa_secp384r1_sha384:
+ signatureName = "ECDSA-P384-SHA384"_ns;
+ break;
+ case ssl_sig_ecdsa_secp521r1_sha512:
+ signatureName = "ECDSA-P521-SHA512"_ns;
+ break;
+ case ssl_sig_rsa_pss_sha256:
+ signatureName = "RSA-PSS-SHA256"_ns;
+ break;
+ case ssl_sig_rsa_pss_sha384:
+ signatureName = "RSA-PSS-SHA384"_ns;
+ break;
+ case ssl_sig_rsa_pss_sha512:
+ signatureName = "RSA-PSS-SHA512"_ns;
+ break;
+ case ssl_sig_ecdsa_sha1:
+ signatureName = "ECDSA-SHA1"_ns;
+ break;
+ case ssl_sig_rsa_pkcs1_sha1md5:
+ signatureName = "RSA-PKCS1-SHA1MD5"_ns;
+ break;
+ // All other groups are not enabled in Firefox. See sEnabledSignatureSchemes
+ // in nsNSSIOLayer.cpp.
+ default:
+ // This really shouldn't happen!
+ MOZ_ASSERT_UNREACHABLE("Invalid signature scheme.");
+ signatureName = "unknown signature"_ns;
+ }
+ return signatureName;
+}
+
+// call with shutdown prevention lock held
+static void PreliminaryHandshakeDone(PRFileDesc* fd) {
+ nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*)fd->higher->secret;
+ if (!infoObject) return;
+
+ SSLChannelInfo channelInfo;
+ if (SSL_GetChannelInfo(fd, &channelInfo, sizeof(channelInfo)) == SECSuccess) {
+ infoObject->SetSSLVersionUsed(channelInfo.protocolVersion);
+ infoObject->SetEarlyDataAccepted(channelInfo.earlyDataAccepted);
+ infoObject->SetResumed(channelInfo.resumed);
+
+ SSLCipherSuiteInfo cipherInfo;
+ if (SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo,
+ sizeof cipherInfo) == SECSuccess) {
+ /* Set the Status information */
+ infoObject->mHaveCipherSuiteAndProtocol = true;
+ infoObject->mCipherSuite = channelInfo.cipherSuite;
+ infoObject->mProtocolVersion = channelInfo.protocolVersion & 0xFF;
+ infoObject->mKeaGroup.Assign(getKeaGroupName(channelInfo.keaGroup));
+ infoObject->mSignatureSchemeName.Assign(
+ getSignatureName(channelInfo.signatureScheme));
+ infoObject->SetKEAUsed(channelInfo.keaType);
+ infoObject->SetKEAKeyBits(channelInfo.keaKeyBits);
+ infoObject->SetMACAlgorithmUsed(cipherInfo.macAlgorithm);
+ infoObject->mIsDelegatedCredential = channelInfo.peerDelegCred;
+ infoObject->mIsAcceptedEch = channelInfo.echAccepted;
+ }
+ }
+
+ // Don't update NPN details on renegotiation.
+ if (infoObject->IsPreliminaryHandshakeDone()) {
+ return;
+ }
+
+ // Get the NPN value.
+ SSLNextProtoState state;
+ unsigned char npnbuf[256];
+ unsigned int npnlen;
+
+ if (SSL_GetNextProto(fd, &state, npnbuf, &npnlen,
+ AssertedCast<unsigned int>(ArrayLength(npnbuf))) ==
+ SECSuccess) {
+ if (state == SSL_NEXT_PROTO_NEGOTIATED ||
+ state == SSL_NEXT_PROTO_SELECTED) {
+ infoObject->SetNegotiatedNPN(BitwiseCast<char*, unsigned char*>(npnbuf),
+ npnlen);
+ } else {
+ infoObject->SetNegotiatedNPN(nullptr, 0);
+ }
+ mozilla::Telemetry::Accumulate(Telemetry::SSL_NPN_TYPE, state);
+ } else {
+ infoObject->SetNegotiatedNPN(nullptr, 0);
+ }
+
+ infoObject->SetPreliminaryHandshakeDone();
+}
+
+SECStatus CanFalseStartCallback(PRFileDesc* fd, void* client_data,
+ PRBool* canFalseStart) {
+ *canFalseStart = false;
+
+ nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*)fd->higher->secret;
+ if (!infoObject) {
+ PR_SetError(PR_INVALID_STATE_ERROR, 0);
+ return SECFailure;
+ }
+
+ infoObject->SetFalseStartCallbackCalled();
+
+ PreliminaryHandshakeDone(fd);
+
+ uint32_t reasonsForNotFalseStarting = 0;
+
+ SSLChannelInfo channelInfo;
+ if (SSL_GetChannelInfo(fd, &channelInfo, sizeof(channelInfo)) != SECSuccess) {
+ return SECSuccess;
+ }
+
+ SSLCipherSuiteInfo cipherInfo;
+ if (SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo,
+ sizeof(cipherInfo)) != SECSuccess) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("CanFalseStartCallback [%p] failed - "
+ " KEA %d\n",
+ fd, static_cast<int32_t>(channelInfo.keaType)));
+ return SECSuccess;
+ }
+
+ // Prevent version downgrade attacks from TLS 1.2, and avoid False Start for
+ // TLS 1.3 and later. See Bug 861310 for all the details as to why.
+ if (channelInfo.protocolVersion != SSL_LIBRARY_VERSION_TLS_1_2) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("CanFalseStartCallback [%p] failed - "
+ "SSL Version must be TLS 1.2, was %x\n",
+ fd, static_cast<int32_t>(channelInfo.protocolVersion)));
+ reasonsForNotFalseStarting |= POSSIBLE_VERSION_DOWNGRADE;
+ }
+
+ // See bug 952863 for why ECDHE is allowed, but DHE (and RSA) are not.
+ if (channelInfo.keaType != ssl_kea_ecdh) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("CanFalseStartCallback [%p] failed - "
+ "unsupported KEA %d\n",
+ fd, static_cast<int32_t>(channelInfo.keaType)));
+ reasonsForNotFalseStarting |= KEA_NOT_SUPPORTED;
+ }
+
+ // Prevent downgrade attacks on the symmetric cipher. We do not allow CBC
+ // mode due to BEAST, POODLE, and other attacks on the MAC-then-Encrypt
+ // design. See bug 1109766 for more details.
+ if (cipherInfo.macAlgorithm != ssl_mac_aead) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("CanFalseStartCallback [%p] failed - non-AEAD cipher used, %d, "
+ "is not supported with False Start.\n",
+ fd, static_cast<int32_t>(cipherInfo.symCipher)));
+ reasonsForNotFalseStarting |= POSSIBLE_CIPHER_SUITE_DOWNGRADE;
+ }
+
+ // XXX: An attacker can choose which protocols are advertised in the
+ // NPN extension. TODO(Bug 861311): We should restrict the ability
+ // of an attacker leverage this capability by restricting false start
+ // to the same protocol we previously saw for the server, after the
+ // first successful connection to the server.
+
+ Telemetry::Accumulate(Telemetry::SSL_REASONS_FOR_NOT_FALSE_STARTING,
+ reasonsForNotFalseStarting);
+
+ if (reasonsForNotFalseStarting == 0) {
+ *canFalseStart = PR_TRUE;
+ infoObject->SetFalseStarted();
+ infoObject->NoteTimeUntilReady();
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("CanFalseStartCallback [%p] ok\n", fd));
+ }
+
+ return SECSuccess;
+}
+
+static void AccumulateNonECCKeySize(Telemetry::HistogramID probe,
+ uint32_t bits) {
+ unsigned int value = bits < 512 ? 1
+ : bits == 512 ? 2
+ : bits < 768 ? 3
+ : bits == 768 ? 4
+ : bits < 1024 ? 5
+ : bits == 1024 ? 6
+ : bits < 1280 ? 7
+ : bits == 1280 ? 8
+ : bits < 1536 ? 9
+ : bits == 1536 ? 10
+ : bits < 2048 ? 11
+ : bits == 2048 ? 12
+ : bits < 3072 ? 13
+ : bits == 3072 ? 14
+ : bits < 4096 ? 15
+ : bits == 4096 ? 16
+ : bits < 8192 ? 17
+ : bits == 8192 ? 18
+ : bits < 16384 ? 19
+ : bits == 16384 ? 20
+ : 0;
+ Telemetry::Accumulate(probe, value);
+}
+
+// XXX: This attempts to map a bit count to an ECC named curve identifier. In
+// the vast majority of situations, we only have the Suite B curves available.
+// In that case, this mapping works fine. If we were to have more curves
+// available, the mapping would be ambiguous since there could be multiple
+// named curves for a given size (e.g. secp256k1 vs. secp256r1). We punt on
+// that for now. See also NSS bug 323674.
+static void AccumulateECCCurve(Telemetry::HistogramID probe, uint32_t bits) {
+ unsigned int value = bits == 255 ? 29 // Curve25519
+ : bits == 256 ? 23 // P-256
+ : bits == 384 ? 24 // P-384
+ : bits == 521 ? 25 // P-521
+ : 0; // Unknown
+ Telemetry::Accumulate(probe, value);
+}
+
+static void AccumulateCipherSuite(Telemetry::HistogramID probe,
+ const SSLChannelInfo& channelInfo) {
+ uint32_t value;
+ switch (channelInfo.cipherSuite) {
+ // ECDHE key exchange
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ value = 1;
+ break;
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ value = 2;
+ break;
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ value = 3;
+ break;
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ value = 4;
+ break;
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ value = 5;
+ break;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ value = 6;
+ break;
+ case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ value = 7;
+ break;
+ case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ value = 10;
+ break;
+ case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+ value = 11;
+ break;
+ case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ value = 12;
+ break;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ value = 13;
+ break;
+ case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ value = 14;
+ break;
+ // DHE key exchange
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ value = 21;
+ break;
+ case TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+ value = 22;
+ break;
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ value = 23;
+ break;
+ case TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+ value = 24;
+ break;
+ case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ value = 25;
+ break;
+ case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ value = 26;
+ break;
+ case TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+ value = 27;
+ break;
+ case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ value = 28;
+ break;
+ case TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+ value = 29;
+ break;
+ case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ value = 30;
+ break;
+ // ECDH key exchange
+ case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ value = 41;
+ break;
+ case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ value = 42;
+ break;
+ case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ value = 43;
+ break;
+ case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ value = 44;
+ break;
+ case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ value = 45;
+ break;
+ case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ value = 46;
+ break;
+ // RSA key exchange
+ case TLS_RSA_WITH_AES_128_CBC_SHA:
+ value = 61;
+ break;
+ case TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+ value = 62;
+ break;
+ case TLS_RSA_WITH_AES_256_CBC_SHA:
+ value = 63;
+ break;
+ case TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+ value = 64;
+ break;
+ case SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA:
+ value = 65;
+ break;
+ case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ value = 66;
+ break;
+ case TLS_RSA_WITH_SEED_CBC_SHA:
+ value = 67;
+ break;
+ case TLS_RSA_WITH_AES_128_GCM_SHA256:
+ value = 68;
+ break;
+ case TLS_RSA_WITH_AES_256_GCM_SHA384:
+ value = 69;
+ break;
+ // TLS 1.3 PSK resumption
+ case TLS_AES_128_GCM_SHA256:
+ value = 70;
+ break;
+ case TLS_CHACHA20_POLY1305_SHA256:
+ value = 71;
+ break;
+ case TLS_AES_256_GCM_SHA384:
+ value = 72;
+ break;
+ // unknown
+ default:
+ value = 0;
+ break;
+ }
+ MOZ_ASSERT(value != 0);
+ Telemetry::Accumulate(probe, value);
+}
+
+// In the case of session resumption, the AuthCertificate hook has been bypassed
+// (because we've previously successfully connected to our peer). That being the
+// case, we unfortunately don't know what the verified certificate chain was, if
+// the peer's server certificate verified as extended validation, or what its CT
+// status is (if enabled). To address this, we attempt to build a certificate
+// chain here using as much of the original context as possible (e.g. stapled
+// OCSP responses, SCTs, the hostname, the first party domain, etc.). Note that
+// because we are on the socket thread, this must not cause any network
+// requests, hence the use of FLAG_LOCAL_ONLY.
+static void RebuildVerifiedCertificateInformation(PRFileDesc* fd,
+ nsNSSSocketInfo* infoObject) {
+ MOZ_ASSERT(fd);
+ MOZ_ASSERT(infoObject);
+
+ if (!fd || !infoObject) {
+ return;
+ }
+
+ UniqueCERTCertificate cert(SSL_PeerCertificate(fd));
+ MOZ_ASSERT(cert, "SSL_PeerCertificate failed in TLS handshake callback?");
+ if (!cert) {
+ return;
+ }
+
+ Maybe<nsTArray<nsTArray<uint8_t>>> maybePeerCertsBytes;
+ UniqueCERTCertList peerCertChain(SSL_PeerCertificateChain(fd));
+ if (!peerCertChain) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("RebuildVerifiedCertificateInformation: failed to get peer "
+ "certificate chain"));
+ } else {
+ nsTArray<nsTArray<uint8_t>> peerCertsBytes;
+ for (CERTCertListNode* n = CERT_LIST_HEAD(peerCertChain);
+ !CERT_LIST_END(n, peerCertChain); n = CERT_LIST_NEXT(n)) {
+ // Don't include the end-entity certificate.
+ if (n == CERT_LIST_HEAD(peerCertChain)) {
+ continue;
+ }
+ nsTArray<uint8_t> certBytes;
+ certBytes.AppendElements(n->cert->derCert.data, n->cert->derCert.len);
+ peerCertsBytes.AppendElement(std::move(certBytes));
+ }
+ maybePeerCertsBytes.emplace(std::move(peerCertsBytes));
+ }
+
+ RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+ MOZ_ASSERT(certVerifier,
+ "Certificate verifier uninitialized in TLS handshake callback?");
+ if (!certVerifier) {
+ return;
+ }
+
+ // We don't own these pointers.
+ const SECItemArray* stapledOCSPResponses = SSL_PeerStapledOCSPResponses(fd);
+ Maybe<nsTArray<uint8_t>> stapledOCSPResponse;
+ // we currently only support single stapled responses
+ if (stapledOCSPResponses && stapledOCSPResponses->len == 1) {
+ stapledOCSPResponse.emplace();
+ stapledOCSPResponse->SetCapacity(stapledOCSPResponses->items[0].len);
+ stapledOCSPResponse->AppendElements(stapledOCSPResponses->items[0].data,
+ stapledOCSPResponses->items[0].len);
+ }
+
+ Maybe<nsTArray<uint8_t>> sctsFromTLSExtension;
+ const SECItem* sctsFromTLSExtensionSECItem = SSL_PeerSignedCertTimestamps(fd);
+ if (sctsFromTLSExtensionSECItem) {
+ sctsFromTLSExtension.emplace();
+ sctsFromTLSExtension->SetCapacity(sctsFromTLSExtensionSECItem->len);
+ sctsFromTLSExtension->AppendElements(sctsFromTLSExtensionSECItem->data,
+ sctsFromTLSExtensionSECItem->len);
+ }
+
+ int flags = mozilla::psm::CertVerifier::FLAG_LOCAL_ONLY;
+ if (!infoObject->SharedState().IsOCSPStaplingEnabled() ||
+ !infoObject->SharedState().IsOCSPMustStapleEnabled()) {
+ flags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
+ }
+
+ SECOidTag evOidPolicy;
+ CertificateTransparencyInfo certificateTransparencyInfo;
+ UniqueCERTCertList builtChain;
+ const bool saveIntermediates = false;
+ bool isBuiltCertChainRootBuiltInRoot = false;
+ mozilla::pkix::Result rv = certVerifier->VerifySSLServerCert(
+ cert, mozilla::pkix::Now(), infoObject, infoObject->GetHostName(),
+ builtChain, flags, maybePeerCertsBytes, stapledOCSPResponse,
+ sctsFromTLSExtension, Nothing(), infoObject->GetOriginAttributes(),
+ saveIntermediates, &evOidPolicy,
+ nullptr, // OCSP stapling telemetry
+ nullptr, // key size telemetry
+ nullptr, // SHA-1 telemetry
+ nullptr, // pinning telemetry
+ &certificateTransparencyInfo,
+ nullptr, // CRLite telemetry,
+ &isBuiltCertChainRootBuiltInRoot);
+
+ if (rv != Success) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("HandshakeCallback: couldn't rebuild verified certificate info"));
+ }
+
+ RefPtr<nsNSSCertificate> nssc(nsNSSCertificate::Create(cert.get()));
+ if (rv == Success && evOidPolicy != SEC_OID_UNKNOWN) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("HandshakeCallback using NEW cert %p (is EV)", nssc.get()));
+ infoObject->SetServerCert(nssc, EVStatus::EV);
+ } else {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("HandshakeCallback using NEW cert %p (is not EV)", nssc.get()));
+ infoObject->SetServerCert(nssc, EVStatus::NotEV);
+ }
+
+ if (rv == Success) {
+ uint16_t status =
+ TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
+ certificateTransparencyInfo);
+ infoObject->SetCertificateTransparencyStatus(status);
+ nsTArray<nsTArray<uint8_t>> certBytesArray =
+ TransportSecurityInfo::CreateCertBytesArray(builtChain);
+ infoObject->SetSucceededCertChain(std::move(certBytesArray));
+ infoObject->SetIsBuiltCertChainRootBuiltInRoot(
+ isBuiltCertChainRootBuiltInRoot);
+ }
+}
+
+nsresult IsCertificateDistrustImminent(
+ const nsTArray<RefPtr<nsIX509Cert>>& aCertArray,
+ /* out */ bool& isDistrusted) {
+ if (aCertArray.IsEmpty()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsIX509Cert> rootCert;
+ nsTArray<RefPtr<nsIX509Cert>> intCerts;
+ nsCOMPtr<nsIX509Cert> eeCert;
+
+ nsresult rv = nsNSSCertificate::SegmentCertificateChain(aCertArray, rootCert,
+ intCerts, eeCert);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Check the test certificate condition first; this is a special certificate
+ // that gets the 'imminent distrust' treatment; this is so that the distrust
+ // UX code does not become stale, as it will need regular use. See Bug 1409257
+ // for context. Please do not remove this when adjusting the rest of the
+ // method.
+ UniqueCERTCertificate nssEECert(eeCert->GetCert());
+ if (!nssEECert) {
+ return NS_ERROR_FAILURE;
+ }
+ isDistrusted =
+ CertDNIsInList(nssEECert.get(), TestImminentDistrustEndEntityDNs);
+ if (isDistrusted) {
+ // Exit early
+ return NS_OK;
+ }
+
+ UniqueCERTCertificate nssRootCert(rootCert->GetCert());
+ if (!nssRootCert) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Proceed with the Symantec imminent distrust algorithm. This algorithm is
+ // to be removed in Firefox 63, when the validity period check will also be
+ // removed from the code in NSSCertDBTrustDomain.
+ if (CertDNIsInList(nssRootCert.get(), RootSymantecDNs)) {
+ rv = CheckForSymantecDistrust(intCerts, RootAppleAndGoogleSPKIs,
+ isDistrusted);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+void HandshakeCallback(PRFileDesc* fd, void* client_data) {
+ SECStatus rv;
+
+ nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*)fd->higher->secret;
+
+ // Do the bookkeeping that needs to be done after the
+ // server's ServerHello...ServerHelloDone have been processed, but that
+ // doesn't need the handshake to be completed.
+ PreliminaryHandshakeDone(fd);
+
+ nsSSLIOLayerHelpers& ioLayerHelpers =
+ infoObject->SharedState().IOLayerHelpers();
+
+ SSLVersionRange versions(infoObject->GetTLSVersionRange());
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[%p] HandshakeCallback: succeeded using TLS version range "
+ "(0x%04x,0x%04x)\n",
+ fd, static_cast<unsigned int>(versions.min),
+ static_cast<unsigned int>(versions.max)));
+
+ // If the handshake completed, then we know the site is TLS tolerant
+ ioLayerHelpers.rememberTolerantAtVersion(infoObject->GetHostName(),
+ infoObject->GetPort(), versions.max);
+
+ SSLChannelInfo channelInfo;
+ rv = SSL_GetChannelInfo(fd, &channelInfo, sizeof(channelInfo));
+ MOZ_ASSERT(rv == SECSuccess);
+ if (rv == SECSuccess) {
+ // Get the protocol version for telemetry
+ // 1=tls1, 2=tls1.1, 3=tls1.2
+ unsigned int versionEnum = channelInfo.protocolVersion & 0xFF;
+ MOZ_ASSERT(versionEnum > 0);
+ Telemetry::Accumulate(Telemetry::SSL_HANDSHAKE_VERSION, versionEnum);
+ AccumulateCipherSuite(infoObject->IsFullHandshake()
+ ? Telemetry::SSL_CIPHER_SUITE_FULL
+ : Telemetry::SSL_CIPHER_SUITE_RESUMED,
+ channelInfo);
+
+ SSLCipherSuiteInfo cipherInfo;
+ rv = SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo,
+ sizeof cipherInfo);
+ MOZ_ASSERT(rv == SECSuccess);
+ if (rv == SECSuccess) {
+ // keyExchange null=0, rsa=1, dh=2, fortezza=3, ecdh=4
+ Telemetry::Accumulate(infoObject->IsFullHandshake()
+ ? Telemetry::SSL_KEY_EXCHANGE_ALGORITHM_FULL
+ : Telemetry::SSL_KEY_EXCHANGE_ALGORITHM_RESUMED,
+ channelInfo.keaType);
+
+ MOZ_ASSERT(infoObject->GetKEAUsed() == channelInfo.keaType);
+
+ if (infoObject->IsFullHandshake()) {
+ switch (channelInfo.keaType) {
+ case ssl_kea_rsa:
+ AccumulateNonECCKeySize(Telemetry::SSL_KEA_RSA_KEY_SIZE_FULL,
+ channelInfo.keaKeyBits);
+ break;
+ case ssl_kea_dh:
+ AccumulateNonECCKeySize(Telemetry::SSL_KEA_DHE_KEY_SIZE_FULL,
+ channelInfo.keaKeyBits);
+ break;
+ case ssl_kea_ecdh:
+ AccumulateECCCurve(Telemetry::SSL_KEA_ECDHE_CURVE_FULL,
+ channelInfo.keaKeyBits);
+ break;
+ default:
+ MOZ_CRASH("impossible KEA");
+ break;
+ }
+
+ Telemetry::Accumulate(Telemetry::SSL_AUTH_ALGORITHM_FULL,
+ channelInfo.authType);
+
+ // RSA key exchange doesn't use a signature for auth.
+ if (channelInfo.keaType != ssl_kea_rsa) {
+ switch (channelInfo.authType) {
+ case ssl_auth_rsa:
+ case ssl_auth_rsa_sign:
+ AccumulateNonECCKeySize(Telemetry::SSL_AUTH_RSA_KEY_SIZE_FULL,
+ channelInfo.authKeyBits);
+ break;
+ case ssl_auth_ecdsa:
+ AccumulateECCCurve(Telemetry::SSL_AUTH_ECDSA_CURVE_FULL,
+ channelInfo.authKeyBits);
+ break;
+ default:
+ MOZ_CRASH("impossible auth algorithm");
+ break;
+ }
+ }
+ }
+
+ Telemetry::Accumulate(infoObject->IsFullHandshake()
+ ? Telemetry::SSL_SYMMETRIC_CIPHER_FULL
+ : Telemetry::SSL_SYMMETRIC_CIPHER_RESUMED,
+ cipherInfo.symCipher);
+ }
+ }
+
+ PRBool siteSupportsSafeRenego;
+ if (channelInfo.protocolVersion != SSL_LIBRARY_VERSION_TLS_1_3) {
+ rv = SSL_HandshakeNegotiatedExtension(fd, ssl_renegotiation_info_xtn,
+ &siteSupportsSafeRenego);
+ MOZ_ASSERT(rv == SECSuccess);
+ if (rv != SECSuccess) {
+ siteSupportsSafeRenego = false;
+ }
+ } else {
+ // TLS 1.3 dropped support for renegotiation.
+ siteSupportsSafeRenego = true;
+ }
+ bool renegotiationUnsafe = !siteSupportsSafeRenego &&
+ ioLayerHelpers.treatUnsafeNegotiationAsBroken();
+
+ bool deprecatedTlsVer =
+ (channelInfo.protocolVersion < SSL_LIBRARY_VERSION_TLS_1_2);
+ RememberCertErrorsTable::GetInstance().LookupCertErrorBits(infoObject);
+
+ uint32_t state;
+ if (renegotiationUnsafe || deprecatedTlsVer) {
+ state = nsIWebProgressListener::STATE_IS_BROKEN;
+ } else {
+ state = nsIWebProgressListener::STATE_IS_SECURE;
+ SSLVersionRange defVersion;
+ rv = SSL_VersionRangeGetDefault(ssl_variant_stream, &defVersion);
+ if (rv == SECSuccess && versions.max >= defVersion.max) {
+ // we know this site no longer requires a version fallback
+ ioLayerHelpers.removeInsecureFallbackSite(infoObject->GetHostName(),
+ infoObject->GetPort());
+ }
+ }
+
+ if (infoObject->HasServerCert()) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("HandshakeCallback KEEPING existing cert\n"));
+ } else {
+ if (StaticPrefs::network_ssl_tokens_cache_enabled()) {
+ infoObject->RebuildCertificateInfoFromSSLTokenCache();
+ } else {
+ RebuildVerifiedCertificateInformation(fd, infoObject);
+ }
+ }
+
+ nsTArray<RefPtr<nsIX509Cert>> succeededCertArray;
+ // The list could be empty. Bug 731478 will reduce the incidence of empty
+ // succeeded cert chains through better caching.
+ nsresult srv = infoObject->GetSucceededCertChain(succeededCertArray);
+
+ bool distrustImminent;
+ if (NS_SUCCEEDED(srv)) {
+ srv = IsCertificateDistrustImminent(succeededCertArray, distrustImminent);
+ }
+
+ if (NS_SUCCEEDED(srv) && distrustImminent) {
+ state |= nsIWebProgressListener::STATE_CERT_DISTRUST_IMMINENT;
+ }
+
+ bool domainMismatch;
+ bool untrusted;
+ bool notValidAtThisTime;
+ // These all return NS_OK, so don't even bother checking the return values.
+ Unused << infoObject->GetIsDomainMismatch(&domainMismatch);
+ Unused << infoObject->GetIsUntrusted(&untrusted);
+ Unused << infoObject->GetIsNotValidAtThisTime(&notValidAtThisTime);
+ // If we're here, the TLS handshake has succeeded. Thus if any of these
+ // booleans are true, the user has added an override for a certificate error.
+ if (domainMismatch || untrusted || notValidAtThisTime) {
+ state |= nsIWebProgressListener::STATE_CERT_USER_OVERRIDDEN;
+ }
+
+ infoObject->SetSecurityState(state);
+
+ // XXX Bug 883674: We shouldn't be formatting messages here in PSM; instead,
+ // we should set a flag on the channel that higher (UI) level code can check
+ // to log the warning. In particular, these warnings should go to the web
+ // console instead of to the error console. Also, the warning is not
+ // localized.
+ if (!siteSupportsSafeRenego) {
+ NS_ConvertASCIItoUTF16 msg(infoObject->GetHostName());
+ msg.AppendLiteral(" : server does not support RFC 5746, see CVE-2009-3555");
+
+ nsContentUtils::LogSimpleConsoleError(
+ msg, "SSL", !!infoObject->GetOriginAttributes().mPrivateBrowsingId,
+ true /* from chrome context */);
+ }
+
+ infoObject->NoteTimeUntilReady();
+ infoObject->SetHandshakeCompleted();
+}