This is an automated email from the git hooks/post-receive script.
richard pushed a commit to branch tor-browser-102.3.0esr-12.0-2 in repository tor-browser.
commit e595af6f0f647c16ffd6c6e35b1a94f29b8b601a Author: Richard Pospesel richard@torproject.org AuthorDate: Fri Jun 8 13:38:40 2018 -0700
Bug 23247: Communicating security expectations for .onion
Encrypting pages hosted on Onion Services with SSL/TLS is redundant (in terms of hiding content) as all traffic within the Tor network is already fully encrypted. Therefore, serving HTTP pages from an Onion Service is more or less fine.
Prior to this patch, Tor Browser would mostly treat pages delivered via Onion Services as well as pages delivered in the ordinary fashion over the internet in the same way. This created some inconsistencies in behaviour and misinformation presented to the user relating to the security of pages delivered via Onion Services:
- HTTP Onion Service pages did not have any 'lock' icon indicating the site was secure - HTTP Onion Service pages would be marked as unencrypted in the Page Info screen - Mixed-mode content restrictions did not apply to HTTP Onion Service pages embedding Non-Onion HTTP content
This patch fixes the above issues, and also adds several new 'Onion' icons to the mix to indicate all of the various permutations of Onion Services hosted HTTP or HTTPS pages with HTTP or HTTPS content.
Strings for Onion Service Page Info page are pulled from Torbutton's localization strings. --- browser/base/content/browser-siteIdentity.js | 61 +++++++++++++++++----- browser/base/content/pageinfo/security.js | 49 +++++++++++++++-- .../shared/identity-block/identity-block.css | 19 +++++++ dom/base/nsContentUtils.cpp | 19 +++++++ dom/base/nsContentUtils.h | 5 ++ dom/base/nsGlobalWindowOuter.cpp | 3 +- dom/ipc/WindowGlobalActor.cpp | 4 +- dom/ipc/WindowGlobalChild.cpp | 6 ++- dom/security/nsMixedContentBlocker.cpp | 16 ++++-- .../modules/geckoview/GeckoViewProgress.jsm | 4 ++ security/certverifier/CertVerifier.cpp | 22 ++++++-- security/manager/ssl/SSLServerCertVerification.cpp | 14 ++++- security/manager/ssl/nsNSSIOLayer.cpp | 13 +++-- security/manager/ssl/nsSecureBrowserUI.cpp | 12 +++++ security/nss/lib/mozpkix/include/pkix/Result.h | 2 + security/nss/lib/mozpkix/include/pkix/pkixnss.h | 1 + 16 files changed, 217 insertions(+), 33 deletions(-)
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js index 02fdb494d290..64a806719a77 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -139,6 +139,12 @@ var gIdentityHandler = { ); },
+ get _uriIsOnionHost() { + return this._uriHasHost + ? this._uri.host.toLowerCase().endsWith(".onion") + : false; + }, + get _isAboutNetErrorPage() { let { documentURI } = gBrowser.selectedBrowser; return documentURI?.scheme == "about" && documentURI.filePath == "neterror"; @@ -737,9 +743,9 @@ var gIdentityHandler = { get pointerlockFsWarningClassName() { // Note that the fullscreen warning does not handle _isSecureInternalUI. if (this._uriHasHost && this._isSecureConnection) { - return "verifiedDomain"; + return this._uriIsOnionHost ? "onionVerifiedDomain" : "verifiedDomain"; } - return "unknownIdentity"; + return this._uriIsOnionHost ? "onionUnknownIdentity" : "unknownIdentity"; },
/** @@ -747,12 +753,18 @@ var gIdentityHandler = { * built-in (returns false) or imported (returns true). */ _hasCustomRoot() { + if (!this._secInfo) { + return false; + } + let issuerCert = null; issuerCert = this._secInfo.succeededCertChain[ this._secInfo.succeededCertChain.length - 1 ]; - - return !issuerCert.isBuiltInRoot; + if (issuerCert) { + return !issuerCert.isBuiltInRoot; + } + return false; },
/** @@ -789,11 +801,17 @@ var gIdentityHandler = { "identity.extension.label", [extensionName] ); - } else if (this._uriHasHost && this._isSecureConnection) { + } else if (this._uriHasHost && this._isSecureConnection && this._secInfo) { // This is a secure connection. - this._identityBox.className = "verifiedDomain"; + // _isSecureConnection implicitly includes onion services, which may not have an SSL certificate + const uriIsOnionHost = this._uriIsOnionHost; + this._identityBox.className = uriIsOnionHost + ? "onionVerifiedDomain" + : "verifiedDomain"; if (this._isMixedActiveContentBlocked) { - this._identityBox.classList.add("mixedActiveBlocked"); + this._identityBox.classList.add( + uriIsOnionHost ? "onionMixedActiveBlocked" : "mixedActiveBlocked" + ); } if (!this._isCertUserOverridden) { // It's a normal cert, verifier is the CA Org. @@ -804,17 +822,27 @@ var gIdentityHandler = { } } else if (this._isBrokenConnection) { // This is a secure connection, but something is wrong. - this._identityBox.className = "unknownIdentity"; + const uriIsOnionHost = this._uriIsOnionHost; + this._identityBox.className = uriIsOnionHost + ? "onionUnknownIdentity" + : "unknownIdentity";
if (this._isMixedActiveContentLoaded) { - this._identityBox.classList.add("mixedActiveContent"); + this._identityBox.classList.add( + uriIsOnionHost ? "onionMixedActiveContent" : "mixedActiveContent" + ); } else if (this._isMixedActiveContentBlocked) { this._identityBox.classList.add( - "mixedDisplayContentLoadedActiveBlocked" + uriIsOnionHost + ? "onionMixedDisplayContentLoadedActiveBlocked" + : "mixedDisplayContentLoadedActiveBlocked" ); } else if (this._isMixedPassiveContentLoaded) { - this._identityBox.classList.add("mixedDisplayContent"); + this._identityBox.classList.add( + uriIsOnionHost ? "onionMixedDisplayContent" : "mixedDisplayContent" + ); } else { + // TODO: ignore weak https cipher for onionsites? this._identityBox.classList.add("weakCipher"); } } else if (this._isCertErrorPage) { @@ -830,8 +858,10 @@ var gIdentityHandler = { // Network errors and blocked pages get a more neutral icon this._identityBox.className = "unknownIdentity"; } else if (this._isPotentiallyTrustworthy) { - // This is a local resource (and shouldn't be marked insecure). - this._identityBox.className = "localResource"; + // This is a local resource or an onion site (and shouldn't be marked insecure). + this._identityBox.className = this._uriIsOnionHost + ? "onionUnknownIdentity" + : "localResource"; } else { // This is an insecure connection. let warnOnInsecure = @@ -855,7 +885,10 @@ var gIdentityHandler = { }
if (this._isCertUserOverridden) { - this._identityBox.classList.add("certUserOverridden"); + const uriIsOnionHost = this._uriIsOnionHost; + this._identityBox.classList.add( + uriIsOnionHost ? "onionCertUserOverridden" : "certUserOverridden" + ); // Cert is trusted because of a security exception, verifier is a special string. tooltip = gNavigatorBundle.getString( "identity.identified.verified_by_you" diff --git a/browser/base/content/pageinfo/security.js b/browser/base/content/pageinfo/security.js index 1222c8b0ec35..a5d354208d79 100644 --- a/browser/base/content/pageinfo/security.js +++ b/browser/base/content/pageinfo/security.js @@ -22,6 +22,11 @@ ChromeUtils.defineModuleGetter( "PluralForm", "resource://gre/modules/PluralForm.jsm" ); +XPCOMUtils.defineLazyGetter(this, "gTorButtonBundle", function() { + return Services.strings.createBundle( + "chrome://torbutton/locale/torbutton.properties" + ); +});
var security = { async init(uri, windowInfo) { @@ -60,6 +65,11 @@ var security = { (Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT | Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT); var isEV = ui.state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL; + var isOnion = false; + const hostName = this.windowInfo.hostName; + if (hostName && hostName.endsWith(".onion")) { + isOnion = true; + }
let retval = { cAName: "", @@ -69,6 +79,7 @@ var security = { isBroken, isMixed, isEV, + isOnion, cert: null, certificateTransparency: null, }; @@ -107,6 +118,7 @@ var security = { isBroken, isMixed, isEV, + isOnion, cert, certChain: certChainArray, certificateTransparency: undefined, @@ -348,13 +360,31 @@ async function securityOnLoad(uri, windowInfo) { } msg2 = pkiBundle.getString("pageInfo_Privacy_None2"); } else if (info.encryptionStrength > 0) { - hdr = pkiBundle.getFormattedString( - "pageInfo_EncryptionWithBitsAndProtocol", - [info.encryptionAlgorithm, info.encryptionStrength + "", info.version] - ); + if (!info.isOnion) { + hdr = pkiBundle.getFormattedString( + "pageInfo_EncryptionWithBitsAndProtocol", + [info.encryptionAlgorithm, info.encryptionStrength + "", info.version] + ); + } else { + try { + hdr = gTorButtonBundle.formatStringFromName( + "pageInfo_OnionEncryptionWithBitsAndProtocol", + [info.encryptionAlgorithm, info.encryptionStrength + "", info.version] + ); + } catch (err) { + hdr = + "Connection Encrypted (Onion Service, " + + info.encryptionAlgorithm + + ", " + + info.encryptionStrength + + " bit keys, " + + info.version + + ")"; + } + } msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1"); msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2"); - } else { + } else if (!info.isOnion) { hdr = pkiBundle.getString("pageInfo_NoEncryption"); if (windowInfo.hostName != null) { msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [ @@ -364,6 +394,15 @@ async function securityOnLoad(uri, windowInfo) { msg1 = pkiBundle.getString("pageInfo_Privacy_None4"); } msg2 = pkiBundle.getString("pageInfo_Privacy_None2"); + } else { + try { + hdr = gTorButtonBundle.GetStringFromName("pageInfo_OnionEncryption"); + } catch (err) { + hdr = "Connection Encrypted (Onion Service)"; + } + + msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1"); + msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2"); } setText("security-technical-shortform", hdr); setText("security-technical-longform1", msg1); diff --git a/browser/themes/shared/identity-block/identity-block.css b/browser/themes/shared/identity-block/identity-block.css index 63c07d21a453..7314a6570fb8 100644 --- a/browser/themes/shared/identity-block/identity-block.css +++ b/browser/themes/shared/identity-block/identity-block.css @@ -193,6 +193,25 @@ list-style-image: url(chrome://global/skin/icons/security-broken.svg); }
+#identity-box[pageproxystate="valid"].onionUnknownIdentity #identity-icon, +#identity-box[pageproxystate="valid"].onionVerifiedDomain #identity-icon, +#identity-box[pageproxystate="valid"].onionMixedActiveBlocked #identity-icon { + list-style-image: url(chrome://browser/skin/onion.svg); + visibility: visible; +} + +#identity-box[pageproxystate="valid"].onionMixedDisplayContent #identity-icon, +#identity-box[pageproxystate="valid"].onionMixedDisplayContentLoadedActiveBlocked #identity-icon, +#identity-box[pageproxystate="valid"].onionCertUserOverridden #identity-icon { + list-style-image: url(chrome://browser/skin/onion-warning.svg); + visibility: visible; +} + +#identity-box[pageproxystate="valid"].onionMixedActiveContent #identity-icon { + list-style-image: url(chrome://browser/skin/onion-slash.svg); + visibility: visible; +} + #permissions-granted-icon { list-style-image: url(chrome://browser/skin/permissions.svg); } diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 0562245205a3..0dced75b718a 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -9451,6 +9451,25 @@ bool nsContentUtils::ComputeIsSecureContext(nsIChannel* aChannel) { return principal->GetIsOriginPotentiallyTrustworthy(); }
+/* static */ bool nsContentUtils::DocumentHasOnionURI(Document* aDocument) { + if (!aDocument) { + return false; + } + + nsIURI* uri = aDocument->GetDocumentURI(); + if (!uri) { + return false; + } + + nsAutoCString host; + if (NS_SUCCEEDED(uri->GetHost(host))) { + bool hasOnionURI = StringEndsWith(host, ".onion"_ns); + return hasOnionURI; + } + + return false; +} + /* static */ void nsContentUtils::TryToUpgradeElement(Element* aElement) { NodeInfo* nodeInfo = aElement->NodeInfo(); diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 739e8ca23c85..12d1c3b998b8 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -3014,6 +3014,11 @@ class nsContentUtils { */ static bool HttpsStateIsModern(Document* aDocument);
+ /** + * Returns true of the document's URI is a .onion + */ + static bool DocumentHasOnionURI(Document* aDocument); + /** * Returns true if the channel is for top-level window and is over secure * context. diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp index 03b90e6098d8..6697ae5ee2ab 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp @@ -1882,7 +1882,8 @@ bool nsGlobalWindowOuter::ComputeIsSecureContext(Document* aDocument, return false; }
- if (nsContentUtils::HttpsStateIsModern(aDocument)) { + if (nsContentUtils::HttpsStateIsModern(aDocument) || + nsContentUtils::DocumentHasOnionURI(aDocument)) { return true; }
diff --git a/dom/ipc/WindowGlobalActor.cpp b/dom/ipc/WindowGlobalActor.cpp index 3413442f1137..7d6a028d676f 100644 --- a/dom/ipc/WindowGlobalActor.cpp +++ b/dom/ipc/WindowGlobalActor.cpp @@ -21,6 +21,7 @@ #include "mozilla/net/CookieJarSettings.h" #include "mozilla/dom/WindowGlobalChild.h" #include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/nsMixedContentBlocker.h"
#include "nsGlobalWindowInner.h" #include "nsNetUtil.h" @@ -133,7 +134,8 @@ WindowGlobalInit WindowGlobalActor::WindowInitializer(
// Init Mixed Content Fields nsCOMPtr<nsIURI> innerDocURI = NS_GetInnermostURI(doc->GetDocumentURI()); - fields.mIsSecure = innerDocURI && innerDocURI->SchemeIs("https"); + fields.mIsSecure = innerDocURI && (innerDocURI->SchemeIs("https") || + nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(innerDocURI));
nsCOMPtr<nsITransportSecurityInfo> securityInfo; if (nsCOMPtr<nsIChannel> channel = doc->GetChannel()) { diff --git a/dom/ipc/WindowGlobalChild.cpp b/dom/ipc/WindowGlobalChild.cpp index f209705183cb..f9ce500f18e2 100644 --- a/dom/ipc/WindowGlobalChild.cpp +++ b/dom/ipc/WindowGlobalChild.cpp @@ -42,6 +42,8 @@ #include "nsIHttpChannelInternal.h" #include "nsIURIMutator.h"
+#include "mozilla/dom/nsMixedContentBlocker.h" + using namespace mozilla::ipc; using namespace mozilla::dom::ipc;
@@ -228,7 +230,9 @@ void WindowGlobalChild::OnNewDocument(Document* aDocument) { nsCOMPtr<nsIURI> innerDocURI = NS_GetInnermostURI(aDocument->GetDocumentURI()); if (innerDocURI) { - txn.SetIsSecure(innerDocURI->SchemeIs("https")); + txn.SetIsSecure( + innerDocURI->SchemeIs("https") || + nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(innerDocURI)); }
MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal->GetIsLocalIpAddress() == diff --git a/dom/security/nsMixedContentBlocker.cpp b/dom/security/nsMixedContentBlocker.cpp index e4352e3cb43b..2a7d878a2fcf 100644 --- a/dom/security/nsMixedContentBlocker.cpp +++ b/dom/security/nsMixedContentBlocker.cpp @@ -666,8 +666,8 @@ nsresult nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, return NS_OK; }
- // Check the parent scheme. If it is not an HTTPS page then mixed content - // restrictions do not apply. + // Check the parent scheme. If it is not an HTTPS or .onion page then mixed + // content restrictions do not apply. nsCOMPtr<nsIURI> innerRequestingLocation = NS_GetInnermostURI(requestingLocation); if (!innerRequestingLocation) { @@ -682,6 +682,17 @@ nsresult nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect,
bool parentIsHttps = innerRequestingLocation->SchemeIs("https"); if (!parentIsHttps) { + bool parentIsOnion = IsPotentiallyTrustworthyOnion(innerRequestingLocation); + if (!parentIsOnion) { + *aDecision = ACCEPT; + return NS_OK; + } + } + + bool isHttpScheme = innerContentLocation->SchemeIs("http"); + // .onion URLs are encrypted and authenticated. Don't treat them as mixed + // content if potentially trustworthy (i.e. whitelisted). + if (isHttpScheme && IsPotentiallyTrustworthyOnion(innerContentLocation)) { *aDecision = ACCEPT; MOZ_LOG(sMCBLog, LogLevel::Verbose, (" -> decision: Request will be allowed because the requesting " @@ -708,7 +719,6 @@ nsresult nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect, return NS_OK; }
- bool isHttpScheme = innerContentLocation->SchemeIs("http"); if (isHttpScheme && IsPotentiallyTrustworthyOrigin(innerContentLocation)) { *aDecision = ACCEPT; return NS_OK; diff --git a/mobile/android/modules/geckoview/GeckoViewProgress.jsm b/mobile/android/modules/geckoview/GeckoViewProgress.jsm index d121ce850f7f..e054bc6bd709 100644 --- a/mobile/android/modules/geckoview/GeckoViewProgress.jsm +++ b/mobile/android/modules/geckoview/GeckoViewProgress.jsm @@ -145,6 +145,10 @@ var IdentityHandler = { result.host = uri.host; }
+ if (!aBrowser.securityUI.secInfo) { + return result; + } + const cert = aBrowser.securityUI.secInfo.serverCert;
result.certificate = aBrowser.securityUI.secInfo.serverCert.getBase64DERString(); diff --git a/security/certverifier/CertVerifier.cpp b/security/certverifier/CertVerifier.cpp index b6ee9f699ea8..2ad18700bd44 100644 --- a/security/certverifier/CertVerifier.cpp +++ b/security/certverifier/CertVerifier.cpp @@ -907,6 +907,9 @@ Result CertVerifier::VerifySSLServerCert( if (rv != Success) { return rv; } + + bool errOnionWithSelfSignedCert = false; + bool isBuiltChainRootBuiltInRootLocal; rv = VerifyCert(peerCertBytes, certificateUsageSSLServer, time, pinarg, PromiseFlatCString(hostname).get(), builtChain, flags, @@ -926,9 +929,16 @@ Result CertVerifier::VerifySSLServerCert( CertIsSelfSigned(peerBackCert, pinarg)) { // In this case we didn't find any issuer for the certificate and the // certificate is self-signed. - return Result::ERROR_SELF_SIGNED_CERT; + if (StringEndsWith(hostname, ".onion"_ns)) { + // Self signed cert over onion is deemed secure, the hidden service provides authentication. + // We defer returning this error and keep processing to determine if there are other legitimate + // certificate errors (such as expired, wrong domain) that we would like to surface to the user + errOnionWithSelfSignedCert = true; + } else { + return Result::ERROR_SELF_SIGNED_CERT; + } } - if (rv == Result::ERROR_UNKNOWN_ISSUER) { + if (rv == Result::ERROR_UNKNOWN_ISSUER && !errOnionWithSelfSignedCert) { // In this case we didn't get any valid path for the cert. Let's see if // the issuer is the same as the issuer for our canary probe. If yes, this // connection is connecting via a misconfigured proxy. @@ -952,7 +962,9 @@ Result CertVerifier::VerifySSLServerCert( return Result::ERROR_MITM_DETECTED; } } - return rv; + if (!errOnionWithSelfSignedCert) { + return rv; + } }
if (dcInfo) { @@ -1002,7 +1014,9 @@ Result CertVerifier::VerifySSLServerCert( if (isBuiltChainRootBuiltInRoot) { *isBuiltChainRootBuiltInRoot = isBuiltChainRootBuiltInRootLocal; } - + if (errOnionWithSelfSignedCert) { + return Result::ERROR_ONION_WITH_SELF_SIGNED_CERT; + } return Success; }
diff --git a/security/manager/ssl/SSLServerCertVerification.cpp b/security/manager/ssl/SSLServerCertVerification.cpp index 52628799176c..2df79a3bc11a 100644 --- a/security/manager/ssl/SSLServerCertVerification.cpp +++ b/security/manager/ssl/SSLServerCertVerification.cpp @@ -309,6 +309,7 @@ SECStatus DetermineCertOverrideErrors(const nsCOMPtr<nsIX509Cert>& cert, case mozilla::pkix::MOZILLA_PKIX_ERROR_MITM_DETECTED: case mozilla::pkix::MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE: case mozilla::pkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT: + case mozilla::pkix::MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT: case mozilla::pkix::MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA: { collectedErrors = nsICertOverrideService::ERROR_UNTRUSTED; errorCodeTrust = defaultErrorCodeToReport; @@ -830,6 +831,17 @@ PRErrorCode AuthCertificateParseResults( gPIPNSSLog, LogLevel::Debug, ("[0x%" PRIx64 "] Certificate error was not overridden\n", aPtrForLog));
+ // If Onion with self signed cert we want to prioritize any other error + if (errorCodeTrust == MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT) { + if (errorCodeMismatch) { + return errorCodeMismatch; + } else if (errorCodeTime) { + return errorCodeTime; + } else { + return MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT; + } + } + // pick the error code to report by priority return errorCodeTrust ? errorCodeTrust : errorCodeMismatch ? errorCodeMismatch @@ -1257,7 +1269,7 @@ SSLServerCertVerificationResult::Run() { // Certificate validation failed; store the peer certificate chain on // infoObject so it can be used for error reporting. mInfoObject->SetFailedCertChain(std::move(mPeerCertChain)); - if (mCollectedErrors != 0) { + if (mCollectedErrors != 0 && mFinalError != MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT) { mInfoObject->SetStatusErrorBits(cert, mCollectedErrors); } } diff --git a/security/manager/ssl/nsNSSIOLayer.cpp b/security/manager/ssl/nsNSSIOLayer.cpp index 2c36893324ff..d79237478cdd 100644 --- a/security/manager/ssl/nsNSSIOLayer.cpp +++ b/security/manager/ssl/nsNSSIOLayer.cpp @@ -420,7 +420,11 @@ void nsNSSSocketInfo::SetCertVerificationResult(PRErrorCode errorCode) { "Invalid state transition to cert_verification_finished");
if (mFd) { - SECStatus rv = SSL_AuthCertificateComplete(mFd, errorCode); + PRErrorCode passCode = errorCode; + if (errorCode == MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT) { + passCode = 0; + } + SECStatus rv = SSL_AuthCertificateComplete(mFd, passCode); // Only replace errorCode if there was originally no error if (rv != SECSuccess && errorCode == 0) { errorCode = PR_GetError(); @@ -431,12 +435,15 @@ void nsNSSSocketInfo::SetCertVerificationResult(PRErrorCode errorCode) { } }
- if (errorCode) { + if (errorCode && + errorCode != MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT) { mFailedVerification = true; SetCanceled(errorCode); }
- if (mPlaintextBytesRead && !errorCode) { + if (mPlaintextBytesRead && + (!errorCode || + errorCode == MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT)) { Telemetry::Accumulate(Telemetry::SSL_BYTES_BEFORE_CERT_CALLBACK, AssertedCast<uint32_t>(mPlaintextBytesRead)); } diff --git a/security/manager/ssl/nsSecureBrowserUI.cpp b/security/manager/ssl/nsSecureBrowserUI.cpp index b4de1a331ffc..f1ce39582854 100644 --- a/security/manager/ssl/nsSecureBrowserUI.cpp +++ b/security/manager/ssl/nsSecureBrowserUI.cpp @@ -9,6 +9,7 @@ #include "mozilla/Logging.h" #include "mozilla/Unused.h" #include "mozilla/dom/Document.h" +#include "mozilla/dom/nsMixedContentBlocker.h" #include "nsContentUtils.h" #include "nsIChannel.h" #include "nsDocShell.h" @@ -85,6 +86,17 @@ void nsSecureBrowserUI::RecomputeSecurityFlags() { } } } + + // any protocol routed over tor is secure + if (!(mState & nsIWebProgressListener::STATE_IS_SECURE)) { + nsCOMPtr<nsIURI> innerDocURI = NS_GetInnermostURI(win->GetDocumentURI()); + if (innerDocURI && + nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(innerDocURI)) { + MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, (" is onion")); + mState = (mState & ~nsIWebProgressListener::STATE_IS_INSECURE) | + nsIWebProgressListener::STATE_IS_SECURE; + } + } }
// Add upgraded-state flags when request has been diff --git a/security/nss/lib/mozpkix/include/pkix/Result.h b/security/nss/lib/mozpkix/include/pkix/Result.h index 29461dc1a510..b2ad3a383ceb 100644 --- a/security/nss/lib/mozpkix/include/pkix/Result.h +++ b/security/nss/lib/mozpkix/include/pkix/Result.h @@ -188,6 +188,8 @@ static const unsigned int FATAL_ERROR_FLAG = 0x800; SEC_ERROR_LIBRARY_FAILURE) \ MOZILLA_PKIX_MAP(FATAL_ERROR_NO_MEMORY, FATAL_ERROR_FLAG | 4, \ SEC_ERROR_NO_MEMORY) \ + MOZILLA_PKIX_MAP(ERROR_ONION_WITH_SELF_SIGNED_CERT, 155, \ + MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT) \ /* nothing here */
enum class Result { diff --git a/security/nss/lib/mozpkix/include/pkix/pkixnss.h b/security/nss/lib/mozpkix/include/pkix/pkixnss.h index c1050c6497a4..75a7c4e9c0fb 100644 --- a/security/nss/lib/mozpkix/include/pkix/pkixnss.h +++ b/security/nss/lib/mozpkix/include/pkix/pkixnss.h @@ -94,6 +94,7 @@ enum ErrorCode { MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED = ERROR_BASE + 13, MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT = ERROR_BASE + 14, MOZILLA_PKIX_ERROR_MITM_DETECTED = ERROR_BASE + 15, + MOZILLA_PKIX_ERROR_ONION_WITH_SELF_SIGNED_CERT = ERROR_BASE + 100, END_OF_LIST };