blob: da487021799da360f862422c0b7fb44ebcff23a6 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/web/net/crw_ssl_status_updater.h"
#import "base/ios/weak_nsobject.h"
#import "base/mac/scoped_nsobject.h"
#import "base/strings/sys_string_conversions.h"
#include "ios/web/public/cert_store.h"
#import "ios/web/public/navigation_item.h"
#import "ios/web/public/navigation_manager.h"
#include "ios/web/public/ssl_status.h"
#import "ios/web/web_state/wk_web_view_security_util.h"
#include "net/cert/x509_certificate.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using base::ScopedCFTypeRef;
using net::CertStatus;
using web::SecurityStyle;
@interface CRWSSLStatusUpdater () {
// DataSource for CRWSSLStatusUpdater.
base::WeakNSProtocol<id<CRWSSLStatusUpdaterDataSource>> _dataSource;
// Backs up property of the same name.
base::WeakNSProtocol<id<CRWSSLStatusUpdaterDelegate>> _delegate;
}
// Unowned pointer to web::NavigationManager.
@property(nonatomic, readonly) web::NavigationManager* navigationManager;
// Identifier used for storing and retrieving certificates.
@property(nonatomic, readonly) int certGroupID;
// Updates |security_style| and |cert_status| for the NavigationItem with ID
// |navigationItemID|, if URL and certificate chain still match |host| and
// |certChain|.
- (void)updateSSLStatusForItemWithID:(int)navigationItemID
trust:(ScopedCFTypeRef<SecTrustRef>)trust
host:(NSString*)host
withSecurityStyle:(SecurityStyle)style
certStatus:(CertStatus)certStatus;
// Asynchronously obtains SSL status from given |secTrust| and |host| and
// updates current navigation item. Before scheduling update changes SSLStatus'
// cert_status and security_style to default.
- (void)scheduleSSLStatusUpdateUsingTrust:(ScopedCFTypeRef<SecTrustRef>)trust
host:(NSString*)host;
// Notifies delegate about SSLStatus change.
- (void)didChangeSSLStatusForNavigationItem:(web::NavigationItem*)navItem;
@end
@implementation CRWSSLStatusUpdater
@synthesize navigationManager = _navigationManager;
@synthesize certGroupID = _certGroupID;
#pragma mark - Public
- (instancetype)initWithDataSource:(id<CRWSSLStatusUpdaterDataSource>)dataSource
navigationManager:(web::NavigationManager*)navigationManager
certGroupID:(int)certGroupID {
DCHECK(dataSource);
DCHECK(navigationManager);
DCHECK(certGroupID);
if (self = [super init]) {
_dataSource.reset(dataSource);
_navigationManager = navigationManager;
_certGroupID = certGroupID;
}
return self;
}
- (id<CRWSSLStatusUpdaterDelegate>)delegate {
return _delegate.get();
}
- (void)setDelegate:(id<CRWSSLStatusUpdaterDelegate>)delegate {
_delegate.reset(delegate);
}
- (void)updateSSLStatusForNavigationItem:(web::NavigationItem*)item
withCertHost:(NSString*)host
trust:(ScopedCFTypeRef<SecTrustRef>)trust
hasOnlySecureContent:(BOOL)hasOnlySecureContent {
web::SSLStatus previousSSLStatus = item->GetSSL();
// Starting from iOS9 WKWebView blocks active mixed content, so if
// |hasOnlySecureContent| returns NO it means passive content.
item->GetSSL().content_status =
hasOnlySecureContent ? web::SSLStatus::NORMAL_CONTENT
: web::SSLStatus::DISPLAYED_INSECURE_CONTENT;
// Try updating SSLStatus for current NavigationItem asynchronously.
scoped_refptr<net::X509Certificate> cert;
if (item->GetURL().SchemeIsCryptographic()) {
cert = web::CreateCertFromTrust(trust);
if (cert) {
int oldCertID = item->GetSSL().cert_id;
std::string oldHost = item->GetSSL().cert_status_host;
item->GetSSL().cert_id = web::CertStore::GetInstance()->StoreCert(
cert.get(), self.certGroupID);
item->GetSSL().cert_status_host = base::SysNSStringToUTF8(host);
// Only recompute the SSLStatus information if the certificate or host has
// since changed. Host can be changed in case of redirect.
if (oldCertID != item->GetSSL().cert_id ||
oldHost != item->GetSSL().cert_status_host) {
// Real SSL status is unknown, reset cert status and security style.
// They will be asynchronously updated in
// |scheduleSSLStatusUpdateUsingTrust:host:|.
item->GetSSL().cert_status = CertStatus();
item->GetSSL().security_style = web::SECURITY_STYLE_UNKNOWN;
[self scheduleSSLStatusUpdateUsingTrust:std::move(trust) host:host];
}
}
}
if (!cert) {
item->GetSSL().cert_id = 0;
if (!item->GetURL().SchemeIsCryptographic()) {
// HTTP or other non-secure connection.
item->GetSSL().security_style = web::SECURITY_STYLE_UNAUTHENTICATED;
} else {
// HTTPS, no certificate (this use-case has not been observed).
item->GetSSL().security_style = web::SECURITY_STYLE_UNKNOWN;
}
}
if (!previousSSLStatus.Equals(item->GetSSL())) {
[self didChangeSSLStatusForNavigationItem:item];
}
}
#pragma mark - Private
- (void)updateSSLStatusForItemWithID:(int)navigationItemID
trust:(ScopedCFTypeRef<SecTrustRef>)trust
host:(NSString*)host
withSecurityStyle:(SecurityStyle)style
certStatus:(CertStatus)certStatus {
// The searched item almost always be the last one, so walk backward rather
// than forward.
for (int i = _navigationManager->GetItemCount() - 1; 0 <= i; i--) {
web::NavigationItem* item = _navigationManager->GetItemAtIndex(i);
if (item->GetUniqueID() != navigationItemID)
continue;
// NavigationItem's UniqueID is preserved even after redirects, so
// checking that cert and URL match is necessary.
scoped_refptr<net::X509Certificate> cert(web::CreateCertFromTrust(trust));
int certID =
web::CertStore::GetInstance()->StoreCert(cert.get(), self.certGroupID);
std::string GURLHost = base::SysNSStringToUTF8(host);
web::SSLStatus& SSLStatus = item->GetSSL();
if (item->GetURL().SchemeIsCryptographic() && SSLStatus.cert_id == certID &&
item->GetURL().host() == GURLHost) {
web::SSLStatus previousSSLStatus = item->GetSSL();
SSLStatus.cert_status = certStatus;
SSLStatus.security_style = style;
if (!previousSSLStatus.Equals(SSLStatus)) {
[self didChangeSSLStatusForNavigationItem:item];
}
}
return;
}
}
- (void)scheduleSSLStatusUpdateUsingTrust:(ScopedCFTypeRef<SecTrustRef>)trust
host:(NSString*)host {
// Use Navigation Item's unique ID to locate requested item after
// obtaining cert status asynchronously.
int itemID = _navigationManager->GetLastCommittedItem()->GetUniqueID();
DCHECK(_dataSource);
base::WeakNSObject<CRWSSLStatusUpdater> weakSelf(self);
[_dataSource SSLStatusUpdater:self
querySSLStatusForTrust:trust
host:host
completionHandler:^(SecurityStyle style, CertStatus certStatus) {
[weakSelf updateSSLStatusForItemWithID:itemID
trust:std::move(trust)
host:host
withSecurityStyle:style
certStatus:certStatus];
}];
}
- (void)didChangeSSLStatusForNavigationItem:(web::NavigationItem*)navItem {
if ([_delegate respondsToSelector:
@selector(SSLStatusUpdater:didChangeSSLStatusForNavigationItem:)]) {
[_delegate SSLStatusUpdater:self
didChangeSSLStatusForNavigationItem:navItem];
}
}
@end