From fc20e76d3b0b6b4d4ac790f505d3afd9e30271ea Mon Sep 17 00:00:00 2001 From: Hunter Laux Date: Wed, 23 Oct 2024 12:57:53 -0400 Subject: [PATCH] Implement OnSelectClientCertificate (fixes #3789) --- include/capi/cef_request_handler_capi.h | 25 ++-- include/cef_request_handler.h | 7 +- .../chrome_content_browser_client_cef.cc | 138 ++++++++++++++++++ .../chrome_content_browser_client_cef.h | 7 + libcef/browser/x509_certificate_impl.cc | 20 +++ libcef/browser/x509_certificate_impl.h | 10 ++ 6 files changed, 194 insertions(+), 13 deletions(-) diff --git a/include/capi/cef_request_handler_capi.h b/include/capi/cef_request_handler_capi.h index 6378e6a93..144785da3 100644 --- a/include/capi/cef_request_handler_capi.h +++ b/include/capi/cef_request_handler_capi.h @@ -33,7 +33,7 @@ // by hand. See the translator.README.txt file in the tools directory for // more information. // -// $hash=db7cfb76483d6ab73eba74deaefafa7700ad1988$ +// $hash=cf3b97164ac99d564ebcd30f927e1331949acbe5$ // #ifndef CEF_INCLUDE_CAPI_CEF_REQUEST_HANDLER_CAPI_H_ @@ -194,16 +194,19 @@ typedef struct _cef_request_handler_t { /// /// Called on the UI thread when a client certificate is being requested for - /// authentication. Return false (0) to use the default behavior and - /// automatically select the first certificate available. Return true (1) and - /// call cef_select_client_certificate_callback_t::Select either in this - /// function or at a later time to select a certificate. Do not call Select or - /// call it with NULL to continue without using any certificate. |isProxy| - /// indicates whether the host is an HTTPS proxy or the origin server. |host| - /// and |port| contains the hostname and port of the SSL server. - /// |certificates| is the list of certificates to choose from; this list has - /// already been pruned by Chromium so that it only contains certificates from - /// issuers that the server trusts. + /// authentication. Return false (0) to use the default behavior. If the + /// |certificates| list is not NULL the default behavior will be to display a + /// dialog for certificate selection. If the |certificates| list is NULL then + /// the default behavior will be not to show a dialog and it will continue + /// without using any certificate. Return true (1) and call + /// cef_select_client_certificate_callback_t::Select either in this function + /// or at a later time to select a certificate. Do not call Select or call it + /// with NULL to continue without using any certificate. |isProxy| indicates + /// whether the host is an HTTPS proxy or the origin server. |host| and |port| + /// contains the hostname and port of the SSL server. |certificates| is the + /// list of certificates to choose from; this list has already been pruned by + /// Chromium so that it only contains certificates from issuers that the + /// server trusts. /// int(CEF_CALLBACK* on_select_client_certificate)( struct _cef_request_handler_t* self, diff --git a/include/cef_request_handler.h b/include/cef_request_handler.h index 99eb50370..fd3bf06a0 100644 --- a/include/cef_request_handler.h +++ b/include/cef_request_handler.h @@ -192,8 +192,11 @@ class CefRequestHandler : public virtual CefBaseRefCounted { /// /// Called on the UI thread when a client certificate is being requested for - /// authentication. Return false to use the default behavior and automatically - /// select the first certificate available. Return true and call + /// authentication. Return false to use the default behavior. If the + /// |certificates| list is not empty the default behavior will be to display a + /// dialog for certificate selection. If the |certificates| list is empty then + /// the default behavior will be not to show a dialog and it will continue + /// without using any certificate. Return true and call /// CefSelectClientCertificateCallback::Select either in this method or at a /// later time to select a certificate. Do not call Select or call it with /// NULL to continue without using any certificate. |isProxy| indicates diff --git a/libcef/browser/chrome/chrome_content_browser_client_cef.cc b/libcef/browser/chrome/chrome_content_browser_client_cef.cc index f55bddeef..683f43fa3 100644 --- a/libcef/browser/chrome/chrome_content_browser_client_cef.cc +++ b/libcef/browser/chrome/chrome_content_browser_client_cef.cc @@ -23,6 +23,7 @@ #include "cef/libcef/browser/net_service/resource_request_handler_wrapper.h" #include "cef/libcef/browser/prefs/browser_prefs.h" #include "cef/libcef/browser/prefs/renderer_prefs.h" +#include "cef/libcef/browser/x509_certificate_impl.h" #include "cef/libcef/common/app_manager.h" #include "cef/libcef/common/cef_switches.h" #include "cef/libcef/common/command_line_impl.h" @@ -32,6 +33,7 @@ #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "components/performance_manager/embedder/performance_manager_registry.h" +#include "content/public/browser/client_certificate_delegate.h" #include "content/public/browser/navigation_throttle.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" @@ -39,6 +41,8 @@ #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/weak_document_ptr.h" #include "content/public/common/content_switches.h" +#include "net/ssl/ssl_cert_request_info.h" +#include "net/ssl/ssl_private_key.h" #include "third_party/blink/public/common/web_preferences/web_preferences.h" #include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h" @@ -48,6 +52,82 @@ namespace { +class CefSelectClientCertificateCallbackImpl + : public CefSelectClientCertificateCallback { + public: + explicit CefSelectClientCertificateCallbackImpl( + std::unique_ptr delegate) + : delegate_(std::move(delegate)) {} + + CefSelectClientCertificateCallbackImpl( + const CefSelectClientCertificateCallbackImpl&) = delete; + CefSelectClientCertificateCallbackImpl& operator=( + const CefSelectClientCertificateCallbackImpl&) = delete; + + ~CefSelectClientCertificateCallbackImpl() override { + // If Select has not been called, call it with NULL to continue without any + // client certificate. + RunNow(std::move(delegate_), nullptr); + } + + void Select(CefRefPtr cert) override { + if (!CEF_CURRENTLY_ON_UIT()) { + CEF_POST_TASK( + CEF_UIT, + base::BindOnce(&CefSelectClientCertificateCallbackImpl::Select, this, + cert)); + } else { + RunNow(std::move(delegate_), cert); + } + } + + [[nodiscard]] std::unique_ptr + DisconnectDelegate() { + CEF_REQUIRE_UIT(); + return std::move(delegate_); + } + + private: + static void RunNow( + std::unique_ptr delegate, + CefRefPtr cert) { + CEF_REQUIRE_UIT(); + + if (delegate) { + if (cert) { + CefX509CertificateImpl* certImpl = + static_cast(cert.get()); + certImpl->AcquirePrivateKey(base::BindOnce( + &CefSelectClientCertificateCallbackImpl::RunWithPrivateKey, + std::move(delegate), cert)); + return; + } + + delegate->ContinueWithCertificate(nullptr, nullptr); + } + } + + static void RunWithPrivateKey( + std::unique_ptr delegate, + CefRefPtr cert, + scoped_refptr key) { + CEF_REQUIRE_UIT(); + DCHECK(cert); + + if (key) { + CefX509CertificateImpl* certImpl = + static_cast(cert.get()); + delegate->ContinueWithCertificate(certImpl->GetInternalCertObject(), key); + } else { + delegate->ContinueWithCertificate(nullptr, nullptr); + } + } + + std::unique_ptr delegate_; + + IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(CefSelectClientCertificateCallbackImpl); +}; + void HandleExternalProtocolHelper( ChromeContentBrowserClientCef* self, content::WebContents::Getter web_contents_getter, @@ -201,6 +281,64 @@ void ChromeContentBrowserClientCef::AllowCertificateError( strict_enforcement, std::move(returned_callback)); } +base::OnceClosure ChromeContentBrowserClientCef::SelectClientCertificate( + content::BrowserContext* browser_context, + int process_id, + content::WebContents* web_contents, + net::SSLCertRequestInfo* cert_request_info, + net::ClientCertIdentityList client_certs, + std::unique_ptr delegate) { + CEF_REQUIRE_UIT(); + + CefRefPtr handler; + CefRefPtr browser = + CefBrowserHostBase::GetBrowserForContents(web_contents); + if (browser) { + if (auto client = browser->GetClient()) { + handler = client->GetRequestHandler(); + } + } + + if (!handler) { + return ChromeContentBrowserClient::SelectClientCertificate( + browser_context, process_id, web_contents, cert_request_info, + std::move(client_certs), std::move(delegate)); + } + + CefRequestHandler::X509CertificateList certs; + for (auto& client_cert : client_certs) { + certs.push_back(new CefX509CertificateImpl(std::move(client_cert))); + } + + CefRefPtr callbackImpl( + new CefSelectClientCertificateCallbackImpl(std::move(delegate))); + + bool handled = handler->OnSelectClientCertificate( + browser.get(), cert_request_info->is_proxy, + cert_request_info->host_and_port.host(), + cert_request_info->host_and_port.port(), certs, callbackImpl.get()); + + if (!handled) { + delegate = callbackImpl->DisconnectDelegate(); + if (delegate) { + client_certs.clear(); + for (auto& cert : certs) { + CefX509CertificateImpl* certImpl = + static_cast(cert.get()); + client_certs.push_back(certImpl->DisconnectIdentity()); + } + return ChromeContentBrowserClient::SelectClientCertificate( + browser_context, process_id, web_contents, cert_request_info, + std::move(client_certs), std::move(delegate)); + } else { + LOG(ERROR) << "Should return true from OnSelectClientCertificate when " + "executing the callback"; + } + } + + return base::OnceClosure(); +} + bool ChromeContentBrowserClientCef::CanCreateWindow( content::RenderFrameHost* opener, const GURL& opener_url, diff --git a/libcef/browser/chrome/chrome_content_browser_client_cef.h b/libcef/browser/chrome/chrome_content_browser_client_cef.h index 0de602b44..d09ccca65 100644 --- a/libcef/browser/chrome/chrome_content_browser_client_cef.h +++ b/libcef/browser/chrome/chrome_content_browser_client_cef.h @@ -43,6 +43,13 @@ class ChromeContentBrowserClientCef : public ChromeContentBrowserClient { bool strict_enforcement, base::OnceCallback callback) override; + base::OnceClosure SelectClientCertificate( + content::BrowserContext* browser_context, + int process_id, + content::WebContents* web_contents, + net::SSLCertRequestInfo* cert_request_info, + net::ClientCertIdentityList client_certs, + std::unique_ptr delegate) override; bool CanCreateWindow(content::RenderFrameHost* opener, const GURL& opener_url, const GURL& opener_top_level_frame_url, diff --git a/libcef/browser/x509_certificate_impl.cc b/libcef/browser/x509_certificate_impl.cc index 95cfd13b9..c243413ed 100644 --- a/libcef/browser/x509_certificate_impl.cc +++ b/libcef/browser/x509_certificate_impl.cc @@ -7,6 +7,7 @@ #include "cef/libcef/browser/x509_cert_principal_impl.h" #include "cef/libcef/common/time_util.h" #include "net/cert/x509_util.h" +#include "net/ssl/ssl_private_key.h" namespace { @@ -27,6 +28,10 @@ CefRefPtr EncodeCertificate(const CRYPTO_BUFFER* cert_buffer, } // namespace +CefX509CertificateImpl::CefX509CertificateImpl( + std::unique_ptr identity) + : identity_(std::move(identity)), cert_(identity_->certificate()) {} + CefX509CertificateImpl::CefX509CertificateImpl( scoped_refptr cert) : cert_(cert) {} @@ -94,6 +99,21 @@ size_t CefX509CertificateImpl::GetIssuerChainSize() { return 0; } +void CefX509CertificateImpl::AcquirePrivateKey( + base::OnceCallback)> + private_key_callback) { + if (identity_) { + identity_->AcquirePrivateKey(std::move(private_key_callback)); + } else { + std::move(private_key_callback).Run(nullptr); + } +} + +std::unique_ptr +CefX509CertificateImpl::DisconnectIdentity() { + return std::move(identity_); +} + void CefX509CertificateImpl::GetEncodedIssuerChain( CefX509Certificate::IssuerChainBinaryList& chain, bool der) { diff --git a/libcef/browser/x509_certificate_impl.h b/libcef/browser/x509_certificate_impl.h index 92ab8bf41..4af347c96 100644 --- a/libcef/browser/x509_certificate_impl.h +++ b/libcef/browser/x509_certificate_impl.h @@ -10,6 +10,7 @@ #include "cef/include/cef_x509_certificate.h" #include "net/cert/x509_certificate.h" +#include "net/ssl/client_cert_identity.h" // CefX509Certificate implementation class CefX509CertificateImpl : public CefX509Certificate { @@ -19,6 +20,9 @@ class CefX509CertificateImpl : public CefX509Certificate { CefX509CertificateImpl(const CefX509CertificateImpl&) = delete; CefX509CertificateImpl& operator=(const CefX509CertificateImpl&) = delete; + explicit CefX509CertificateImpl( + std::unique_ptr identity); + // CefX509Certificate methods. CefRefPtr GetSubject() override; CefRefPtr GetIssuer() override; @@ -32,10 +36,16 @@ class CefX509CertificateImpl : public CefX509Certificate { void GetPEMEncodedIssuerChain(IssuerChainBinaryList& chain) override; scoped_refptr GetInternalCertObject() { return cert_; } + void AcquirePrivateKey( + base::OnceCallback)> + private_key_callback); + + [[nodiscard]] std::unique_ptr DisconnectIdentity(); private: void GetEncodedIssuerChain(IssuerChainBinaryList& chain, bool der); + std::unique_ptr identity_; scoped_refptr cert_; IssuerChainBinaryList pem_encoded_issuer_chain_; IssuerChainBinaryList der_encoded_issuer_chain_;