这个问题试图为我的特定用例找到解决方案,并记录我为其他正在遵循此过程的人尝试做的事情。
我们有一个RESTful服务器和一个iOS应用程序。我们拥有自己的证书颁发机构,服务器具有根证书颁发机构和自签名证书。我们按照此过程生成以下文件:
http://datacenteroverlords.com/2012/03/01/creating-your-own-ssl-certificate-authority/
Root.frrrrrrrrrrrr.a server.a服务器
只有服务器证书存储在我们的服务器上,并且作为SSL过程的一部分,公钥与API调用一起发送以进行验证。
我按照这个过程使用AFNetworking来使用证书锁定以及公钥锁定来验证我们的自签名证书:
http://initwithfunk.com/blog/2014/03/12/afnetworking-ssl-pinning-with-self-signed-certificates/
我们根据本指南将.crt文件转换为.cer文件(DER格式):
并在iOS应用包中包含.cer文件(server.cer)。这成功地允许我们的应用程序向我们的服务器发出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,我们可以通过固定公钥来信任我们的服务器(也尝试过AFSecurityPolicyPinningMode.Certificate);这很有效,因为包含了确切的证书。但是因为我们可能会更改服务器所具有的server.crt文件,所以我们希望能够只使用rootCA.cer来完成它。
但是,只有应用程序包中包含rootCA,这似乎不起作用。 rootCA是否没有足够的有关公钥的信息来验证与根CA签名的服务器证书? server.crt文件也可能具有更改的CommonName。
此外,由于我对SSL术语的流畅性非常粗糙,如果有人能够澄清我是否在问正确的问题,那就太棒了。具体问题是:
借助一堆不同的SSL资源,我找到了允许使用自签名证书来验证启用SSL的私有服务器的解决方案。我对SSL,现有的iOS解决方案以及每个问题的小问题都有了更好的理解,这使得它在我的系统中无法运行。我将尝试概述进入我的解决方案的所有资源以及哪些小事情产生了影响。
我们仍在使用AFNetworking,目前它是2.6.0,据说包括证书固定。这是我们问题的根源;我们无法验证我们的私人服务器的身份,该服务器正在发送由自签名CA根签名的叶证书。在我们的iOS应用程序中,我们捆绑了自签名根证书,然后由AFNetworking将其设置为可信锚。但是,由于服务器是本地服务器(我们产品附带的硬件),因此IP地址是动态的,因此AFNetworking的证书验证失败,因为我们无法禁用IP检查。
为了找到答案的根源,我们使用AFHTTPSessionManager来实现自定义sessionDidReceiveAuthenticationChallengeCallback。 (参见:https://gist.github.com/r00m/e450b8b391a4bf312966)在该回调中,我们使用不检查主机名的SecPolicy验证服务器证书;请参阅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的知识。
凭证参数是需要分配的引用/输入变量。在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)
所以这里只是解决我在服务器上遇到的问题的解决方案。我想在这里发布所有内容,希望能帮助运行本地或开发服务器的其他人使用自签名CA和需要启用SSL的iOS产品。当然,对于iOS 9中的ATS,我希望很快再次深入研究SSL。
此代码目前存在一些内存管理问题,并将在不久的将来更新。此外,如果有人看到这个实现并说“啊哈,这就像为无效证书返回TRUE一样糟糕”,请告诉我!据我所知,我们通过自己的测试判断,应用程序拒绝未由我们的根CA签名的无效服务器证书,并接受由根CA生成和签名的叶证书。应用程序包仅包含根CA,因此服务器证书在过期后可以循环,现有应用程序不会失败。
如果我更多地深入了解AFNetworking并找出所有这一切的一到三线解决方案(通过切换它们提供的所有那些小标志),我还会发布更新。
如果AlamoFire开始支持SSL,也可以在这里发布解决方案。
如果你正在使用coco pods,那么继承AFSecurityPolicy类,并根据mitrenegade的回答https://stackoverflow.com/a/32469609/4000434实施安全检查
听到我的代码。
在发布请求时初始化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类的子类。请参阅下面的RootCAAFSecurityPolicy .h和.m类重写方法
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
RootCAAFSecurityPolicy.h类
#import <AFNetworking/AFNetworking.h>
@interface RootCAAFSecurityPolicy : AFSecurityPolicy
@end
RootCAAFSecurityPolicy.m类
将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
我遇到了同样的问题,我通过比较didReceiveChallenge
的AFURLSessionManager
方法中链的公钥来修复它。
-(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
)的帮助方法,比较公钥(AFSecKeyIsEqualToKey
)和方法从证书(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;
}