nsHTTPSOnlyStreamListener.cpp (9352B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "nsHTTPSOnlyStreamListener.h" 8 9 #include "NSSErrorsService.h" 10 #include "mozilla/TimeStamp.h" 11 #include "mozilla/dom/WindowGlobalParent.h" 12 #include "mozilla/glean/DomSecurityMetrics.h" 13 #include "mozpkix/pkixnss.h" 14 #include "nsCOMPtr.h" 15 #include "nsHTTPSOnlyUtils.h" 16 #include "nsIChannel.h" 17 #include "nsIRequest.h" 18 #include "nsITransportSecurityInfo.h" 19 #include "nsIURI.h" 20 #include "nsIWebProgressListener.h" 21 #include "nsPrintfCString.h" 22 #include "secerr.h" 23 #include "sslerr.h" 24 25 NS_IMPL_ISUPPORTS(nsHTTPSOnlyStreamListener, nsIStreamListener, 26 nsIRequestObserver) 27 28 nsHTTPSOnlyStreamListener::nsHTTPSOnlyStreamListener( 29 nsIStreamListener* aListener, nsILoadInfo* aLoadInfo) 30 : mListener(aListener), mCreationStart(mozilla::TimeStamp::Now()) { 31 RefPtr<mozilla::dom::WindowGlobalParent> wgp = 32 mozilla::dom::WindowGlobalParent::GetByInnerWindowId( 33 aLoadInfo->GetInnerWindowID()); 34 // For Top-level document loads (which don't have a requesting window-context) 35 // we compute these flags once we create the Document in nsSecureBrowserUI. 36 if (wgp) { 37 wgp->TopWindowContext()->AddSecurityState( 38 nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED); 39 } 40 } 41 42 NS_IMETHODIMP 43 nsHTTPSOnlyStreamListener::OnDataAvailable(nsIRequest* aRequest, 44 nsIInputStream* aInputStream, 45 uint64_t aOffset, uint32_t aCount) { 46 return mListener->OnDataAvailable(aRequest, aInputStream, aOffset, aCount); 47 } 48 49 NS_IMETHODIMP 50 nsHTTPSOnlyStreamListener::OnStartRequest(nsIRequest* request) { 51 return mListener->OnStartRequest(request); 52 } 53 54 NS_IMETHODIMP 55 nsHTTPSOnlyStreamListener::OnStopRequest(nsIRequest* request, 56 nsresult aStatus) { 57 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); 58 59 // Note: CouldBeHttpsOnlyError also returns true if there was no error 60 if (nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(channel, aStatus)) { 61 RecordUpgradeTelemetry(request, aStatus); 62 LogUpgradeFailure(request, aStatus); 63 64 // If the request failed and there is a requesting window-context, set 65 // HTTPS-Only state flag to indicate a failed upgrade. 66 // For Top-level document loads (which don't have a requesting 67 // window-context) we simply check in the UI code whether we landed on the 68 // HTTPS-Only error page. 69 if (NS_FAILED(aStatus)) { 70 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); 71 RefPtr<mozilla::dom::WindowGlobalParent> wgp = 72 mozilla::dom::WindowGlobalParent::GetByInnerWindowId( 73 loadInfo->GetInnerWindowID()); 74 75 if (wgp) { 76 wgp->TopWindowContext()->AddSecurityState( 77 nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED); 78 } 79 } 80 } 81 82 return mListener->OnStopRequest(request, aStatus); 83 } 84 85 void nsHTTPSOnlyStreamListener::RecordUpgradeTelemetry(nsIRequest* request, 86 nsresult aStatus) { 87 // 1. Get time between now and when the initial upgrade request started 88 mozilla::TimeDuration duration = mozilla::TimeStamp::Now() - mCreationStart; 89 90 // 2. Assemble the category string 91 // [!] All strings have to be present in Histograms.json 92 nsresult rv; 93 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv); 94 if (NS_FAILED(rv)) { 95 return; 96 } 97 98 nsAutoCString category; 99 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); 100 nsContentPolicyType internalType = loadInfo->InternalContentPolicyType(); 101 102 if (internalType == nsIContentPolicy::TYPE_DOCUMENT) { 103 category.AppendLiteral("top_"); 104 } else { 105 category.AppendLiteral("sub_"); 106 } 107 108 if (NS_SUCCEEDED(aStatus)) { 109 category.AppendLiteral("successful"); 110 } else { 111 int32_t code = -1 * NS_ERROR_GET_CODE(aStatus); 112 113 if (aStatus == NS_ERROR_REDIRECT_LOOP) { 114 category.AppendLiteral("f_redirectloop"); 115 } else if (aStatus == NS_ERROR_NET_TIMEOUT || 116 aStatus == NS_ERROR_NET_TIMEOUT_EXTERNAL) { 117 category.AppendLiteral("f_timeout"); 118 } else if (aStatus == NS_BINDING_ABORTED) { 119 category.AppendLiteral("f_aborted"); 120 } else if (aStatus == NS_ERROR_CONNECTION_REFUSED) { 121 category.AppendLiteral("f_cxnrefused"); 122 } else if (mozilla::psm::IsNSSErrorCode(code)) { 123 switch (code) { 124 case mozilla::pkix::MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT: 125 category.AppendLiteral("f_ssl_selfsignd"); 126 break; 127 case SSL_ERROR_BAD_CERT_DOMAIN: 128 category.AppendLiteral("f_ssl_badcertdm"); 129 break; 130 case SEC_ERROR_UNKNOWN_ISSUER: 131 category.AppendLiteral("f_ssl_unkwnissr"); 132 break; 133 default: 134 category.AppendLiteral("f_ssl_other"); 135 break; 136 } 137 } else { 138 category.AppendLiteral("f_other"); 139 } 140 } 141 mozilla::glean::security::https_only_mode_upgrade_time.Get(category) 142 .AccumulateRawDuration(duration); 143 144 bool success = NS_SUCCEEDED(aStatus); 145 ExtContentPolicyType externalType = loadInfo->GetExternalContentPolicyType(); 146 auto typeKey = nsAutoCString("unknown"); 147 148 if (externalType == ExtContentPolicy::TYPE_MEDIA) { 149 switch (internalType) { 150 case nsIContentPolicy::TYPE_INTERNAL_AUDIO: 151 case nsIContentPolicy::TYPE_INTERNAL_TRACK: 152 typeKey = "audio"_ns; 153 break; 154 155 case nsIContentPolicy::TYPE_INTERNAL_VIDEO: 156 typeKey = "video"_ns; 157 break; 158 159 default: 160 MOZ_ASSERT_UNREACHABLE(); 161 break; 162 } 163 } else { 164 switch (externalType) { 165 case ExtContentPolicy::TYPE_SCRIPT: 166 typeKey = "script"_ns; 167 break; 168 169 case ExtContentPolicy::TYPE_OBJECT: 170 typeKey = "object"_ns; 171 break; 172 173 case ExtContentPolicy::TYPE_DOCUMENT: 174 typeKey = "document"_ns; 175 break; 176 177 case ExtContentPolicy::TYPE_SUBDOCUMENT: 178 typeKey = "subdocument"_ns; 179 break; 180 181 case ExtContentPolicy::TYPE_XMLHTTPREQUEST: 182 typeKey = "xmlhttprequest"_ns; 183 break; 184 185 case ExtContentPolicy::TYPE_IMAGE: 186 case ExtContentPolicy::TYPE_IMAGESET: 187 typeKey = "image"_ns; 188 break; 189 190 case ExtContentPolicy::TYPE_DTD: 191 typeKey = "dtd"_ns; 192 break; 193 194 case ExtContentPolicy::TYPE_FONT: 195 case ExtContentPolicy::TYPE_UA_FONT: 196 typeKey = "font"_ns; 197 break; 198 199 case ExtContentPolicy::TYPE_FETCH: 200 typeKey = "fetch"_ns; 201 break; 202 203 case ExtContentPolicy::TYPE_WEBSOCKET: 204 typeKey = "websocket"_ns; 205 break; 206 207 case ExtContentPolicy::TYPE_STYLESHEET: 208 typeKey = "stylesheet"_ns; 209 break; 210 211 case ExtContentPolicy::TYPE_CSP_REPORT: 212 typeKey = "cspreport"_ns; 213 break; 214 215 case ExtContentPolicy::TYPE_WEB_MANIFEST: 216 typeKey = "webmanifest"_ns; 217 break; 218 219 case ExtContentPolicy::TYPE_PING: 220 typeKey = "ping"_ns; 221 break; 222 223 case ExtContentPolicy::TYPE_XSLT: 224 typeKey = "xslt"_ns; 225 break; 226 227 case ExtContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA: 228 typeKey = "proxied-webrtc"_ns; 229 break; 230 231 case ExtContentPolicy::TYPE_INVALID: 232 case ExtContentPolicy::TYPE_OTHER: 233 case ExtContentPolicy::TYPE_MEDIA: // already handled above 234 case ExtContentPolicy::TYPE_BEACON: 235 case ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD: 236 case ExtContentPolicy::TYPE_SPECULATIVE: 237 case ExtContentPolicy::TYPE_WEB_TRANSPORT: 238 case ExtContentPolicy::TYPE_WEB_IDENTITY: 239 case ExtContentPolicy::TYPE_JSON: 240 break; 241 // Do not add default: so that compilers can catch the missing case. 242 } 243 } 244 245 // Needs bug 1657470 (New Metric Type: "Keyed Categorical") before 246 // this can be migrated to Glean. 247 mozilla::glean::security::https_only_mode_upgrade_type 248 .Get(typeKey, success ? "true"_ns : "false"_ns) 249 .Add(); 250 } 251 252 void nsHTTPSOnlyStreamListener::LogUpgradeFailure(nsIRequest* request, 253 nsresult aStatus) { 254 // If the request failed we'll log it to the console with the error-code 255 if (NS_SUCCEEDED(aStatus)) { 256 return; 257 } 258 nsresult rv; 259 // Try to query for the channel-object 260 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv); 261 if (NS_FAILED(rv)) { 262 return; 263 } 264 265 nsCOMPtr<nsIURI> uri; 266 rv = channel->GetURI(getter_AddRefs(uri)); 267 if (NS_FAILED(rv)) { 268 return; 269 } 270 // Logging URI as well as Module- and Error-Code 271 AutoTArray<nsString, 2> params = { 272 NS_ConvertUTF8toUTF16(uri->GetSpecOrDefault()), 273 NS_ConvertUTF8toUTF16(nsPrintfCString("M%u-C%u", 274 NS_ERROR_GET_MODULE(aStatus), 275 NS_ERROR_GET_CODE(aStatus)))}; 276 277 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); 278 nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyFailedRequest", params, 279 nsIScriptError::errorFlag, loadInfo, 280 uri); 281 }