diff --git a/atom/browser/api/atom_api_app.cc b/atom/browser/api/atom_api_app.cc index e1fa03af5fbd..df28829c46a4 100644 --- a/atom/browser/api/atom_api_app.cc +++ b/atom/browser/api/atom_api_app.cc @@ -62,21 +62,6 @@ struct Converter { }; #endif -template<> -struct Converter> { - static v8::Local ToV8( - v8::Isolate* isolate, - const scoped_refptr& val) { - mate::Dictionary dict(isolate, v8::Object::New(isolate)); - std::string encoded_data; - net::X509Certificate::GetPEMEncoded( - val->os_cert_handle(), &encoded_data); - dict.Set("data", encoded_data); - dict.Set("issuerName", val->issuer().GetDisplayName()); - return dict.GetHandle(); - } -}; - } // namespace mate diff --git a/atom/browser/api/atom_api_session.cc b/atom/browser/api/atom_api_session.cc index 0ec9c05ed84e..07b9b68a94d7 100644 --- a/atom/browser/api/atom_api_session.cc +++ b/atom/browser/api/atom_api_session.cc @@ -16,6 +16,7 @@ #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/file_path_converter.h" +#include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/node_includes.h" #include "base/files/file_path.h" #include "base/prefs/pref_service.h" @@ -237,11 +238,21 @@ void SetProxyInIO(net::URLRequestContextGetter* getter, RunCallbackInUI(callback); } +void PassVerificationResult( + scoped_refptr request, + bool success) { + int result = net::OK; + if (!success) + result = net::ERR_FAILED; + request->ContinueWithResult(result); +} + } // namespace Session::Session(AtomBrowserContext* browser_context) : browser_context_(browser_context) { AttachAsUserData(browser_context); + browser_context->cert_verifier()->SetDelegate(this); // Observe DownloadManger to get download notifications. content::BrowserContext::GetDownloadManager(browser_context)-> @@ -254,6 +265,18 @@ Session::~Session() { Destroy(); } +void Session::RequestCertVerification( + const scoped_refptr& request) { + bool prevent_default = Emit( + "verify-certificate", + request->hostname(), + request->certificate(), + base::Bind(&PassVerificationResult, request)); + + if (!prevent_default) + request->ContinueWithResult(net::ERR_IO_PENDING); +} + void Session::OnDownloadCreated(content::DownloadManager* manager, content::DownloadItem* item) { auto web_contents = item->GetWebContents(); diff --git a/atom/browser/api/atom_api_session.h b/atom/browser/api/atom_api_session.h index 39712f6c8486..05d67b8842ef 100644 --- a/atom/browser/api/atom_api_session.h +++ b/atom/browser/api/atom_api_session.h @@ -8,6 +8,7 @@ #include #include "atom/browser/api/trackable_object.h" +#include "atom/browser/atom_cert_verifier.h" #include "content/public/browser/download_manager.h" #include "native_mate/handle.h" #include "net/base/completion_callback.h" @@ -34,6 +35,7 @@ class AtomBrowserContext; namespace api { class Session: public mate::TrackableObject, + public AtomCertVerifier::Delegate, public content::DownloadManager::Observer { public: using ResolveProxyCallback = base::Callback; @@ -52,6 +54,10 @@ class Session: public mate::TrackableObject, explicit Session(AtomBrowserContext* browser_context); ~Session(); + // AtomCertVerifier::Delegate: + void RequestCertVerification( + const scoped_refptr&) override; + // content::DownloadManager::Observer: void OnDownloadCreated(content::DownloadManager* manager, content::DownloadItem* item) override; diff --git a/atom/browser/atom_browser_context.cc b/atom/browser/atom_browser_context.cc index 6cfb160489fc..b1092757ae48 100644 --- a/atom/browser/atom_browser_context.cc +++ b/atom/browser/atom_browser_context.cc @@ -5,6 +5,7 @@ #include "atom/browser/atom_browser_context.h" #include "atom/browser/atom_browser_main_parts.h" +#include "atom/browser/atom_cert_verifier.h" #include "atom/browser/atom_download_manager_delegate.h" #include "atom/browser/atom_ssl_config_service.h" #include "atom/browser/browser.h" @@ -60,6 +61,7 @@ std::string RemoveWhitespace(const std::string& str) { AtomBrowserContext::AtomBrowserContext(const std::string& partition, bool in_memory) : brightray::BrowserContext(partition, in_memory), + cert_verifier_(new AtomCertVerifier), job_factory_(new AtomURLRequestJobFactory), allow_ntlm_everywhere_(false) { } @@ -158,6 +160,10 @@ content::BrowserPluginGuestManager* AtomBrowserContext::GetGuestManager() { return guest_manager_.get(); } +net::CertVerifier* AtomBrowserContext::CreateCertVerifier() { + return cert_verifier_; +} + net::SSLConfigService* AtomBrowserContext::CreateSSLConfigService() { return new AtomSSLConfigService; } diff --git a/atom/browser/atom_browser_context.h b/atom/browser/atom_browser_context.h index aafa092442bc..d3d7735c810d 100644 --- a/atom/browser/atom_browser_context.h +++ b/atom/browser/atom_browser_context.h @@ -12,6 +12,7 @@ namespace atom { class AtomDownloadManagerDelegate; +class AtomCertVerifier; class AtomURLRequestJobFactory; class WebViewManager; @@ -27,6 +28,7 @@ class AtomBrowserContext : public brightray::BrowserContext { content::URLRequestInterceptorScopedVector* interceptors) override; net::HttpCache::BackendFactory* CreateHttpCacheBackendFactory( const base::FilePath& base_path) override; + net::CertVerifier* CreateCertVerifier() override; net::SSLConfigService* CreateSSLConfigService() override; bool AllowNTLMCredentialsForDomain(const GURL& auth_origin) override; @@ -39,6 +41,8 @@ class AtomBrowserContext : public brightray::BrowserContext { void AllowNTLMCredentialsForAllDomains(bool should_allow); + AtomCertVerifier* cert_verifier() const { return cert_verifier_; } + AtomURLRequestJobFactory* job_factory() const { return job_factory_; } private: @@ -46,6 +50,7 @@ class AtomBrowserContext : public brightray::BrowserContext { scoped_ptr guest_manager_; // Managed by brightray::BrowserContext. + AtomCertVerifier* cert_verifier_; AtomURLRequestJobFactory* job_factory_; bool allow_ntlm_everywhere_; diff --git a/atom/browser/atom_cert_verifier.cc b/atom/browser/atom_cert_verifier.cc new file mode 100644 index 000000000000..e56e611faa81 --- /dev/null +++ b/atom/browser/atom_cert_verifier.cc @@ -0,0 +1,176 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/atom_cert_verifier.h" + +#include "atom/browser/browser.h" +#include "atom/common/native_mate_converters/net_converter.h" +#include "base/sha1.h" +#include "base/stl_util.h" +#include "content/public/browser/browser_thread.h" +#include "net/base/net_errors.h" + +using content::BrowserThread; + +namespace atom { + +AtomCertVerifier::RequestParams::RequestParams( + const net::SHA1HashValue cert_fingerprint, + const net::SHA1HashValue ca_fingerprint, + const std::string& hostname_arg, + const std::string& ocsp_response_arg, + int flags_arg) + : hostname(hostname_arg), + ocsp_response(ocsp_response_arg), + flags(flags_arg) { + hash_values.reserve(3); + net::SHA1HashValue ocsp_hash; + base::SHA1HashBytes( + reinterpret_cast(ocsp_response.data()), + ocsp_response.size(), ocsp_hash.data); + hash_values.push_back(ocsp_hash); + hash_values.push_back(cert_fingerprint); + hash_values.push_back(ca_fingerprint); +} + +bool AtomCertVerifier::RequestParams::operator<( + const RequestParams& other) const { + if (flags != other.flags) + return flags < other.flags; + if (hostname != other.hostname) + return hostname < other.hostname; + return std::lexicographical_compare( + hash_values.begin(), + hash_values.end(), + other.hash_values.begin(), + other.hash_values.end(), + net::SHA1HashValueLessThan()); +} + +void AtomCertVerifier::CertVerifyRequest::RunResult(int result) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + for (auto& callback : callbacks_) + callback.Run(result); + cert_verifier_->RemoveRequest(this); +} + +void AtomCertVerifier::CertVerifyRequest::DelegateToDefaultVerifier() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + int rv = cert_verifier_->default_cert_verifier()->Verify( + certificate_.get(), + key_.hostname, + key_.ocsp_response, + key_.flags, + crl_set_.get(), + verify_result_, + base::Bind(&CertVerifyRequest::RunResult, + weak_ptr_factory_.GetWeakPtr()), + out_req_, + net_log_); + + if (rv != net::ERR_IO_PENDING) + RunResult(rv); +} + +void AtomCertVerifier::CertVerifyRequest::ContinueWithResult(int result) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + if (handled_) + return; + + handled_ = true; + + if (result != net::ERR_IO_PENDING) { + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&CertVerifyRequest::RunResult, + weak_ptr_factory_.GetWeakPtr(), + result)); + return; + } + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&CertVerifyRequest::DelegateToDefaultVerifier, + weak_ptr_factory_.GetWeakPtr())); +} + +AtomCertVerifier::AtomCertVerifier() + : delegate_(nullptr) { + default_cert_verifier_.reset(net::CertVerifier::CreateDefault()); +} + +AtomCertVerifier::~AtomCertVerifier() { +} + +int AtomCertVerifier::Verify( + net::X509Certificate* cert, + const std::string& hostname, + const std::string& ocsp_response, + int flags, + net::CRLSet* crl_set, + net::CertVerifyResult* verify_result, + const net::CompletionCallback& callback, + scoped_ptr* out_req, + const net::BoundNetLog& net_log) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + if (callback.is_null() || !verify_result || hostname.empty() || !delegate_) + return net::ERR_INVALID_ARGUMENT; + + const RequestParams key(cert->fingerprint(), + cert->ca_fingerprint(), + hostname, + ocsp_response, + flags); + + CertVerifyRequest* request = FindRequest(key); + + if (!request) { + request = new CertVerifyRequest(this, + key, + cert, + crl_set, + verify_result, + out_req, + net_log); + requests_.insert(make_scoped_refptr(request)); + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(&Delegate::RequestCertVerification, + base::Unretained(delegate_), + make_scoped_refptr(request))); + } + + request->AddCompletionCallback(callback); + + return net::ERR_IO_PENDING; +} + +bool AtomCertVerifier::SupportsOCSPStapling() { + return true; +} + +AtomCertVerifier::CertVerifyRequest* AtomCertVerifier::FindRequest( + const RequestParams& key) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + auto it = std::lower_bound(requests_.begin(), + requests_.end(), + key, + CertVerifyRequestToRequestParamsComparator()); + if (it != requests_.end() && !(key < (*it)->key())) + return (*it).get(); + return nullptr; +} + +void AtomCertVerifier::RemoveRequest(CertVerifyRequest* request) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + bool erased = requests_.erase(request) == 1; + DCHECK(erased); +} + +} // namespace atom diff --git a/atom/browser/atom_cert_verifier.h b/atom/browser/atom_cert_verifier.h new file mode 100644 index 000000000000..e5560ff82fe4 --- /dev/null +++ b/atom/browser/atom_cert_verifier.h @@ -0,0 +1,165 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_ATOM_CERT_VERIFIER_H_ +#define ATOM_BROWSER_ATOM_CERT_VERIFIER_H_ + +#include +#include +#include + +#include "base/memory/ref_counted.h" +#include "net/base/hash_value.h" +#include "net/cert/cert_verifier.h" +#include "net/cert/crl_set.h" +#include "net/cert/x509_certificate.h" +#include "net/log/net_log.h" + +namespace atom { + +class AtomCertVerifier : public net::CertVerifier { + public: + struct RequestParams { + RequestParams( + const net::SHA1HashValue cert_fingerprint, + const net::SHA1HashValue ca_fingerprint, + const std::string& hostname_arg, + const std::string& ocsp_response, + int flags); + ~RequestParams() {} + + bool operator<(const RequestParams& other) const; + + std::string hostname; + std::string ocsp_response; + int flags; + std::vector hash_values; + }; + + class CertVerifyRequest + : public base::RefCountedThreadSafe { + public: + CertVerifyRequest( + AtomCertVerifier* cert_verifier, + const RequestParams& key, + scoped_refptr cert, + scoped_refptr crl_set, + net::CertVerifyResult* verify_result, + scoped_ptr* out_req, + const net::BoundNetLog& net_log) + : cert_verifier_(cert_verifier), + key_(key), + certificate_(cert), + crl_set_(crl_set), + verify_result_(verify_result), + out_req_(out_req), + net_log_(net_log), + handled_(false), + weak_ptr_factory_(this) { + } + + void RunResult(int result); + void DelegateToDefaultVerifier(); + void ContinueWithResult(int result); + + void AddCompletionCallback(net::CompletionCallback callback) { + callbacks_.push_back(callback); + } + + const RequestParams key() const { return key_; } + + std::string hostname() const { return key_.hostname; } + + scoped_refptr certificate() const { + return certificate_; + } + + private: + friend class base::RefCountedThreadSafe; + ~CertVerifyRequest() {} + + AtomCertVerifier* cert_verifier_; + const RequestParams key_; + + scoped_refptr certificate_; + scoped_refptr crl_set_; + net::CertVerifyResult* verify_result_; + scoped_ptr* out_req_; + const net::BoundNetLog net_log_; + + std::vector callbacks_; + bool handled_; + + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(CertVerifyRequest); + }; + + class Delegate { + public: + Delegate() {} + virtual ~Delegate() {} + + // Called on UI thread. + virtual void RequestCertVerification( + const scoped_refptr& request) {} + }; + + AtomCertVerifier(); + virtual ~AtomCertVerifier(); + + void SetDelegate(Delegate* delegate) { + delegate_ = delegate; + } + + protected: + // net::CertVerifier: + int Verify(net::X509Certificate* cert, + const std::string& hostname, + const std::string& ocsp_response, + int flags, + net::CRLSet* crl_set, + net::CertVerifyResult* verify_result, + const net::CompletionCallback& callback, + scoped_ptr* out_req, + const net::BoundNetLog& net_log) override; + bool SupportsOCSPStapling() override; + + net::CertVerifier* default_cert_verifier() const { + return default_cert_verifier_.get(); + } + + private: + CertVerifyRequest* FindRequest(const RequestParams& key); + void RemoveRequest(CertVerifyRequest* request); + + struct CertVerifyRequestToRequestParamsComparator { + bool operator()(const scoped_refptr request, + const RequestParams& key) const { + return request->key() < key; + } + }; + + struct CertVerifyRequestComparator { + bool operator()(const scoped_refptr req1, + const scoped_refptr req2) const { + return req1->key() < req2->key(); + } + }; + + using ActiveRequestSet = + std::set, + CertVerifyRequestComparator>; + ActiveRequestSet requests_; + + Delegate* delegate_; + + scoped_ptr default_cert_verifier_; + + DISALLOW_COPY_AND_ASSIGN(AtomCertVerifier); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_ATOM_CERT_VERIFIER_H_ diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index 4796d962660a..4749a4fedfc2 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -4,7 +4,11 @@ #include "atom/common/native_mate_converters/net_converter.h" +#include + +#include "atom/common/node_includes.h" #include "native_mate/dictionary.h" +#include "net/cert/x509_certificate.h" #include "net/url_request/url_request.h" namespace mate { @@ -31,4 +35,19 @@ v8::Local Converter::ToV8( return mate::ConvertToV8(isolate, dict); } +// static +v8::Local Converter>::ToV8( + v8::Isolate* isolate, const scoped_refptr& val) { + mate::Dictionary dict(isolate, v8::Object::New(isolate)); + std::string encoded_data; + net::X509Certificate::GetPEMEncoded( + val->os_cert_handle(), &encoded_data); + auto buffer = node::Buffer::Copy(isolate, + encoded_data.data(), + encoded_data.size()).ToLocalChecked(); + dict.Set("data", buffer); + dict.Set("issuerName", val->issuer().GetDisplayName()); + return dict.GetHandle(); +} + } // namespace mate diff --git a/atom/common/native_mate_converters/net_converter.h b/atom/common/native_mate_converters/net_converter.h index 352c613eaabb..b11c55929b98 100644 --- a/atom/common/native_mate_converters/net_converter.h +++ b/atom/common/native_mate_converters/net_converter.h @@ -5,11 +5,13 @@ #ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ #define ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ +#include "base/memory/ref_counted.h" #include "native_mate/converter.h" namespace net { class AuthChallengeInfo; class URLRequest; +class X509Certificate; } namespace mate { @@ -26,6 +28,12 @@ struct Converter { const net::AuthChallengeInfo* val); }; +template<> +struct Converter> { + static v8::Local ToV8(v8::Isolate* isolate, + const scoped_refptr& val); +}; + } // namespace mate #endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_NET_CONVERTER_H_ diff --git a/docs/api/app.md b/docs/api/app.md index 9d10bbb835c5..e1a4cbf964e3 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -139,8 +139,8 @@ Returns: * `webContents` [WebContents](web-contents.md) * `url` URL * `certificateList` [Objects] - * `data` PEM encoded data - * `issuerName` Issuer's Common Name + * `data` Buffer - PEM encoded data + * `issuerName` String - Issuer's Common Name * `callback` Function Emitted when a client certificate is requested. diff --git a/docs/api/session.md b/docs/api/session.md index c3a3c91eea93..210d58753519 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -34,6 +34,30 @@ session.on('will-download', function(event, item, webContents) { }); ``` +### Event: 'verify-certificate' + +* `event` Event +* `hostname` String +* `certificate` Object + * `data` Buffer - PEM encoded data + * `issuerName` String +* `callback` Function + +Fired whenever a server certificate verification is requested by the +network layer with `hostname`, `certificate` and `callback`. +`callback` should be called with a boolean response to +indicate continuation or cancellation of the request. + +```js +session.on('verify-certificate', function(event, hostname, certificate, callback) { + if (hostname == "github.com") { + // verification logic + callback(true); + } + callback(false); +}); +``` + ## Methods The `session` object has the following methods: diff --git a/filenames.gypi b/filenames.gypi index 0e91dd21e16d..fd8856abd4d0 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -131,6 +131,8 @@ 'atom/browser/atom_download_manager_delegate.h', 'atom/browser/atom_browser_main_parts.cc', 'atom/browser/atom_browser_main_parts.h', + 'atom/browser/atom_cert_verifier.cc', + 'atom/browser/atom_cert_verifier.h', 'atom/browser/atom_browser_main_parts_mac.mm', 'atom/browser/atom_browser_main_parts_posix.cc', 'atom/browser/atom_javascript_dialog_manager.cc',