Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'didReceiveChallenge' not called for HttpBasic authentication after ServerTrust

I am converting an iOS app from NSURLConnection to NSURLSession.

The server is interacts with uses both https (cert signed by a recognised CA) and Basic Authentication.

Rather than using completion blocks for the data return, I'm using custom delegates. I've seen elsewhere, that using custom delegates means I should response to AuthenticationChallenges rather than rely on CredentialStorage (not that that works either, but that's another issue).

My problem is that the challenge occurs once for the ServerTrust, but doesn't get called again for the HttpBasic Authentication. So, my sessions time out.

I've tried using completion blocks for the 'defaultSession dataTaskWithRequest' rather than custom delegates, just to see if I can get past this point, but it makes no difference. I also tried using CredentialStorage for the HttpBasic credentials but, as mentioned above, no joy.

This has got me stumped. Any ideas?

(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
}
like image 943
Snips Avatar asked Jan 01 '17 17:01

Snips


1 Answers

In no particular order:

  • You've effectively completely disabled TLS for the specific host in question, because you aren't checking the certificate in any meaningful way, and are only checking that it provided a certificate that matches a given hostname. This is very, very dangerous. Instead, you need to perform some custom validation on the certificate (e.g. checking if the key matches a pinned key or checking to see if it is signed by a known-trusted internal cert) and THEN do what you're dong ONLY if it validates.
  • You've (I think) effectively completely broken TLS for all other hosts by specifying that you don't want to allow the server to provide its certificate. You should be asking for default handling in that case.
  • For any other challenge type, your method is ignoring the challenge, which means the connection will just hang forever and never make progress. You should be asking for default handling in those cases as well. Otherwise, when (not if) you get asked for a client certificate, your connection will appear to hang and will eventually just time out.
  • You're handling the actual notification wrong. You should never call methods on the challenge sender with NSURLSession. You must call the completion method. Otherwise, the session will continue waiting for you to call it until the request times out.

I'm not sure if there are other bugs in the code, but all three of those could cause serious misbehavior, and one of them is a major security hole. Start by fixing those issues, and if it still doesn't work, add further comments. :-)

like image 70
dgatwood Avatar answered Sep 28 '22 17:09

dgatwood