NSURLSessionAuthChallengeUseCredential does not help. How to make iOS trust my server? - ios

My server uses self signed SSL certificates. And iOS does not want to accept them no matter what I do. This is my code:
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler
{
NSString* authenticationMethod = challenge.protectionSpace.authenticationMethod;
if (![authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
return;
}
SecTrustRef trust = challenge.protectionSpace.serverTrust;
CFIndex count = SecTrustGetCertificateCount(trust);
CFMutableArrayRef originalCertificates = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
for (CFIndex i = 0; i < count; i++)
{
SecCertificateRef certRef = SecTrustGetCertificateAtIndex(trust, i);
CFArrayAppendValue(originalCertificates, certRef);
CFStringRef certSummary = SecCertificateCopySubjectSummary(certRef);
NSLog(#"CERT %ld %#", i, certSummary);
}
//SecPolicyRef policyRef = SecPolicyCreateSSL(true, CFSTR("192.168.50.80"));
SecPolicyRef policyRef = SecPolicyCreateBasicX509();
SecTrustRef newTrust;
OSStatus status = SecTrustCreateWithCertificates(originalCertificates, policyRef, & newTrust);
assert(status == noErr);
NSString* path = [[NSBundle mainBundle] pathForResource:#"no1bcCA" ofType:#"der"];
NSData* data = [NSData dataWithContentsOfFile:path];
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (CFDataRef) data);
assert(cert);
NSString* rootPath = [[NSBundle mainBundle] pathForResource:#"no1bcRootCA" ofType:#"der"];
NSData* rootData = [NSData dataWithContentsOfFile:rootPath];
SecCertificateRef rootCert = SecCertificateCreateWithData(NULL, (CFDataRef) rootData);
assert(rootCert);
SecTrustSetAnchorCertificates(newTrust, (CFArrayRef)#[(__bridge id)rootCert, (__bridge id)cert]);
SecTrustSetAnchorCertificatesOnly(newTrust, NO);
SecTrustResultType trustResult;
SecTrustEvaluate(newTrust, &trustResult);
if (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)
{
NSURLCredential* credential = [NSURLCredential credentialForTrust:newTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
else
{
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
}
so trustResult is kSecTrustResultUnspecified but in the completion handler of my NSURLSessionDataTask I still receive the following error:
Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x6000003040b0>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=(
"<cert(0x7f81ef80ca00) s: sems.no1bc.local i: no1bcCA>",
"<cert(0x7f81ef80d400) s: no1bcCA i: no1bcRootCA>",
"<cert(0x7f81ef82b800) s: no1bcRootCA i: no1bcRootCA>"
), NSUnderlyingError=0x604000255030 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x6000003040b0>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
"<cert(0x7f81ef80ca00) s: sems.no1bc.local i: no1bcCA>",
"<cert(0x7f81ef80d400) s: no1bcCA i: no1bcRootCA>",
"<cert(0x7f81ef82b800) s: no1bcRootCA i: no1bcRootCA>"
)}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://192.168.50.80/pgpuniversaldesktop, NSErrorFailingURLStringKey=https://192.168.50.80/pgpuniversaldesktop, NSErrorClientCertificateStateKey=0}
I love the recovery suggestion. It says
Would you like to connect to the server anyway?
Yes, I would, but how? What do I do?
Apart from all that I also tried to play with ATS, this is what I put into the plist file:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>192.168.50.80</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSRequiresCertificateTransparency</key>
<false/>
</dict>
</dict>
</dict>
but it never helps. So, I explicitly tell iOS: "Trust this server, trust it", but it doesn't. What may be the reason? How do I force the system to trust the server? And how am I supposed to connect to the server anyway?
It is funny that it works without problems if I run this code from a Mac app. But doesn't work on iOS

In iOS 11, apparently you need to also set NSExceptionRequiresForwardSecrecy to false for that domain. Otherwise, App Transport Security won't even let your custom cert reach your custom authentication code. This is arguably a bug.
For more info, see this thread in Apple's developer forums.

It turned out that the server ran some outdated TLS. Mac was able to deal with it whereas iOS not, so it never allowed to connect to the server no matter what. The solution was to fix the server

Related

SSL Pinning with AFNetworking doesn't work

I'm trying to add SSL pinning to my app, with a self-signed certificate, but I can't seem to get it to work.
I have tried everything I could find on the internet with no success, and not being an expert at how SSL works doesn't help.
I'm using objective-c with the latest version of AFNetworking.
I made a very simple piece of code to test my API calls (I'm using a placeholder URL for this post) :
NSString *url = #"https://api.example.net/webservice";
NSString *cerPath = [[NSBundle mainBundle] pathForResource:#"example.net" ofType:#"der"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:url]];
manager.requestSerializer = [AFJSONRequestSerializer new];
manager.responseSerializer = [AFJSONResponseSerializer new];
AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
[policy setAllowInvalidCertificates:YES];
[policy setValidatesDomainName:NO];
policy.pinnedCertificates = [NSSet setWithObject:certData];
manager.securityPolicy = policy;
[manager POST:url parameters:nil headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(#"SUCCESS");
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(#"FAILURE : %#", error.localizedDescription);
}];
Every time I try executing this code, I get a failure with the following error :
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.example.net” which could put your confidential information at risk."
I tried using different formats for my certificate (.der, .cer, ...), but I still always get the same error.
I tried using NSAllowsArbitraryLoads in my info.plist but nothing changes.
To make sure I'm using working code, I also downloaded the example project from a Ray Wenderlich tutorial, but my own certificate is still invalid (in the tutorial they use the stackexchange certificate, this one works).
I have been researching this issue for days and haven't found a solution yet.
The same certificate works perfectly on our Android app, as well as Postman.
Is this because I use a self-signed certificate and iOS doesn't like it?
Is there anything obvious I missed in my code or in my app configuration?
Is there something specific to implement server-side to make sure it works with iOS?
Do I have to export my certificate in a very specific format?
Any information is welcome.
Thanks!
I'm looking at an old project where I used self signed certificates without a problem. These are just comments that may help - I make them here because I have more space and can format them better.
The der version worked.
In Info.plist you need something like the following.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>server1.local</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>server2.local</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>server3.local</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
Note your error message - it is complaining about the server certificate, so maybe the problem is in the DNS or server certificate. Both needs to be correct and the name you use e.g. server1.local must match the DNS name of the server as well as the CN of the certificate for iOS to work.
I added both the CA and the server certificates to the chain in iOS.
I trust this will help you.
FWIW my implementation did not use AFNetworking but I used SecTrustSetAnchorCertificates inside URLSession:didReceiveChallenge:completionHandler: in the NSURLSessionDelegate that I used to support a normal NSURLRequest.
Here is that piece of code.
// Look to see if we can handle the challenge
- ( void ) URLSession:( NSURLSession * ) session
didReceiveChallenge:( NSURLAuthenticationChallenge * ) challenge
completionHandler:( void ( ^ ) ( NSURLSessionAuthChallengeDisposition, NSURLCredential * ) ) completionHandler
{
#ifdef DEBUG
NSLog( #"didReceiveChallenge %# %zd", challenge.protectionSpace.authenticationMethod, ( ssize_t ) challenge.previousFailureCount );
#endif
NSURLCredential * credential = nil;
NSURLProtectionSpace * protectionSpace;
SecTrustRef trust;
int err;
// Setup
protectionSpace = challenge.protectionSpace;
trust = protectionSpace.serverTrust;
credential = [NSURLCredential credentialForTrust:trust];
if ( protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust )
{
// Build up the trust anchor using server certificates
err = SecTrustSetAnchorCertificates ( trust, ( CFArrayRef ) fhWebSupportDelegate.serverCertificates );
SecTrustResultType trustResult = 0;
if ( err == noErr )
{
SecTrustSetAnchorCertificatesOnly ( trust, true );
err = SecTrustEvaluate ( trust, & trustResult );
#ifdef DEBUG
NSLog ( #"Trust result %lu", ( unsigned long ) trustResult );
#endif
}
BOOL trusted =
( err == noErr ) &&
( ( trustResult == kSecTrustResultProceed ) || ( trustResult == kSecTrustResultUnspecified ) || ( trustResult == kSecTrustResultRecoverableTrustFailure ) );
// Return based on whether we decided to trust or not
if ( trusted )
{
#ifdef DEBUG
NSLog ( #"Trust evaluation succeeded" );
#endif
if ( completionHandler )
{
completionHandler ( NSURLSessionAuthChallengeUseCredential, credential );
}
}
else
{
#ifdef DEBUG
NSLog ( #"Trust evaluation failed" );
#endif
if ( completionHandler )
{
completionHandler ( NSURLSessionAuthChallengeCancelAuthenticationChallenge, credential );
}
}
}
else if ( completionHandler )
{
completionHandler ( NSURLSessionAuthChallengePerformDefaultHandling, nil );
}
}
Here fhWebSupportDelegate.serverCertificates returns an array with the CA as well as the server certificate. Also I was extremely lenient in when I granted trust to the server as can be seen in the code.

Alamofire/Moya SSL request fails with ATS failed system trust. System Trust failed for X

I'm trying to make a https request with SSL and a self signed certificate through Alamofire with RxMoya, but it keeps giving me ATS failed system trust.
I've searched in many places for solutions, but I can't figure this out yet. I had a .crt certificate and converted to .der and put it on my main bundle Here's the Stacktrace:
2017-12-07 13:01:05.918360+0100 SmartBackpackerApp[86030:5429201] ATS failed system trust
2017-12-07 13:01:05.919271+0100 SmartBackpackerApp[86030:5429201] System Trust failed for [4:0x60000016b4c0]
2017-12-07 13:01:05.920438+0100 SmartBackpackerApp[86030:5429201] TIC SSL Trust Error [4:0x60000016b4c0]: 3:0
2017-12-07 13:01:05.921390+0100 SmartBackpackerApp[86030:5429201] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)
2017-12-07 13:01:05.922103+0100 SmartBackpackerApp[86030:5429201] Task <F73E31A0-8AC4-40B4-B80C-05182D3E5647>.<1> HTTP load failed (error code: -1200 [3:-9802])
2017-12-07 13:01:05.923528+0100 SmartBackpackerApp[86030:5429201] Task <F73E31A0-8AC4-40B4-B80C-05182D3E5647>.<1> finished with error - code: -1200
Moya_Logger: [07/12/2017 13:01:05] Response: Received empty network response for airlines("Easy Jet").
2017-12-07 13:01:05.945: SBAirlinesService.swift:39 (getAirlineInfo(name:)) -> Event error(underlying(Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x604000307980>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=(
"<cert(0x7fca6a853800) s: api.smartbackpacker.com i: api.smartbackpacker.com>"
), NSUnderlyingError=0x600000656080 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x604000307980>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
"<cert(0x7fca6a853800) s: api.smartbackpacker.com i: api.smartbackpacker.com>"
)}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://api.smartbackpackerapp.com/v1/airlines?name=Easy%20Jet, NSErrorFailingURLStringKey=https://api.smartbackpackerapp.com/v1/airlines?name=Easy%20Jet, NSErrorClientCertificateStateKey=0}, nil))
Here's the code I'm using to make the request :
let serverTrustPolicies = ["api.smartbackpackerapp.com": ServerTrustPolicy.pinCertificates(certificates: ServerTrustPolicy.certificates(), validateCertificateChain: false, validateHost: true)]
self.manager = Manager(configuration: URLSessionConfiguration.default, serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
self.manager.delegate.sessionDidReceiveChallenge = { [weak self] session, challenge in
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, let trust = challenge.protectionSpace.serverTrust {
disposition = URLSession.AuthChallengeDisposition.useCredential
credential = URLCredential(trust: trust)
} else {
if challenge.previousFailureCount > 0 {
disposition = .cancelAuthenticationChallenge
} else {
credential = self?.manager.session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
if credential != nil {
disposition = .useCredential
}
}
}
return (disposition, credential)
}
}
provider = RxMoyaProvider<SBApi>(manager: self.manager, plugins: plugins)
return provider.request(.airlines(name: name))
.debug()
.filterSuccessfulStatusCodes()
.mapObject(SBAirline.self)
.asObservable()
In my Info.plist file:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>https://api.smartbackpackerapp.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</dict>
OMG, I had the wrong domain on the Info.plist, it is without the https://
Lost a few hours with this. :(

How can I use an AVPlayer with HTTPS and self-signed server certificates?

I have a server that uses self-signed SSL certificates for HTTPS. I have the self-signed root cert bundled into my app. I'm able to get NSURLSession to use and validate the self-signed root cert by using SecTrustSetAnchorCertificates() in the -URLSession:didReceiveChallenge:completionHandler: delegate method.
When I try this with AVPlayer, however, I get an SSL error and playback fails. This is my AVAssetResourceLoader delegate implementation:
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForResponseToAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge.protectionSpace.authenticationMethod isEqual:NSURLAuthenticationMethodServerTrust]) {
SecTrustRef trust = challenge.protectionSpace.serverTrust;
SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)self.secTrustCertificates);
SecTrustResultType trustResult = kSecTrustResultInvalid;
OSStatus status = SecTrustEvaluate(trust, &trustResult);
if (status == errSecSuccess && (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed)) {
[challenge.sender useCredential:[NSURLCredential credentialForTrust:trust] forAuthenticationChallenge:challenge];
return YES;
} else {
[challenge.sender cancelAuthenticationChallenge:challenge];
return YES;
}
}
return NO;
}
The delegate gets called, and the trustResult equates to kSecTrustResultUnspecified (which means "trusted, without explicit user override"), as expected. However, playback fails shortly after, with the following AVPlayerItem.error:
Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSUnderlyingError=0x16c35720 {Error Domain=NSOSStatusErrorDomain Code=-1200 "(null)"}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made.}
How can I get AVPlayer to accept the SSL handshake?
This implementation has worked for me:
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader
shouldWaitForResponseToAuthenticationChallenge:(NSURLAuthenticationChallenge *)authenticationChallenge
{
//server trust
NSURLProtectionSpace *protectionSpace = authenticationChallenge.protectionSpace;
if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
[authenticationChallenge.sender useCredential:[NSURLCredential credentialForTrust:authenticationChallenge.protectionSpace.serverTrust] forAuthenticationChallenge:authenticationChallenge];
[authenticationChallenge.sender continueWithoutCredentialForAuthenticationChallenge:authenticationChallenge];
}
else { // other type: username password, client trust...
}
return YES;
}
However, it stopped working as of iOS 10.0.1 for a reason I have yet to discern. So this may or may not be helpful to you. Good luck!

iOS Objective C HTTPS request failing

I've searched extensively and have made the necessary changes (so i think) to conform to Appl'es ATS restrictions.
Private key 2048 bits or greater
openssl rsa -in privkey.pem -text -noout
Private-Key: (2048 bit)
Running ssl v1.2 on nginx
ssl verified at v1.2
And have even run the make nscurl utility to check the connection, all tests passed.
I also can verify that the server is functioning properly by making a GET on https from the browser and having everything work properly.
My though was that maybe the subdomain is causing an issue, so i updated the info.plist file to the following
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>boramash.com</key> (also tried gateway.boramash.com)
<dict>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
With what I believe to be everything working, I get the following errors.
2016-01-25 15:59:17.345 StripePlayground[2999:84984]
NSURLSession/NSURLConnection HTTP load failed
(kCFStreamErrorDomainSSL, -9802) 2016-01-25 15:59:17.348
StripePlayground[2999:84989] (null) 2016-01-25 15:59:17.348
StripePlayground[2999:84989] Error Domain=NSURLErrorDomain Code=-1200
"An SSL error has occurred and a secure connection to the server
cannot be made."
UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=, NSLocalizedRecoverySuggestion=Would you like to
connect to the server anyway?, _kCFStreamErrorDomainKey=3,
_kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey={type = immutable, count = 1, values = (
0 : )}, NSUnderlyingError=0x7fd97252e580 {Error
Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)"
UserInfo={_kCFStreamPropertySSLClientCertificateState=0,
kCFStreamPropertySSLPeerTrust=,
_kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates={type = immutable, count = 1, values = ( 0 :
)}}}, NSLocalizedDescription=An SSL error has occurred
and a secure connection to the server cannot be made.,
NSErrorFailingURLKey=https://gateway.boramash.com/stripe-add-customer,
NSErrorFailingURLStringKey=
prependingtext_for_stack_overflowhttps://gateway.boramash.com/stripe-add-customer,
NSErrorClientCertificateStateKey=0}
Also here is my request making code, pretty basic.
NSString *myrequest = #"https://gateway.boramash.com/stripe-add-customer";
// NSURL *newcustomerURL = [NSURL URLWithString:#"http//45.55.154.107:5050/create-customer"];
NSURL *newcustomerURL = [NSURL URLWithString: myrequest];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: newcustomerURL];
//request.HTTPBody = [[NSString stringWithFormat:#"customer_id=%#&first_name=%#&last_name=%#", testID, firstName, lastName] dataUsingEncoding: NSUTF8StringEncoding ];
request.HTTPMethod = #"GET";
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse *_Nullable response, NSError * _Nullable error) {
//print the result here - new customer has been created!
NSString *myresponse = [NSString stringWithFormat:#"%#", response];
NSString *myerror = [NSString stringWithFormat:#"%#", error];
NSLog(#"%#", myresponse);
NSLog(#"%#", myerror);
}] resume];
Any advice would be much appreciated!
TL;DR: For some reason, your server is not (always?) sending the intermediate certificate. Check your server configuration, and the certificate/intermediate certificate format (check for errors in your logs, and check that the server was properly restarted).
You can check on the command line with openssl s_client -connect gateway.boramash.com:443.
It currently returns:
depth=0 CN = gateway.boramash.com
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = gateway.boramash.com
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:/CN=gateway.boramash.com
i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X1
...
Verify return code: 21 (unable to verify the first certificate)
Which means it can't find a certificate to validate the signature on the certificate.
You want it to return:
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X1
verify return:1
depth=0 CN = gateway.boramash.com
verify return:1
---
Certificate chain
0 s:/CN=gateway.boramash.com
i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X1
...
Verify return code: 0 (ok)
(this was obtained by downloading the intermediate certificate and feeding it to openssl with -CAfile lets-encrypt-x1-cross-signed.pem).
You can also verify that the intermediate certificate is indeed not sent by adding -showcerts.
The weird part is that it indeed works (for me) in Safari, though it doesn't work in Firefox. Not quite sure what makes the difference (maybe the intermediate cert was cached from another request to a properly configured server using a certificate from the same CA), but double-check your server configuration (and the format of your certificate file) until openssl likes it, and iOS should like it too.
The issue isn't ATS, the issue is that you are receiving an invalid SSL certificate when you make the GET request to https://gateway.boramash.com/...
To get past this without replacing the certificate on the backend, you will need to implement the following delegate method:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler;
Here is an example:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
NSString *host = challenge.protectionSpace.host;
NSArray *acceptedDomains = #[#".boramash.com$"];
BOOL accept = NO;
for (NSString *pattern in acceptedDomains)
{
NSRange range = [host rangeOfString:pattern options:NSCaseInsensitiveSearch|NSRegularExpressionSearch];
if (range.location != NSNotFound)
{
accept = YES;
break;
}
}
if (accept)
{
NSLog(#"%#", [NSString stringWithFormat:#"WARNING: accepting an invalid certificate from host: %#", host]);
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
else
{
NSLog(#"%#", [NSString stringWithFormat:#"WARNING: discarding an invalid certificate from host: %#", host]);
}
}
}
Try adding NSExceptionAllowsInsecureHTTPLoads and setting that the true

Can't connect to https://test.salesforce.com with ios9

With iOS 9's improved security we are not able to connect to https://test.salesforce.com
We get
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
which based on SecureTransport.h is related to a handshake failure
errSSLPeerHandshakeFail = -9824, /* handshake failure */
We can disable security and still connect (using NSAllowsArbitraryLoads) but we would prefer to use the new more secure ios features.
We tried making an exception just for Salesforce but still get same error
<key>NSExceptionDomains</key>
<dict>
<key>salesforce.com</key>
<dict>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
edit(to add a question): What is the best way to make requests from ios apps to https://test.salesforce.com as securely as possible? (Do I need to wait for Salesforce to update their certificates? Or is there something more under my control?)
I ran the nscurl command line utility on the OAuth 2.0 endpoint (ROPC flow, sandbox environment):
nscurl --ats-diagnostics --verbose https://test.salesforce.com/services/oauth2/token
TL; DR:
I found out that the most secure setup that will PASS is the following:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>salesforce.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
</dict>
</dict>
(No need to lower the minimum required TLS version. Also, Salesforce is dropping support for TLS 1.0)
The full results of the nscurl diagnostic:
Starting ATS Diagnostics
Configuring ATS Info.plist keys and displaying the result of HTTPS loads to https://test.salesforce.com/services/oauth2/token.
A test will "PASS" if URLSession:task:didCompleteWithError: returns a nil error.
================================================================================
Default ATS Secure Connection
---
ATS Default Connection
ATS Dictionary:
{
}
2016-06-17 10:49:21.533 nscurl[975:53055] CFNetwork SSLHandshake failed (-9824)
2016-06-17 10:49:21.533 nscurl[975:53055] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_kCFStreamErrorCodeKey=-9824, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSUnderlyingError=0x7fd67d100230 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9824, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9824}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://test.salesforce.com/services/oauth2/token, NSErrorFailingURLStringKey=https://test.salesforce.com/services/oauth2/token, _kCFStreamErrorDomainKey=3}
---
================================================================================
Allowing Arbitrary Loads
---
Allow All Loads
ATS Dictionary:
{
NSAllowsArbitraryLoads = true;
}
Result : PASS
---
================================================================================
Configuring TLS exceptions for test.salesforce.com
---
TLSv1.2
ATS Dictionary:
{
NSExceptionDomains = {
"test.salesforce.com" = {
NSExceptionMinimumTLSVersion = "TLSv1.2";
};
};
}
2016-06-17 10:49:21.760 nscurl[975:53055] CFNetwork SSLHandshake failed (-9824)
2016-06-17 10:49:21.760 nscurl[975:53055] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_kCFStreamErrorCodeKey=-9824, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSUnderlyingError=0x7fd67d000aa0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9824, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9824}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://test.salesforce.com/services/oauth2/token, NSErrorFailingURLStringKey=https://test.salesforce.com/services/oauth2/token, _kCFStreamErrorDomainKey=3}
---
---
TLSv1.1
ATS Dictionary:
{
NSExceptionDomains = {
"test.salesforce.com" = {
NSExceptionMinimumTLSVersion = "TLSv1.1";
};
};
}
2016-06-17 10:49:21.817 nscurl[975:53055] CFNetwork SSLHandshake failed (-9824)
2016-06-17 10:49:21.817 nscurl[975:53055] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_kCFStreamErrorCodeKey=-9824, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSUnderlyingError=0x7fd67b49bf10 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9824, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9824}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://test.salesforce.com/services/oauth2/token, NSErrorFailingURLStringKey=https://test.salesforce.com/services/oauth2/token, _kCFStreamErrorDomainKey=3}
---
---
TLSv1.0
ATS Dictionary:
{
NSExceptionDomains = {
"test.salesforce.com" = {
NSExceptionMinimumTLSVersion = "TLSv1.0";
};
};
}
2016-06-17 10:49:21.878 nscurl[975:53055] CFNetwork SSLHandshake failed (-9824)
2016-06-17 10:49:21.879 nscurl[975:53055] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
Result : FAIL
Error : Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_kCFStreamErrorCodeKey=-9824, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSUnderlyingError=0x7fd67d1002c0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, _kCFNetworkCFStreamSSLErrorOriginalValue=-9824, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9824}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://test.salesforce.com/services/oauth2/token, NSErrorFailingURLStringKey=https://test.salesforce.com/services/oauth2/token, _kCFStreamErrorDomainKey=3}
---
================================================================================
Configuring PFS exceptions for test.salesforce.com
---
Disabling Perfect Forward Secrecy
ATS Dictionary:
{
NSExceptionDomains = {
"test.salesforce.com" = {
NSExceptionRequiresForwardSecrecy = false;
};
};
}
Result : PASS
---
================================================================================
Configuring PFS exceptions and allowing insecure HTTP for test.salesforce.com
---
Disabling Perfect Forward Secrecy and Allowing Insecure HTTP
ATS Dictionary:
{
NSExceptionDomains = {
"test.salesforce.com" = {
NSExceptionAllowsInsecureHTTPLoads = true;
NSExceptionRequiresForwardSecrecy = false;
};
};
}
Result : PASS
---
================================================================================
Configuring TLS exceptions with PFS disabled for test.salesforce.com
---
TLSv1.2 with PFS disabled
ATS Dictionary:
{
NSExceptionDomains = {
"test.salesforce.com" = {
NSExceptionMinimumTLSVersion = "TLSv1.2";
NSExceptionRequiresForwardSecrecy = false;
};
};
}
Result : PASS
---
---
TLSv1.1 with PFS disabled
ATS Dictionary:
{
NSExceptionDomains = {
"test.salesforce.com" = {
NSExceptionMinimumTLSVersion = "TLSv1.1";
NSExceptionRequiresForwardSecrecy = false;
};
};
}
Result : PASS
---
---
TLSv1.0 with PFS disabled
ATS Dictionary:
{
NSExceptionDomains = {
"test.salesforce.com" = {
NSExceptionMinimumTLSVersion = "TLSv1.0";
NSExceptionRequiresForwardSecrecy = false;
};
};
}
Result : PASS
---
================================================================================
Configuring TLS exceptions with PFS disabled and insecure HTTP allowed for test.salesforce.com
---
TLSv1.2 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
NSExceptionDomains = {
"test.salesforce.com" = {
NSExceptionAllowsInsecureHTTPLoads = true;
NSExceptionMinimumTLSVersion = "TLSv1.2";
NSExceptionRequiresForwardSecrecy = false;
};
};
}
Result : PASS
---
---
TLSv1.1 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
NSExceptionDomains = {
"test.salesforce.com" = {
NSExceptionAllowsInsecureHTTPLoads = true;
NSExceptionMinimumTLSVersion = "TLSv1.1";
NSExceptionRequiresForwardSecrecy = false;
};
};
}
Result : PASS
---
---
TLSv1.0 with PFS disabled and insecure HTTP allowed
ATS Dictionary:
{
NSExceptionDomains = {
"test.salesforce.com" = {
NSExceptionAllowsInsecureHTTPLoads = true;
NSExceptionMinimumTLSVersion = "TLSv1.0";
NSExceptionRequiresForwardSecrecy = false;
};
};
}
Result : PASS
---
================================================================================
To continue down the path of making an exception, try adding force.com to the exception list (instead of only salesforce.com)
So... add this as another exception key:
<key>NSExceptionDomains</key>
<dict>
<key>force.com</key>
<dict>
<key>NSExceptionMinimumTLSVersion</key>
<string>TLSv1.0</string>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
I also await a more permanent solution.

Resources