在证书锁定SSL Pinning简介及用途文中我们介绍了SSL Pinning的概念和用途,和Android中的证书锁定一样,在IOS开发中,证书锁定也同样重要,通过内置证书公钥或证书以实现指定服务端与客户端通信的安全!IOS中主要有Swift原生开发和基于AFNetworking实现。
1. 准备材料
在证书锁定SSL Pinning简介及用途文中,我们以infinisign.com为例可以获取到证书infinisign.der和公钥:infinisign.pubkey,并且获取的编码格式也是der格式,注意证书不是x.509编码的pem格式,如果是x.509编码格式则需要转换为der格式,参考:SSL/TLS多种证书类型的转换。
2. NSURLSession方式
对于NSURLSession方式中,需要手动执行所有检查
Swift中NSURLSession配置如下:
self.urlSession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, delegateQueue: nil)
NSURLSession使用NSURLSessionTask发送请求,其中dataTaskWithURL:completionHandler
用于SSL Pinning的测试,请求代码如下:
self.urlSession?.dataTaskWithURL(NSURL(string:self.urlTextField.text!)!, completionHandler: { (NSData data, NSURLResponse response, NSError error) Void in
// 响应代码
}).resume()
主要依赖URLSession:didReceiveChallenge:completionHandler:delegate
方法实现
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
let serverTrust = challenge.protectionSpace.serverTrust
let certificate = SecTrustGetCertificateAtIndex(serverTrust!, 0)
// 设置SSL域名检查
let policies = NSMutableArray();
policies.addObject(SecPolicyCreateSSL(true, (challenge.protectionSpace.host)))
SecTrustSetPolicies(serverTrust!, policies);
// 设置SSL证书策略
var result: SecTrustResultType = 0
SecTrustEvaluate(serverTrust!, &result)
let isServerTrusted:Bool = (Int(result) == kSecTrustResultUnspecified || Int(result) == kSecTrustResultProceed)
// 获取本地证书和远程证书数据进行比对
let remoteCertificateData:NSData = SecCertificateCopyData(certificate!)
let pathToCert = NSBundle.mainBundle().pathForResource(githubCert, ofType: "cer")
let localCertificate:NSData = NSData(contentsOfFile: pathToCert!)!
if (isServerTrusted && remoteCertificateData.isEqualToData(localCertificate)) {
let credential:NSURLCredential = NSURLCredential(forTrust: serverTrust!)
completionHandler(.UseCredential, credential)
} else {
completionHandler(.CancelAuthenticationChallenge, nil)
}
}
上述方法开始使用SecTrustGetCertificateAtIndex
从challenge.protectionSpace.serverTrust
获取了证书,设置SSL证书策略SecTrustSetPolicies
public var kSecTrustResultInvalid: Int { get }
public var kSecTrustResultProceed: Int { get }
@available(*, deprecated)
public var kSecTrustResultConfirm: Int { get }
public var kSecTrustResultDeny: Int { get }
public var kSecTrustResultUnspecified: Int { get }
public var kSecTrustResultRecoverableTrustFailure: Int { get }
public var kSecTrustResultFatalTrustFailure: Int { get }
public var kSecTrustResultOtherError: Int { get }
如果结果是kSecTrustResultProceed
和kSecTrustResultUnspecified
则表示可信,其它则全部为不可信。
以上检查服务器证书和本地证书的一致性,如果一致则通过completionHandler
方法执行请求,否则则取消执行并拒绝与服务器之间的通信。
3. ALAMOFIRE
使用AlamoFire进行证书锁定则非常简单,前文中我们强调过公钥、证书锁定的区别,选择了类型后在APP中物理内置证书或公钥。
func configAFSSLPinning {
// 主机名和端点
let hostname = "YOUR_HOST_NAME"
let endpoint = "YOUR_ENDPOINT"
let cert = "YOUR_CERT" // .der格式证书物理路径
// 设置证书
let pathToCert = Bundle.main.path(forResource: cert, ofType: "der")
let localCertificate = NSData(contentsOfFile: pathToCert!)
let certificates = [SecCertificateCreateWithData(nil, localCertificate!)!]
// 配置验证类型,例如下述代码要求验证证书链的完整性、验证主机
self.serverTrustPolicy = ServerTrustPolicy.pinCertificates(
certificates: certificates,
validateCertificateChain: true,
validateHost: true
)
self.serverTrustPolicies = [hostname: serverTrustPolicy]
self.serverTrustPolicyManager = ServerTrustPolicyManager(policies: self.serverTrustPolicies)
// 配置session管理
self.afManager = SessionManager(
configuration: NSURLSessionConfiguration.default,
serverTrustPolicyManager: self.serverTrustPolicyManager
)
}
// 握手方法
func aFRequestHandler {
self.afManager.request(.GET, self.urlTextField.text!)
.response { request, response, data, error in
// 响应代码
}
}
3. AFNetworking 方案
置入物理证书,勾选Copy items if neede
和Add to targets
3.1 安全模式设置
AFSecurityPolicy是AFNetworking中三种安全策略模块,提供了证书锁定模式
-
AFSSLPinningModeNone
:完全信任服务器证书; -
AFSSLPinningModePublicKey
:只比对服务器证书和本地证书的Public Key是否一致,如果一致则信任服务器证书; -
AFSSLPinningModeCertificate
:比对服务器证书和本地证书的所有内容,完全一致则信任服务器证书;
针对三种模式,我们在证书锁定SSL Pinning简介及用途文中介绍过公钥锁定和证书锁定,所以AFSSLPinningModeCertificate
对应的则是证书锁定,这是一种最安全验证模式,但是发行APP比较麻烦,每次证书需要打包在APP中,服务器证书到期后则要重新内置证书后打包发行,而AFSSLPinningModePublicKey
只使用公钥锁定,证书过期或重签都不会影响。
以本站为例,证书锁定SSL Pinning简介及用途文中获取了infinisign.pubkey,使用AFSSLPinningModePublicKey
进行锁定更为方便。
3.2 验证证书链
通过验证证书链的完整性进行强校验,注:validatesCertificateChain
已在AFNetworking v2.6.0中移除
/**
如果要求验证证书链的完整性,则需要配置validatesCertificateChain为yes
*/
@property (nonatomic, assign) BOOL validatesCertificateChain;
3.3 是否信任过期证书
是否信任非法证书,默认是NO。
/**
默认值是No,不信任过期证书
*/
@property (nonatomic, assign) BOOL allowInvalidCertificates;
3.4 是否验证域名
验证域名是否与证书的Common Name一致,默认值是YES
/**
验证域名是否和证书Common Name一致,默认值是YES
*/
@property (nonatomic, assign) BOOL validatesDomainName;
3.5 AFSecurityPolicy设置
最后,我们使用AFHTTPSessionManager
设置安全验证代码如下:
+ (AFHTTPSessionManager *)manager
{
static AFHTTPSessionManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
manager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:config];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey withPinnedCertificates:[AFSecurityPolicy certificatesInBundle:[NSBundle mainBundle]]];
manager.securityPolicy = securityPolicy;
});
return manager;
}
到此,我们可以在AFNetworking中正确使用证书锁定。
4. 总结
在IOS中通常使用最多的是AFNetworking,除此外还有类似的一些开源封装方案,例如TrustKit,但其根本原理仍然是基于内置证书或公钥实现证书锁定,关于Android的SSl/TLS Pinning请参考Android SSL证书设置和锁定(SSL/TLS Pinning)。
参考文章:
- Swift 3 How to validate server certificate using SSL Pinning and AlamoFire?
- IOS证书动态锁定示例
- TrustKit
- How to make your iOS apps more secure with SSL pinning
- 如何正確設定 AFNetworking 的安全連線
相关文章
- Android SSL证书设置和锁定(SSL/TLS Pinning)
- 证书锁定SSL Pinning简介及用途
- SSL/TLS多种证书类型的证书转换