| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "ios/web/net/crw_cert_verification_controller.h" | 5 #import "ios/web/net/crw_cert_verification_controller.h" |
| 6 | 6 |
| 7 #include <memory> | 7 #include <memory> |
| 8 | 8 |
| 9 #include "base/ios/block_types.h" | 9 #include "base/ios/block_types.h" |
| 10 #include "base/logging.h" | 10 #include "base/logging.h" |
| 11 #include "base/mac/bind_objc_block.h" | 11 #include "base/mac/bind_objc_block.h" |
| 12 #include "base/mac/scoped_block.h" | |
| 13 #import "base/memory/ref_counted.h" | 12 #import "base/memory/ref_counted.h" |
| 14 #include "base/metrics/histogram_macros.h" | |
| 15 #include "base/rand_util.h" | |
| 16 #include "base/strings/sys_string_conversions.h" | 13 #include "base/strings/sys_string_conversions.h" |
| 17 #include "base/threading/worker_pool.h" | 14 #include "base/threading/worker_pool.h" |
| 18 #include "ios/web/net/cert_verifier_block_adapter.h" | |
| 19 #include "ios/web/public/browser_state.h" | 15 #include "ios/web/public/browser_state.h" |
| 20 #include "ios/web/public/certificate_policy_cache.h" | 16 #include "ios/web/public/certificate_policy_cache.h" |
| 21 #include "ios/web/public/web_thread.h" | 17 #include "ios/web/public/web_thread.h" |
| 22 #import "ios/web/web_state/wk_web_view_security_util.h" | 18 #import "ios/web/web_state/wk_web_view_security_util.h" |
| 23 #include "net/cert/cert_verify_result.h" | 19 #include "net/cert/cert_verify_proc_ios.h" |
| 24 #include "net/url_request/url_request_context.h" | |
| 25 #include "net/url_request/url_request_context_getter.h" | |
| 26 | |
| 27 namespace { | |
| 28 | |
| 29 // Enum for Web.CertVerifyAgreement UMA metric to report certificate | |
| 30 // verification mismatch between SecTrust API and CertVerifier. SecTrust API is | |
| 31 // used for making load/no-load decision and CertVerifier is used for getting | |
| 32 // the reason of verification failure. It is expected that mismatches will | |
| 33 // happen for those 2 approaches (e.g. SecTrust API accepts the cert but | |
| 34 // CertVerifier rejects it). This metric helps to understand how common | |
| 35 // mismatches are. | |
| 36 // Note: This enum is used to back an UMA histogram, and should be treated as | |
| 37 // append-only. | |
| 38 enum CertVerifyAgreement { | |
| 39 // There is no mismach. Both SecTrust API and CertVerifier accepted this cert. | |
| 40 CERT_VERIFY_AGREEMENT_ACCEPTED_BY_BOTH = 0, | |
| 41 // There is no mismach. Both SecTrust API and CertVerifier rejected this cert. | |
| 42 CERT_VERIFY_AGREEMENT_REJECTED_BY_BOTH, | |
| 43 // SecTrust API accepted the cert, but CertVerifier rejected it (e.g. MDM cert | |
| 44 // or CertVerifier was more strict during verification), this mismach is | |
| 45 // expected to be common because of MDM certs. | |
| 46 CERT_VERIFY_AGREEMENT_ACCEPTED_ONLY_BY_IOS, | |
| 47 // SecTrust API rejected the cert, but CertVerifier accepted it. This mismatch | |
| 48 // is expected to be uncommon. | |
| 49 CERT_VERIFY_AGREEMENT_ACCEPTED_ONLY_BY_NSS, | |
| 50 CERT_VERIFY_AGREEMENT_COUNT, | |
| 51 }; | |
| 52 | |
| 53 // This class takes ownership of block and releases it on UI thread, even if | |
| 54 // |BlockHolder| is destructed on a background thread. On destruction calls its | |
| 55 // block with default arguments, if block was not called before. This way | |
| 56 // BlockHolder guarantees that block is always called to satisfy API contract | |
| 57 // for CRWCertVerificationController (completion handlers are always called). | |
| 58 template <class T> | |
| 59 class BlockHolder : public base::RefCountedThreadSafe<BlockHolder<T>> { | |
| 60 public: | |
| 61 // Takes ownership of |block|, which must not be null. On destruction calls | |
| 62 // this block with |DefaultArgs|, if block was not called before. | |
| 63 // |DefaultArgs| must be passed by value, not by ponter or reference. | |
| 64 template <typename... Arguments> | |
| 65 BlockHolder(T block, Arguments... DefaultArgs) | |
| 66 : block_([block copy]), | |
| 67 called_(false), | |
| 68 default_block_([^{ | |
| 69 block(DefaultArgs...); | |
| 70 } copy]) { | |
| 71 DCHECK(block_); | |
| 72 } | |
| 73 | |
| 74 // Calls underlying block with the given variadic arguments. | |
| 75 template <typename... Arguments> | |
| 76 void call(Arguments... Args) { | |
| 77 block_(Args...); | |
| 78 called_ = true; | |
| 79 } | |
| 80 | |
| 81 private: | |
| 82 BlockHolder() = delete; | |
| 83 friend class base::RefCountedThreadSafe<BlockHolder>; | |
| 84 | |
| 85 // Finalizes object's destruction on UI thread by calling |default_block| (if | |
| 86 // |block| has not been called yet) and releasing all blocks. Must be called | |
| 87 // on UI thread. | |
| 88 static void Finalize(id block, | |
| 89 ProceduralBlock default_block, | |
| 90 bool block_was_called) { | |
| 91 DCHECK_CURRENTLY_ON(web::WebThread::UI); | |
| 92 // By calling default_block, BlockHolder guarantees that block is always | |
| 93 // called to satisfy API contract for CRWCertVerificationController | |
| 94 // (completion handlers are always called). | |
| 95 if (!block_was_called) | |
| 96 default_block(); | |
| 97 [block release]; | |
| 98 [default_block release]; | |
| 99 } | |
| 100 | |
| 101 // Releases underlying |block_| on UI thread, calls |default_block_| if | |
| 102 // |block_| has not been called yet. | |
| 103 ~BlockHolder() { | |
| 104 if (web::WebThread::CurrentlyOn(web::WebThread::UI)) { | |
| 105 Finalize(block_, default_block_, called_); | |
| 106 } else { | |
| 107 web::WebThread::PostTask( | |
| 108 web::WebThread::UI, FROM_HERE, | |
| 109 base::Bind(&BlockHolder::Finalize, block_, default_block_, called_)); | |
| 110 } | |
| 111 } | |
| 112 | |
| 113 T block_; | |
| 114 // true if this |block_| has already been called. | |
| 115 bool called_; | |
| 116 // Called on destruction if |block_| was not called. | |
| 117 ProceduralBlock default_block_; | |
| 118 }; | |
| 119 | |
| 120 typedef scoped_refptr<BlockHolder<web::PolicyDecisionHandler>> | |
| 121 PolicyDecisionHandlerHolder; | |
| 122 | |
| 123 } // namespace | |
| 124 | 20 |
| 125 @interface CRWCertVerificationController () { | 21 @interface CRWCertVerificationController () { |
| 126 // Cert verification object which wraps |net::CertVerifier|. Must be created, | |
| 127 // used and destroyed on IO Thread. | |
| 128 std::unique_ptr<web::CertVerifierBlockAdapter> _certVerifier; | |
| 129 | |
| 130 // URLRequestContextGetter for obtaining net layer objects. | |
| 131 net::URLRequestContextGetter* _contextGetter; | |
| 132 | |
| 133 // Used to remember user exceptions to invalid certs. | 22 // Used to remember user exceptions to invalid certs. |
| 134 scoped_refptr<web::CertificatePolicyCache> _certPolicyCache; | 23 scoped_refptr<web::CertificatePolicyCache> _certPolicyCache; |
| 135 } | 24 } |
| 136 | 25 |
| 137 // Cert verification flags. Must be used on IO Thread. | 26 // Returns cert status for the given |trust|. |
| 138 @property(nonatomic, readonly) int certVerifyFlags; | 27 - (net::CertStatus)certStatusFromTrustResult:(SecTrustResultType)trustResult |
| 139 | 28 serverTrust: |
| 140 // Returns YES if CertVerifier should be run (even if SecTrust API considers | 29 (base::ScopedCFTypeRef<SecTrustRef>)trust; |
| 141 // cert as valid) and Web.CertVerifyAgreement UMA metric should be reported. | |
| 142 // The result of this method is random and nondeterministic. | |
| 143 - (BOOL)shouldReportCertVerifyAgreement; | |
| 144 | |
| 145 // Reports Web.CertVerifyAgreement UMA metric. | |
| 146 - (void)reportUMAForCertVerifyAgreement:(CertVerifyAgreement)agreement; | |
| 147 | |
| 148 // Creates _certVerifier object on IO thread. | |
| 149 - (void)createCertVerifier; | |
| 150 | 30 |
| 151 // Decides the policy for the given |trust| which was rejected by iOS and the | 31 // Decides the policy for the given |trust| which was rejected by iOS and the |
| 152 // given |host| and calls |handler| on completion. | 32 // given |host| and calls |handler| on completion. Must be called on UI thread. |
| 33 // |handler| can not be null and will be called on UI thread. |
| 153 - (void) | 34 - (void) |
| 154 decideLoadPolicyForRejectedTrustResult:(SecTrustResultType)trustResult | 35 decideLoadPolicyForRejectedTrustResult:(SecTrustResultType)trustResult |
| 155 serverTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust | 36 serverTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust |
| 156 host:(NSString*)host | 37 host:(NSString*)host |
| 157 completionHandler:(PolicyDecisionHandlerHolder)handler; | 38 completionHandler:(web::PolicyDecisionHandler)handler; |
| 158 | |
| 159 // Decides the policy for the given |trust| which was accepted by iOS and the | |
| 160 // given |host| and calls |handler| on completion. | |
| 161 - (void) | |
| 162 decideLoadPolicyForAcceptedTrustResult:(SecTrustResultType)trustResult | |
| 163 serverTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust | |
| 164 host:(NSString*)host | |
| 165 completionHandler:(PolicyDecisionHandlerHolder)handler; | |
| 166 | |
| 167 // Verifies the given |cert| for the given |host| using |net::CertVerifier| and | |
| 168 // calls |completionHandler| on completion. This method can be called on any | |
| 169 // thread. |completionHandler| cannot be null and will be called asynchronously | |
| 170 // on IO thread or will never be called if IO task can't start or complete. | |
| 171 - (void)verifyCert:(const scoped_refptr<net::X509Certificate>&)cert | |
| 172 forHost:(NSString*)host | |
| 173 completionHandler:(void (^)(net::CertVerifyResult))completionHandler; | |
| 174 | 39 |
| 175 // Verifies the given |trust| using SecTrustRef API. |completionHandler| cannot | 40 // Verifies the given |trust| using SecTrustRef API. |completionHandler| cannot |
| 176 // be null and will be either called asynchronously on Worker thread or will | 41 // be null and will be called on UI thread or never be called if the worker task |
| 177 // never be called if the worker task can't start or complete. | 42 // can't start or complete. Must be called on UI thread. |
| 178 - (void)verifyTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust | 43 - (void)verifyTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust |
| 179 completionHandler:(void (^)(SecTrustResultType))completionHandler; | 44 completionHandler:(void (^)(SecTrustResultType))completionHandler; |
| 180 | 45 |
| 181 // Returns cert accept policy for the given SecTrust result. |trustResult| must | 46 // Returns cert accept policy for the given SecTrust result. |trustResult| must |
| 182 // not be for a valid cert. Must be called on IO thread. | 47 // not be for a valid cert. Must be called on IO thread. |
| 183 - (web::CertAcceptPolicy) | 48 - (web::CertAcceptPolicy) |
| 184 loadPolicyForRejectedTrustResult:(SecTrustResultType)trustResult | 49 loadPolicyForRejectedTrustResult:(SecTrustResultType)trustResult |
| 185 certVerifierResult:(net::CertVerifyResult)certVerifierResult | 50 certStatus:(net::CertStatus)certStatus |
| 186 serverTrust:(SecTrustRef)trust | 51 serverTrust:(SecTrustRef)trust |
| 187 host:(NSString*)host; | 52 host:(NSString*)host; |
| 188 | 53 |
| 189 @end | 54 @end |
| 190 | 55 |
| 191 @implementation CRWCertVerificationController | 56 @implementation CRWCertVerificationController |
| 192 | 57 |
| 193 #pragma mark - Superclass | |
| 194 | |
| 195 - (void)dealloc { | |
| 196 DCHECK(!_certVerifier); | |
| 197 [super dealloc]; | |
| 198 } | |
| 199 | |
| 200 #pragma mark - Public | 58 #pragma mark - Public |
| 201 | 59 |
| 202 - (instancetype)init { | 60 - (instancetype)init { |
| 203 NOTREACHED(); | 61 NOTREACHED(); |
| 204 return nil; | 62 return nil; |
| 205 } | 63 } |
| 206 | 64 |
| 207 - (instancetype)initWithBrowserState:(web::BrowserState*)browserState { | 65 - (instancetype)initWithBrowserState:(web::BrowserState*)browserState { |
| 208 DCHECK(browserState); | 66 DCHECK(browserState); |
| 209 DCHECK_CURRENTLY_ON(web::WebThread::UI); | 67 DCHECK_CURRENTLY_ON(web::WebThread::UI); |
| 210 self = [super init]; | 68 self = [super init]; |
| 211 if (self) { | 69 if (self) { |
| 212 _contextGetter = browserState->GetRequestContext(); | |
| 213 DCHECK(_contextGetter); | |
| 214 _certPolicyCache = | 70 _certPolicyCache = |
| 215 web::BrowserState::GetCertificatePolicyCache(browserState); | 71 web::BrowserState::GetCertificatePolicyCache(browserState); |
| 216 | |
| 217 [self createCertVerifier]; | |
| 218 } | 72 } |
| 219 return self; | 73 return self; |
| 220 } | 74 } |
| 221 | 75 |
| 222 - (void)decideLoadPolicyForTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust | 76 - (void)decideLoadPolicyForTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust |
| 223 host:(NSString*)host | 77 host:(NSString*)host |
| 224 completionHandler:(web::PolicyDecisionHandler)completionHandler { | 78 completionHandler:(web::PolicyDecisionHandler)completionHandler { |
| 225 DCHECK_CURRENTLY_ON(web::WebThread::UI); | 79 DCHECK_CURRENTLY_ON(web::WebThread::UI); |
| 226 // completionHandler of |verifyCert:forHost:completionHandler:| is called on | 80 DCHECK(completionHandler); |
| 227 // IO thread and then bounces back to UI thread. As a result all objects | 81 |
| 228 // captured by completionHandler may be released on either UI or IO thread. | |
| 229 // Since |completionHandler| can potentially capture multiple thread unsafe | |
| 230 // objects (like Web Controller) |completionHandler| itself should never be | |
| 231 // released on background thread and |BlockHolder| ensures that. | |
| 232 __block PolicyDecisionHandlerHolder handlerHolder( | |
| 233 new BlockHolder<web::PolicyDecisionHandler>( | |
| 234 completionHandler, web::CERT_ACCEPT_POLICY_NON_RECOVERABLE_ERROR, | |
| 235 net::CertStatus())); | |
| 236 [self verifyTrust:trust | 82 [self verifyTrust:trust |
| 237 completionHandler:^(SecTrustResultType trustResult) { | 83 completionHandler:^(SecTrustResultType trustResult) { |
| 238 if (web::GetSecurityStyleFromTrustResult(trustResult) == | 84 DCHECK_CURRENTLY_ON(web::WebThread::UI); |
| 239 web::SECURITY_STYLE_AUTHENTICATED) { | 85 if (trustResult == kSecTrustResultProceed || |
| 240 [self decideLoadPolicyForAcceptedTrustResult:trustResult | 86 trustResult == kSecTrustResultUnspecified) { |
| 241 serverTrust:trust | 87 completionHandler(web::CERT_ACCEPT_POLICY_ALLOW, net::CertStatus()); |
| 242 host:host | 88 return; |
| 243 completionHandler:handlerHolder]; | |
| 244 } else { | |
| 245 [self decideLoadPolicyForRejectedTrustResult:trustResult | |
| 246 serverTrust:trust | |
| 247 host:host | |
| 248 completionHandler:handlerHolder]; | |
| 249 } | 89 } |
| 90 [self decideLoadPolicyForRejectedTrustResult:trustResult |
| 91 serverTrust:trust |
| 92 host:host |
| 93 completionHandler:completionHandler]; |
| 250 }]; | 94 }]; |
| 251 } | 95 } |
| 252 | 96 |
| 253 - (void)querySSLStatusForTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust | 97 - (void)querySSLStatusForTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust |
| 254 host:(NSString*)host | 98 host:(NSString*)host |
| 255 completionHandler:(web::StatusQueryHandler)completionHandler { | 99 completionHandler:(web::StatusQueryHandler)completionHandler { |
| 256 DCHECK_CURRENTLY_ON(web::WebThread::UI); | 100 DCHECK_CURRENTLY_ON(web::WebThread::UI); |
| 101 DCHECK(completionHandler); |
| 257 | 102 |
| 258 // The completion handlers of |verifyCert:forHost:completionHandler:| and | |
| 259 // |verifyTrust:completionHandler:| will be deallocated on background thread. | |
| 260 // |completionHandler| itself should never be released on background thread | |
| 261 // and |BlockHolder| ensures that. | |
| 262 __block scoped_refptr<BlockHolder<web::StatusQueryHandler>> handlerHolder( | |
| 263 new BlockHolder<web::StatusQueryHandler>( | |
| 264 completionHandler, web::SECURITY_STYLE_UNKNOWN, net::CertStatus())); | |
| 265 [self verifyTrust:trust | 103 [self verifyTrust:trust |
| 266 completionHandler:^(SecTrustResultType trustResult) { | 104 completionHandler:^(SecTrustResultType trustResult) { |
| 267 web::SecurityStyle securityStyle = | 105 web::SecurityStyle securityStyle = |
| 268 web::GetSecurityStyleFromTrustResult(trustResult); | 106 web::GetSecurityStyleFromTrustResult(trustResult); |
| 269 if (securityStyle == web::SECURITY_STYLE_AUTHENTICATED) { | |
| 270 // SecTrust API considers this cert as valid. | |
| 271 dispatch_async(dispatch_get_main_queue(), ^{ | |
| 272 handlerHolder->call(securityStyle, net::CertStatus()); | |
| 273 }); | |
| 274 return; | |
| 275 } | |
| 276 | 107 |
| 277 // Retrieve the net::CertStatus for invalid certificates to determine | 108 net::CertStatus certStatus = |
| 278 // the rejection reason, it is possible that rejection reason could not | 109 [self certStatusFromTrustResult:trustResult serverTrust:trust]; |
| 279 // be determined and |cert_status| will be empty. | 110 completionHandler(securityStyle, certStatus); |
| 280 scoped_refptr<net::X509Certificate> cert( | |
| 281 web::CreateCertFromTrust(trust)); | |
| 282 [self verifyCert:cert | |
| 283 forHost:host | |
| 284 completionHandler:^(net::CertVerifyResult certVerifierResult) { | |
| 285 dispatch_async(dispatch_get_main_queue(), ^{ | |
| 286 handlerHolder->call(securityStyle, | |
| 287 certVerifierResult.cert_status); | |
| 288 }); | |
| 289 }]; | |
| 290 }]; | 111 }]; |
| 291 } | 112 } |
| 292 | 113 |
| 293 - (void)allowCert:(scoped_refptr<net::X509Certificate>)cert | 114 - (void)allowCert:(scoped_refptr<net::X509Certificate>)cert |
| 294 forHost:(NSString*)host | 115 forHost:(NSString*)host |
| 295 status:(net::CertStatus)status { | 116 status:(net::CertStatus)status { |
| 296 DCHECK_CURRENTLY_ON(web::WebThread::UI); | 117 DCHECK_CURRENTLY_ON(web::WebThread::UI); |
| 297 // Store user decisions with the leaf cert, ignoring any intermediates. | 118 // Store user decisions with the leaf cert, ignoring any intermediates. |
| 298 // This is because WKWebView returns the verified certificate chain in | 119 // This is because WKWebView returns the verified certificate chain in |
| 299 // |webView:didReceiveAuthenticationChallenge:completionHandler:|, | 120 // |webView:didReceiveAuthenticationChallenge:completionHandler:|, |
| 300 // but the server-supplied chain in | 121 // but the server-supplied chain in |
| 301 // |webView:didFailProvisionalNavigation:withError:|. | 122 // |webView:didFailProvisionalNavigation:withError:|. |
| 302 if (!cert->GetIntermediateCertificates().empty()) { | 123 if (!cert->GetIntermediateCertificates().empty()) { |
| 303 cert = net::X509Certificate::CreateFromHandle( | 124 cert = net::X509Certificate::CreateFromHandle( |
| 304 cert->os_cert_handle(), net::X509Certificate::OSCertHandles()); | 125 cert->os_cert_handle(), net::X509Certificate::OSCertHandles()); |
| 305 } | 126 } |
| 306 DCHECK(cert->GetIntermediateCertificates().empty()); | 127 DCHECK(cert->GetIntermediateCertificates().empty()); |
| 307 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, base::BindBlock(^{ | 128 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, base::BindBlock(^{ |
| 308 _certPolicyCache->AllowCertForHost( | 129 _certPolicyCache->AllowCertForHost( |
| 309 cert.get(), base::SysNSStringToUTF8(host), status); | 130 cert.get(), base::SysNSStringToUTF8(host), status); |
| 310 })); | 131 })); |
| 311 } | 132 } |
| 312 | 133 |
| 313 - (void)shutDown { | |
| 314 DCHECK_CURRENTLY_ON(web::WebThread::UI); | |
| 315 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, base::BindBlock(^{ | |
| 316 // This block captures |self| delaying its deallocation and causing dealloc | |
| 317 // to happen on either IO or UI thread (which is fine for this class). | |
| 318 _certVerifier.reset(); | |
| 319 })); | |
| 320 } | |
| 321 | |
| 322 #pragma mark - Private | 134 #pragma mark - Private |
| 323 | 135 |
| 324 - (int)certVerifyFlags { | 136 - (net::CertStatus)certStatusFromTrustResult:(SecTrustResultType)trustResult |
| 325 DCHECK(web::WebThread::CurrentlyOn(web::WebThread::IO)); | 137 serverTrust: |
| 326 DCHECK(_contextGetter); | 138 (base::ScopedCFTypeRef<SecTrustRef>)trust { |
| 327 // |net::URLRequestContextGetter| lifetime is expected to be at least the same | 139 net::CertStatus certStatus = net::CertStatus(); |
| 328 // or longer than |BrowserState| lifetime. | 140 switch (trustResult) { |
| 329 net::URLRequestContext* context = _contextGetter->GetURLRequestContext(); | 141 case kSecTrustResultProceed: |
| 330 DCHECK(context); | 142 case kSecTrustResultUnspecified: |
| 331 net::SSLConfigService* SSLConfigService = context->ssl_config_service(); | 143 break; |
| 332 DCHECK(SSLConfigService); | 144 case kSecTrustResultDeny: |
| 333 net::SSLConfig config; | 145 certStatus |= net::CERT_STATUS_AUTHORITY_INVALID; |
| 334 SSLConfigService->GetSSLConfig(&config); | 146 break; |
| 335 return config.GetCertVerifyFlags(); | 147 default: |
| 336 } | 148 certStatus |= |
| 337 | 149 net::CertVerifyProcIOS::GetCertFailureStatusFromTrust(trust); |
| 338 - (BOOL)shouldReportCertVerifyAgreement { | 150 if (!net::IsCertStatusError(certStatus)) { |
| 339 // Cert verification is an expensive operation. Since extra verification will | 151 certStatus |= net::CERT_STATUS_INVALID; |
| 340 // be used only for UMA reporting purposes, do that only for 1% of cases. | 152 } |
| 341 return base::RandGenerator(100) == 0; | 153 } |
| 342 } | 154 return certStatus; |
| 343 | |
| 344 - (void)reportUMAForCertVerifyAgreement:(CertVerifyAgreement)agreement { | |
| 345 UMA_HISTOGRAM_ENUMERATION("Web.CertVerifyAgreement", agreement, | |
| 346 CERT_VERIFY_AGREEMENT_COUNT); | |
| 347 } | |
| 348 | |
| 349 - (void)createCertVerifier { | |
| 350 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, base::BindBlock(^{ | |
| 351 net::URLRequestContext* context = _contextGetter->GetURLRequestContext(); | |
| 352 _certVerifier.reset(new web::CertVerifierBlockAdapter( | |
| 353 context->cert_verifier(), context->net_log())); | |
| 354 })); | |
| 355 } | 155 } |
| 356 | 156 |
| 357 - (void) | 157 - (void) |
| 358 decideLoadPolicyForRejectedTrustResult:(SecTrustResultType)trustResult | 158 decideLoadPolicyForRejectedTrustResult:(SecTrustResultType)trustResult |
| 359 serverTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust | 159 serverTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust |
| 360 host:(NSString*)host | 160 host:(NSString*)host |
| 361 completionHandler:(PolicyDecisionHandlerHolder)handler { | 161 completionHandler:(web::PolicyDecisionHandler)handler { |
| 362 // SecTrust API considers this cert as invalid. Check the reason and | 162 DCHECK_CURRENTLY_ON(web::WebThread::UI); |
| 363 // whether or not user has decided to proceed with this bad cert. | 163 DCHECK(handler); |
| 364 scoped_refptr<net::X509Certificate> cert(web::CreateCertFromTrust(trust)); | 164 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, base::BindBlock(^{ |
| 365 [self verifyCert:cert | 165 // |loadPolicyForRejectedTrustResult:certStatus:serverTrust:host:| can |
| 366 forHost:host | 166 // only be called on IO thread. |
| 367 completionHandler:^(net::CertVerifyResult certVerifierResult) { | 167 net::CertStatus certStatus = |
| 368 if (!net::IsCertStatusError(certVerifierResult.cert_status)) { | 168 [self certStatusFromTrustResult:trustResult serverTrust:trust]; |
| 369 // |cert_status| must not be no-error if SecTrust API considers the | |
| 370 // cert as invalid. Otherwise there will be issues with errors | |
| 371 // reporting and recovery. | |
| 372 certVerifierResult.cert_status = net::CERT_STATUS_INVALID; | |
| 373 } | |
| 374 | 169 |
| 375 web::CertAcceptPolicy policy = | 170 web::CertAcceptPolicy policy = |
| 376 [self loadPolicyForRejectedTrustResult:trustResult | 171 [self loadPolicyForRejectedTrustResult:trustResult |
| 377 certVerifierResult:certVerifierResult | 172 certStatus:certStatus |
| 378 serverTrust:trust.get() | 173 serverTrust:trust.get() |
| 379 host:host]; | 174 host:host]; |
| 380 | 175 |
| 381 dispatch_async(dispatch_get_main_queue(), ^{ | 176 web::WebThread::PostTask(web::WebThread::UI, FROM_HERE, base::BindBlock(^{ |
| 382 if ([self shouldReportCertVerifyAgreement]) { | 177 handler(policy, certStatus); |
| 383 CertVerifyAgreement agreement = | 178 })); |
| 384 net::IsCertStatusError(certVerifierResult.cert_status) | |
| 385 ? CERT_VERIFY_AGREEMENT_REJECTED_BY_BOTH | |
| 386 : CERT_VERIFY_AGREEMENT_ACCEPTED_ONLY_BY_NSS; | |
| 387 [self reportUMAForCertVerifyAgreement:agreement]; | |
| 388 } | |
| 389 handler->call(policy, certVerifierResult.cert_status); | |
| 390 }); | |
| 391 }]; | |
| 392 } | |
| 393 | |
| 394 - (void) | |
| 395 decideLoadPolicyForAcceptedTrustResult:(SecTrustResultType)trustResult | |
| 396 serverTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust | |
| 397 host:(NSString*)host | |
| 398 completionHandler:(PolicyDecisionHandlerHolder)handler { | |
| 399 // SecTrust API considers this cert as valid. | |
| 400 dispatch_async(dispatch_get_main_queue(), ^{ | |
| 401 handler->call(web::CERT_ACCEPT_POLICY_ALLOW, net::CertStatus()); | |
| 402 }); | |
| 403 | |
| 404 if ([self shouldReportCertVerifyAgreement]) { | |
| 405 // Execute CertVerifier::Verify to collect CertVerifyAgreement UMA. | |
| 406 scoped_refptr<net::X509Certificate> cert(web::CreateCertFromTrust(trust)); | |
| 407 [self verifyCert:cert | |
| 408 forHost:host | |
| 409 completionHandler:^(net::CertVerifyResult certVerifierResult) { | |
| 410 // SecTrust API accepted this cert and |PolicyDecisionHandler| has | |
| 411 // been called already. |CertVerifier| verification is executed only | |
| 412 // to collect CertVerifyAgreement UMA. | |
| 413 dispatch_async(dispatch_get_main_queue(), ^{ | |
| 414 CertVerifyAgreement agreement = | |
| 415 net::IsCertStatusError(certVerifierResult.cert_status) | |
| 416 ? CERT_VERIFY_AGREEMENT_ACCEPTED_ONLY_BY_IOS | |
| 417 : CERT_VERIFY_AGREEMENT_ACCEPTED_BY_BOTH; | |
| 418 [self reportUMAForCertVerifyAgreement:agreement]; | |
| 419 }); | |
| 420 }]; | |
| 421 } | |
| 422 } | |
| 423 | |
| 424 - (void)verifyCert:(const scoped_refptr<net::X509Certificate>&)cert | |
| 425 forHost:(NSString*)host | |
| 426 completionHandler:(void (^)(net::CertVerifyResult))completionHandler { | |
| 427 DCHECK(completionHandler); | |
| 428 __block scoped_refptr<net::X509Certificate> blockCert = cert; | |
| 429 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, base::BindBlock(^{ | |
| 430 // WeakNSObject does not work across different threads, hence this block | |
| 431 // retains self. | |
| 432 if (!_certVerifier) { | |
| 433 completionHandler(net::CertVerifyResult()); | |
| 434 return; | |
| 435 } | |
| 436 | |
| 437 web::CertVerifierBlockAdapter::Params params(std::move(blockCert), | |
| 438 base::SysNSStringToUTF8(host)); | |
| 439 params.flags = self.certVerifyFlags; | |
| 440 // OCSP response is not provided by iOS API. | |
| 441 // CRLSets are not used, as the OS is used to make load/no-load | |
| 442 // decisions, not the CertVerifier. | |
| 443 _certVerifier->Verify(params, ^(net::CertVerifyResult result, int) { | |
| 444 completionHandler(result); | |
| 445 }); | |
| 446 })); | 179 })); |
| 447 } | 180 } |
| 448 | 181 |
| 449 - (void)verifyTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust | 182 - (void)verifyTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust |
| 450 completionHandler:(void (^)(SecTrustResultType))completionHandler { | 183 completionHandler:(void (^)(SecTrustResultType))completionHandler { |
| 184 DCHECK_CURRENTLY_ON(web::WebThread::UI); |
| 451 DCHECK(completionHandler); | 185 DCHECK(completionHandler); |
| 452 // SecTrustEvaluate performs trust evaluation synchronously, possibly making | 186 // SecTrustEvaluate performs trust evaluation synchronously, possibly making |
| 453 // network requests. The UI thread should not be blocked by that operation. | 187 // network requests. The UI thread should not be blocked by that operation. |
| 454 base::WorkerPool::PostTask(FROM_HERE, base::BindBlock(^{ | 188 base::WorkerPool::PostTask(FROM_HERE, base::BindBlock(^{ |
| 455 SecTrustResultType trustResult = kSecTrustResultInvalid; | 189 SecTrustResultType trustResult = kSecTrustResultInvalid; |
| 456 if (SecTrustEvaluate(trust.get(), &trustResult) != errSecSuccess) { | 190 if (SecTrustEvaluate(trust.get(), &trustResult) != errSecSuccess) { |
| 457 trustResult = kSecTrustResultInvalid; | 191 trustResult = kSecTrustResultInvalid; |
| 458 } | 192 } |
| 459 completionHandler(trustResult); | 193 web::WebThread::PostTask(web::WebThread::UI, FROM_HERE, base::BindBlock(^{ |
| 194 completionHandler(trustResult); |
| 195 })); |
| 460 }), false /* task_is_slow */); | 196 }), false /* task_is_slow */); |
| 461 } | 197 } |
| 462 | 198 |
| 463 - (web::CertAcceptPolicy) | 199 - (web::CertAcceptPolicy) |
| 464 loadPolicyForRejectedTrustResult:(SecTrustResultType)trustResult | 200 loadPolicyForRejectedTrustResult:(SecTrustResultType)trustResult |
| 465 certVerifierResult:(net::CertVerifyResult)certVerifierResult | 201 certStatus:(net::CertStatus)certStatus |
| 466 serverTrust:(SecTrustRef)trust | 202 serverTrust:(SecTrustRef)trust |
| 467 host:(NSString*)host { | 203 host:(NSString*)host { |
| 468 DCHECK_CURRENTLY_ON(web::WebThread::IO); | 204 DCHECK_CURRENTLY_ON(web::WebThread::IO); |
| 469 DCHECK_NE(web::SECURITY_STYLE_AUTHENTICATED, | 205 DCHECK_NE(web::SECURITY_STYLE_AUTHENTICATED, |
| 470 web::GetSecurityStyleFromTrustResult(trustResult)); | 206 web::GetSecurityStyleFromTrustResult(trustResult)); |
| 471 | 207 |
| 472 if (trustResult != kSecTrustResultRecoverableTrustFailure || | 208 if (trustResult != kSecTrustResultRecoverableTrustFailure || |
| 473 SecTrustGetCertificateCount(trust) == 0) { | 209 SecTrustGetCertificateCount(trust) == 0) { |
| 474 // Trust result is not recoverable or leaf cert is missing. | 210 // Trust result is not recoverable or leaf cert is missing. |
| 475 return web::CERT_ACCEPT_POLICY_NON_RECOVERABLE_ERROR; | 211 return web::CERT_ACCEPT_POLICY_NON_RECOVERABLE_ERROR; |
| 476 } | 212 } |
| 477 | 213 |
| 478 // Check if user has decided to proceed with this bad cert. | 214 // Check if user has decided to proceed with this bad cert. |
| 479 scoped_refptr<net::X509Certificate> leafCert = | 215 scoped_refptr<net::X509Certificate> leafCert = |
| 480 net::X509Certificate::CreateFromHandle( | 216 net::X509Certificate::CreateFromHandle( |
| 481 SecTrustGetCertificateAtIndex(trust, 0), | 217 SecTrustGetCertificateAtIndex(trust, 0), |
| 482 net::X509Certificate::OSCertHandles()); | 218 net::X509Certificate::OSCertHandles()); |
| 483 | 219 |
| 484 web::CertPolicy::Judgment judgment = _certPolicyCache->QueryPolicy( | 220 web::CertPolicy::Judgment judgment = _certPolicyCache->QueryPolicy( |
| 485 leafCert.get(), base::SysNSStringToUTF8(host), | 221 leafCert.get(), base::SysNSStringToUTF8(host), certStatus); |
| 486 certVerifierResult.cert_status); | |
| 487 | 222 |
| 488 return (judgment == web::CertPolicy::ALLOWED) | 223 return (judgment == web::CertPolicy::ALLOWED) |
| 489 ? web::CERT_ACCEPT_POLICY_RECOVERABLE_ERROR_ACCEPTED_BY_USER | 224 ? web::CERT_ACCEPT_POLICY_RECOVERABLE_ERROR_ACCEPTED_BY_USER |
| 490 : web::CERT_ACCEPT_POLICY_RECOVERABLE_ERROR_UNDECIDED_BY_USER; | 225 : web::CERT_ACCEPT_POLICY_RECOVERABLE_ERROR_UNDECIDED_BY_USER; |
| 491 } | 226 } |
| 492 | 227 |
| 493 @end | 228 @end |
| OLD | NEW |