【问题标题】:iOS NSURLErrorDomain Code=-1005 with ClientCertificate Validation challengeiOS NSURLErrorDomain 代码=-1005,带有 ClientCertificate 验证挑战
【发布时间】:2026-02-16 00:50:01
【问题描述】:

我正在尝试使用NSURLSession 在 iOS 中实现 https 客户端证书身份验证。这是我正在做的事情:

-(void) httpPostWithCustomDelegate :(NSDictionary *) params
{
    NSString *ppyRequestURL = [NSString stringWithFormat:@"%@/fetchcountryCities", PPBaseURL];

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:ppyRequestURL]
                                                           cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                       timeoutInterval:60.0];

    [request setHTTPMethod:@"POST"];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
       Log(@"ASDAD");
    }];
    [postDataTask resume]; 
}

我在质询处理程序中提供客户端证书,如下所示:

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{

    if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
        NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
    }

    else    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
               NSURLCredential *credential = [self provideClientCertificate];
                completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
            }
}

这是我加载客户端证书的方式,

- (SecIdentityRef)findClientCertificate {
    SecIdentityRef clientCertificate = NULL;

    if (clientCertificate) {
        CFRelease(clientCertificate);
        clientCertificate = NULL;
    }

    NSString *pkcs12Path = [[NSBundle mainBundle] pathForResource:@"johndoe" ofType:@"p12"];
    NSData *pkcs12Data = [[NSData alloc] initWithContentsOfFile:pkcs12Path];

    CFDataRef inPKCS12Data = (__bridge CFDataRef)pkcs12Data;
    CFStringRef password = CFSTR("password");
    const void *keys[] = { kSecImportExportPassphrase };
    const void *values[] = { password };
    CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
    CFArrayRef items = NULL;

    OSStatus err = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items);

    CFRelease(optionsDictionary);
    CFRelease(password);

    if (err == errSecSuccess && CFArrayGetCount(items) > 0) {
        CFDictionaryRef pkcsDict = CFArrayGetValueAtIndex(items, 0);

        SecTrustRef trust = (SecTrustRef)CFDictionaryGetValue(pkcsDict, kSecImportItemTrust);

        if (trust != NULL) {
            clientCertificate = (SecIdentityRef)CFDictionaryGetValue(pkcsDict, kSecImportItemIdentity);
            CFRetain(clientCertificate);
        }
    }

    if (items) {
        CFRelease(items);
    }

    return clientCertificate;
}

- (NSURLCredential *)provideClientCertificate {
    SecIdentityRef identity = [self findClientCertificate];

    if (!identity) {
        return nil;
    }

    SecCertificateRef certificate = NULL;
    SecIdentityCopyCertificate (identity, &certificate);
    const void *certs[] = {certificate};
    CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL);
    NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray *)certArray persistence:NSURLCredentialPersistencePermanent];
    CFRelease(certArray);

    return credential;
}

现在当 API 被调用时,我得到了这个错误:

Error Domain=NSURLErrorDomain Code=-1005 "网络连接丢失。" UserInfo={NSUnderlyingError=0x7f8428df4d40 {错误域=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorCodeKey=-4, _kCFStreamErrorDomainKey=4}}

我在模拟器和设备上遇到了同样的错误。我完全被困住了。不知道这里出了什么问题。

*****更新***** 我确实与查尔斯代理核实以了解更多详细信息。令我惊讶的是,当我将客户端证书添加到 charles 代理时,我收到了来自服务器的响应,所以我是否缺少 plist 中的某些设置或加载 p12 时出现问题?

来自 plist 设置,

<key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>test.mydomain.com</key>
            <dict>
                <key>NSExceptionAllowsInsecureHTTPLoads</key>
                <true/>
                <key>NSExceptionMinimumTLSVersion</key>
                <string>TLSv1.2</string>
                <key>NSExceptionRequiresForwardSecrecy</key>
                <true/>
                <key>NSIncludesSubdomains</key>
                <true/>
                <key>NSRequiresCertificateTransparency</key>
                <false/>
                <key>NSThirdPartyExceptionAllowsInsecureHTTPLoads</key>
                <false/>
                <key>NSThirdPartyExceptionMinimumTLSVersion</key>
                <string>TLSv1.2</string>
                <key>NSThirdPartyExceptionRequiresForwardSecrecy</key>
                <true/>
            </dict>
        </dict>
    </dict>

【问题讨论】:

    标签: ios objective-c authentication nsurlsession nsurlsessiondatatask


    【解决方案1】:

    一目了然,我发现三个问题:

    • 您在未提供请求正文的情况下发出 POST 请求。这可能会导致请求直接失败,甚至没有发送到服务器。

    • 您的服务器信任处理,正如所写的那样,通过告诉操作系统盲目信任它(我认为),有效地消除了您本来可以从 TLS 获得的任何保护。

      您应该 A. 告诉 NSURLSession 在服务器信任情况下执行默认处理,或者 B. 自己检查证书并然后告诉它使用证书。

    • 您的身份仅包括客户端证书,不包括服务器信任它可能需要的任何中间证书。

      您可能应该结合这两种方法,使用您找到的第一个身份,但获取您在身份文件中找到的每个证书并将它们全部添加到证书数组中(可能与客户端证书本身一起,但我隐约记得你不应该在那里添加它;尝试两种方法,看看哪一种失败)。

    请注意,如果您知道您的任何用户身份都不会有证书链,那么第三个可能无关紧要。

    【讨论】:

    • 感谢您的回复。我尝试了你所有的建议,但仍然得到同样的错误。
    • 我确实与 charles proxy 联系以了解更多详细信息。令我惊讶的是,当我将客户端证书添加到 charles 代理时,我收到了来自服务器的响应,所以我是否缺少 plist 中的一些设置或加载 p12 时出现问题?
    • 等等...如果您通过代理,它会起作用,否则会失败?
    • 是的,当我将客户端证书添加到代理时,它通过代理工作。但是我的应用程序在客户端质询后无法连接。我正在回复上面提到的消息。
    • 您的意思是让代理将客户端证书发送到您的服务器?你不应该那样做;所有告诉我们的是客户端证书是有效的。使用 Charles Proxy 的目的是查看您的应用实际发送到代理的内容,以确定这与(例如)Safari 登录到同一服务器有何不同。获得这些信息后,应该很容易找出错误或遗漏的地方。