在使用各种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()
manager.setSessionDidReceiveAuthenticationChallengeBlock { (session, challenge, credential) -> NSURLSessionAuthChallengeDisposition in
if self.shouldTrustProtectionSpace(challenge, credential: credential) {
return NSURLSessionAuthChallengeDisposition.UseCredential
}
return NSURLSessionAuthChallengeDisposition.PerformDefaultHandling
}
自定义验证的实现
class func shouldTrustProtectionSpace(challenge: NSURLAuthenticationChallenge, var credential: AutoreleasingUnsafeMutablePointer<NSURLCredential?>) -> Bool {
let protectionSpace: NSURLProtectionSpace = challenge.protectionSpace
var trust: SecTrustRef = protectionSpace.serverTrust!
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
}
let domain: CFString? = nil
let policy:SecPolicy = SecPolicyCreateSSL(1, domain).takeRetainedValue()
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!)
}
var newTrust: Unmanaged<SecTrust>? = nil
var err: OSStatus = SecTrustCreateWithCertificates(certs, policy, &newTrust)
if err != noErr {
println("Could not create trust")
}
trust = newTrust!.takeUnretainedValue()
let rootCerts: [AnyObject] = [cert!]
err = SecTrustSetAnchorCertificates(trust, rootCerts)
var trustResult: SecTrustResultType = SecTrustResultType()
SecTrustEvaluate(trust, &trustResult)
if Int(trustResult) == Int(kSecTrustResultProceed) || Int(trustResult) == Int(kSecTrustResultUnspecified) {
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参数是一个需要分配值的引用/入出变量。在Swift中,它看起来像这样:AutoreleasingUnsafeMutablePointer。为了在C中将某些内容分配给它,您需要执行以下操作:
*credential = [[NSURLCredential alloc] initWithTrust...]
在Swift中,它看起来像这样:(来自
将NSArray转换为RLMArray与RKValueTransFormer失败将outputValue转换为AutoreleasingUnsafeMutablePointer<AnyObject?>)
credential.memory = NSURLCredential(trust: trust)
SecPolicyCreateSSL、SecCertificateCreateWithData 和 SecTrustGetCertificateAtIndex 返回 Unmanaged! 对象,你需要使用 takeRetainedValue() 或 takeUnretainedValue() 进行转换/桥接。 (参见 http://nshipster.com/unmanaged/)。我们在使用 takeRetainedValue() 并多次调用该方法时遇到了内存问题/崩溃(在 SecDestroy 上崩溃)。现在我们切换到使用 takeUnretainedValue(),因为在验证之后你不需要证书或 SSL 策略,所以构建似乎是稳定的。
TLS 会话缓存。 https://developer.apple.com/library/ios/qa/qa1727/_index.html 这意味着当你在挑战中获得成功验证时,你永远不会再次获得挑战。当你测试有效证书,然后测试无效证书时,这可能会让你感到困惑,因为无效证书会跳过所有验证,并从服务器获得成功响应。解决方案是在每次使用有效证书并通过验证挑战后,在 iOS 模拟器中进行 Product->Clean。否则,你可能会花费一些时间错误地认为你最终已经使根 CA 验证通过。
这里有一个解决我在服务器上遇到的问题的简单工作方案。我希望将所有内容发布在这里,以帮助其他使用自签名CA和需要启用SSL的iOS产品的本地或开发服务器用户。当然,在iOS 9中,由于ATS,我预计很快会再次深入研究SSL。
目前这段代码存在一些内存管理问题,将在不久的将来进行更新。此外,如果有人看到这个实现并说“啊哈,这与为无效证书返回TRUE一样糟糕”,请让我知道!据我们自己的测试,应用程序拒绝未经我们根CA签名的无效服务器证书,并接受由根CA生成和签名的叶子证书。应用程序包仅包含根CA,因此可以在服务器证书过期后循环使用,而现有应用程序不会失败。
如果我进一步深入了解AFNetworking并找出一个一到三行的解决方案(通过切换它们提供的所有小标志),我也会发布更新。
如果AlamoFire也开始支持SSL,请在此处发布解决方案。