'didReceiveChallenge' 在 ServerTrust 之后未调用 HttpBasic 身份验证

'didReceiveChallenge' not called for HttpBasic authentication after ServerTrust

我正在将 iOS 应用程序从 NSURLConnection 转换为 NSURLSession。

服务器使用 https(由公认的 CA 签名的证书)和基本身份验证进行交互。

我没有使用数据 return 的完成块,而是使用自定义委托。我在别处看到,使用自定义委托意味着我应该响应 AuthenticationChallenges 而不是依赖 CredentialStorage(这也行不通,但那是另一个问题)。

我的问题是对 ServerTrust 进行了一次质询,但对 HttpBasic 身份验证不会再次调用。所以,我的会话超时了。

我试过为 'defaultSession dataTaskWithRequest' 使用完成块而不是自定义委托,只是想看看我是否可以通过这一点,但这没有什么区别。我还尝试将 CredentialStorage 用于 HttpBasic 凭据,但如上所述,我并不高兴。

这让我难住了。有什么想法吗?

(void)URLSession:(NSURLSession *)connection
//    task:(NSURLSessionTask *)task
    didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
    completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler
{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
#if 1 && defined(DEBUG)
        NSLog (@"didReceiveChallenge: Using SSL");
#endif // DEBUG

        if ([challenge.protectionSpace.host isEqualToString:HOST])
        {
#if 1 && defined(DEBUG)
            NSLog (@"didReceiveChallenge: Using Protection Space Host - %@", HOST);
#endif // DEBUG

            [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
        }
        else
        {
            [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
        }
    }
    else

    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic] ||
        [challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPDigest])
    {
#if 1 && defined(DEBUG)
        NSLog (@"didReceiveChallenge: (Basic / Digest) #%ld - user: %@, password: %@",
               (long)[challenge previousFailureCount],  USERNAME, PASSWORD);

#endif // DEBUG

        if ([challenge previousFailureCount] == 0)
        {
#if 1 && defined(DEBUG)
            NSLog (@"didReceiveChallenge: previousFailureCount == 0");
#endif // DEBUG

            NSURLCredential *newCredential;

            newCredential = [NSURLCredential credentialWithUser:USERNAME
                            password:PASSWORD
                            persistence:NSURLCredentialPersistenceForSession];

            [[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];

        }
        else
        {
            [[challenge sender] cancelAuthenticationChallenge:challenge];

            // inform the user that the user name and password
            // in the preferences are incorrect

#if 1 && defined(DEBUG)
            NSLog (@"didReceiveChallenge: Failed Authentication");
#endif // DEBUG

            // ...error will be handled by connection didFailWithError
        }
    }
#ifdef DEBUG
    else
    {
        NSLog(@"didReceiveChallenge: Not handled!");
    }

#endif // DEBUG
}

排名不分先后:

  • 您实际上已经为相关的特定主机完全禁用了 TLS,因为您没有以任何有意义的方式检查证书,而只是检查它是否提供了与给定主机名匹配的证书。这是非常非常危险的。相反,您需要对证书执行一些自定义验证(例如,检查密钥是否与固定密钥匹配或检查它是否由已知可信的内部证书签名),然后仅在验证时执行您正在执行的操作.
  • 您(我认为)通过指定您不想让服务器提供其证书,有效地完全破坏了所有其他主机的 TLS。在这种情况下,您应该要求进行默认处理。
  • 对于任何其他质询类型,您的方法将忽略质询,这意味着连接将永远挂起并且永远不会取得进展。在这些情况下,您也应该要求进行默认处理。否则,当(而不是如果)您被要求提供客户端证书时,您的连接将出现挂起并最终超时。
  • 您对实际通知的处理有误。你应该 never 使用 NSURLSession 调用质询发送者的方法。您必须 调用完成方法。否则,会话将继续等待您调用它,直到请求超时。

我不确定代码中是否还有其他错误,但这三个错误都可能导致严重的不当行为,其中一个是主要的安全漏洞。首先解决这些问题,如果仍然不起作用,请添加更多评论。 :-)