Determining Trust With NSURLConnection and NSURLPr

2019-01-17 16:51发布

问题:

I would like to ask a followup question to a previously posed question. I've got the code to create an NSURLRequest/Connection, run it and have the callback methods for authentication get called. Here's the specific code:

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust] || [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodDefault];
}

-(void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{   

    if ([challenge previousFailureCount] > 0) {
        [[challenge sender] cancelAuthenticationChallenge:challenge];
        NSLog(@"Bad Username Or Password");
        badUsernameAndPassword = YES;
        finished = YES;
        return;
    }

    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        if (appDelegate._allowInvalidCert)
        {
            // Go ahead...trust me!
            [challenge.sender useCredential:
             [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] 
                 forAuthenticationChallenge: challenge];
        }
        else
        {
            TrustGenerator *tg = [[TrustGenerator alloc] init];

            if ([tg getTrust:challenge.protectionSpace])
            {
                // Go ahead...trust me!
                [challenge.sender useCredential:
                 [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] 
                     forAuthenticationChallenge: challenge];
            }
            else {
                [[challenge sender] cancelAuthenticationChallenge:challenge];
            }
        }
    }
    else if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodDefault) {
        NSURLCredential *newCredential = [NSURLCredential credentialWithUser:_username password:_password persistence:NSURLCredentialPersistenceNone];
        [[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
    }
}

What I'm running into is that "didReceiveAuthenticationChallenge" with "[challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]" is ALWAYS being called, even when the certificate on the server I'm attempting to connect to is trusted (doing testing with a Verisign cert). So what I'm seeing is my application is always prompting the end user to trust even when the website is trusted. Bad karma considering that's what's suppose to happen with a man in the middle attack, etc. What I'm really looking for is some code like this:

        if (appDelegate._allowInvalidCert)
        {
            // Go ahead...trust me!
            [challenge.sender useCredential:
             [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] 
                 forAuthenticationChallenge: challenge];
        }
        else if(The OS trusts the cert on the server)
        {
             [challenge.sender useCredential:
                 [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] 
                     forAuthenticationChallenge: challenge];
        }
        else{...

回答1:

So I spent a few days researching this. It looks like while the NSURLConnection API cannot determine if a certificate is trusted, there's a method in the Security Framework that handels that. So here's the code I came up with:

-(void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{   

    if ([challenge previousFailureCount] > 0) {
        [[challenge sender] cancelAuthenticationChallenge:challenge];
        NSLog(@"Bad Username Or Password");
        badUsernameAndPassword = YES;
        finished = YES;
        return;
    }

    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {

        SecTrustResultType result;
        //This takes the serverTrust object and checkes it against your keychain
        SecTrustEvaluate(challenge.protectionSpace.serverTrust, &result);

        if (appDelegate._allowInvalidCert)
        {
            [challenge.sender useCredential:
             [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] 
                 forAuthenticationChallenge: challenge];
        }
        //When testing this against a trusted server I got kSecTrustResultUnspecified every time. But the other two match the description of a trusted server
        else if(result == kSecTrustResultProceed || result == kSecTrustResultConfirm ||  result == kSecTrustResultUnspecified){
            [challenge.sender useCredential:
             [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] 
                 forAuthenticationChallenge: challenge];
        }
        else
        {
            //Asks the user for trust
            TrustGenerator *tg = [[TrustGenerator alloc] init];

            if ([tg getTrust:challenge.protectionSpace])
            {

                //May need to add a method to add serverTrust to the keychain like Firefox's "Add Excpetion"
                [challenge.sender useCredential:
                 [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] 
                     forAuthenticationChallenge: challenge];
            }
            else {
                [[challenge sender] cancelAuthenticationChallenge:challenge];
            }
        }
    }
    else if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodDefault) {
        NSURLCredential *newCredential = [NSURLCredential credentialWithUser:_username password:_password persistence:NSURLCredentialPersistenceNone];
        [[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
    }
}


回答2:

If the result is kSecTrustResultConfirm, you should actually ask the user if it's a trusted server.



回答3:

The answer above just works if you have a CA trusted Certificate, because in this case your are using the apple allowed CA certificates for validating.

If you have self-signed certificates you should use your own CA server certificate for checking if it is valid...

I found an good (bit confusing) here. It covers too a dual handshake....

Hope it help some !