kSecTrustResultRecoverableTrustFailure when connec

2019-05-27 03:37发布

问题:

I've seen here a few questions but none of them helped me. People resolve issues mostly regenerating server certificates: What is the reason of kSecTrustResultRecoverableTrustFailure?

Suppose I need to make a https connection to server with self-signed certificate. I don't have any internal data from the server such as its private keys. For example the server is https://www.pcwebshop.co.uk/

As far as I understand I can bundle a client certificate into app and use it for verification. Am I right I can obtain a valid client certificate without having any internal data from the server?

I've googled a tutorial here http://www.indelible.org/ink/trusted-ssl-certificates

Here's how I'm obtaining a client certificate

openssl s_client \
    -showcerts -connect "${HOST}:443" </dev/null 2>/dev/null | \
openssl x509 -outform DER >"../resources/${HOST}.der"

Here's the code (almost unchanged):

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

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if ([self shouldTrustProtectionSpace:challenge.protectionSpace]) {
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]
             forAuthenticationChallenge:challenge];
    } else {
        [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
    }
}

- (BOOL)shouldTrustProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
    // load up the bundled certificate
    NSString *certPath = [[NSBundle mainBundle] pathForResource:protectionSpace.host ofType:@"der"];

    if (certPath == nil)
        return NO;

    OSStatus status;
    NSData *certData = [[NSData alloc] initWithContentsOfFile:certPath];
    CFDataRef certDataRef = (__bridge_retained CFDataRef)certData;
    SecCertificateRef cert = SecCertificateCreateWithData(NULL, certDataRef);

    // establish a chain of trust anchored on our bundled certificate
    CFArrayRef certArrayRef = CFArrayCreate(NULL, (void *)&cert, 1, NULL);
    SecTrustRef serverTrust = protectionSpace.serverTrust;
    status = SecTrustSetAnchorCertificates(serverTrust, certArrayRef);
    // status == 0

    // verify that trust
    SecTrustResultType trustResult;
    status = SecTrustEvaluate(serverTrust, &trustResult);
    // status == 0

    CFRelease(certArrayRef);
    CFRelease(cert);
    CFRelease(certDataRef);

    return trustResult == kSecTrustResultUnspecified;
}

trustResult is always kSecTrustResultRecoverableTrustFailure.

What am I doing wrong? Thank you.

UPDATE: Ok, I found out that the reason is "Server's certificate does not match the URL".

Is it possible to fix the issue from the client side by ignoring the URL (hostname) of the server's certificate?

回答1:

Suppose I need to make a https connection to server with self-signed certificate. I don't have any internal data from the server such as its private keys.

In this case, you need a security diversification strategy. Gutmann covers it in great detail in his book Engineering Security.

The short of it: validate the certificate sensibly the first time you encounter it. You can still use most of the traditional PKI/PKIX tests. Once the certificate passes all tests (other than the "trusted root path"), you then call it "Trusted". This strategy is called Trust On First Use or TOFU.

In subsequent connections, you don't need TOFU again because you already encountered the certificate or public key. In the subsequent connections, you ensure the certificate or public key is continuous (i.e., does not change), the IP is from the same area as previously encountered, etc. If the certificate changes, then be sure its because the self signed is expiring. Be wary of unexpected changes.


Here's the code (almost unchanged):
...
trustResult == trustResult == kSecTrustResultUnspecified

For kSecTrustResultUnspecified, see Technical Q&A QA1360. Essentially, its a recoverable error. The Q&A says to prompt the user. Gutmann (and I) say to use a security diversification strategy as described above.

You need to take the user out of the loop because they will always make a decision that gets them past the Message Boxes as quickly as possibly. It dies not matter if they answer right or wrong - they want to see the dancing bunnies.

Also, the security diversification strategy even applies to kSecTrustResultProceed. Consider: Both Diginotar and Trustwave broke PKI{X}, and Cocoa/CocoaTouch was more than happy to return kSecTrustResultProceed. Its not really Cocoa/CocoaTouch's fault - there are architectural defects incumbent to PKI{X}.


Is it possible to fix the issue from the client side by ignoring the URL (hostname) of the server's certificate?

That kind of defeats the purpose of PKI{X}. If you will accept any host, any public key or any signature, why even bother with PKI{X} in the first place? The whole point of X509 in PKI{X} is to bind a entity or host to a public key using a trusted third party signature (or self signed, in this case).

If you don't care about the binding, just use Anonymous Diffie-Hellman and put an end to the security theater.