AFNet使用TLSvalidation自签名服务器根CA的问题

这是一个试图为我的特定用例find解决scheme的问题,并logging我为尝试遵循这个过程的其他人所做的努力。

我们有一个RESTful服务器和一个iOS应用程序。 我们有自己的证书颁发机构,服务器有一个根证书颁发机构和一个自签名证书。 我们遵循这个过程来生成以下文件:

Creating Your Own SSL Certificate Authority (and Dumping Self Signed Certs)

rootCA.pem rootCA.key server.crt server.key

只有服务器证书存储在我们的服务器上,并且作为SSL过程的一部分,公钥与API调用一起被发送以进行validation。

我遵循这个过程来使用AFNetworking来使用证书locking以及公钥locking来validation我们的自签名证书:

http://initwithfunk.com/blog/2014/03/12/afnetworking-ssl-pinning-with-self-signed-certificates/

根据本指南,将.crt文件转换为.cer文件(DER格式):

https://support.ssl.com/Knowledgebase/Article/View/19/0/der-vs-crt-vs-cer-vs-pem-certificates-and-how-to-convert-them

并将.cer文件(server.cer)包含在iOS应用程序包中。 这成功地允许我们的应用程序向我们的服务器发出GET / POST请求。 但是,由于我们的服务器证书可能会过期或重新发布,因此我们想要使用根CA,就像AFNetworking上的此线程中的人员所做的那样:

https://github.com/AFNetworking/AFNetworking/issues/1944

目前我们已经更新到AFNetworking 2.6.0,所以我们的networking库应该包括所有的更新,包括在这个讨论中的:

https://github.com/AFNetworking/AFNetworking/issues/2744

用于创build我们的安全策略的代码:

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没有足够的有关公钥的信息来validation服务器证书,该证书是使用根CA签名的? server.crt文件也可能具有更改的CommonName。

另外,由于我对SSL术语的stream利程度是相当原始的,如果任何人有可以澄清我是否问正确的问题,这将是伟大的。 具体的问题是:

  1. 我是否正确生成证书,以便服务器可以使用自签名server.crt文件certificate其身份?
  2. 是否有可能只包含rootCA.cer文件到bundle中,并能够validation叶证书server.crt? 它是否能够validation另一个由相同的rootCA签名的server2.crt文件? 还是应该在根CA和叶之间包含一个中间证书?
  3. 公钥密码或证书是否为此提供了正确的解决scheme? 我读过的每一个论坛和博客文章都说是的,但即使是最新的AFNetwork库,我们也没有任何运气。
  4. 服务器是否需要以某种方式发送server.crt和roomCA.pem签名?

在一堆不同的SSL资源的帮助下,我find了启用自签名证书来validation启用SSL的私有服务器的解决scheme。 我也对SSL,现有的iOS解决scheme以及每一个小问题都有了更好的理解,这些问题使我的系统无法正常工作。 我将试图概述进入我的解决scheme的所有资源,以及小事情的不同之处。

我们还在使用AFNetworking,目前它是包含证书locking的2.6.0。 这是我们问题的根源。 我们无法validation我们的私人服务器的身份,这是发送由自签名CA根签署的叶证书。 在我们的iOS应用程序中,我们绑定了自签名根证书,然后通过AFNetworking将其设置为可信锚点。 但是,由于服务器是本地服务器(本产品附带的硬件),因此IP地址是dynamic的,所以AFNetworking的证书validation失败,因为我们无法禁用IP检查。

为了find答案的根源,我们使用AFHTTPSessionManager来实现一个自定义的sessionDidReceiveAuthenticationChallengeCallback。 (请参阅: https ://gist.github.com/r00m/e450b8b391a4bf312966)在该callback中,我们使用不检查主机名的SecPolicyvalidation服务器证书; 请参阅http://blog.roderickmann.org/2013/05/validating-a-self-signed-ssl-certificate-in-ios-and-os-x-against-a-changing-host-name/ ,它是对于NSURLConnection而不是NSURLSession的更早的实现。

代码:

创build一个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 } 

自定义validation的实现

 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 } 

我通过代码快速学到了一些东西。

  1. AFNetworking的setSessionDidReceiveAuthenticationChallengeBlock的实现有这样的签名:

    • (void)setSessionDidReceiveAuthenticationChallengeBlock :(可为空的NSURLSessionAuthChallengeDisposition(^)(NSURLSession * session,NSURLAuthenticationChallenge * challenge,NSURLCredential * __nullable __aoreoreleasing * __nullable credential))block;

凭证参数是需要分配的参考/inputvariables。 在swift中看起来像这样:AutoreleasingUnsafeMutablePointer。 为了在C中分配一些东西,你可以这样做:

 *credential = [[NSURLCredential alloc] initWithTrust...]; 

在swift中,它看起来像这样:(从使用RKValueTransFormer将NSArray转换为RLMArray无法将outputValue转换为AutoreleasingUnsafeMutablePointer <AnyObject?> )

 credential.memory = NSURLCredential(trust: trust) 
  1. SecPolicyCreateSSL,SecCertificateCreateWithData和SecTrustGetCertificateAtIndex返回非托pipe! 对象,必须使用takeRetainedValue()或takeUnretainedValue()来实质上转换它们。 (请参阅http://nshipster.com/unmanaged/ )。 当我们使用了takeRetainedValue()并且多次调用方法时(SecDestroy发生崩溃),我们遇到了内存问题/崩溃。 现在在我们切换到使用takeUnretainedValue()之后,构build看起来很稳定,因为validation之后不需要证书或SSL策略。

  2. TLS会话caching。 https://developer.apple.com/library/ios/qa/qa1727/_index.html这意味着当您在挑战中获得成功的validation时,您再也无法获得挑战&#x3002; 当你testing一个有效的证书时,这真的会让你头痛,然后testing一个无效的证书,然后跳过所有的validation,并从服务器获得成功的响应。 解决方法是每次使用有效证书并通过validation质询后,在iOS模拟器中进行Product-> Clean。 否则,你可能会花一些时间错误地思考,你终于得到了根CA来validation。

所以这里只是针对我在服务器上遇到的问题的一个工作解决scheme。 我希望在此发布所有内容,希望能够帮助运行本地或开发服务器的其他人使用自签名CA和需要启用SSL的iOS产品。 当然,在iOS 9的ATS中,我希望能够很快再次深入到SSL。

此代码目前有一些内存pipe理问题,并将在不久的将来更新。 此外,如果有人看到这个实施,并说:“啊,这是一样糟糕的返回真正的无效证书”,请让我知道! 据我们自己的testing可以看出,这个应用程序拒绝了没有被我们的根CA签名的无效的服务器证书,并且接受由根CA生成并签名的叶证书。 应用程序包仅包含根CA,因此服务器证书可以在到期后循环,并且现有的应用程序不会失败。

如果我深入挖掘AFNetworking,并找出所有这一切的一对三解决scheme(通过切换他们提供的所有小标志),我也会发布一个更新。

如果AlamoFire开始支持SSL,也可以在这里发布解决scheme。

如果您正在使用coco pod,那么inheritanceAFSecurityPolicy类并根据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类

用您的证书文件名replaceRootCA

 #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