NSURLSessionAuthChallengeUseCredential 没有帮助。如何让 iOS 信任我的服务器?

NSURLSessionAuthChallengeUseCredential does not help. How to make iOS trust my server?

我的服务器使用自签名 SSL 证书。而且 iOS 无论我做什么都不想接受他们。这是我的代码:

- (void)URLSession:(NSURLSession *)session
 didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
                         NSURLCredential *credential))completionHandler
{
NSString* authenticationMethod = challenge.protectionSpace.authenticationMethod;

if (![authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
    completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
    return;
}

SecTrustRef trust = challenge.protectionSpace.serverTrust;

CFIndex count = SecTrustGetCertificateCount(trust);
CFMutableArrayRef originalCertificates = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);

for (CFIndex i = 0; i < count; i++)
{
    SecCertificateRef certRef = SecTrustGetCertificateAtIndex(trust, i);
    CFArrayAppendValue(originalCertificates, certRef);
    CFStringRef certSummary = SecCertificateCopySubjectSummary(certRef);
    NSLog(@"CERT %ld %@", i, certSummary);
}

//SecPolicyRef policyRef = SecPolicyCreateSSL(true, CFSTR("192.168.50.80"));
SecPolicyRef policyRef = SecPolicyCreateBasicX509();
SecTrustRef newTrust;

OSStatus status = SecTrustCreateWithCertificates(originalCertificates, policyRef, & newTrust);

assert(status == noErr);

NSString* path = [[NSBundle mainBundle] pathForResource:@"no1bcCA" ofType:@"der"];
NSData* data   = [NSData dataWithContentsOfFile:path];
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef) data);

assert(cert);

NSString* rootPath = [[NSBundle mainBundle] pathForResource:@"no1bcRootCA" ofType:@"der"];
NSData* rootData   = [NSData dataWithContentsOfFile:rootPath];

SecCertificateRef rootCert = SecCertificateCreateWithData(NULL, (CFDataRef) rootData);
assert(rootCert);

SecTrustSetAnchorCertificates(newTrust, (CFArrayRef)@[(__bridge id)rootCert, (__bridge id)cert]);
SecTrustSetAnchorCertificatesOnly(newTrust, NO);

SecTrustResultType trustResult;

SecTrustEvaluate(newTrust, &trustResult);

if (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)
{
    NSURLCredential* credential = [NSURLCredential credentialForTrust:newTrust];
    completionHandler(NSURLSessionAuthChallengeUseCredential, credential);

}
else
{

 completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
}

所以 trustResultkSecTrustResultUnspecified 但是在我的 NSURLSessionDataTask 的完成处理程序中我仍然收到以下错误:

Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x6000003040b0>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=(
"<cert(0x7f81ef80ca00) s: sems.no1bc.local i: no1bcCA>",
"<cert(0x7f81ef80d400) s: no1bcCA i: no1bcRootCA>",
"<cert(0x7f81ef82b800) s: no1bcRootCA i: no1bcRootCA>"
), NSUnderlyingError=0x604000255030 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x6000003040b0>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
"<cert(0x7f81ef80ca00) s: sems.no1bc.local i: no1bcCA>",
"<cert(0x7f81ef80d400) s: no1bcCA i: no1bcRootCA>",
"<cert(0x7f81ef82b800) s: no1bcRootCA i: no1bcRootCA>"
)}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://192.168.50.80/pgpuniversaldesktop, NSErrorFailingURLStringKey=https://192.168.50.80/pgpuniversaldesktop, NSErrorClientCertificateStateKey=0}

我喜欢恢复建议。它说

Would you like to connect to the server anyway?

是的,我愿意,但是怎么做呢?我该怎么办?

除了我还尝试使用 ATS 的所有内容之外,这是我放入 plist 文件的内容:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>192.168.50.80</key>
        <dict>
            <key>NSIncludesSubdomains</key>
            <true/>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSExceptionMinimumTLSVersion</key>
            <string>TLSv1.2</string>
            <key>NSExceptionRequiresForwardSecrecy</key>
            <false/>
            <key>NSRequiresCertificateTransparency</key>
            <false/>
        </dict>
    </dict>
</dict>

但这无济于事。所以,我明确地告诉 iOS: "Trust this server, trust it",但它没有。可能是什么原因?如何强制系统信任服务器?我应该如何连接到服务器?

有趣的是,如果我 运行 来自 Mac 应用程序的代码,它可以毫无问题地工作。但不适用于 iOS

在 iOS 11 中,显然您还需要为该域将 NSExceptionRequiresForwardSecrecy 设置为 false。否则,App Transport Security 甚至不会让您的自定义证书到达您的自定义身份验证代码。这可以说是一个错误。

有关详细信息,请参阅 Apple 开发者论坛中的 this thread

结果是服务器 运行 一些过时的 TLS。 Mac 能够处理它而 iOS 不能,所以无论如何它都不允许连接到服务器。解决方案是修复服务器