iOS Objective C HTTPS request failing - ios

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

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.

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

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

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!

NSURLSession at https (kCFErrorDomainCFNetwork Code=310) [duplicate]

I'm fairly new to consuming webservices using SSL channel. After fairly good search I had found a way to perform SSL/HTTPS authentication using NSURLConnection delegate APIs. Following is the code snippet that does the actual authentication thing:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
[self printLogToConsole:#"Authenticating...."];
[self printLogToConsole:[NSString stringWithFormat:#"\n%#\n", [challenge description]]];
NSLog(#"\n\nserverTrust: %#\n", [[challenge protectionSpace] serverTrust]);
/* Extract the server certificate for trust validation
*/
NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
assert(protectionSpace);
SecTrustRef trust = [protectionSpace serverTrust];
assert(trust);
CFRetain(trust); // Make sure this thing stays around until we're done with it
NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];
/* On iOS
* we need to convert it to 'der' certificate. It can be done easily through Terminal as follows:
* $ openssl x509 -in certificate.pem -outform der -out rootcert.der
*/
NSString *path = [[NSBundle mainBundle] pathForResource:#"rootcert" ofType:#"der"];
assert(path);
NSData *data = [NSData dataWithContentsOfFile:path];
assert(data);
/* Set up the array of certificates, we will authenticate against and create credentials */
SecCertificateRef rtCertificate = SecCertificateCreateWithData(NULL, CFBridgingRetain(data));
const void *array[1] = { rtCertificate };
trustedCerts = CFArrayCreate(NULL, array, 1, &kCFTypeArrayCallBacks);
CFRelease(rtCertificate); // for completeness, really does not matter
/* Build up the trust anchor using our root cert */
int err;
SecTrustResultType trustResult = 0;
err = SecTrustSetAnchorCertificates(trust, trustedCerts);
if (err == noErr) {
err = SecTrustEvaluate(trust, &trustResult);
}
CFRelease(trust); // OK, now we're done with it
[self printLogToConsole:[NSString stringWithFormat:#"trustResult: %d\n", trustResult]];
/* http://developer.apple.com/library/mac/#qa/qa1360/_index.html
*/
BOOL trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultConfirm) || (trustResult == kSecTrustResultUnspecified));
// Return based on whether we decided to trust or not
if (trusted) {
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
[self printLogToConsole:#"Success! Trust validation successful."];
} else {
[self printLogToConsole:#"Failed! Trust evaluation failed for service root certificate.\n"];
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
But I'm getting following error:
2012-06-11 17:10:12.541 SecureLogin[3424:f803] Error during connection: Error Domain=NSURLErrorDomain Code=-1012 "The operation couldn’t be completed. (NSURLErrorDomain error -1012.)" UserInfo=0x682c790 {NSErrorFailingURLKey=https://staging.esecure.url/authentication/signin/merchants, NSErrorFailingURLStringKey=https://staging.esecure.url/authentication/signin/merchants}
I'm using the same certificate that I got from the server and converted it to 'der' format. I'm building app for iOS 5.x.
I'm not sure whether I'm missing out on something. Let me know of your suggestions.
Thanks.
EDIT
After examining the certificate here how the output looks:
Let me know if there is something wrong.
Thanks.
I cannot tell if your code is valid or not, because I use RestKit for consuming REST interfaces, however the most common problem that results in NSURLErrorDomain Code=-1012 is that the self-signed certificate does not have subject alternative name extension pointing to the web service if address.
To examine your certificate, download the Portecle app, very useful if you need to look inside ssl certificates. Run it and choose Examine->Examine Certificate from the menu and navigate to your certificate. You will see basic information about your certificate, now press the Examine button, then Subject alternative name, and make sure proper ip address of your web service is there. If not, you need to create the certificate again with this information in place.
I did figure out how to resolve this issue.
I ended up comparing the client and server trust certificates, byte-by-byte. Although there could be another way to resolve such issues of self-signed certificate, but for this solution did work.
Here is how I'm doing comparison of the client and server certificates, byte-by-byte, using their CFData objects(you can also reference 'AdvancedURLConnections' example code provided by Apple):
success = NO;
pServerCert = SecTrustGetLeafCertificate(trust);
if (clientCert != NULL) {
CFDataRef clientCertData;
CFDataRef serverCertData;
clientCertData = SecCertificateCopyData(clientCert);
serverCertData = SecCertificateCopyData(pServerCert);
assert(clientCertData != NULL);
assert(serverCertData != NULL);
success = CFEqual(clientCertData, serverCertData);
CFRelease(clientCertData);
CFRelease(serverCertData);
}
if (success) {
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
[self printLogToConsole:#"Success! Trust validation successful."];
} else {
[self printLogToConsole:#"Failed! Trust evaluation failed for service root certificate.\n"];
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
Hope this will help someone, who is looking for solution of similar issue,
Thanks.

iOS and SSL: Unable to validate self-signed server certificate

I'm fairly new to consuming webservices using SSL channel. After fairly good search I had found a way to perform SSL/HTTPS authentication using NSURLConnection delegate APIs. Following is the code snippet that does the actual authentication thing:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
[self printLogToConsole:#"Authenticating...."];
[self printLogToConsole:[NSString stringWithFormat:#"\n%#\n", [challenge description]]];
NSLog(#"\n\nserverTrust: %#\n", [[challenge protectionSpace] serverTrust]);
/* Extract the server certificate for trust validation
*/
NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
assert(protectionSpace);
SecTrustRef trust = [protectionSpace serverTrust];
assert(trust);
CFRetain(trust); // Make sure this thing stays around until we're done with it
NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];
/* On iOS
* we need to convert it to 'der' certificate. It can be done easily through Terminal as follows:
* $ openssl x509 -in certificate.pem -outform der -out rootcert.der
*/
NSString *path = [[NSBundle mainBundle] pathForResource:#"rootcert" ofType:#"der"];
assert(path);
NSData *data = [NSData dataWithContentsOfFile:path];
assert(data);
/* Set up the array of certificates, we will authenticate against and create credentials */
SecCertificateRef rtCertificate = SecCertificateCreateWithData(NULL, CFBridgingRetain(data));
const void *array[1] = { rtCertificate };
trustedCerts = CFArrayCreate(NULL, array, 1, &kCFTypeArrayCallBacks);
CFRelease(rtCertificate); // for completeness, really does not matter
/* Build up the trust anchor using our root cert */
int err;
SecTrustResultType trustResult = 0;
err = SecTrustSetAnchorCertificates(trust, trustedCerts);
if (err == noErr) {
err = SecTrustEvaluate(trust, &trustResult);
}
CFRelease(trust); // OK, now we're done with it
[self printLogToConsole:[NSString stringWithFormat:#"trustResult: %d\n", trustResult]];
/* http://developer.apple.com/library/mac/#qa/qa1360/_index.html
*/
BOOL trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultConfirm) || (trustResult == kSecTrustResultUnspecified));
// Return based on whether we decided to trust or not
if (trusted) {
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
[self printLogToConsole:#"Success! Trust validation successful."];
} else {
[self printLogToConsole:#"Failed! Trust evaluation failed for service root certificate.\n"];
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
But I'm getting following error:
2012-06-11 17:10:12.541 SecureLogin[3424:f803] Error during connection: Error Domain=NSURLErrorDomain Code=-1012 "The operation couldn’t be completed. (NSURLErrorDomain error -1012.)" UserInfo=0x682c790 {NSErrorFailingURLKey=https://staging.esecure.url/authentication/signin/merchants, NSErrorFailingURLStringKey=https://staging.esecure.url/authentication/signin/merchants}
I'm using the same certificate that I got from the server and converted it to 'der' format. I'm building app for iOS 5.x.
I'm not sure whether I'm missing out on something. Let me know of your suggestions.
Thanks.
EDIT
After examining the certificate here how the output looks:
Let me know if there is something wrong.
Thanks.
I cannot tell if your code is valid or not, because I use RestKit for consuming REST interfaces, however the most common problem that results in NSURLErrorDomain Code=-1012 is that the self-signed certificate does not have subject alternative name extension pointing to the web service if address.
To examine your certificate, download the Portecle app, very useful if you need to look inside ssl certificates. Run it and choose Examine->Examine Certificate from the menu and navigate to your certificate. You will see basic information about your certificate, now press the Examine button, then Subject alternative name, and make sure proper ip address of your web service is there. If not, you need to create the certificate again with this information in place.
I did figure out how to resolve this issue.
I ended up comparing the client and server trust certificates, byte-by-byte. Although there could be another way to resolve such issues of self-signed certificate, but for this solution did work.
Here is how I'm doing comparison of the client and server certificates, byte-by-byte, using their CFData objects(you can also reference 'AdvancedURLConnections' example code provided by Apple):
success = NO;
pServerCert = SecTrustGetLeafCertificate(trust);
if (clientCert != NULL) {
CFDataRef clientCertData;
CFDataRef serverCertData;
clientCertData = SecCertificateCopyData(clientCert);
serverCertData = SecCertificateCopyData(pServerCert);
assert(clientCertData != NULL);
assert(serverCertData != NULL);
success = CFEqual(clientCertData, serverCertData);
CFRelease(clientCertData);
CFRelease(serverCertData);
}
if (success) {
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
[self printLogToConsole:#"Success! Trust validation successful."];
} else {
[self printLogToConsole:#"Failed! Trust evaluation failed for service root certificate.\n"];
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
Hope this will help someone, who is looking for solution of similar issue,
Thanks.

Resources