I am developing an iPhone app. During development, I need to connect to a server that's using a self-signed SSL certificate. I'm pretty certain - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
is my opportunity to write some exception code to allow this. However, I can't find any resources that tell me how to do this. I can see the following error in the log:
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
In addition to this, when I NSLog(@"error = %@", error);
from within the above delegate method I get:
Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be
api.mydevelopmenturl.example
which could put your confidential information at risk." UserInfo=0x10cbdbcf0 {NSUnderlyingError=0x112ec9730 "The certificate for this server is invalid. You might be connecting to a server that is pretending to beapi.mydevelopmenturl.example
which could put your confidential information at risk.", NSErrorFailingURLStringKey=https://api.mydevelopmenturl.example/posts, NSErrorFailingURLKey=https://api.mydevelopmenturl.example/posts, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x112e5a020>, NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to beapi.mydevelopmenturl.example
which could put your confidential information at risk.}
Any ideas on how to resolve this issue? Please post code as I've read the conceptual docs and I don't understand them. Here's an example of one that's beyond me: https://developer.apple.com/library/content/technotes/tn2232/_index.html
For this, open again the iOS Settings app. Then navigate to “General” > “About” > “Certificate Trust Settings”. In the section “Enable Full Trust for Root Certificates”, enable your root certificate. With this, your app can connect now with the self-signed certificate to the backend.
This works for me:
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:Nil];
...
...
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
if([challenge.protectionSpace.host isEqualToString:@"mydomain.example"]){
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
}
}
Apple has a Technical Note 2232 which is quite informative and explains in detail HTTPS server trust evaluation.
In this case error -1202 in the NSURLErrorDomain
domain is NSURLErrorServerCertificateUntrusted
, which means that server trust evaluation has failed. You might also receive a variety of other errors; Appendix A: Common Server Trust Evaluation Errors lists the most common ones.
From the Technical Note:
In most cases the best way to resolve a server trust evaluation failure is to fix the server. This has two benefits: it offers the best security and it reduces the amount of code you have to write. The remainder of this technote describes how you can diagnose server trust evaluation failures and, if it's not possible to fix the server, how you can customize server trust evaluation to allow your connection to proceed without completely undermining the user's security.
The particular bit that is germane to this question is the section on NSURLSession server trust evaluation:
NSURLSession
allows you to customize HTTPS server trust evaluation by implementing the-URLSession:didReceiveChallenge:completionHandler:
delegate method. To customize HTTPS server trust evaluation, look for a challenge whose protection space has an authentication method ofNSURLAuthenticationMethodServerTrust
. For those challenges, resolve them as described below. For other challenges, the ones that you don't care about, call the completion handler block with theNSURLSessionAuthChallengePerformDefaultHandling
disposition and a NULL credential.When dealing with the NSURLAuthenticationMethodServerTrust authentication challenge, you can get the trust object from the challenge's protection space by calling the -serverTrust method. After using the trust object to do your own custom HTTPS server trust evaluation, you must resolve the challenge in one of two ways:
If you want to deny the connection, call the completion handler block with the
NSURLSessionAuthChallengeCancelAuthenticationChallenge
disposition and a NULL credential.If you want to allow the connection, create a credential from your trust object (using
+[NSURLCredential credentialForTrust:]
) and call the completion handler block with that credential and theNSURLSessionAuthChallengeUseCredential
disposition.
The upshot of all this is that if you implement the following delegate method, you can override server trust for a particular server:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
if([challenge.protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust])
{
if([challenge.protectionSpace.host
isEqualToString:@"domaintooverride.example"])
{
NSURLCredential *credential =
[NSURLCredential credentialForTrust:
challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
else
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
Note that you have to handle both the case of the host matching the one you want to override and all other cases. If you don't handle the "all other cases" part, the behavior result is undefined.
Find a trusted SSL certificate authority online that's offering a free 90 day trial for new certificates. Install the certificate on your server. You now have 90 days to develop your app to a point where you can make a decision as to whether or not it's worth it to pay money to "renew" the certificate. This is the best answer for me since my decision to use the self-signed certificate was financially motivated and 90 days gives me enough time develop my app to a point where I can decide if it's worth it to spend money on an SSL certificate or not. This approach avoids having to deal with the security implications of running a codebase that is tweaked to accept self-signed certificates. Sweet! Yay for bootstrapping!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With