1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/ssl/ssl_manager.h"
6
7 #include <set>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/macros.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/supports_user_data.h"
15 #include "base/task/post_task.h"
16 #include "content/browser/devtools/devtools_instrumentation.h"
17 #include "content/browser/renderer_host/navigation_entry_impl.h"
18 #include "content/browser/renderer_host/render_frame_host_impl.h"
19 #include "content/browser/ssl/ssl_error_handler.h"
20 #include "content/browser/web_contents/web_contents_impl.h"
21 #include "content/public/browser/browser_context.h"
22 #include "content/public/browser/browser_task_traits.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/certificate_request_result_type.h"
25 #include "content/public/browser/content_browser_client.h"
26 #include "content/public/browser/devtools_agent_host.h"
27 #include "content/public/browser/navigation_details.h"
28 #include "content/public/browser/ssl_host_state_delegate.h"
29 #include "content/public/common/content_client.h"
30 #include "services/metrics/public/cpp/ukm_builders.h"
31 #include "services/metrics/public/cpp/ukm_recorder.h"
32 #include "services/metrics/public/cpp/ukm_source_id.h"
33 #include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
34
35 namespace content {
36
37 namespace {
38
39 const char kSSLManagerKeyName[] = "content_ssl_manager";
40
41 // Used to log type of mixed content displayed/ran, matches histogram enum
42 // (MixedContentType). DO NOT REORDER.
43 enum class MixedContentType {
44 kDisplayMixedContent = 0,
45 kDisplayWithCertErrors = 1,
46 kMixedForm = 2,
47 kScriptingMixedContent = 3,
48 kScriptingWithCertErrors = 4,
49 kMaxValue = kScriptingWithCertErrors,
50 };
51
OnAllowCertificate(SSLErrorHandler * handler,SSLHostStateDelegate * state_delegate,bool record_decision,CertificateRequestResultType decision)52 void OnAllowCertificate(SSLErrorHandler* handler,
53 SSLHostStateDelegate* state_delegate,
54 bool record_decision,
55 CertificateRequestResultType decision) {
56 DCHECK(handler->ssl_info().is_valid());
57 switch (decision) {
58 case CERTIFICATE_REQUEST_RESULT_TYPE_CONTINUE:
59 // Note that we should not call SetMaxSecurityStyle here, because
60 // the active NavigationEntry has just been deleted (in
61 // HideInterstitialPage) and the new NavigationEntry will not be
62 // set until DidNavigate. This is ok, because the new
63 // NavigationEntry will have its max security style set within
64 // DidNavigate.
65 //
66 // While AllowCert() executes synchronously on this thread,
67 // ContinueRequest() gets posted to a different thread. Calling
68 // AllowCert() first ensures deterministic ordering.
69 if (record_decision && state_delegate) {
70 state_delegate->AllowCert(
71 handler->request_url().host(), *handler->ssl_info().cert.get(),
72 handler->cert_error(), handler->web_contents());
73 }
74 handler->ContinueRequest();
75 return;
76 case CERTIFICATE_REQUEST_RESULT_TYPE_DENY:
77 handler->DenyRequest();
78 return;
79 case CERTIFICATE_REQUEST_RESULT_TYPE_CANCEL:
80 handler->CancelRequest();
81 return;
82 }
83 }
84
85 class SSLManagerSet : public base::SupportsUserData::Data {
86 public:
SSLManagerSet()87 SSLManagerSet() {
88 }
89
get()90 std::set<SSLManager*>& get() { return set_; }
91
92 private:
93 std::set<SSLManager*> set_;
94
95 DISALLOW_COPY_AND_ASSIGN(SSLManagerSet);
96 };
97
LogMixedContentMetrics(MixedContentType type,ukm::SourceId source_id,ukm::UkmRecorder * recorder)98 void LogMixedContentMetrics(MixedContentType type,
99 ukm::SourceId source_id,
100 ukm::UkmRecorder* recorder) {
101 UMA_HISTOGRAM_ENUMERATION("SSL.MixedContentShown", type);
102 ukm::builders::SSL_MixedContentShown(source_id)
103 .SetType(static_cast<int64_t>(type))
104 .Record(recorder);
105 }
106
107 } // namespace
108
109 // static
OnSSLCertificateError(const base::WeakPtr<SSLErrorHandler::Delegate> & delegate,bool is_main_frame_request,const GURL & url,WebContents * web_contents,int net_error,const net::SSLInfo & ssl_info,bool fatal)110 void SSLManager::OnSSLCertificateError(
111 const base::WeakPtr<SSLErrorHandler::Delegate>& delegate,
112 bool is_main_frame_request,
113 const GURL& url,
114 WebContents* web_contents,
115 int net_error,
116 const net::SSLInfo& ssl_info,
117 bool fatal) {
118 DCHECK(delegate.get());
119 DVLOG(1) << "OnSSLCertificateError() cert_error: " << net_error
120 << " url: " << url.spec() << " cert_status: " << std::hex
121 << ssl_info.cert_status;
122 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
123
124 std::unique_ptr<SSLErrorHandler> handler(
125 new SSLErrorHandler(web_contents, delegate, is_main_frame_request, url,
126 net_error, ssl_info, fatal));
127
128 if (!web_contents) {
129 // Requests can fail to dispatch because they don't have a WebContents. See
130 // https://crbug.com/86537. In this case we have to make a decision in this
131 // function.
132 handler->DenyRequest();
133 return;
134 }
135
136 // Check if we should deny certificate errors using the main frame's URL.
137 if (GetContentClient()->browser()->ShouldDenyRequestOnCertificateError(
138 web_contents->GetLastCommittedURL())) {
139 handler->DenyRequest();
140 return;
141 }
142
143 NavigationControllerImpl* controller =
144 static_cast<NavigationControllerImpl*>(&web_contents->GetController());
145 controller->SetPendingNavigationSSLError(true);
146
147 SSLManager* manager = controller->ssl_manager();
148 manager->OnCertError(std::move(handler));
149 }
150
SSLManager(NavigationControllerImpl * controller)151 SSLManager::SSLManager(NavigationControllerImpl* controller)
152 : controller_(controller),
153 ssl_host_state_delegate_(
154 controller->GetBrowserContext()->GetSSLHostStateDelegate()) {
155 DCHECK(controller_);
156
157 SSLManagerSet* managers = static_cast<SSLManagerSet*>(
158 controller_->GetBrowserContext()->GetUserData(kSSLManagerKeyName));
159 if (!managers) {
160 auto managers_owned = std::make_unique<SSLManagerSet>();
161 managers = managers_owned.get();
162 controller_->GetBrowserContext()->SetUserData(kSSLManagerKeyName,
163 std::move(managers_owned));
164 }
165 managers->get().insert(this);
166 }
167
~SSLManager()168 SSLManager::~SSLManager() {
169 SSLManagerSet* managers = static_cast<SSLManagerSet*>(
170 controller_->GetBrowserContext()->GetUserData(kSSLManagerKeyName));
171 managers->get().erase(this);
172 }
173
DidCommitProvisionalLoad(const LoadCommittedDetails & details)174 void SSLManager::DidCommitProvisionalLoad(const LoadCommittedDetails& details) {
175 NavigationEntryImpl* entry = controller_->GetLastCommittedEntry();
176 int add_content_status_flags = 0;
177 int remove_content_status_flags = 0;
178
179 if (!details.is_main_frame || details.is_same_document) {
180 // For subframe navigations, and for same-document main-frame navigations,
181 // carry over content status flags from the previously committed entry. For
182 // example, the mixed content flag shouldn't clear because of a subframe
183 // navigation, or because of a back/forward navigation that doesn't leave
184 // the current document. (See https://crbug.com/959571.)
185 NavigationEntryImpl* previous_entry =
186 controller_->GetEntryAtIndex(details.previous_entry_index);
187 if (previous_entry) {
188 add_content_status_flags = previous_entry->GetSSL().content_status;
189 }
190 } else {
191 // For main-frame non-same-page navigations, clear content status
192 // flags. These flags are set based on the content on the page, and thus
193 // should reflect the current content, even if the navigation was to an
194 // existing entry that already had content status flags set.
195 remove_content_status_flags = ~0;
196 // Also clear any UserData from the SSLStatus.
197 if (entry)
198 entry->GetSSL().user_data = nullptr;
199 }
200
201 if (!UpdateEntry(entry, add_content_status_flags,
202 remove_content_status_flags)) {
203 // Ensure the WebContents is notified that the SSL state changed when a
204 // load is committed, in case the active navigation entry has changed.
205 NotifyDidChangeVisibleSSLState();
206 }
207 }
208
DidDisplayMixedContent()209 void SSLManager::DidDisplayMixedContent() {
210 NavigationEntryImpl* entry = controller_->GetLastCommittedEntry();
211 if (entry && entry->GetURL().SchemeIsCryptographic() &&
212 entry->GetSSL().certificate) {
213 WebContentsImpl* contents = static_cast<WebContentsImpl*>(
214 controller_->delegate()->GetWebContents());
215 ukm::SourceId source_id = contents->GetMainFrame()->GetPageUkmSourceId();
216 LogMixedContentMetrics(MixedContentType::kDisplayMixedContent, source_id,
217 ukm::UkmRecorder::Get());
218 }
219 UpdateLastCommittedEntry(SSLStatus::DISPLAYED_INSECURE_CONTENT, 0);
220 }
221
DidContainInsecureFormAction()222 void SSLManager::DidContainInsecureFormAction() {
223 NavigationEntryImpl* entry = controller_->GetLastCommittedEntry();
224 if (entry && entry->GetURL().SchemeIsCryptographic() &&
225 entry->GetSSL().certificate) {
226 WebContentsImpl* contents = static_cast<WebContentsImpl*>(
227 controller_->delegate()->GetWebContents());
228 ukm::SourceId source_id = contents->GetMainFrame()->GetPageUkmSourceId();
229 LogMixedContentMetrics(MixedContentType::kMixedForm, source_id,
230 ukm::UkmRecorder::Get());
231 }
232 UpdateLastCommittedEntry(SSLStatus::DISPLAYED_FORM_WITH_INSECURE_ACTION, 0);
233 }
234
DidDisplayContentWithCertErrors()235 void SSLManager::DidDisplayContentWithCertErrors() {
236 NavigationEntryImpl* entry = controller_->GetLastCommittedEntry();
237 if (!entry)
238 return;
239 // Only record information about subresources with cert errors if the
240 // main page is HTTPS with a certificate.
241 if (entry->GetURL().SchemeIsCryptographic() && entry->GetSSL().certificate) {
242 WebContentsImpl* contents = static_cast<WebContentsImpl*>(
243 controller_->delegate()->GetWebContents());
244 ukm::SourceId source_id = contents->GetMainFrame()->GetPageUkmSourceId();
245 LogMixedContentMetrics(MixedContentType::kDisplayWithCertErrors, source_id,
246 ukm::UkmRecorder::Get());
247 UpdateLastCommittedEntry(SSLStatus::DISPLAYED_CONTENT_WITH_CERT_ERRORS, 0);
248 }
249 }
250
DidRunMixedContent(const GURL & security_origin)251 void SSLManager::DidRunMixedContent(const GURL& security_origin) {
252 NavigationEntryImpl* entry = controller_->GetLastCommittedEntry();
253 if (!entry)
254 return;
255
256 if (entry->GetURL().SchemeIsCryptographic() && entry->GetSSL().certificate) {
257 WebContentsImpl* contents = static_cast<WebContentsImpl*>(
258 controller_->delegate()->GetWebContents());
259 ukm::SourceId source_id = contents->GetMainFrame()->GetPageUkmSourceId();
260 LogMixedContentMetrics(MixedContentType::kScriptingMixedContent, source_id,
261 ukm::UkmRecorder::Get());
262 }
263
264 SiteInstance* site_instance = entry->site_instance();
265 if (!site_instance)
266 return;
267
268 if (ssl_host_state_delegate_) {
269 ssl_host_state_delegate_->HostRanInsecureContent(
270 security_origin.host(), site_instance->GetProcess()->GetID(),
271 SSLHostStateDelegate::MIXED_CONTENT);
272 }
273 UpdateEntry(entry, 0, 0);
274 NotifySSLInternalStateChanged(controller_->GetBrowserContext());
275 }
276
DidRunContentWithCertErrors(const GURL & security_origin)277 void SSLManager::DidRunContentWithCertErrors(const GURL& security_origin) {
278 NavigationEntryImpl* entry = controller_->GetLastCommittedEntry();
279 if (!entry)
280 return;
281
282 if (entry->GetURL().SchemeIsCryptographic() && entry->GetSSL().certificate) {
283 WebContentsImpl* contents = static_cast<WebContentsImpl*>(
284 controller_->delegate()->GetWebContents());
285 ukm::SourceId source_id = contents->GetMainFrame()->GetPageUkmSourceId();
286 LogMixedContentMetrics(MixedContentType::kScriptingWithCertErrors,
287 source_id, ukm::UkmRecorder::Get());
288 }
289
290 SiteInstance* site_instance = entry->site_instance();
291 if (!site_instance)
292 return;
293
294 if (ssl_host_state_delegate_) {
295 ssl_host_state_delegate_->HostRanInsecureContent(
296 security_origin.host(), site_instance->GetProcess()->GetID(),
297 SSLHostStateDelegate::CERT_ERRORS_CONTENT);
298 }
299 UpdateEntry(entry, 0, 0);
300 NotifySSLInternalStateChanged(controller_->GetBrowserContext());
301 }
302
OnCertError(std::unique_ptr<SSLErrorHandler> handler)303 void SSLManager::OnCertError(std::unique_ptr<SSLErrorHandler> handler) {
304 // First we check if we know the policy for this error.
305 DCHECK(handler->ssl_info().is_valid());
306 SSLHostStateDelegate::CertJudgment judgment =
307 ssl_host_state_delegate_
308 ? ssl_host_state_delegate_->QueryPolicy(
309 handler->request_url().host(), *handler->ssl_info().cert.get(),
310 handler->cert_error(), handler->web_contents())
311 : SSLHostStateDelegate::DENIED;
312
313 if (judgment == SSLHostStateDelegate::ALLOWED) {
314 handler->ContinueRequest();
315 return;
316 }
317
318 DCHECK(net::IsCertificateError(handler->cert_error()));
319 OnCertErrorInternal(std::move(handler));
320 }
321
DidStartResourceResponse(const GURL & url,bool has_certificate_errors)322 void SSLManager::DidStartResourceResponse(const GURL& url,
323 bool has_certificate_errors) {
324 if (!url.SchemeIsCryptographic() || has_certificate_errors)
325 return;
326
327 // If the scheme is https: or wss and the cert did not have any errors, revoke
328 // any previous decisions that have occurred.
329 if (!ssl_host_state_delegate_ ||
330 !ssl_host_state_delegate_->HasAllowException(
331 url.host(), controller_->GetWebContents())) {
332 return;
333 }
334
335 // If there's no certificate error, a good certificate has been seen, so
336 // clear out any exceptions that were made by the user for bad
337 // certificates. This intentionally does not apply to cached resources
338 // (see https://crbug.com/634553 for an explanation).
339 ssl_host_state_delegate_->RevokeUserAllowExceptions(url.host());
340 }
341
OnCertErrorInternal(std::unique_ptr<SSLErrorHandler> handler)342 void SSLManager::OnCertErrorInternal(std::unique_ptr<SSLErrorHandler> handler) {
343 WebContents* web_contents = handler->web_contents();
344 int cert_error = handler->cert_error();
345 const net::SSLInfo& ssl_info = handler->ssl_info();
346 const GURL& request_url = handler->request_url();
347 bool is_main_frame_request = handler->is_main_frame_request();
348 bool fatal = handler->fatal();
349
350 base::RepeatingCallback<void(bool, content::CertificateRequestResultType)>
351 callback = base::BindRepeating(&OnAllowCertificate,
352 base::Owned(handler.release()),
353 ssl_host_state_delegate_);
354
355 if (devtools_instrumentation::HandleCertificateError(
356 web_contents, cert_error, request_url,
357 base::BindRepeating(callback, false))) {
358 return;
359 }
360
361 GetContentClient()->browser()->AllowCertificateError(
362 web_contents, cert_error, ssl_info, request_url, is_main_frame_request,
363 fatal, base::BindOnce(std::move(callback), true));
364 }
365
UpdateEntry(NavigationEntryImpl * entry,int add_content_status_flags,int remove_content_status_flags)366 bool SSLManager::UpdateEntry(NavigationEntryImpl* entry,
367 int add_content_status_flags,
368 int remove_content_status_flags) {
369 // We don't always have a navigation entry to update, for example in the
370 // case of the Web Inspector.
371 if (!entry)
372 return false;
373
374 SSLStatus original_ssl_status = entry->GetSSL(); // Copy!
375 entry->GetSSL().initialized = true;
376 entry->GetSSL().content_status &= ~remove_content_status_flags;
377 entry->GetSSL().content_status |= add_content_status_flags;
378
379 SiteInstance* site_instance = entry->site_instance();
380 // Note that |site_instance| can be NULL here because NavigationEntries don't
381 // necessarily have site instances. Without a process, the entry can't
382 // possibly have insecure content. See bug https://crbug.com/12423.
383 if (site_instance && ssl_host_state_delegate_) {
384 const base::Optional<url::Origin>& entry_origin =
385 entry->root_node()->frame_entry->committed_origin();
386 // In some cases (e.g., unreachable URLs), navigation entries might not have
387 // origins attached to them. We don't care about tracking mixed content for
388 // those cases.
389 if (entry_origin.has_value()) {
390 const std::string& host = entry_origin->host();
391 int process_id = site_instance->GetProcess()->GetID();
392 if (ssl_host_state_delegate_->DidHostRunInsecureContent(
393 host, process_id, SSLHostStateDelegate::MIXED_CONTENT)) {
394 entry->GetSSL().content_status |= SSLStatus::RAN_INSECURE_CONTENT;
395 }
396
397 // Only record information about subresources with cert errors if the
398 // main page is HTTPS with a certificate.
399 if (entry->GetURL().SchemeIsCryptographic() &&
400 entry->GetSSL().certificate &&
401 ssl_host_state_delegate_->DidHostRunInsecureContent(
402 host, process_id, SSLHostStateDelegate::CERT_ERRORS_CONTENT)) {
403 entry->GetSSL().content_status |=
404 SSLStatus::RAN_CONTENT_WITH_CERT_ERRORS;
405 }
406 }
407 }
408
409 if (entry->GetSSL().initialized != original_ssl_status.initialized ||
410 entry->GetSSL().content_status != original_ssl_status.content_status) {
411 NotifyDidChangeVisibleSSLState();
412 return true;
413 }
414
415 return false;
416 }
417
UpdateLastCommittedEntry(int add_content_status_flags,int remove_content_status_flags)418 void SSLManager::UpdateLastCommittedEntry(int add_content_status_flags,
419 int remove_content_status_flags) {
420 NavigationEntryImpl* entry = controller_->GetLastCommittedEntry();
421 if (!entry)
422 return;
423 UpdateEntry(entry, add_content_status_flags, remove_content_status_flags);
424 }
425
NotifyDidChangeVisibleSSLState()426 void SSLManager::NotifyDidChangeVisibleSSLState() {
427 WebContentsImpl* contents =
428 static_cast<WebContentsImpl*>(controller_->delegate()->GetWebContents());
429 contents->DidChangeVisibleSecurityState();
430 }
431
432 // static
NotifySSLInternalStateChanged(BrowserContext * context)433 void SSLManager::NotifySSLInternalStateChanged(BrowserContext* context) {
434 SSLManagerSet* managers =
435 static_cast<SSLManagerSet*>(context->GetUserData(kSSLManagerKeyName));
436
437 for (auto i = managers->get().begin(); i != managers->get().end(); ++i) {
438 (*i)->UpdateEntry((*i)->controller()->GetLastCommittedEntry(), 0, 0);
439 }
440 }
441
442 } // namespace content
443