Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
237 views
in Technique[技术] by (71.8m points)

ios - Why is a HTTPS NSURLSession connection only challenged once per domain?

When connecting via HTTPS to a server, I implement the NSURLSessionDelegate method URLSession:didReceiveChallenge:completionHandler: to implement some custom functionality.

The problem is that this delegate method is only being called the first time a request is made (subsequent requests do not invoke this method). My custom functionality requires the delegate method to be called for every request.

Here's an example:

- (IBAction)reload:(id)sender {
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:self delegateQueue:nil];
    // Note that https://www.example.com is not the site I'm really connecting to.
    NSURL *URL = [NSURL URLWithString:@"https://www.example.com"];
    NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:URL];

    [[session dataTaskWithRequest:URLRequest
                completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                    // Response received here.
                }] resume];
}

#pragma NSURLSessionDelegate

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    // Called only for the first request, subsequent requests do no invoke this method.
    completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}

Since I want the URLCredential to be per session or per task, I checked the NSURLCredential that I pass to completionHandler, and I found it has a persistence of NSURLCredentialPersistenceForSession (which is immutable), which seems correct.

I also checked [NSURLCredentialStorage allCredentials] and it's empty, so it's not caching the credentials there.

I noticed that if I subsequently make a request to a HTTPS URL with a different domain, the challenge is called for that domain once, so it is on a per domain basis.

So how come the challenge is only made once?

EDIT

Switching to the NSURLSessionTaskDelegate and using URLSession:task:didReceiveChallenge:completionHandler: makes no difference.

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}

EDIT related question

EDIT Since there seems to be no way to fix this at the moment, I've filed an Apple bug report: 19072802

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

What you're really trying to do here is evaluate the trust of the server's credentials for each request.

Technote 2232: HTTPS Server Trust Evaluation describes HTTP trust evaluation at a high level, and goes into more detail about implementing it.

When you connect to a host using SSL/TLS, the host presents a set of cryptographic credentials. Your application (and potentially the user directly) must evaluate those credentials and decide if the can be trusted.

This is like looking at someone's driver's license or passport, and deciding wether they are who they say they are.

Imagine if you looked at someone's identification once for each word they speak. That would get tedius! It would not make sense unless the person changed, or their identification changed. iOS will perform trust evaluation if the server or it's credentials change.

This actually happens at the transport (socket) layer underneath HTTP, but Foundation thankfully exposes this higher up in APIs such as NSURLConnection and NSURLSession as a credential challenge for a given protection space. If the protection space (the host) or the server credentials change, a new credential challenge occurs. This will in turn prompt trust evaluation.

Since SSL/TLS is a socket-level security measure, the real work happens far below the Foundation URL loading system inside SecureTransport, the secure socket framework. SecureTransport maintains its own per-process TLS session cache. This is the layer you would have to circumvent to get the behavior you are looking for - you would need to clear the TLS session cache for each connection, or force SecureTransport to disregard the session cache for your process.

Technical Q&A 1727: TLS Session Cache describes the SecureTransport session cache in more detail, and may provide some interesting options for circumventing the TLS cache (i.e. messing with DNS).

At this time there is no API for clearing or modifying the SecureTransport TLS session cache. You can file a radar requesting this functionality.

TL;DR;

"So how come the challenge is only made once?" The result of the first TLS trust evaluation is cached by SecureTransport in the session cache.

There is not a way to control that particular behavior at this time.

You can try using some other HTTPS library or framework (such as OpenSSL), YMMV.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...