Chromium Code Reviews| Index: ios/web/net/crw_cert_verification_controller.mm |
| diff --git a/ios/web/net/crw_cert_verification_controller.mm b/ios/web/net/crw_cert_verification_controller.mm |
| index c5dca87c376a89361b8837eaa5b24990958ae5b2..43d9d58d4494e685e49913c0e2b792daad9e441d 100644 |
| --- a/ios/web/net/crw_cert_verification_controller.mm |
| +++ b/ios/web/net/crw_cert_verification_controller.mm |
| @@ -12,6 +12,7 @@ |
| #include "base/threading/worker_pool.h" |
| #include "ios/web/net/cert_verifier_block_adapter.h" |
| #include "ios/web/public/browser_state.h" |
| +#include "ios/web/public/certificate_policy_cache.h" |
| #include "ios/web/public/web_thread.h" |
| #import "ios/web/web_state/wk_web_view_security_util.h" |
| #include "net/cert/cert_verify_result.h" |
| @@ -67,6 +68,9 @@ class BlockHolder : public base::RefCountedThreadSafe<BlockHolder<T>> { |
| // URLRequestContextGetter for obtaining net layer objects. |
| net::URLRequestContextGetter* _contextGetter; |
| + |
| + // Used to remember user exceptions to invalid certs. |
| + scoped_refptr<web::CertificatePolicyCache> _certPolicyCache; |
| } |
| // Cert verification flags. Must be used on IO Thread. |
| @@ -78,10 +82,11 @@ class BlockHolder : public base::RefCountedThreadSafe<BlockHolder<T>> { |
| // Verifies the given |cert| for the given |host| using |net::CertVerifier| and |
| // calls |completionHandler| on completion. This method can be called on any |
| // thread. |completionHandler| cannot be null and will be called asynchronously |
| -// on IO thread. |
| +// on IO thread or synchronously on current thread if IO task can't start (in |
| +// this case |dispatched| argument will be NO). |
| - (void)verifyCert:(const scoped_refptr<net::X509Certificate>&)cert |
| forHost:(NSString*)host |
| - completionHandler:(void (^)(net::CertVerifyResult, int))completionHandler; |
| + completionHandler:(void (^)(net::CertVerifyResult, BOOL dispatched))handler; |
| // Verifies the given |trust| using SecTrustRef API. |completionHandler| cannot |
| // be null and will be either called asynchronously on Worker thread or |
| @@ -90,6 +95,14 @@ class BlockHolder : public base::RefCountedThreadSafe<BlockHolder<T>> { |
| - (void)verifyTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust |
| completionHandler:(void (^)(SecTrustResultType, BOOL dispatched))handler; |
| +// Returns cert accept policy for the given SecTrust result. |trustResult| must |
| +// not be for a valid cert. Must be called on IO thread. |
| +- (web::CertAcceptPolicy) |
| + loadPolicyForBadTrustResult:(SecTrustResultType)trustResult |
| + certVerifierResult:(net::CertVerifyResult)certVerifierResult |
| + serverTrust:(SecTrustRef)trust |
| + host:(NSString*)host; |
| + |
| @end |
| @implementation CRWCertVerificationController |
| @@ -115,14 +128,17 @@ class BlockHolder : public base::RefCountedThreadSafe<BlockHolder<T>> { |
| if (self) { |
| _contextGetter = browserState->GetRequestContext(); |
| DCHECK(_contextGetter); |
| + _certPolicyCache = |
| + web::BrowserState::GetCertificatePolicyCache(browserState); |
| + |
| [self createCertVerifier]; |
| } |
| return self; |
| } |
| -- (void)decidePolicyForCert:(const scoped_refptr<net::X509Certificate>&)cert |
| - host:(NSString*)host |
| - completionHandler:(web::PolicyDecisionHandler)completionHandler { |
| +- (void)decideLoadPolicyForTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust |
| + host:(NSString*)host |
| + completionHandler:(web::PolicyDecisionHandler)completionHandler { |
| DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| // completionHandler of |verifyCert:forHost:completionHandler:| is called on |
| // IO thread and then bounces back to UI thread. As a result all objects |
| @@ -132,22 +148,55 @@ class BlockHolder : public base::RefCountedThreadSafe<BlockHolder<T>> { |
| // released on background thread and |BlockHolder| ensures that. |
| __block scoped_refptr<BlockHolder<web::PolicyDecisionHandler>> handlerHolder( |
| new BlockHolder<web::PolicyDecisionHandler>(completionHandler)); |
| - [self verifyCert:cert |
| - forHost:host |
| - completionHandler:^(net::CertVerifyResult result, int error) { |
| - web::CertAcceptPolicy policy = |
| - web::CERT_ACCEPT_POLICY_NON_RECOVERABLE_ERROR; |
| - if (error == net::OK) { |
| - policy = web::CERT_ACCEPT_POLICY_ALLOW; |
| - } else if (net::IsCertStatusError(result.cert_status)) { |
| - policy = net::IsCertStatusMinorError(result.cert_status) |
| - ? web::CERT_ACCEPT_POLICY_ALLOW |
| - : web::CERT_ACCEPT_POLICY_RECOVERABLE_ERROR; |
| + [self verifyTrust:trust |
| + completionHandler:^(SecTrustResultType trustResult, BOOL dispatched) { |
| + if (!dispatched) { |
| + // Cert verification task did not start. |
| + dispatch_async(dispatch_get_main_queue(), ^{ |
| + handlerHolder->call(web::CERT_ACCEPT_POLICY_NON_RECOVERABLE_ERROR, |
| + net::CertStatus()); |
| + }); |
| + return; |
| } |
| - dispatch_async(dispatch_get_main_queue(), ^{ |
| - handlerHolder->call(policy, result.cert_status); |
| - }); |
| + if (web::GetSecurityStyleFromTrustResult(trustResult) == |
| + web::SECURITY_STYLE_AUTHENTICATED) { |
| + // SecTrust API considers this cert as valid. |
| + dispatch_async(dispatch_get_main_queue(), ^{ |
| + handlerHolder->call(web::CERT_ACCEPT_POLICY_ALLOW, |
| + net::CertStatus()); |
| + }); |
| + return; |
| + } |
| + |
| + // SecTrust API considers this cert as invalid. Check the reason and |
| + // whether or not user has decided to proceed with this bad cert. |
| + scoped_refptr<net::X509Certificate> cert( |
| + web::CreateCertFromTrust(trust)); |
| + [self verifyCert:cert |
| + forHost:host |
| + completionHandler:^(net::CertVerifyResult certVerifierResult, |
| + BOOL dispatched) { |
| + if (!dispatched) { |
| + // Cert verification task did not start. |
| + dispatch_async(dispatch_get_main_queue(), ^{ |
| + handlerHolder->call( |
| + web::CERT_ACCEPT_POLICY_NON_RECOVERABLE_ERROR, |
| + net::CertStatus()); |
| + }); |
| + return; |
| + } |
| + |
| + web::CertAcceptPolicy policy = |
| + [self loadPolicyForBadTrustResult:trustResult |
| + certVerifierResult:certVerifierResult |
| + serverTrust:trust.get() |
| + host:host]; |
| + |
| + dispatch_async(dispatch_get_main_queue(), ^{ |
| + handlerHolder->call(policy, certVerifierResult.cert_status); |
| + }); |
| + }]; |
| }]; |
| } |
| @@ -191,7 +240,8 @@ class BlockHolder : public base::RefCountedThreadSafe<BlockHolder<T>> { |
| web::CreateCertFromTrust(trust)); |
| [self verifyCert:cert |
| forHost:host |
| - completionHandler:^(net::CertVerifyResult certVerifierResult, int) { |
| + completionHandler:^(net::CertVerifyResult certVerifierResult, |
| + BOOL) { |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| handlerHolder->call(securityStyle, |
| certVerifierResult.cert_status); |
| @@ -200,6 +250,26 @@ class BlockHolder : public base::RefCountedThreadSafe<BlockHolder<T>> { |
| }]; |
| } |
| +- (void)allowCert:(scoped_refptr<net::X509Certificate>)cert |
| + forHost:(NSString*)host |
| + status:(net::CertStatus)status { |
| + DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| + // Store user decisions with the leaf cert, ignoring any intermediates. |
| + // This is because WKWebView returns the verified certificate chain in |
| + // |webView:didReceiveAuthenticationChallenge:completionHandler:|, |
| + // but the server-supplied chain in |
| + // |webView:didFailProvisionalNavigation:withError:|. |
| + if (cert->GetIntermediateCertificates().size() > 0) { |
|
Ryan Sleevi
2015/10/28 21:28:51
nit: You could optimize this with !.empty()
For a
Eugene But (OOO till 7-30)
2015/10/29 00:39:14
Yeah. This is second codereview, where you explain
|
| + cert = net::X509Certificate::CreateFromHandle( |
| + cert->os_cert_handle(), net::X509Certificate::OSCertHandles()); |
| + } |
| + DCHECK_EQ(0U, cert->GetIntermediateCertificates().size()); |
|
Ryan Sleevi
2015/10/28 21:28:51
ditto re: .empty()
Eugene But (OOO till 7-30)
2015/10/29 00:39:14
Done.
|
| + web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, base::BindBlock(^{ |
| + _certPolicyCache->AllowCertForHost( |
| + cert.get(), base::SysNSStringToUTF8(host), status); |
| + })); |
| +} |
| + |
| - (void)shutDown { |
| DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::UI); |
| web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, base::BindBlock(^{ |
| @@ -235,10 +305,10 @@ class BlockHolder : public base::RefCountedThreadSafe<BlockHolder<T>> { |
| - (void)verifyCert:(const scoped_refptr<net::X509Certificate>&)cert |
| forHost:(NSString*)host |
| - completionHandler:(void (^)(net::CertVerifyResult, int))completionHandler { |
| + completionHandler:(void (^)(net::CertVerifyResult, BOOL))completionHandler { |
| DCHECK(completionHandler); |
| __block scoped_refptr<net::X509Certificate> blockCert = cert; |
| - web::WebThread::PostTask( |
| + bool dispatched = web::WebThread::PostTask( |
| web::WebThread::IO, FROM_HERE, base::BindBlock(^{ |
| // WeakNSObject does not work across different threads, hence this block |
| // retains self. |
| @@ -252,8 +322,14 @@ class BlockHolder : public base::RefCountedThreadSafe<BlockHolder<T>> { |
| params.flags = self.certVerifyFlags; |
| params.crl_set = net::SSLConfigService::GetCRLSet(); |
| // OCSP response is not provided by iOS API. |
| - _certVerifier->Verify(params, completionHandler); |
| + _certVerifier->Verify(params, ^(net::CertVerifyResult result, int) { |
| + completionHandler(result, YES); |
| + }); |
| })); |
| + |
| + if (!dispatched) { |
| + completionHandler(net::CertVerifyResult(), NO); |
| + } |
| } |
| - (void)verifyTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust |
| @@ -274,4 +350,34 @@ class BlockHolder : public base::RefCountedThreadSafe<BlockHolder<T>> { |
| } |
| } |
| +- (web::CertAcceptPolicy) |
| + loadPolicyForBadTrustResult:(SecTrustResultType)trustResult |
| + certVerifierResult:(net::CertVerifyResult)certVerifierResult |
| + serverTrust:(SecTrustRef)trust |
| + host:(NSString*)host { |
| + DCHECK_CURRENTLY_ON_WEB_THREAD(web::WebThread::IO); |
| + DCHECK_NE(web::SECURITY_STYLE_AUTHENTICATED, |
| + web::GetSecurityStyleFromTrustResult(trustResult)); |
| + |
| + if (trustResult != kSecTrustResultRecoverableTrustFailure || |
| + SecTrustGetCertificateCount(trust) == 0) { |
| + // Trust result is not recoverable or leaf cert is missing. |
| + return web::CERT_ACCEPT_POLICY_NON_RECOVERABLE_ERROR; |
| + } |
| + |
| + // Check if user has decided to proceed with this bad cert. |
| + scoped_refptr<net::X509Certificate> leafCert = |
| + net::X509Certificate::CreateFromHandle( |
| + SecTrustGetCertificateAtIndex(trust, 0), |
| + net::X509Certificate::OSCertHandles()); |
| + |
| + web::CertPolicy::Judgment judgment = _certPolicyCache->QueryPolicy( |
| + leafCert.get(), base::SysNSStringToUTF8(host), |
| + certVerifierResult.cert_status); |
| + |
| + return (judgment == web::CertPolicy::ALLOWED) |
| + ? web::CERT_ACCEPT_POLICY_RECOVERABLE_ERROR_ACCEPTED_BY_USER |
| + : web::CERT_ACCEPT_POLICY_RECOVERABLE_ERROR_UNDECIDED_BY_USER; |
| +} |
| + |
| @end |