Line data Source code
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 : #include "HttpLog.h"
7 :
8 : #include "nsHttp.h"
9 :
10 : #include "HSTSPrimerListener.h"
11 : #include "nsIHstsPrimingCallback.h"
12 : #include "nsIPrincipal.h"
13 : #include "nsSecurityHeaderParser.h"
14 : #include "nsISiteSecurityService.h"
15 : #include "nsISocketProvider.h"
16 : #include "nsISSLStatus.h"
17 : #include "nsISSLStatusProvider.h"
18 : #include "nsStreamUtils.h"
19 : #include "nsStreamListenerWrapper.h"
20 : #include "nsHttpChannel.h"
21 : #include "LoadInfo.h"
22 : #include "mozilla/Unused.h"
23 : #include "nsQueryObject.h"
24 :
25 : namespace mozilla {
26 : namespace net {
27 :
28 : using namespace mozilla;
29 :
30 0 : NS_IMPL_ISUPPORTS(HSTSPrimingListener, nsIStreamListener,
31 : nsIRequestObserver, nsIInterfaceRequestor,
32 : nsITimerCallback)
33 :
34 : // default to 2000ms, same as the preference
35 : // security.mixed_content.hsts_priming_request_timeout
36 : uint32_t HSTSPrimingListener::sHSTSPrimingTimeout = 2000;
37 :
38 :
39 0 : HSTSPrimingListener::HSTSPrimingListener(nsIHstsPrimingCallback* aCallback)
40 0 : : mCallback(aCallback)
41 : {
42 : static nsresult rv =
43 0 : Preferences::AddUintVarCache(&sHSTSPrimingTimeout,
44 0 : "security.mixed_content.hsts_priming_request_timeout");
45 : Unused << rv;
46 0 : }
47 :
48 : NS_IMETHODIMP
49 0 : HSTSPrimingListener::GetInterface(const nsIID & aIID, void **aResult)
50 : {
51 0 : return QueryInterface(aIID, aResult);
52 : }
53 :
54 : void
55 0 : HSTSPrimingListener::ReportTiming(nsIHstsPrimingCallback* aCallback, nsresult aResult)
56 : {
57 : nsCOMPtr<nsITimedChannel> timingChannel =
58 0 : do_QueryInterface(aCallback);
59 0 : if (!timingChannel) {
60 0 : LOG(("HSTS priming: mCallback is not an nsITimedChannel!"));
61 0 : return;
62 : }
63 :
64 0 : TimeStamp channelCreationTime;
65 0 : nsresult rv = timingChannel->GetChannelCreation(&channelCreationTime);
66 0 : if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) {
67 : PRUint32 interval =
68 0 : (PRUint32) (TimeStamp::Now() - channelCreationTime).ToMilliseconds();
69 0 : Telemetry::Accumulate(Telemetry::HSTS_PRIMING_REQUEST_DURATION,
70 0 : (NS_SUCCEEDED(aResult)) ? NS_LITERAL_CSTRING("success")
71 0 : : NS_LITERAL_CSTRING("failure"),
72 0 : interval);
73 : }
74 : }
75 :
76 : NS_IMETHODIMP
77 0 : HSTSPrimingListener::OnStartRequest(nsIRequest *aRequest,
78 : nsISupports *aContext)
79 : {
80 0 : nsCOMPtr<nsIHstsPrimingCallback> callback;
81 0 : callback.swap(mCallback);
82 :
83 0 : if (mHSTSPrimingTimer) {
84 0 : Unused << mHSTSPrimingTimer->Cancel();
85 0 : mHSTSPrimingTimer = nullptr;
86 : }
87 :
88 : // if callback is null, we have already canceled this request and reported
89 : // the failure
90 0 : if (!callback) {
91 0 : return NS_OK;
92 : }
93 :
94 0 : nsresult primingResult = CheckHSTSPrimingRequestStatus(aRequest);
95 0 : ReportTiming(callback, primingResult);
96 :
97 0 : if (NS_FAILED(primingResult)) {
98 0 : LOG(("HSTS Priming Failed (request was not approved)"));
99 0 : return callback->OnHSTSPrimingFailed(primingResult, false);
100 : }
101 :
102 0 : LOG(("HSTS Priming Succeeded (request was approved)"));
103 0 : return callback->OnHSTSPrimingSucceeded(false);
104 : }
105 :
106 : NS_IMETHODIMP
107 0 : HSTSPrimingListener::OnStopRequest(nsIRequest *aRequest,
108 : nsISupports *aContext,
109 : nsresult aStatus)
110 : {
111 0 : return NS_OK;
112 : }
113 :
114 : nsresult
115 0 : HSTSPrimingListener::CheckHSTSPrimingRequestStatus(nsIRequest* aRequest)
116 : {
117 : nsresult status;
118 0 : nsresult rv = aRequest->GetStatus(&status);
119 0 : NS_ENSURE_SUCCESS(rv, rv);
120 0 : if (NS_FAILED(status)) {
121 0 : return NS_ERROR_CONTENT_BLOCKED;
122 : }
123 :
124 : // Test that things worked on a HTTP level
125 0 : nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
126 0 : NS_ENSURE_STATE(httpChannel);
127 0 : nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest);
128 0 : NS_ENSURE_STATE(internal);
129 :
130 : bool succeedded;
131 0 : rv = httpChannel->GetRequestSucceeded(&succeedded);
132 0 : if (NS_FAILED(rv) || !succeedded) {
133 : // If the request did not return a 2XX response, don't process it
134 0 : return NS_ERROR_CONTENT_BLOCKED;
135 : }
136 :
137 0 : bool synthesized = false;
138 0 : RefPtr<nsHttpChannel> rawHttpChannel = do_QueryObject(httpChannel);
139 0 : NS_ENSURE_STATE(rawHttpChannel);
140 :
141 0 : rv = rawHttpChannel->GetResponseSynthesized(&synthesized);
142 0 : NS_ENSURE_SUCCESS(rv, rv);
143 0 : if (synthesized) {
144 : // Don't consider synthesized responses
145 0 : return NS_ERROR_CONTENT_BLOCKED;
146 : }
147 :
148 : // check to see if the HSTS cache was updated
149 0 : nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
150 0 : NS_ENSURE_SUCCESS(rv, rv);
151 :
152 0 : nsCOMPtr<nsIURI> uri;
153 0 : rv = httpChannel->GetURI(getter_AddRefs(uri));
154 0 : NS_ENSURE_SUCCESS(rv, rv);
155 0 : NS_ENSURE_TRUE(uri, NS_ERROR_CONTENT_BLOCKED);
156 :
157 0 : OriginAttributes originAttributes;
158 0 : NS_GetOriginAttributes(httpChannel, originAttributes);
159 :
160 : bool hsts;
161 0 : rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0,
162 0 : originAttributes, nullptr, nullptr, &hsts);
163 0 : NS_ENSURE_SUCCESS(rv, rv);
164 :
165 0 : if (hsts) {
166 : // An HSTS upgrade was found
167 0 : return NS_OK;
168 : }
169 :
170 : // There is no HSTS upgrade available
171 0 : return NS_ERROR_CONTENT_BLOCKED;
172 : }
173 :
174 : /** nsIStreamListener methods **/
175 :
176 : NS_IMETHODIMP
177 0 : HSTSPrimingListener::OnDataAvailable(nsIRequest *aRequest,
178 : nsISupports *ctxt,
179 : nsIInputStream *inStr,
180 : uint64_t sourceOffset,
181 : uint32_t count)
182 : {
183 : uint32_t totalRead;
184 0 : return inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &totalRead);
185 : }
186 :
187 : /** nsITimerCallback **/
188 : NS_IMETHODIMP
189 0 : HSTSPrimingListener::Notify(nsITimer* timer)
190 : {
191 : nsresult rv;
192 0 : nsCOMPtr<nsIHstsPrimingCallback> callback;
193 0 : callback.swap(mCallback);
194 0 : if (!callback) {
195 : // we already processed this channel
196 0 : return NS_OK;
197 : }
198 :
199 0 : ReportTiming(callback, NS_ERROR_HSTS_PRIMING_TIMEOUT);
200 :
201 0 : if (mPrimingChannel) {
202 0 : rv = mPrimingChannel->Cancel(NS_ERROR_HSTS_PRIMING_TIMEOUT);
203 0 : if (NS_FAILED(rv)) {
204 0 : NS_ERROR("HSTS Priming timed out, and we got an error canceling the priming channel.");
205 : }
206 : }
207 :
208 0 : rv = callback->OnHSTSPrimingFailed(NS_ERROR_HSTS_PRIMING_TIMEOUT, false);
209 0 : if (NS_FAILED(rv)) {
210 0 : NS_ERROR("HSTS Priming timed out, and we got an error reporting the failure.");
211 : }
212 :
213 0 : return NS_OK; // unused
214 : }
215 :
216 : // static
217 : nsresult
218 0 : HSTSPrimingListener::StartHSTSPriming(nsIChannel* aRequestChannel,
219 : nsIHstsPrimingCallback* aCallback)
220 : {
221 0 : nsCOMPtr<nsIURI> finalChannelURI;
222 0 : nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(finalChannelURI));
223 0 : NS_ENSURE_SUCCESS(rv, rv);
224 :
225 0 : nsCOMPtr<nsIURI> uri;
226 0 : rv = NS_GetSecureUpgradedURI(finalChannelURI, getter_AddRefs(uri));
227 0 : NS_ENSURE_SUCCESS(rv,rv);
228 :
229 : // check the HSTS cache
230 : bool hsts;
231 : bool hstsCached;
232 0 : nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
233 0 : NS_ENSURE_SUCCESS(rv, rv);
234 :
235 0 : OriginAttributes originAttributes;
236 0 : NS_GetOriginAttributes(aRequestChannel, originAttributes);
237 :
238 0 : rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0,
239 0 : originAttributes, &hstsCached, nullptr, &hsts);
240 0 : NS_ENSURE_SUCCESS(rv, rv);
241 :
242 0 : if (hsts) {
243 : // already saw this host and will upgrade if allowed by preferences
244 : Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
245 0 : HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_CACHED_HSTS);
246 0 : return aCallback->OnHSTSPrimingSucceeded(true);
247 : }
248 :
249 0 : if (hstsCached) {
250 : // there is a non-expired entry in the cache that doesn't allow us to
251 : // upgrade, so go ahead and fail early.
252 : Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
253 0 : HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_CACHED_NO_HSTS);
254 0 : return aCallback->OnHSTSPrimingFailed(NS_ERROR_CONTENT_BLOCKED, true);
255 : }
256 :
257 : // Either it wasn't cached or the cached result has expired. Build a
258 : // channel for the HEAD request.
259 :
260 0 : nsCOMPtr<nsILoadInfo> originalLoadInfo = aRequestChannel->GetLoadInfo();
261 0 : MOZ_ASSERT(originalLoadInfo, "can not perform HSTS priming without a loadInfo");
262 0 : if (!originalLoadInfo) {
263 0 : return NS_ERROR_FAILURE;
264 : }
265 :
266 : nsCOMPtr<nsILoadInfo> loadInfo = static_cast<mozilla::LoadInfo*>
267 0 : (originalLoadInfo.get())->CloneForNewRequest();
268 0 : loadInfo->SetIsHSTSPriming(true);
269 :
270 : // the LoadInfo must have a security flag set in order to pass through priming
271 : // if none of these security flags are set, go ahead and fail now instead of
272 : // crashing in nsContentSecurityManager::ValidateSecurityFlags
273 0 : nsSecurityFlags securityMode = loadInfo->GetSecurityMode();
274 0 : if (securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS &&
275 0 : securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED &&
276 0 : securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS &&
277 0 : securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
278 : securityMode != nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) {
279 0 : return aCallback->OnHSTSPrimingFailed(NS_ERROR_CONTENT_BLOCKED, true);
280 : }
281 :
282 0 : nsCOMPtr<nsILoadGroup> loadGroup;
283 0 : rv = aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup));
284 0 : NS_ENSURE_SUCCESS(rv, rv);
285 :
286 : nsLoadFlags loadFlags;
287 0 : rv = aRequestChannel->GetLoadFlags(&loadFlags);
288 0 : NS_ENSURE_SUCCESS(rv, rv);
289 :
290 0 : loadFlags &= HttpBaseChannel::INHIBIT_CACHING |
291 : HttpBaseChannel::INHIBIT_PERSISTENT_CACHING |
292 : HttpBaseChannel::LOAD_BYPASS_CACHE |
293 : HttpBaseChannel::LOAD_FROM_CACHE |
294 0 : HttpBaseChannel::VALIDATE_ALWAYS;
295 : // Priming requests should never be intercepted by service workers and
296 : // are always anonymous.
297 0 : loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER |
298 0 : nsIRequest::LOAD_ANONYMOUS;
299 :
300 : // Create a new channel to send the priming request
301 0 : nsCOMPtr<nsIChannel> primingChannel;
302 0 : rv = NS_NewChannelInternal(getter_AddRefs(primingChannel),
303 : uri,
304 : loadInfo,
305 : loadGroup,
306 : nullptr, // aCallbacks are set later
307 : loadFlags);
308 0 : NS_ENSURE_SUCCESS(rv, rv);
309 :
310 : // Set method and headers
311 0 : nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(primingChannel);
312 0 : if (!httpChannel) {
313 0 : NS_ERROR("HSTSPrimingListener: Failed to QI to nsIHttpChannel!");
314 0 : return NS_ERROR_FAILURE;
315 : }
316 0 : nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(primingChannel);
317 0 : NS_ENSURE_STATE(internal);
318 :
319 : // Since this is a perfomrance critical request (blocks the page load) we
320 : // want to get the response ASAP.
321 0 : nsCOMPtr<nsIClassOfService> classOfService(do_QueryInterface(primingChannel));
322 0 : if (classOfService) {
323 0 : classOfService->AddClassFlags(nsIClassOfService::UrgentStart);
324 : }
325 :
326 : // Currently using HEAD per the draft, but under discussion to change to GET
327 : // with credentials so if the upgrade is approved the result is already cached.
328 0 : rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("HEAD"));
329 0 : NS_ENSURE_SUCCESS(rv, rv);
330 :
331 0 : rv = httpChannel->
332 0 : SetRequestHeader(NS_LITERAL_CSTRING("Upgrade-Insecure-Requests"),
333 0 : NS_LITERAL_CSTRING("1"), false);
334 0 : NS_ENSURE_SUCCESS(rv, rv);
335 :
336 : // attempt to set the class of service flags on the new channel
337 0 : nsCOMPtr<nsIClassOfService> requestClass = do_QueryInterface(aRequestChannel);
338 0 : if (!requestClass) {
339 0 : NS_ERROR("HSTSPrimingListener: aRequestChannel is not an nsIClassOfService");
340 0 : return NS_ERROR_FAILURE;
341 : }
342 0 : nsCOMPtr<nsIClassOfService> primingClass = do_QueryInterface(httpChannel);
343 0 : if (!primingClass) {
344 0 : NS_ERROR("HSTSPrimingListener: httpChannel is not an nsIClassOfService");
345 0 : return NS_ERROR_FAILURE;
346 : }
347 :
348 0 : uint32_t classFlags = 0;
349 0 : rv = requestClass ->GetClassFlags(&classFlags);
350 0 : NS_ENSURE_SUCCESS(rv, rv);
351 0 : rv = primingClass->SetClassFlags(classFlags);
352 0 : NS_ENSURE_SUCCESS(rv, rv);
353 :
354 : // The priming channel should have highest priority so that it completes as
355 : // quickly as possible, allowing the load to proceed.
356 0 : nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(primingChannel);
357 0 : if (p) {
358 0 : uint32_t priority = nsISupportsPriority::PRIORITY_HIGHEST;
359 :
360 0 : p->SetPriority(priority);
361 : }
362 :
363 : // Set up listener which will start the original channel
364 0 : HSTSPrimingListener* listener = new HSTSPrimingListener(aCallback);
365 : // Start priming
366 0 : rv = primingChannel->AsyncOpen2(listener);
367 0 : NS_ENSURE_SUCCESS(rv, rv);
368 0 : listener->mPrimingChannel.swap(primingChannel);
369 :
370 0 : nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
371 0 : NS_ENSURE_STATE(timer);
372 :
373 0 : rv = timer->InitWithCallback(listener,
374 : sHSTSPrimingTimeout,
375 0 : nsITimer::TYPE_ONE_SHOT);
376 0 : if (NS_FAILED(rv)) {
377 0 : NS_ERROR("HSTS Priming failed to initialize channel cancellation timer");
378 : }
379 :
380 0 : listener->mHSTSPrimingTimer.swap(timer);
381 :
382 : Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
383 0 : HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_SENT);
384 :
385 0 : return NS_OK;
386 : }
387 :
388 : } // namespace net
389 : } // namespace mozilla
|