Files
tubestation/security/manager/ssl/NSSSocketControl.cpp
Dana Keeler b1e99b2e7e Bug 1945985 - rsclientcerts: only search for new objects when the platform is actually looking for client auth certs r=jschanck
Previously, rsclientcerts would have to heuristically determine when the
platform was searching for client auth certificates in order to avoid
re-querying the underlying certificate and key sources every time any part of
NSS looked for PKCS#11 objects. This heuristic was imprecise and constrained
the rest of the implementation that handles client auth certificates.

Now that osclientcerts and ipcclientcerts (the consumers of rsclientcerts) are
part of libxul, they can directly check if gecko is searching for client auth
certificates and update their list of known certificates and keys accordingly.

In theory, multiple simultaneous connections could cause multiple updates of
the known list of certificates and keys, so to prevent unnecessary costly work,
rsclientcerts will still only refresh its list of objects every 2 seconds (down
from 3 seconds previously).

Differential Revision: https://phabricator.services.mozilla.com/D237108
2025-02-18 19:26:59 +00:00

777 lines
24 KiB
C++

/* -*- 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 "NSSSocketControl.h"
#include "ssl.h"
#include "sslexp.h"
#include "nsISocketProvider.h"
#include "secerr.h"
#include "mozilla/Base64.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/glean/SecurityManagerSslMetrics.h"
#include "nsNSSCallbacks.h"
#include "nsNSSComponent.h"
#include "nsProxyRelease.h"
using namespace mozilla;
using namespace mozilla::psm;
extern LazyLogModule gPIPNSSLog;
NSSSocketControl::NSSSocketControl(
const nsCString& aHostName, int32_t aPort,
already_AddRefed<nsSSLIOLayerHelpers> aSSLIOLayerHelpers,
uint32_t providerFlags, uint32_t providerTlsFlags)
: CommonSocketControl(aHostName, aPort, providerFlags),
mFd(nullptr),
mCertVerificationState(BeforeCertVerification),
mSSLIOLayerHelpers(aSSLIOLayerHelpers),
mForSTARTTLS(false),
mTLSVersionRange{0, 0},
mHandshakePending(true),
mPreliminaryHandshakeDone(false),
mEarlyDataAccepted(false),
mDenyClientCert(false),
mFalseStartCallbackCalled(false),
mFalseStarted(false),
mIsFullHandshake(false),
mNotedTimeUntilReady(false),
mEchExtensionStatus(EchExtensionStatus::kNotPresent),
mSentMlkemShare(false),
mHasTls13HandshakeSecrets(false),
mIsShortWritePending(false),
mShortWritePendingByte(0),
mShortWriteOriginalAmount(-1),
mKEAUsed(nsITLSSocketControl::KEY_EXCHANGE_UNKNOWN),
mKEAKeyBits(0),
mMACAlgorithmUsed(nsITLSSocketControl::SSL_MAC_UNKNOWN),
mProviderTlsFlags(providerTlsFlags),
mSocketCreationTimestamp(TimeStamp::Now()),
mPlaintextBytesRead(0),
mClaimed(!(providerFlags & nsISocketProvider::IS_SPECULATIVE_CONNECTION)),
mPendingSelectClientAuthCertificate(nullptr),
mBrowserId(0) {}
NS_IMETHODIMP
NSSSocketControl::GetKEAUsed(int16_t* aKea) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
*aKea = mKEAUsed;
return NS_OK;
}
NS_IMETHODIMP
NSSSocketControl::GetKEAKeyBits(uint32_t* aKeyBits) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
*aKeyBits = mKEAKeyBits;
return NS_OK;
}
NS_IMETHODIMP
NSSSocketControl::GetSSLVersionOffered(int16_t* aSSLVersionOffered) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
*aSSLVersionOffered = mTLSVersionRange.max;
return NS_OK;
}
NS_IMETHODIMP
NSSSocketControl::GetMACAlgorithmUsed(int16_t* aMac) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
*aMac = mMACAlgorithmUsed;
return NS_OK;
}
void NSSSocketControl::NoteTimeUntilReady() {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
if (mNotedTimeUntilReady) {
return;
}
mNotedTimeUntilReady = true;
auto duration = TimeStamp::Now() - mSocketCreationTimestamp;
if (!(mProviderFlags & nsISocketProvider::IS_RETRY)) {
glean::ssl::time_until_ready_first_try.AccumulateRawDuration(duration);
}
if (mProviderFlags & nsISocketProvider::BE_CONSERVATIVE) {
glean::ssl::time_until_ready_conservative.AccumulateRawDuration(duration);
}
switch (GetEchExtensionStatus()) {
case EchExtensionStatus::kGREASE:
glean::ssl::time_until_ready_ech_grease.AccumulateRawDuration(duration);
break;
case EchExtensionStatus::kReal:
glean::ssl::time_until_ready_ech.AccumulateRawDuration(duration);
break;
default:
break;
}
// This will include TCP and proxy tunnel wait time
glean::ssl::time_until_ready.AccumulateRawDuration(duration);
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[%p] NSSSocketControl::NoteTimeUntilReady\n", mFd));
}
void NSSSocketControl::SetHandshakeCompleted() {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
if (!mHandshakeCompleted) {
enum HandshakeType {
Resumption = 1,
FalseStarted = 2,
ChoseNotToFalseStart = 3,
NotAllowedToFalseStart = 4,
};
HandshakeType handshakeType = !IsFullHandshake() ? Resumption
: mFalseStarted ? FalseStarted
: mFalseStartCallbackCalled
? ChoseNotToFalseStart
: NotAllowedToFalseStart;
// This will include TCP and proxy tunnel wait time
if (mKeaGroupName.isSome()) {
glean::ssl::time_until_handshake_finished_keyed_by_ka.Get(*mKeaGroupName)
.AccumulateRawDuration(TimeStamp::Now() - mSocketCreationTimestamp);
}
// If the handshake is completed for the first time from just 1 callback
// that means that TLS session resumption must have been used.
glean::ssl::resumed_session
.EnumGet(static_cast<glean::ssl::ResumedSessionLabel>(handshakeType ==
Resumption))
.Add();
glean::ssl_handshake::completed.AccumulateSingleSample(handshakeType);
}
// Remove the plaintext layer as it is not needed anymore.
// The plaintext layer is not always present - so it's not a fatal error if it
// cannot be removed.
// Note that PR_PopIOLayer may modify its stack, so a pointer returned by
// PR_GetIdentitiesLayer may not point to what we think it points to after
// calling PR_PopIOLayer. We must operate on the pointer returned by
// PR_PopIOLayer.
if (PR_GetIdentitiesLayer(mFd,
nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity)) {
PRFileDesc* poppedPlaintext =
PR_PopIOLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity);
poppedPlaintext->dtor(poppedPlaintext);
}
mHandshakeCompleted = true;
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[%p] NSSSocketControl::SetHandshakeCompleted\n", (void*)mFd));
mIsFullHandshake = false; // reset for next handshake on this connection
if (mTlsHandshakeCallback) {
auto callback = std::move(mTlsHandshakeCallback);
Unused << callback->HandshakeDone();
}
}
void NSSSocketControl::SetNegotiatedNPN(const char* value, uint32_t length) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
if (!value) {
mNegotiatedNPN.Truncate();
} else {
mNegotiatedNPN.Assign(value, length);
}
mNPNCompleted = true;
}
#define MAX_ALPN_LENGTH 255
NS_IMETHODIMP
NSSSocketControl::GetAlpnEarlySelection(nsACString& aAlpnSelected) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
aAlpnSelected.Truncate();
SSLPreliminaryChannelInfo info;
SECStatus rv = SSL_GetPreliminaryChannelInfo(mFd, &info, sizeof(info));
if (rv != SECSuccess || !info.canSendEarlyData) {
return NS_ERROR_NOT_AVAILABLE;
}
SSLNextProtoState alpnState;
unsigned char chosenAlpn[MAX_ALPN_LENGTH];
unsigned int chosenAlpnLen;
rv = SSL_GetNextProto(mFd, &alpnState, chosenAlpn, &chosenAlpnLen,
AssertedCast<unsigned int>(std::size(chosenAlpn)));
if (rv != SECSuccess) {
return NS_ERROR_NOT_AVAILABLE;
}
if (alpnState == SSL_NEXT_PROTO_EARLY_VALUE) {
aAlpnSelected.Assign(BitwiseCast<char*, unsigned char*>(chosenAlpn),
chosenAlpnLen);
}
return NS_OK;
}
NS_IMETHODIMP
NSSSocketControl::GetEarlyDataAccepted(bool* aAccepted) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
*aAccepted = mEarlyDataAccepted;
return NS_OK;
}
void NSSSocketControl::SetEarlyDataAccepted(bool aAccepted) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
mEarlyDataAccepted = aAccepted;
}
bool NSSSocketControl::GetDenyClientCert() {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
return mDenyClientCert;
}
void NSSSocketControl::SetDenyClientCert(bool aDenyClientCert) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
mDenyClientCert = aDenyClientCert;
}
NS_IMETHODIMP
NSSSocketControl::DriveHandshake() {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
if (!mFd) {
return NS_ERROR_FAILURE;
}
if (IsCanceled()) {
PRErrorCode errorCode = GetErrorCode();
MOZ_DIAGNOSTIC_ASSERT(errorCode, "handshake cancelled without error code");
return GetXPCOMFromNSSError(errorCode);
}
SECStatus rv = SSL_ForceHandshake(mFd);
if (rv != SECSuccess) {
PRErrorCode errorCode = PR_GetError();
MOZ_ASSERT(errorCode, "handshake failed without error code");
// There is a bug in NSS. Sometimes SSL_ForceHandshake will return
// SECFailure without setting an error code. In these cases, cancel
// the connection with SEC_ERROR_LIBRARY_FAILURE.
if (!errorCode) {
errorCode = SEC_ERROR_LIBRARY_FAILURE;
}
if (errorCode == PR_WOULD_BLOCK_ERROR) {
return NS_BASE_STREAM_WOULD_BLOCK;
}
SetCanceled(errorCode);
return GetXPCOMFromNSSError(errorCode);
}
return NS_OK;
}
bool NSSSocketControl::GetForSTARTTLS() {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
return mForSTARTTLS;
}
void NSSSocketControl::SetForSTARTTLS(bool aForSTARTTLS) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
mForSTARTTLS = aForSTARTTLS;
}
NS_IMETHODIMP
NSSSocketControl::ProxyStartSSL() {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
return ActivateSSL();
}
NS_IMETHODIMP
NSSSocketControl::StartTLS() {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
return ActivateSSL();
}
NS_IMETHODIMP
NSSSocketControl::AsyncStartTLS(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<nsIEventTarget> target(
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
if (!target) {
return NS_ERROR_UNEXPECTED;
}
auto promiseHolder = MakeRefPtr<nsMainThreadPtrHolder<dom::Promise>>(
"AsyncStartTLS promise", promise);
nsCOMPtr<nsIRunnable> runnable(NS_NewRunnableFunction(
"AsyncStartTLS::StartTLS",
[promiseHolder = std::move(promiseHolder), self = RefPtr{this}]() {
nsresult rv = self->StartTLS();
NS_DispatchToMainThread(NS_NewRunnableFunction(
"AsyncStartTLS::Resolve", [rv, promiseHolder]() {
dom::Promise* promise = promiseHolder.get()->get();
if (NS_FAILED(rv)) {
promise->MaybeReject(rv);
} else {
promise->MaybeResolveWithUndefined();
}
}));
}));
nsresult rv = target->Dispatch(runnable, NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
return rv;
}
promise.forget(aPromise);
return NS_OK;
}
NS_IMETHODIMP
NSSSocketControl::SetNPNList(nsTArray<nsCString>& protocolArray) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
if (!mFd) return NS_ERROR_FAILURE;
// the npn list is a concatenated list of 8 bit byte strings.
nsCString npnList;
for (uint32_t index = 0; index < protocolArray.Length(); ++index) {
if (protocolArray[index].IsEmpty() || protocolArray[index].Length() > 255)
return NS_ERROR_ILLEGAL_VALUE;
npnList.Append(protocolArray[index].Length());
npnList.Append(protocolArray[index]);
}
if (SSL_SetNextProtoNego(
mFd, BitwiseCast<const unsigned char*, const char*>(npnList.get()),
npnList.Length()) != SECSuccess)
return NS_ERROR_FAILURE;
return NS_OK;
}
nsresult NSSSocketControl::ActivateSSL() {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
if (SECSuccess != SSL_OptionSet(mFd, SSL_SECURITY, true))
return NS_ERROR_FAILURE;
if (SECSuccess != SSL_ResetHandshake(mFd, false)) return NS_ERROR_FAILURE;
mHandshakePending = true;
return SetResumptionTokenFromExternalCache(mFd);
}
nsresult NSSSocketControl::GetFileDescPtr(PRFileDesc** aFilePtr) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
*aFilePtr = mFd;
return NS_OK;
}
nsresult NSSSocketControl::SetFileDescPtr(PRFileDesc* aFilePtr) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
mFd = aFilePtr;
return NS_OK;
}
void NSSSocketControl::SetCertVerificationWaiting() {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
// mCertVerificationState may be BeforeCertVerification for the first
// handshake on the connection, or AfterCertVerification for subsequent
// renegotiation handshakes.
MOZ_ASSERT(mCertVerificationState != WaitingForCertVerification,
"Invalid state transition to WaitingForCertVerification");
mCertVerificationState = WaitingForCertVerification;
}
// Be careful that SetCertVerificationResult does NOT get called while we are
// processing a SSL callback function, because SSL_AuthCertificateComplete will
// attempt to acquire locks that are already held by libssl when it calls
// callbacks.
void NSSSocketControl::SetCertVerificationResult(PRErrorCode errorCode) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
SetUsedPrivateDNS(GetProviderFlags() & nsISocketProvider::USED_PRIVATE_DNS);
MOZ_ASSERT(mCertVerificationState == WaitingForCertVerification,
"Invalid state transition to AfterCertVerification");
if (mFd) {
SECStatus rv = SSL_AuthCertificateComplete(mFd, errorCode);
// Only replace errorCode if there was originally no error.
// SSL_AuthCertificateComplete will return SECFailure with the error code
// set to PR_WOULD_BLOCK_ERROR if there is a pending event to select a
// client authentication certificate. This is not an error.
if (rv != SECSuccess && PR_GetError() != PR_WOULD_BLOCK_ERROR &&
errorCode == 0) {
errorCode = PR_GetError();
if (errorCode == 0) {
NS_ERROR("SSL_AuthCertificateComplete didn't set error code");
errorCode = PR_INVALID_STATE_ERROR;
}
}
}
if (errorCode) {
mFailedVerification = true;
SetCanceled(errorCode);
}
if (mPlaintextBytesRead && !errorCode) {
glean::ssl::bytes_before_cert_callback.Accumulate(
AssertedCast<uint32_t>(mPlaintextBytesRead));
}
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[%p] SetCertVerificationResult to AfterCertVerification, "
"mTlsHandshakeCallback=%p",
(void*)mFd, mTlsHandshakeCallback.get()));
mCertVerificationState = AfterCertVerification;
if (mTlsHandshakeCallback) {
Unused << mTlsHandshakeCallback->CertVerificationDone();
}
}
void NSSSocketControl::ClientAuthCertificateSelected(
nsTArray<uint8_t>& certBytes, nsTArray<nsTArray<uint8_t>>& certChainBytes) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
// If mFd is nullptr, the connection has been closed already, so we don't
// need to do anything here.
if (!mFd) {
return;
}
SECItem certItem = {
siBuffer,
const_cast<uint8_t*>(certBytes.Elements()),
static_cast<unsigned int>(certBytes.Length()),
};
// Ensure that osclientcerts (or ipcclientcerts, in the socket process) will
// populate its list of certificates and keys.
AutoSearchingForClientAuthCertificates _;
UniqueCERTCertificate cert(CERT_NewTempCertificate(
CERT_GetDefaultCertDB(), &certItem, nullptr, false, true));
UniqueSECKEYPrivateKey key;
if (cert) {
key.reset(PK11_FindKeyByAnyCert(cert.get(), nullptr));
mClientCertChain.reset(CERT_NewCertList());
if (key && mClientCertChain) {
for (const auto& certBytes : certChainBytes) {
SECItem certItem = {
siBuffer,
const_cast<uint8_t*>(certBytes.Elements()),
static_cast<unsigned int>(certBytes.Length()),
};
UniqueCERTCertificate cert(CERT_NewTempCertificate(
CERT_GetDefaultCertDB(), &certItem, nullptr, false, true));
if (cert) {
if (CERT_AddCertToListTail(mClientCertChain.get(), cert.get()) ==
SECSuccess) {
Unused << cert.release();
}
}
}
}
}
bool sendingClientAuthCert = cert && key;
if (sendingClientAuthCert) {
mSentClientCert = true;
glean::security::client_auth_cert_usage.Get("sent"_ns).Add(1);
}
Unused << SSL_ClientCertCallbackComplete(
mFd, sendingClientAuthCert ? SECSuccess : SECFailure,
sendingClientAuthCert ? key.release() : nullptr,
sendingClientAuthCert ? cert.release() : nullptr);
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[%p] ClientAuthCertificateSelected mTlsHandshakeCallback=%p",
(void*)mFd, mTlsHandshakeCallback.get()));
if (mTlsHandshakeCallback) {
Unused << mTlsHandshakeCallback->ClientAuthCertificateSelected();
}
}
NS_IMETHODIMP
NSSSocketControl::DisableEarlyData() {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
if (!mFd) {
return NS_OK;
}
if (IsCanceled()) {
return NS_OK;
}
if (SSL_OptionSet(mFd, SSL_ENABLE_0RTT_DATA, false) != SECSuccess) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
NSSSocketControl::SetHandshakeCallbackListener(
nsITlsHandshakeCallbackListener* callback) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
mTlsHandshakeCallback = callback;
return NS_OK;
}
PRStatus NSSSocketControl::CloseSocketAndDestroy() {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
mPendingSelectClientAuthCertificate = nullptr;
PRFileDesc* popped = PR_PopIOLayer(mFd, PR_TOP_IO_LAYER);
MOZ_ASSERT(
popped && popped->identity == nsSSLIOLayerHelpers::nsSSLIOLayerIdentity,
"SSL Layer not on top of stack");
// The plaintext layer is not always present - so it's not a fatal error if it
// cannot be removed.
// Note that PR_PopIOLayer may modify its stack, so a pointer returned by
// PR_GetIdentitiesLayer may not point to what we think it points to after
// calling PR_PopIOLayer. We must operate on the pointer returned by
// PR_PopIOLayer.
if (PR_GetIdentitiesLayer(mFd,
nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity)) {
PRFileDesc* poppedPlaintext =
PR_PopIOLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity);
poppedPlaintext->dtor(poppedPlaintext);
}
// We need to clear the callback to make sure the ssl layer cannot call the
// callback after mFD is nulled.
SSL_SetResumptionTokenCallback(mFd, nullptr, nullptr);
PRStatus status = mFd->methods->close(mFd);
// the NSSSocketControl instance can out-live the connection, so we need some
// indication that the connection has been closed. mFd == nullptr is that
// indication. This is needed, for example, when the connection is closed
// before we have finished validating the server's certificate.
mFd = nullptr;
if (status != PR_SUCCESS) return status;
popped->identity = PR_INVALID_IO_LAYER;
popped->dtor(popped);
return PR_SUCCESS;
}
NS_IMETHODIMP
NSSSocketControl::GetEsniTxt(nsACString& aEsniTxt) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
aEsniTxt = mEsniTxt;
return NS_OK;
}
NS_IMETHODIMP
NSSSocketControl::SetEsniTxt(const nsACString& aEsniTxt) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
mEsniTxt = aEsniTxt;
if (mEsniTxt.Length()) {
nsAutoCString esniBin;
if (NS_OK != Base64Decode(mEsniTxt, esniBin)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Error,
("[%p] Invalid ESNIKeys record. Couldn't base64 decode\n",
(void*)mFd));
return NS_OK;
}
if (SECSuccess !=
SSL_EnableESNI(mFd, reinterpret_cast<const PRUint8*>(esniBin.get()),
esniBin.Length(), nullptr)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Error,
("[%p] Invalid ESNIKeys record %s\n", (void*)mFd,
PR_ErrorToName(PR_GetError())));
return NS_OK;
}
}
return NS_OK;
}
NS_IMETHODIMP
NSSSocketControl::GetEchConfig(nsACString& aEchConfig) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
aEchConfig = mEchConfig;
return NS_OK;
}
NS_IMETHODIMP
NSSSocketControl::SetEchConfig(const nsACString& aEchConfig) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
mEchConfig = aEchConfig;
if (mEchConfig.Length()) {
if (SECSuccess !=
SSL_SetClientEchConfigs(
mFd, reinterpret_cast<const PRUint8*>(aEchConfig.BeginReading()),
aEchConfig.Length())) {
MOZ_LOG(gPIPNSSLog, LogLevel::Error,
("[%p] Invalid EchConfig record %s\n", (void*)mFd,
PR_ErrorToName(PR_GetError())));
return NS_OK;
}
UpdateEchExtensionStatus(EchExtensionStatus::kReal);
}
return NS_OK;
}
NS_IMETHODIMP
NSSSocketControl::GetRetryEchConfig(nsACString& aEchConfig) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
if (!mFd) {
return NS_ERROR_FAILURE;
}
ScopedAutoSECItem retryConfigItem;
SECStatus rv = SSL_GetEchRetryConfigs(mFd, &retryConfigItem);
if (rv != SECSuccess) {
return NS_ERROR_FAILURE;
}
aEchConfig = nsCString(reinterpret_cast<const char*>(retryConfigItem.data),
retryConfigItem.len);
return NS_OK;
}
NS_IMETHODIMP
NSSSocketControl::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.AppendPrintf("tlsflags0x%08x:", mProviderTlsFlags);
mPeerId.Append(mHostName);
mPeerId.Append(':');
mPeerId.AppendInt(GetPort());
nsAutoCString suffix;
mOriginAttributes.CreateSuffix(suffix);
mPeerId.Append(suffix);
aResult.Assign(mPeerId);
return NS_OK;
}
nsresult NSSSocketControl::SetResumptionTokenFromExternalCache(PRFileDesc* fd) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
if (!fd) {
return NS_ERROR_INVALID_ARG;
}
// If SSL_NO_CACHE option was set, we must not use the cache
PRIntn val;
if (SSL_OptionGet(fd, SSL_NO_CACHE, &val) != SECSuccess) {
return NS_ERROR_FAILURE;
}
if (val != 0) {
return NS_OK;
}
nsTArray<uint8_t> token;
nsAutoCString peerId;
nsresult rv = GetPeerId(peerId);
if (NS_FAILED(rv)) {
return rv;
}
uint64_t tokenId = 0;
mozilla::net::SessionCacheInfo info;
rv = mozilla::net::SSLTokensCache::Get(peerId, token, info, &tokenId);
if (NS_FAILED(rv)) {
if (rv == NS_ERROR_NOT_AVAILABLE) {
// It's ok if we can't find the token.
return NS_OK;
}
return rv;
}
SECStatus srv = SSL_SetResumptionToken(fd, token.Elements(), token.Length());
if (srv == SECFailure) {
PRErrorCode error = PR_GetError();
mozilla::net::SSLTokensCache::Remove(peerId, tokenId);
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("Setting token failed with NSS error %d [id=%s]", error,
PromiseFlatCString(peerId).get()));
// We don't consider SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR as a hard error,
// since this error means this token is just expired or can't be decoded
// correctly.
if (error == SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR) {
return NS_OK;
}
return NS_ERROR_FAILURE;
}
SetSessionCacheInfo(std::move(info));
return NS_OK;
}
void NSSSocketControl::SetPreliminaryHandshakeInfo(
const SSLChannelInfo& channelInfo, const SSLCipherSuiteInfo& cipherInfo) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
mResumed = channelInfo.resumed;
mCipherSuite.emplace(channelInfo.cipherSuite);
mProtocolVersion.emplace(channelInfo.protocolVersion & 0xFF);
mKeaGroupName.emplace(getKeaGroupName(channelInfo.keaGroup));
mSignatureSchemeName.emplace(getSignatureName(channelInfo.signatureScheme));
mIsDelegatedCredential.emplace(channelInfo.peerDelegCred);
mIsAcceptedEch.emplace(channelInfo.echAccepted);
}
NS_IMETHODIMP NSSSocketControl::Claim() {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
mClaimed = true;
return NS_OK;
}
NS_IMETHODIMP NSSSocketControl::SetBrowserId(uint64_t browserId) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
mBrowserId = browserId;
return NS_OK;
}
NS_IMETHODIMP NSSSocketControl::GetBrowserId(uint64_t* browserId) {
COMMON_SOCKET_CONTROL_ASSERT_ON_OWNING_THREAD();
if (!browserId) {
return NS_ERROR_INVALID_ARG;
}
*browserId = mBrowserId;
return NS_OK;
}