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