自签名服务器根 CA 的 TLS 验证的 AFNetworking 问题
AFNetworking problems with TLS Verification of a self signed server root CA
这个问题试图为我的特定用例找到解决方案,并记录我尝试为遵循此过程的其他人所做的事情。
我们有一个 RESTful 服务器和一个 iOS 应用程序。我们有自己的证书颁发机构,服务器有根证书颁发机构和自签名证书。我们按照这个过程生成了以下文件:
http://datacenteroverlords.com/2012/03/01/creating-your-own-ssl-certificate-authority/
rootCA.pem
rootCA.key
server.crt
server.key
只有服务器证书存储在我们的服务器上,作为 SSL 过程的一部分,public 密钥随 API 调用一起发送以进行验证。
我已按照此过程使用 AFNetworking 来使用证书固定以及 public 密钥固定来验证我们的自签名证书:
http://initwithfunk.com/blog/2014/03/12/afnetworking-ssl-pinning-with-self-signed-certificates/
我们根据本指南将 .crt 文件转换为 .cer 文件(DER 格式):
并将 .cer 文件 (server.cer) 包含在 iOS 应用程序包中。这成功地允许我们的应用程序向我们的服务器发出 GET/POST 请求。但是,由于我们的服务器证书可能会过期或重新颁发,因此我们希望改用根 CA,正如 AFNetworking 上此线程中的人们所做的那样:
https://github.com/AFNetworking/AFNetworking/issues/1944
目前我们已经更新到 AFNetworking 2.6.0,因此我们的网络库绝对应该包括所有更新,包括本次讨论中的更新:
https://github.com/AFNetworking/AFNetworking/issues/2744
用于创建我们的安全策略的代码:
var manager: AFHTTPRequestOperationManager = AFHTTPRequestOperationManager()
manager.requestSerializer = AFJSONRequestSerializer() // force serializer to use JSON encoding
let policy: AFSecurityPolicy = AFSecurityPolicy(pinningMode: AFSSLPinningMode.PublicKey)
var data: [NSData] = [NSData]()
for name: String in ["rootCA", "server"] {
let path: String? = NSBundle.mainBundle().pathForResource(name, ofType: "cer")
let keyData: NSData = NSData(contentsOfFile: path!)!
data.append(keyData)
}
policy.pinnedCertificates = data
policy.allowInvalidCertificates = true
policy.validatesDomainName = false
manager.securityPolicy = policy
包含 server.cer 后,我们可以通过固定 public 密钥来信任我们的服务器(也尝试过 AFSecurityPolicyPinningMode.Certificate);这是有效的,因为包含了确切的证书。然而,因为我们可能会更改服务器拥有的 server.crt 文件,所以我们希望能够仅使用 rootCA.cer.
但是,应用程序包中仅包含 rootCA,这似乎不起作用。是不是 rootCA 没有足够的关于 public 密钥的信息来验证服务器证书,该证书是用根 CA 签名的? server.crt 文件也可能有一个不断变化的 CommonName。
此外,由于我对 SSL 术语的了解还很原始,如果有人可以澄清我是否在问正确的问题,那就太好了。具体问题是:
- 我是否正确生成证书以便服务器可以使用自签名 server.crt 文件证明其身份?
- 是否可以仅将 rootCA.cer 文件包含到捆绑包中并能够验证叶证书 server.crt?它是否能够验证由同一个 rootCA 签名的另一个 server2.crt 文件?或者我们应该在 rootCA 和叶子之间包含一个中间证书?
- public 密钥固定或证书固定是正确的解决方案吗?我读过的每个论坛和博客 post 都说是,但即使使用最新的 AFNetworking 库,我们也没有任何运气。
- 服务器是否需要以某种方式同时发送 server.crt 和 roomCA.pem 签名?
在一堆不同的 SSL 资源的帮助下,我找到了允许使用自签名证书来验证启用 SSL 的专用服务器的解决方案。我也对 SSL、现有的 iOS 解决方案以及每个解决方案在我的系统中无法运行的小问题有了更深入的了解。我将尝试概述我的解决方案中使用的所有资源,以及哪些小细节有所不同。
我们仍在使用 AFNetworking,目前它是 2.6.0,据说包括证书固定。这是我们问题的根源;我们无法验证我们的私人服务器的身份,它正在发送由自签名 CA 根签名的叶证书。在我们的 iOS 应用程序中,我们捆绑了自签名根证书,然后由 AFNetworking 将其设置为可信锚点。但是,由于服务器是本地服务器(我们产品中包含的硬件),IP 地址是动态的,因此 AFNetworking 的证书验证失败,因为我们无法禁用 IP 检查。
为了找到答案的根源,我们使用 AFHTTPSessionManager 来实现自定义 sessionDidReceiveAuthenticationChallengeCallback。 (参见:https://gist.github.com/r00m/e450b8b391a4bf312966) In that callback, we validate the server certificate using a SecPolicy that doesn't check for host name; see http://blog.roderickmann.org/2013/05/validating-a-self-signed-ssl-certificate-in-ios-and-os-x-against-a-changing-host-name/,这是 NSURLConnection 而不是 NSURLSession 的旧实现。
代码:
创建 AFHTTPSessionManager
var manager: AFHTTPSessionManager = AFHTTPSessionManager()
manager.requestSerializer = AFJSONRequestSerializer() // force serializer to use JSON encoding
manager.setSessionDidReceiveAuthenticationChallengeBlock { (session, challenge, credential) -> NSURLSessionAuthChallengeDisposition in
if self.shouldTrustProtectionSpace(challenge, credential: credential) {
// shouldTrustProtectionSpace will evaluate the challenge using bundled certificates, and set a value into credential if it succeeds
return NSURLSessionAuthChallengeDisposition.UseCredential
}
return NSURLSessionAuthChallengeDisposition.PerformDefaultHandling
}
自定义验证的实现
class func shouldTrustProtectionSpace(challenge: NSURLAuthenticationChallenge, var credential: AutoreleasingUnsafeMutablePointer<NSURLCredential?>) -> Bool {
// note: credential is a reference; any created credential should be sent back using credential.memory
let protectionSpace: NSURLProtectionSpace = challenge.protectionSpace
var trust: SecTrustRef = protectionSpace.serverTrust!
// load the root CA bundled with the app
let certPath: String? = NSBundle.mainBundle().pathForResource("rootCA", ofType: "cer")
if certPath == nil {
println("Certificate does not exist!")
return false
}
let certData: NSData = NSData(contentsOfFile: certPath!)!
let cert: SecCertificateRef? = SecCertificateCreateWithData(kCFAllocatorDefault, certData).takeUnretainedValue()
if cert == nil {
println("Certificate data could not be loaded. DER format?")
return false
}
// create a policy that ignores hostname
let domain: CFString? = nil
let policy:SecPolicy = SecPolicyCreateSSL(1, domain).takeRetainedValue()
// takes all certificates from existing trust
let numCerts = SecTrustGetCertificateCount(trust)
var certs: [SecCertificateRef] = [SecCertificateRef]()
for var i = 0; i < numCerts; i++ {
let c: SecCertificateRef? = SecTrustGetCertificateAtIndex(trust, i).takeUnretainedValue()
certs.append(c!)
}
// and adds them to the new policy
var newTrust: Unmanaged<SecTrust>? = nil
var err: OSStatus = SecTrustCreateWithCertificates(certs, policy, &newTrust)
if err != noErr {
println("Could not create trust")
}
trust = newTrust!.takeUnretainedValue() // replace old trust
// set root cert
let rootCerts: [AnyObject] = [cert!]
err = SecTrustSetAnchorCertificates(trust, rootCerts)
// evaluate the certificate and product a trustResult
var trustResult: SecTrustResultType = SecTrustResultType()
SecTrustEvaluate(trust, &trustResult)
if Int(trustResult) == Int(kSecTrustResultProceed) || Int(trustResult) == Int(kSecTrustResultUnspecified) {
// create the credential to be used
credential.memory = NSURLCredential(trust: trust)
return true
}
return false
}
我在阅读这段代码时学到了一些关于 swift 的东西。
AFNetworking 的 setSessionDidReceiveAuthenticationChallengeBlock 实现具有此签名:
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(可为 nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __nullable __autoreleasing * __nullable credential))block;
credential参数是一个reference/inout变量,需要赋值。在 swift 中,它看起来像这样:AutoreleasingUnsafeMutablePointer。为了在 C 中给它赋值,你会做这样的事情:
*credential = [[NSURLCredential alloc] initWithTrust...];
在swift中,它看起来像这样:(来自converting NSArray to RLMArray with RKValueTransFormer fails converting outputValue to AutoreleasingUnsafeMutablePointer<AnyObject?>)
credential.memory = NSURLCredential(trust: trust)
SecPolicyCreateSSL、SecCertificateCreateWithData 和 SecTrustGetCertificateAtIndex return 不受管理!对象,您必须使用 takeRetainedValue() 或 takeUnretainedValue() 本质上转换 them/bridge 它们。 (参见 http://nshipster.com/unmanaged/)。当我们使用 takeRetainedValue() 并多次调用该方法时,我们有内存 issues/crashes(SecDestroy 发生崩溃)。现在,在我们切换到使用 takeUnretainedValue() 之后,构建看起来很稳定,因为验证后您不需要证书或 ssl 策略。
TLS 会话缓存。 https://developer.apple.com/library/ios/qa/qa1727/_index.html 这意味着当您成功通过挑战验证后,您将永远不会再接受挑战。当您测试一个有效证书时,这真的会让您感到困惑,然后测试一个无效证书,然后跳过所有验证,然后您从服务器获得成功响应。解决方案是在每次使用有效证书并通过验证挑战后,在 iOS 模拟器中进行 Product->Clean。否则您可能会花一些时间错误地认为您终于获得了要验证的根 CA。
所以这只是一个解决我的服务器问题的有效解决方案。我想 post 这里的所有内容,希望能帮助 运行 具有自签名 CA 和 iOS 需要启用 SSL 的产品的本地或开发服务器的其他人。当然,随着 iOS 9 中的 ATS,我希望很快再次深入研究 SSL。
此代码目前存在一些内存管理问题,将在近期更新。另外,如果有人看到这个实现并说 "Ah hah, this is just as bad as returning TRUE for invalid certificates",请告诉我!据我所知,通过我们自己的测试,该应用程序拒绝未经我们的根 CA 签名的无效服务器证书,并接受由根 CA 生成和签名的叶证书。应用包只包含根CA,所以服务器证书可以在它们过期后循环,现有应用不会失败。
如果我更深入地研究 AFNetworking 并找出所有这些的一到三行解决方案(通过切换它们提供的所有这些小标志)我也会 post 更新.
如果 AlamoFire 开始支持 SSL,也可以在这里 post 提出解决方案。
如果您使用的是 coco pods,则子class AFSecurityPolicy class 并根据 mitrenegade 的回答实施安全检查
听到的是我的代码。
在发布请求时初始化 AFHttpRequestOperationManager,如下所示。
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.securityPolicy = [RootCAAFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[manager POST:Domain_Name parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
success(operation,responseObject);
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
NSLog(@"Error %@",error);
failure(operation,error);
}];
RootCAAFSecurityPolicy 是 AFSecurityPolicy Class 的子class。请参阅下面的 RootCAAFSecurityPolicy .h 和 .m class
覆盖方法
-(BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
RootCAAFSecurityPolicy.h class
#import <AFNetworking/AFNetworking.h>
@interface RootCAAFSecurityPolicy : AFSecurityPolicy
@end
RootCAAFSecurityPolicy.m class
用您的证书文件名替换 RootCA
#import "RootCAAFSecurityPolicy.h"
@implementation RootCAAFSecurityPolicy
-(BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
{
if(self.SSLPinningMode == AFSSLPinningModeCertificate)
{
return [self shouldTrustServerTrust:serverTrust];
}
else
{
return [super evaluateServerTrust:serverTrust forDomain:domain];
}
}
- (BOOL)shouldTrustServerTrust:(SecTrustRef)serverTrust
{
// load up the bundled root CA
NSString *certPath = [[NSBundle mainBundle] pathForResource:@"RootCA" ofType:@"der"];
NSAssert(certPath != nil, @"Specified certificate does not exist!");
NSData *certData = [[NSData alloc] initWithContentsOfFile:certPath];
CFDataRef certDataRef = (__bridge_retained CFDataRef)certData;
SecCertificateRef cert = SecCertificateCreateWithData(NULL, certDataRef);
NSAssert(cert != NULL, @"Failed to create certificate object. Is the certificate in DER format?");
// establish a chain of trust anchored on our bundled certificate
CFArrayRef certArrayRef = CFArrayCreate(NULL, (void *)&cert, 1, NULL);
OSStatus anchorCertificateStatus = SecTrustSetAnchorCertificates(serverTrust, certArrayRef);
NSAssert(anchorCertificateStatus == errSecSuccess, @"Failed to specify custom anchor certificate");
// trust also built-in certificates besides the specified CA
OSStatus trustBuiltinCertificatesStatus = SecTrustSetAnchorCertificatesOnly(serverTrust, false);
NSAssert(trustBuiltinCertificatesStatus == errSecSuccess, @"Failed to reenable trusting built-in anchor certificates");
// verify that trust
SecTrustResultType trustResult;
OSStatus evalStatus = SecTrustEvaluate(serverTrust, &trustResult);
NSAssert(evalStatus == errSecSuccess, @"Failed to evaluate certificate trust");
// clean up
CFRelease(certArrayRef);
CFRelease(cert);
CFRelease(certDataRef);
// did our custom trust chain evaluate successfully
return (trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified);
}
@end
我遇到了同样的问题,我已经通过比较 AFURLSessionManager
.
的 didReceiveChallenge
方法中链的 public 键来修复它
-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
// Get remote certificate
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
NSMutableArray *policies = [NSMutableArray array];
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef) challenge.protectionSpace.host)];
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
// The pinnning check
if (trustedPublicKeyCount > 0) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, NULL);
}
}
这里是pinnedPublicKeys
的初始化:
// Get local certificates
NSArray *certNames = @[@"root_cert"];
self.pinnedPublicKeys = [NSMutableSet new];
for (NSString *certName in certNames) {
NSString *path = [bundle pathForResource:certName ofType:@"der"];
NSData *certificate = [NSData dataWithContentsOfFile:path];
id publicKey = AFPublicKeyForCertificate(certificate);
if (publicKey) {
[self.pinnedPublicKeys addObject:publicKey];
}
}
这里是获取密钥信任链的辅助方法(AFPublicKeyTrustChainForServerTrust
),比较public密钥(AFSecKeyIsEqualToKey
)和获取public密钥的方法来自证书(AFPublicKeyTrustChainForServerTrust
):
static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
SecPolicyRef policy = SecPolicyCreateBasicX509();
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
SecCertificateRef someCertificates[] = {certificate};
CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);
SecTrustRef trust;
SecTrustCreateWithCertificates(certificates, policy, &trust);
SecTrustResultType result;
SecTrustEvaluate(trust, &result);
[trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];
if (trust) {
CFRelease(trust);
}
if (certificates) {
CFRelease(certificates);
}
continue;
}
CFRelease(policy);
return [NSArray arrayWithArray:trustChain];
}
static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
return [(__bridge id)key1 isEqual:(__bridge id)key2];
}
static id AFPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecCertificateRef allowedCertificates[1];
CFArrayRef tempCertificates = nil;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
allowedCertificates[0] = allowedCertificate;
tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
policy = SecPolicyCreateBasicX509();
SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust);
SecTrustEvaluate(allowedTrust, &result);
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (tempCertificates) {
CFRelease(tempCertificates);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
return allowedPublicKey;
}
这个问题试图为我的特定用例找到解决方案,并记录我尝试为遵循此过程的其他人所做的事情。
我们有一个 RESTful 服务器和一个 iOS 应用程序。我们有自己的证书颁发机构,服务器有根证书颁发机构和自签名证书。我们按照这个过程生成了以下文件:
http://datacenteroverlords.com/2012/03/01/creating-your-own-ssl-certificate-authority/
rootCA.pem rootCA.key server.crt server.key
只有服务器证书存储在我们的服务器上,作为 SSL 过程的一部分,public 密钥随 API 调用一起发送以进行验证。
我已按照此过程使用 AFNetworking 来使用证书固定以及 public 密钥固定来验证我们的自签名证书:
http://initwithfunk.com/blog/2014/03/12/afnetworking-ssl-pinning-with-self-signed-certificates/
我们根据本指南将 .crt 文件转换为 .cer 文件(DER 格式):
并将 .cer 文件 (server.cer) 包含在 iOS 应用程序包中。这成功地允许我们的应用程序向我们的服务器发出 GET/POST 请求。但是,由于我们的服务器证书可能会过期或重新颁发,因此我们希望改用根 CA,正如 AFNetworking 上此线程中的人们所做的那样:
https://github.com/AFNetworking/AFNetworking/issues/1944
目前我们已经更新到 AFNetworking 2.6.0,因此我们的网络库绝对应该包括所有更新,包括本次讨论中的更新:
https://github.com/AFNetworking/AFNetworking/issues/2744
用于创建我们的安全策略的代码:
var manager: AFHTTPRequestOperationManager = AFHTTPRequestOperationManager()
manager.requestSerializer = AFJSONRequestSerializer() // force serializer to use JSON encoding
let policy: AFSecurityPolicy = AFSecurityPolicy(pinningMode: AFSSLPinningMode.PublicKey)
var data: [NSData] = [NSData]()
for name: String in ["rootCA", "server"] {
let path: String? = NSBundle.mainBundle().pathForResource(name, ofType: "cer")
let keyData: NSData = NSData(contentsOfFile: path!)!
data.append(keyData)
}
policy.pinnedCertificates = data
policy.allowInvalidCertificates = true
policy.validatesDomainName = false
manager.securityPolicy = policy
包含 server.cer 后,我们可以通过固定 public 密钥来信任我们的服务器(也尝试过 AFSecurityPolicyPinningMode.Certificate);这是有效的,因为包含了确切的证书。然而,因为我们可能会更改服务器拥有的 server.crt 文件,所以我们希望能够仅使用 rootCA.cer.
但是,应用程序包中仅包含 rootCA,这似乎不起作用。是不是 rootCA 没有足够的关于 public 密钥的信息来验证服务器证书,该证书是用根 CA 签名的? server.crt 文件也可能有一个不断变化的 CommonName。
此外,由于我对 SSL 术语的了解还很原始,如果有人可以澄清我是否在问正确的问题,那就太好了。具体问题是:
- 我是否正确生成证书以便服务器可以使用自签名 server.crt 文件证明其身份?
- 是否可以仅将 rootCA.cer 文件包含到捆绑包中并能够验证叶证书 server.crt?它是否能够验证由同一个 rootCA 签名的另一个 server2.crt 文件?或者我们应该在 rootCA 和叶子之间包含一个中间证书?
- public 密钥固定或证书固定是正确的解决方案吗?我读过的每个论坛和博客 post 都说是,但即使使用最新的 AFNetworking 库,我们也没有任何运气。
- 服务器是否需要以某种方式同时发送 server.crt 和 roomCA.pem 签名?
在一堆不同的 SSL 资源的帮助下,我找到了允许使用自签名证书来验证启用 SSL 的专用服务器的解决方案。我也对 SSL、现有的 iOS 解决方案以及每个解决方案在我的系统中无法运行的小问题有了更深入的了解。我将尝试概述我的解决方案中使用的所有资源,以及哪些小细节有所不同。
我们仍在使用 AFNetworking,目前它是 2.6.0,据说包括证书固定。这是我们问题的根源;我们无法验证我们的私人服务器的身份,它正在发送由自签名 CA 根签名的叶证书。在我们的 iOS 应用程序中,我们捆绑了自签名根证书,然后由 AFNetworking 将其设置为可信锚点。但是,由于服务器是本地服务器(我们产品中包含的硬件),IP 地址是动态的,因此 AFNetworking 的证书验证失败,因为我们无法禁用 IP 检查。
为了找到答案的根源,我们使用 AFHTTPSessionManager 来实现自定义 sessionDidReceiveAuthenticationChallengeCallback。 (参见:https://gist.github.com/r00m/e450b8b391a4bf312966) In that callback, we validate the server certificate using a SecPolicy that doesn't check for host name; see http://blog.roderickmann.org/2013/05/validating-a-self-signed-ssl-certificate-in-ios-and-os-x-against-a-changing-host-name/,这是 NSURLConnection 而不是 NSURLSession 的旧实现。
代码:
创建 AFHTTPSessionManager
var manager: AFHTTPSessionManager = AFHTTPSessionManager()
manager.requestSerializer = AFJSONRequestSerializer() // force serializer to use JSON encoding
manager.setSessionDidReceiveAuthenticationChallengeBlock { (session, challenge, credential) -> NSURLSessionAuthChallengeDisposition in
if self.shouldTrustProtectionSpace(challenge, credential: credential) {
// shouldTrustProtectionSpace will evaluate the challenge using bundled certificates, and set a value into credential if it succeeds
return NSURLSessionAuthChallengeDisposition.UseCredential
}
return NSURLSessionAuthChallengeDisposition.PerformDefaultHandling
}
自定义验证的实现
class func shouldTrustProtectionSpace(challenge: NSURLAuthenticationChallenge, var credential: AutoreleasingUnsafeMutablePointer<NSURLCredential?>) -> Bool {
// note: credential is a reference; any created credential should be sent back using credential.memory
let protectionSpace: NSURLProtectionSpace = challenge.protectionSpace
var trust: SecTrustRef = protectionSpace.serverTrust!
// load the root CA bundled with the app
let certPath: String? = NSBundle.mainBundle().pathForResource("rootCA", ofType: "cer")
if certPath == nil {
println("Certificate does not exist!")
return false
}
let certData: NSData = NSData(contentsOfFile: certPath!)!
let cert: SecCertificateRef? = SecCertificateCreateWithData(kCFAllocatorDefault, certData).takeUnretainedValue()
if cert == nil {
println("Certificate data could not be loaded. DER format?")
return false
}
// create a policy that ignores hostname
let domain: CFString? = nil
let policy:SecPolicy = SecPolicyCreateSSL(1, domain).takeRetainedValue()
// takes all certificates from existing trust
let numCerts = SecTrustGetCertificateCount(trust)
var certs: [SecCertificateRef] = [SecCertificateRef]()
for var i = 0; i < numCerts; i++ {
let c: SecCertificateRef? = SecTrustGetCertificateAtIndex(trust, i).takeUnretainedValue()
certs.append(c!)
}
// and adds them to the new policy
var newTrust: Unmanaged<SecTrust>? = nil
var err: OSStatus = SecTrustCreateWithCertificates(certs, policy, &newTrust)
if err != noErr {
println("Could not create trust")
}
trust = newTrust!.takeUnretainedValue() // replace old trust
// set root cert
let rootCerts: [AnyObject] = [cert!]
err = SecTrustSetAnchorCertificates(trust, rootCerts)
// evaluate the certificate and product a trustResult
var trustResult: SecTrustResultType = SecTrustResultType()
SecTrustEvaluate(trust, &trustResult)
if Int(trustResult) == Int(kSecTrustResultProceed) || Int(trustResult) == Int(kSecTrustResultUnspecified) {
// create the credential to be used
credential.memory = NSURLCredential(trust: trust)
return true
}
return false
}
我在阅读这段代码时学到了一些关于 swift 的东西。
AFNetworking 的 setSessionDidReceiveAuthenticationChallengeBlock 实现具有此签名:
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(可为 nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __nullable __autoreleasing * __nullable credential))block;
credential参数是一个reference/inout变量,需要赋值。在 swift 中,它看起来像这样:AutoreleasingUnsafeMutablePointer。为了在 C 中给它赋值,你会做这样的事情:
*credential = [[NSURLCredential alloc] initWithTrust...];
在swift中,它看起来像这样:(来自converting NSArray to RLMArray with RKValueTransFormer fails converting outputValue to AutoreleasingUnsafeMutablePointer<AnyObject?>)
credential.memory = NSURLCredential(trust: trust)
SecPolicyCreateSSL、SecCertificateCreateWithData 和 SecTrustGetCertificateAtIndex return 不受管理!对象,您必须使用 takeRetainedValue() 或 takeUnretainedValue() 本质上转换 them/bridge 它们。 (参见 http://nshipster.com/unmanaged/)。当我们使用 takeRetainedValue() 并多次调用该方法时,我们有内存 issues/crashes(SecDestroy 发生崩溃)。现在,在我们切换到使用 takeUnretainedValue() 之后,构建看起来很稳定,因为验证后您不需要证书或 ssl 策略。
TLS 会话缓存。 https://developer.apple.com/library/ios/qa/qa1727/_index.html 这意味着当您成功通过挑战验证后,您将永远不会再接受挑战。当您测试一个有效证书时,这真的会让您感到困惑,然后测试一个无效证书,然后跳过所有验证,然后您从服务器获得成功响应。解决方案是在每次使用有效证书并通过验证挑战后,在 iOS 模拟器中进行 Product->Clean。否则您可能会花一些时间错误地认为您终于获得了要验证的根 CA。
所以这只是一个解决我的服务器问题的有效解决方案。我想 post 这里的所有内容,希望能帮助 运行 具有自签名 CA 和 iOS 需要启用 SSL 的产品的本地或开发服务器的其他人。当然,随着 iOS 9 中的 ATS,我希望很快再次深入研究 SSL。
此代码目前存在一些内存管理问题,将在近期更新。另外,如果有人看到这个实现并说 "Ah hah, this is just as bad as returning TRUE for invalid certificates",请告诉我!据我所知,通过我们自己的测试,该应用程序拒绝未经我们的根 CA 签名的无效服务器证书,并接受由根 CA 生成和签名的叶证书。应用包只包含根CA,所以服务器证书可以在它们过期后循环,现有应用不会失败。
如果我更深入地研究 AFNetworking 并找出所有这些的一到三行解决方案(通过切换它们提供的所有这些小标志)我也会 post 更新.
如果 AlamoFire 开始支持 SSL,也可以在这里 post 提出解决方案。
如果您使用的是 coco pods,则子class AFSecurityPolicy class 并根据 mitrenegade 的回答实施安全检查
听到的是我的代码。
在发布请求时初始化 AFHttpRequestOperationManager,如下所示。
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.securityPolicy = [RootCAAFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[manager POST:Domain_Name parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
success(operation,responseObject);
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
NSLog(@"Error %@",error);
failure(operation,error);
}];
RootCAAFSecurityPolicy 是 AFSecurityPolicy Class 的子class。请参阅下面的 RootCAAFSecurityPolicy .h 和 .m class 覆盖方法
-(BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
RootCAAFSecurityPolicy.h class
#import <AFNetworking/AFNetworking.h>
@interface RootCAAFSecurityPolicy : AFSecurityPolicy
@end
RootCAAFSecurityPolicy.m class
用您的证书文件名替换 RootCA
#import "RootCAAFSecurityPolicy.h"
@implementation RootCAAFSecurityPolicy
-(BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
{
if(self.SSLPinningMode == AFSSLPinningModeCertificate)
{
return [self shouldTrustServerTrust:serverTrust];
}
else
{
return [super evaluateServerTrust:serverTrust forDomain:domain];
}
}
- (BOOL)shouldTrustServerTrust:(SecTrustRef)serverTrust
{
// load up the bundled root CA
NSString *certPath = [[NSBundle mainBundle] pathForResource:@"RootCA" ofType:@"der"];
NSAssert(certPath != nil, @"Specified certificate does not exist!");
NSData *certData = [[NSData alloc] initWithContentsOfFile:certPath];
CFDataRef certDataRef = (__bridge_retained CFDataRef)certData;
SecCertificateRef cert = SecCertificateCreateWithData(NULL, certDataRef);
NSAssert(cert != NULL, @"Failed to create certificate object. Is the certificate in DER format?");
// establish a chain of trust anchored on our bundled certificate
CFArrayRef certArrayRef = CFArrayCreate(NULL, (void *)&cert, 1, NULL);
OSStatus anchorCertificateStatus = SecTrustSetAnchorCertificates(serverTrust, certArrayRef);
NSAssert(anchorCertificateStatus == errSecSuccess, @"Failed to specify custom anchor certificate");
// trust also built-in certificates besides the specified CA
OSStatus trustBuiltinCertificatesStatus = SecTrustSetAnchorCertificatesOnly(serverTrust, false);
NSAssert(trustBuiltinCertificatesStatus == errSecSuccess, @"Failed to reenable trusting built-in anchor certificates");
// verify that trust
SecTrustResultType trustResult;
OSStatus evalStatus = SecTrustEvaluate(serverTrust, &trustResult);
NSAssert(evalStatus == errSecSuccess, @"Failed to evaluate certificate trust");
// clean up
CFRelease(certArrayRef);
CFRelease(cert);
CFRelease(certDataRef);
// did our custom trust chain evaluate successfully
return (trustResult == kSecTrustResultProceed || trustResult == kSecTrustResultUnspecified);
}
@end
我遇到了同样的问题,我已经通过比较 AFURLSessionManager
.
didReceiveChallenge
方法中链的 public 键来修复它
-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
// Get remote certificate
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
NSMutableArray *policies = [NSMutableArray array];
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef) challenge.protectionSpace.host)];
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
// The pinnning check
if (trustedPublicKeyCount > 0) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
} else {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, NULL);
}
}
这里是pinnedPublicKeys
的初始化:
// Get local certificates
NSArray *certNames = @[@"root_cert"];
self.pinnedPublicKeys = [NSMutableSet new];
for (NSString *certName in certNames) {
NSString *path = [bundle pathForResource:certName ofType:@"der"];
NSData *certificate = [NSData dataWithContentsOfFile:path];
id publicKey = AFPublicKeyForCertificate(certificate);
if (publicKey) {
[self.pinnedPublicKeys addObject:publicKey];
}
}
这里是获取密钥信任链的辅助方法(AFPublicKeyTrustChainForServerTrust
),比较public密钥(AFSecKeyIsEqualToKey
)和获取public密钥的方法来自证书(AFPublicKeyTrustChainForServerTrust
):
static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
SecPolicyRef policy = SecPolicyCreateBasicX509();
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
SecCertificateRef someCertificates[] = {certificate};
CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);
SecTrustRef trust;
SecTrustCreateWithCertificates(certificates, policy, &trust);
SecTrustResultType result;
SecTrustEvaluate(trust, &result);
[trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];
if (trust) {
CFRelease(trust);
}
if (certificates) {
CFRelease(certificates);
}
continue;
}
CFRelease(policy);
return [NSArray arrayWithArray:trustChain];
}
static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
return [(__bridge id)key1 isEqual:(__bridge id)key2];
}
static id AFPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecCertificateRef allowedCertificates[1];
CFArrayRef tempCertificates = nil;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
allowedCertificates[0] = allowedCertificate;
tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
policy = SecPolicyCreateBasicX509();
SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust);
SecTrustEvaluate(allowedTrust, &result);
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (tempCertificates) {
CFRelease(tempCertificates);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
return allowedPublicKey;
}