首页 > 解决方案 > 使用 AFNetworking 进行 SSL 固定不起作用

问题描述

我正在尝试使用自签名证书将 SSL 固定添加到我的应用程序,但我似乎无法让它工作。我已经尝试了所有在互联网上可以找到的东西,但都没有成功,而且不是 SSL 工作原理方面的专家也无济于事。

我正在将objective-c 与最新版本的AFNetworking 一起使用。

我编写了一段非常简单的代码来测试我的 API 调用(我在这篇文章中使用了占位符 URL):

NSString *url = @"https://api.example.net/webservice";

    NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"example.net" ofType:@"der"];
    NSData *certData = [NSData dataWithContentsOfFile:cerPath];

    AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:url]];
    manager.requestSerializer = [AFJSONRequestSerializer new];
    manager.responseSerializer = [AFJSONResponseSerializer new];

    AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
    [policy setAllowInvalidCertificates:YES];
    [policy setValidatesDomainName:NO];
    policy.pinnedCertificates = [NSSet setWithObject:certData];

    manager.securityPolicy = policy;

    [manager POST:url parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"SUCCESS");
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"FAILURE : %@", error.localizedDescription);
    }];

每次我尝试执行此代码时,都会失败并出现以下错误:

Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “api.example.net” which could put your confidential information at risk."

我尝试为我的证书使用不同的格式(.der、.cer、...),但我仍然总是遇到同样的错误。

我尝试NSAllowsArbitraryLoads在我的 info.plist 中使用,但没有任何变化。

为了确保我使用的是工作代码,我还从Ray Wenderlich 教程下载了示例项目,但我自己的证书仍然无效(在教程中他们使用 stackexchange 证书,这个有效)。

我已经研究这个问题好几天了,还没有找到解决办法。

相同的证书在我们的 Android 应用程序以及 Postman 上都能完美运行。

这是因为我使用自签名证书而 iOS 不喜欢它吗?我的代码或应用程序配置中是否有任何明显的遗漏?是否有特定的东西来实现服务器端以确保它与 iOS 一起工作?我必须以非常特定的格式导出我的证书吗?

欢迎任何信息。

谢谢!

标签: iosobjective-csslafnetworkingpinning

解决方案


我正在查看一个旧项目,我使用自签名证书没有问题。这些只是可能有帮助的评论 - 我在这里制作它们是因为我有更多空间并且可以更好地格式化它们。

der版本有效。

Info.plist你需要类似下面的东西。

    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>server1.local</key>
            <dict>
                <key>NSIncludesSubdomains</key>
                <true/>
                <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
                <true/>
            </dict>
            <key>server2.local</key>
            <dict>
                <key>NSIncludesSubdomains</key>
                <true/>
                <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
                <true/>
            </dict>
            <key>server3.local</key>
            <dict>
                <key>NSIncludesSubdomains</key>
                <true/>
                <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
                <true/>
            </dict>
        </dict>
    </dict>

请注意您的错误消息 - 它抱怨服务器证书,因此问题可能出在 DNS 或服务器证书中。两者都需要正确,并且您使用的名称(例如server1.local)必须与服务器的 DNS 名称以及CNiOS 工作的证书匹配。

我将CA 和服务器证书都添加到 iOS 中的链中。

我相信这会对你有所帮助。

FWIW 我的实现没有使用 AFNetworking 但我SecTrustSetAnchorCertificates在里面URLSession:didReceiveChallenge:completionHandler:使用了NSURLSessionDelegate我曾经支持正常的NSURLRequest.

这是那段代码。

// Look to see if we can handle the challenge
- ( void ) URLSession:( NSURLSession                 * ) session
  didReceiveChallenge:( NSURLAuthenticationChallenge * ) challenge
    completionHandler:( void ( ^ ) ( NSURLSessionAuthChallengeDisposition, NSURLCredential * ) ) completionHandler
{
#ifdef DEBUG
    NSLog( @"didReceiveChallenge %@ %zd", challenge.protectionSpace.authenticationMethod, ( ssize_t ) challenge.previousFailureCount );
#endif
    NSURLCredential      * credential = nil;
    NSURLProtectionSpace * protectionSpace;
    SecTrustRef            trust;
    int                    err;

    // Setup
    protectionSpace = challenge.protectionSpace;
    trust = protectionSpace.serverTrust;
    credential = [NSURLCredential credentialForTrust:trust];

    if ( protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust )
    {
        // Build up the trust anchor using server certificates
        err = SecTrustSetAnchorCertificates ( trust, ( CFArrayRef ) fhWebSupportDelegate.serverCertificates );
        SecTrustResultType trustResult = 0;

        if ( err == noErr )
        {
            SecTrustSetAnchorCertificatesOnly ( trust, true );
            err = SecTrustEvaluate ( trust, & trustResult );
#ifdef DEBUG
            NSLog ( @"Trust result %lu", ( unsigned long ) trustResult );
#endif
        }

        BOOL trusted =
        ( err == noErr ) &&
        ( ( trustResult == kSecTrustResultProceed ) || ( trustResult == kSecTrustResultUnspecified ) || ( trustResult == kSecTrustResultRecoverableTrustFailure ) );


        // Return based on whether we decided to trust or not
        if ( trusted )
        {
#ifdef DEBUG
            NSLog ( @"Trust evaluation succeeded" );
#endif
            if ( completionHandler )
            {
                completionHandler ( NSURLSessionAuthChallengeUseCredential, credential );
            }
        }
        else
        {
#ifdef DEBUG
            NSLog ( @"Trust evaluation failed" );
#endif
            if ( completionHandler )
            {
                completionHandler ( NSURLSessionAuthChallengeCancelAuthenticationChallenge, credential );
            }
        }
    }
    else if ( completionHandler )
    {
        completionHandler ( NSURLSessionAuthChallengePerformDefaultHandling, nil );
    }
}

这里fhWebSupportDelegate.serverCertificates返回一个包含 CA 以及服务器证书的数组。此外,当我向服务器授予信任时,我也非常宽容,如代码中所示。


推荐阅读