Bug 1652677 - P2: Implement necko part of echconfig r=dragana

Differential Revision: https://phabricator.services.mozilla.com/D89455
This commit is contained in:
Kershaw Chang 2020-09-25 07:35:04 +00:00
parent 0ec383b700
commit 93e628b4ae
19 changed files with 98 additions and 154 deletions

View file

@ -178,7 +178,11 @@ class FakeSocketTransportProvider : public nsISocketTransport {
MOZ_ASSERT(false);
return NS_OK;
}
NS_IMETHOD GetEsniUsed(bool* aEsniUsed) override {
NS_IMETHOD GetEchConfigUsed(bool* aEchConfigUsed) override {
MOZ_ASSERT(false);
return NS_OK;
}
NS_IMETHOD SetEchConfig(const nsACString& aEchConfig) override {
MOZ_ASSERT(false);
return NS_OK;
}

View file

@ -311,10 +311,10 @@ FuzzySecurityInfo::SetNPNList(nsTArray<nsCString>& protocolArray) {
}
NS_IMETHODIMP
FuzzySecurityInfo::GetEsniTxt(nsACString& aEsniTxt) { return NS_OK; }
FuzzySecurityInfo::GetEchConfig(nsACString& aEchConfig) { return NS_OK; }
NS_IMETHODIMP
FuzzySecurityInfo::SetEsniTxt(const nsACString& aEsniTxt) {
FuzzySecurityInfo::SetEchConfig(const nsACString& aEchConfig) {
MOZ_CRASH("Unused");
return NS_OK;
}

View file

@ -255,9 +255,9 @@ interface nsISocketTransport : nsITransport
/**
* If we know that a server speaks only tls <1.3 there is no need to try
* to use esni and query dns for esni keys.
* to use ech and query dns for echconfig.
*/
const unsigned long DONT_TRY_ESNI = (1 << 10);
const unsigned long DONT_TRY_ECH = (1 << 10);
/**
* These two bits encode the TRR mode of the request.
@ -326,10 +326,12 @@ interface nsISocketTransport : nsITransport
readonly attribute boolean resetIPFamilyPreference;
/**
* This attribute holds information whether esni has been used.
* This attribute holds information whether echconfig has been used.
* The value is set after PR_Connect is called.
*/
readonly attribute boolean esniUsed;
readonly attribute boolean echConfigUsed;
void setEchConfig(in ACString echConfig);
/**
* IP address resolved using TRR.

View file

@ -710,10 +710,7 @@ nsSocketTransport::nsSocketTransport()
mInputClosed(true),
mOutputClosed(true),
mResolving(false),
mDNSLookupStatus(NS_OK),
mDNSARequestFinished(0),
mEsniQueried(false),
mEsniUsed(false),
mEchConfigUsed(false),
mResolvedByTRR(false),
mNetAddrIsSet(false),
mSelfAddrIsSet(false),
@ -1072,36 +1069,6 @@ nsresult nsSocketTransport::ResolveHost() {
dns->AsyncResolveNative(SocketHost(), nsIDNSService::RESOLVE_TYPE_DEFAULT,
dnsFlags, nullptr, this, mSocketTransportService,
mOriginAttributes, getter_AddRefs(mDNSRequest));
mEsniQueried = false;
if (mSocketTransportService->IsEsniEnabled() && NS_SUCCEEDED(rv) &&
!(mConnectionFlags & (DONT_TRY_ESNI | BE_CONSERVATIVE))) {
bool isSSL = false;
for (unsigned int i = 0; i < mTypes.Length(); ++i) {
if (mTypes[i].EqualsLiteral("ssl")) {
isSSL = true;
break;
}
}
if (isSSL) {
SOCKET_LOG((" look for esni txt record"));
nsAutoCString esniHost;
esniHost.Append("_esni.");
// This might end up being the SocketHost
// see https://github.com/ekr/draft-rescorla-tls-esni/issues/61
esniHost.Append(SocketHost());
rv = dns->AsyncResolveNative(esniHost, nsIDNSService::RESOLVE_TYPE_TXT,
dnsFlags, nullptr, this,
mSocketTransportService, mOriginAttributes,
getter_AddRefs(mDNSTxtRequest));
if (NS_FAILED(rv)) {
SOCKET_LOG((" dns request by type failed."));
mDNSTxtRequest = nullptr;
rv = NS_OK;
} else {
mEsniQueried = true;
}
}
}
if (NS_SUCCEEDED(rv)) {
SOCKET_LOG((" advancing to STATE_RESOLVING\n"));
@ -1558,15 +1525,16 @@ nsresult nsSocketTransport::InitiateSocket() {
}
#endif
if (!mDNSRecordTxt.IsEmpty() && !mUsingQuic && mSecInfo) {
if (!mEchConfig.IsEmpty() &&
!(mConnectionFlags & (DONT_TRY_ECH | BE_CONSERVATIVE)) && mSecInfo) {
nsCOMPtr<nsISSLSocketControl> secCtrl = do_QueryInterface(mSecInfo);
if (secCtrl) {
SOCKET_LOG(("nsSocketTransport::InitiateSocket set esni keys."));
rv = secCtrl->SetEsniTxt(mDNSRecordTxt);
SOCKET_LOG(("nsSocketTransport::InitiateSocket set echconfig."));
rv = secCtrl->SetEchConfig(mEchConfig);
if (NS_FAILED(rv)) {
return rv;
}
mEsniUsed = true;
mEchConfigUsed = true;
}
}
@ -2181,14 +2149,12 @@ void nsSocketTransport::OnSocketEvent(uint32_t type, nsresult status,
break;
case MSG_DNS_LOOKUP_COMPLETE:
if (mDNSRequest ||
mDNSTxtRequest) { // only send this if we actually resolved anything
if (mDNSRequest) { // only send this if we actually resolved anything
SendStatus(NS_NET_STATUS_RESOLVED_HOST);
}
SOCKET_LOG((" MSG_DNS_LOOKUP_COMPLETE\n"));
mDNSRequest = nullptr;
mDNSTxtRequest = nullptr;
if (mDNSRecord) {
mDNSRecord->GetNextAddr(SocketPort(), &mNetAddr);
mDNSRecord->IsTRR(&mResolvedByTRR);
@ -2461,11 +2427,6 @@ void nsSocketTransport::OnSocketDetached(PRFileDesc* fd) {
mDNSRequest = nullptr;
}
if (mDNSTxtRequest) {
mDNSTxtRequest->Cancel(NS_ERROR_ABORT);
mDNSTxtRequest = nullptr;
}
//
// notify input/output streams
//
@ -2990,65 +2951,21 @@ nsSocketTransport::OnLookupComplete(nsICancelable* request, nsIDNSRecord* rec,
".",
this, static_cast<uint32_t>(status)));
if (request == mDNSTxtRequest) {
if (NS_SUCCEEDED(status)) {
nsCOMPtr<nsIDNSTXTRecord> txtResponse = do_QueryInterface(rec);
txtResponse->GetRecordsAsOneString(mDNSRecordTxt);
mDNSRecordTxt.Trim(" ");
}
Telemetry::Accumulate(Telemetry::ESNI_KEYS_RECORDS_FOUND,
NS_SUCCEEDED(status));
// flag host lookup complete for the benefit of the ResolveHost method.
if (!mDNSRequest) {
mResolving = false;
MOZ_ASSERT(mDNSARequestFinished);
Telemetry::Accumulate(
Telemetry::ESNI_KEYS_RECORD_FETCH_DELAYS,
PR_IntervalToMilliseconds(PR_IntervalNow() - mDNSARequestFinished));
nsresult rv =
PostEvent(MSG_DNS_LOOKUP_COMPLETE, mDNSLookupStatus, nullptr);
// if posting a message fails, then we should assume that the socket
// transport has been shutdown. this should never happen! if it does
// it means that the socket transport service was shutdown before the
// DNS service.
if (NS_FAILED(rv)) {
NS_WARNING("unable to post DNS lookup complete message");
}
} else {
mDNSTxtRequest = nullptr;
}
return NS_OK;
}
if (NS_FAILED(status) && mDNSTxtRequest) {
mDNSTxtRequest->Cancel(NS_ERROR_ABORT);
} else if (NS_SUCCEEDED(status)) {
if (NS_SUCCEEDED(status)) {
mDNSRecord = do_QueryInterface(rec);
MOZ_ASSERT(mDNSRecord);
}
// flag host lookup complete for the benefit of the ResolveHost method.
if (!mDNSTxtRequest) {
if (mEsniQueried) {
Telemetry::Accumulate(Telemetry::ESNI_KEYS_RECORD_FETCH_DELAYS, 0);
}
mResolving = false;
nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, nullptr);
mResolving = false;
nsresult rv = PostEvent(MSG_DNS_LOOKUP_COMPLETE, status, nullptr);
// if posting a message fails, then we should assume that the socket
// transport has been shutdown. this should never happen! if it does
// it means that the socket transport service was shutdown before the
// DNS service.
if (NS_FAILED(rv)) {
NS_WARNING("unable to post DNS lookup complete message");
}
} else {
mDNSLookupStatus =
status; // remember the status to send it when esni lookup is ready.
mDNSRequest = nullptr;
mDNSARequestFinished = PR_IntervalNow();
// if posting a message fails, then we should assume that the socket
// transport has been shutdown. this should never happen! if it does
// it means that the socket transport service was shutdown before the
// DNS service.
if (NS_FAILED(rv)) {
NS_WARNING("unable to post DNS lookup complete message");
}
return NS_OK;
@ -3623,8 +3540,14 @@ nsSocketTransport::GetResetIPFamilyPreference(bool* aReset) {
}
NS_IMETHODIMP
nsSocketTransport::GetEsniUsed(bool* aEsniUsed) {
*aEsniUsed = mEsniUsed;
nsSocketTransport::GetEchConfigUsed(bool* aEchConfigUsed) {
*aEchConfigUsed = mEchConfigUsed;
return NS_OK;
}
NS_IMETHODIMP
nsSocketTransport::SetEchConfig(const nsACString& aEchConfig) {
mEchConfig = aEchConfig;
return NS_OK;
}

View file

@ -327,12 +327,8 @@ class nsSocketTransport final : public nsASocketHandler,
nsCOMPtr<nsICancelable> mDNSRequest;
nsCOMPtr<nsIDNSAddrRecord> mDNSRecord;
nsresult mDNSLookupStatus;
PRIntervalTime mDNSARequestFinished;
nsCOMPtr<nsICancelable> mDNSTxtRequest;
nsCString mDNSRecordTxt;
bool mEsniQueried;
bool mEsniUsed;
nsCString mEchConfig;
bool mEchConfigUsed;
bool mResolvedByTRR;
// mNetAddr/mSelfAddr is valid from GetPeerAddr()/GetSelfAddr() once we have

View file

@ -214,6 +214,11 @@ Maybe<uint16_t> SVCBRecord::GetPort() { return mPort; }
Maybe<nsCString> SVCBRecord::GetAlpn() { return mAlpn; }
NS_IMETHODIMP SVCBRecord::GetEchConfig(nsACString& aEchConfig) {
aEchConfig = mData.mEchConfig;
return NS_OK;
}
NS_IMETHODIMP SVCBRecord::GetValues(nsTArray<RefPtr<nsISVCParam>>& aValues) {
for (const auto& v : mData.mSvcFieldValue) {
RefPtr<nsISVCParam> param = new SvcParam(v.mValue);

View file

@ -89,6 +89,7 @@ struct SVCB {
void GetIPHints(CopyableTArray<mozilla::net::NetAddr>& aAddresses) const;
uint16_t mSvcFieldPriority = 0;
nsCString mSvcDomainName;
nsCString mEchConfig;
bool mHasIPHints = false;
bool mHasEchConfig = false;
CopyableTArray<SvcFieldValue> mSvcFieldValue;

View file

@ -1097,6 +1097,7 @@ nsresult TRR::DohDecode(nsCString& aHost) {
}
if (value.mValue.is<SvcParamEchConfig>()) {
parsed.mHasEchConfig = true;
parsed.mEchConfig = value.mValue.as<SvcParamEchConfig>().mValue;
}
parsed.mSvcFieldValue.AppendElement(value);
}

View file

@ -92,6 +92,7 @@ interface nsISVCBRecord : nsISupports {
readonly attribute ACString name;
[noscript, nostdcall, notxpcom] readonly attribute MaybePort port;
[noscript, nostdcall, notxpcom] readonly attribute MaybeAlpn alpn;
readonly attribute ACString echConfig;
readonly attribute bool hasIPHintAddress;
readonly attribute Array<nsISVCParam> values;
};

View file

@ -367,6 +367,7 @@ struct HttpConnectionInfoCloneArgs
nsCString topWindowOrigin;
bool isHttp3;
bool hasIPHintAddress;
nsCString echConfig;
ProxyInfoCloneArgs[] proxyInfo;
};

View file

@ -2060,9 +2060,17 @@ SocketTransportShim::GetFirstRetryError(nsresult* aFirstRetryError) {
}
NS_IMETHODIMP
SocketTransportShim::GetEsniUsed(bool* aEsniUsed) {
SocketTransportShim::GetEchConfigUsed(bool* aEchConfigUsed) {
if (mIsWebsocket) {
LOG3(("WARNING: SocketTransportShim::GetEsniUsed %p", this));
LOG3(("WARNING: SocketTransportShim::GetEchConfigUsed %p", this));
}
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
SocketTransportShim::SetEchConfig(const nsACString& aEchConfig) {
if (mIsWebsocket) {
LOG3(("WARNING: SocketTransportShim::SetEchConfig %p", this));
}
return NS_ERROR_NOT_IMPLEMENTED;
}

View file

@ -443,8 +443,6 @@ bool nsHttpConnection::EnsureNPNComplete(nsresult& aOut0RTTWriteHandshakeValue,
nsCOMPtr<nsITransportSecurityInfo> info;
nsCOMPtr<nsISSLSocketControl> ssl;
nsAutoCString negotiatedNPN;
// This is neede for telemetry
bool handshakeSucceeded = false;
GetSecurityInfo(getter_AddRefs(securityInfo));
if (!securityInfo) {
@ -561,8 +559,6 @@ bool nsHttpConnection::EnsureNPNComplete(nsresult& aOut0RTTWriteHandshakeValue,
this, mConnInfo->HashKey().get(), negotiatedNPN.get(),
mTLSFilter ? " [Double Tunnel]" : ""));
handshakeSucceeded = true;
int16_t tlsVersion;
ssl->GetSSLVersionUsed(&tlsVersion);
mConnInfo->SetLessThanTls13(
@ -701,19 +697,6 @@ npnComplete:
mDid0RTTSpdy = false;
}
if (ssl) {
// Telemetry for tls failure rate with and without esni;
bool esni = false;
rv = mSocketTransport->GetEsniUsed(&esni);
if (NS_SUCCEEDED(rv)) {
Telemetry::Accumulate(
Telemetry::ESNI_NOESNI_TLS_SUCCESS_RATE,
(esni)
? ((handshakeSucceeded) ? ESNI_SUCCESSFUL : ESNI_FAILED)
: ((handshakeSucceeded) ? NO_ESNI_SUCCESSFUL : NO_ESNI_FAILED));
}
}
if (rv == psm::GetXPCOMFromNSSError(
mozilla::pkix::MOZILLA_PKIX_ERROR_MITM_DETECTED)) {
gSocketTransportService->SetNotTrustedMitmDetected();

View file

@ -339,6 +339,7 @@ already_AddRefed<nsHttpConnectionInfo> nsHttpConnectionInfo::Clone() const {
clone->SetIPv4Disabled(GetIPv4Disabled());
clone->SetIPv6Disabled(GetIPv6Disabled());
clone->SetHasIPHintAddress(HasIPHintAddress());
clone->SetEchConfig(GetEchConfig());
MOZ_ASSERT(clone->Equals(this));
return clone.forget();
@ -393,6 +394,10 @@ nsHttpConnectionInfo::CloneAndAdoptHTTPSSVCRecord(
clone->SetHasIPHintAddress(hasIPHint);
}
nsAutoCString echConfig;
Unused << aRecord->GetEchConfig(echConfig);
clone->SetEchConfig(echConfig);
return clone.forget();
}
@ -421,6 +426,7 @@ void nsHttpConnectionInfo::SerializeHttpConnectionInfo(
aArgs.topWindowOrigin() = aInfo->GetTopWindowOrigin();
aArgs.isHttp3() = aInfo->IsHttp3();
aArgs.hasIPHintAddress() = aInfo->HasIPHintAddress();
aArgs.echConfig() = aInfo->GetEchConfig();
if (!aInfo->ProxyInfo()) {
return;
@ -465,6 +471,7 @@ nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs(
cinfo->SetIPv4Disabled(aInfoArgs.isIPv4Disabled());
cinfo->SetIPv6Disabled(aInfoArgs.isIPv6Disabled());
cinfo->SetHasIPHintAddress(aInfoArgs.hasIPHintAddress());
cinfo->SetEchConfig(aInfoArgs.echConfig());
return cinfo.forget();
}
@ -491,6 +498,7 @@ void nsHttpConnectionInfo::CloneAsDirectRoute(nsHttpConnectionInfo** outCI) {
clone->SetIPv4Disabled(GetIPv4Disabled());
clone->SetIPv6Disabled(GetIPv6Disabled());
clone->SetHasIPHintAddress(HasIPHintAddress());
clone->SetEchConfig(GetEchConfig());
clone.forget(outCI);
}

View file

@ -212,6 +212,8 @@ class nsHttpConnectionInfo final : public ARefBase {
void SetHasIPHintAddress(bool aHasIPHint) { mHasIPHintAddress = aHasIPHint; }
bool HasIPHintAddress() const { return mHasIPHintAddress; }
const nsCString& GetEchConfig() const { return mEchConfig; }
private:
// These constructor versions are intended to be used from Clone() and
// DeserializeHttpConnectionInfoCloneArgs().
@ -234,6 +236,7 @@ class nsHttpConnectionInfo final : public ARefBase {
nsProxyInfo* proxyInfo, const OriginAttributes& originAttributes,
bool EndToEndSSL, bool aIsHttp3);
void SetOriginServer(const nsACString& host, int32_t port);
void SetEchConfig(const nsACString& aEchConfig) { mEchConfig = aEchConfig; }
nsCString mOrigin;
int32_t mOriginPort;
@ -264,6 +267,7 @@ class nsHttpConnectionInfo final : public ARefBase {
bool mIsHttp3;
bool mHasIPHintAddress = false;
nsCString mEchConfig;
// for RefPtr
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpConnectionInfo, override)

View file

@ -4237,7 +4237,7 @@ nsresult nsHttpConnectionMgr::nsHalfOpenSocket::SetupStreams(
}
if (ci->GetLessThanTls13()) {
tmpFlags |= nsISocketTransport::DONT_TRY_ESNI;
tmpFlags |= nsISocketTransport::DONT_TRY_ECH;
}
if (((mCaps & NS_HTTP_BE_CONSERVATIVE) || ci->GetBeConservative()) &&
@ -4330,6 +4330,11 @@ nsresult nsHttpConnectionMgr::nsHalfOpenSocket::SetupStreams(
rv = socketTransport->SetSecurityCallbacks(this);
NS_ENSURE_SUCCESS(rv, rv);
if (gHttpHandler->EchConfigEnabled()) {
rv = socketTransport->SetEchConfig(ci->GetEchConfig());
NS_ENSURE_SUCCESS(rv, rv);
}
Telemetry::Accumulate(Telemetry::HTTP_CONNECTION_ENTRY_CACHE_HIT_1,
mEnt->mUsedForConnection);
mEnt->mUsedForConnection = true;

View file

@ -146,10 +146,10 @@ interface nsISSLSocketControl : nsISupports {
[infallible] readonly attribute boolean failedVerification;
/*
* esniTxt is a string that consists of the concatenated _esni. TXT records.
* This is a base64 encoded ESNIKeys structure.
* echConfig is defined for conveying the ECH configuration.
* This is encoded in base64.
*/
attribute ACString esniTxt;
attribute ACString echConfig;
/**
* The id used to uniquely identify the connection to the peer.

View file

@ -276,12 +276,12 @@ CommonSocketControl::GetFailedVerification(bool* arg) {
}
NS_IMETHODIMP
CommonSocketControl::GetEsniTxt(nsACString& aEsniTxt) {
CommonSocketControl::GetEchConfig(nsACString& aEchConfig) {
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
CommonSocketControl::SetEsniTxt(const nsACString& aEsniTxt) {
CommonSocketControl::SetEchConfig(const nsACString& aEchConfig) {
return NS_ERROR_NOT_IMPLEMENTED;
}

View file

@ -697,33 +697,35 @@ PRStatus nsNSSSocketInfo::CloseSocketAndDestroy() {
}
NS_IMETHODIMP
nsNSSSocketInfo::GetEsniTxt(nsACString& aEsniTxt) {
aEsniTxt = mEsniTxt;
nsNSSSocketInfo::GetEchConfig(nsACString& aEchConfig) {
aEchConfig = mEchConfig;
return NS_OK;
}
NS_IMETHODIMP
nsNSSSocketInfo::SetEsniTxt(const nsACString& aEsniTxt) {
mEsniTxt = aEsniTxt;
nsNSSSocketInfo::SetEchConfig(const nsACString& aEchConfig) {
mEchConfig = aEchConfig;
if (mEsniTxt.Length()) {
nsAutoCString esniBin;
if (NS_OK != Base64Decode(mEsniTxt, esniBin)) {
#if 0
if (mEchConfig.Length()) {
nsAutoCString echBin;
if (NS_OK != Base64Decode(mEchConfig, echBin)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Error,
("[%p] Invalid ESNIKeys record. Couldn't base64 decode\n",
("[%p] Invalid EchConfig 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)) {
if (SECSuccess != SSL_SetClientEchConfigs(
mFd, reinterpret_cast<const PRUint8*>(echBin.get()),
echBin.Length())) {
MOZ_LOG(gPIPNSSLog, LogLevel::Error,
("[%p] Invalid ESNIKeys record %s\n", (void*)mFd,
("[%p] Invalid EchConfig record %s\n", (void*)mFd,
PR_ErrorToName(PR_GetError())));
return NS_OK;
}
}
#endif
return NS_OK;
}

View file

@ -66,8 +66,8 @@ class nsNSSSocketInfo final : public CommonSocketControl {
void SetDenyClientCert(bool aDenyClientCert) override;
NS_IMETHOD GetClientCert(nsIX509Cert** aClientCert) override;
NS_IMETHOD SetClientCert(nsIX509Cert* aClientCert) override;
NS_IMETHOD GetEsniTxt(nsACString& aEsniTxt) override;
NS_IMETHOD SetEsniTxt(const nsACString& aEsniTxt) override;
NS_IMETHOD GetEchConfig(nsACString& aEchConfig) override;
NS_IMETHOD SetEchConfig(const nsACString& aEchConfig) override;
NS_IMETHOD GetPeerId(nsACString& aResult) override;
PRStatus CloseSocketAndDestroy();
@ -180,7 +180,7 @@ class nsNSSSocketInfo final : public CommonSocketControl {
nsresult ActivateSSL();
nsCString mEsniTxt;
nsCString mEchConfig;
nsCString mPeerId;
bool mEarlyDataAccepted;
bool mDenyClientCert;