技术支持

证书锁定SSL Pinning简介及用途文中我们介绍了SSL Pinning的概念和用途,和Android中的证书锁定一样,在IOS开发中,证书锁定也同样重要,通过内置证书公钥或证书以实现指定服务端与客户端通信的安全!IOS中主要有Swift原生开发和基于AFNetworking实现。

e0703b00a07692357ca61199537ecb44.png

证书锁定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)
    }
}

上述方法开始使用SecTrustGetCertificateAtIndexchallenge.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 }

如果结果是kSecTrustResultProceedkSecTrustResultUnspecified则表示可信,其它则全部为不可信。

以上检查服务器证书和本地证书的一致性,如果一致则通过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 needeAdd to targets

92bb497042b2cbd33cfeb42f712094ef.png

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)

参考文章:

相关文章