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

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.

Related

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 how to reset NSURLCredential for a given server or make my app forget it ever spoke to that server?

I'm looking for a way to implement being able to switch which NSURLCredential my web app is using to talk to a given server.
Is there a way for me to "reset" NSURLCredential ssl credentials for a given server trust and force the entire handshaking process to repeat using different set of credentials? In other words, I want my web app to forget it ever spoke to this server.
I think the code snippet below is what is retrieving the credential that I previously have evaluated.
- (void)customHTTPProtocol:(CustomHTTPProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSURLCredential * credential = nil;
SecTrustRef trust;
NSURLProtectionSpace* protectionSpace = challenge.protectionSpace;
if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//this verifies that we are talking to a server that we trust by checking it's certificate
// Extract the SecTrust object from the challenge, apply our trusted anchors to that
// object, and then evaluate the trust. If it's OK, create a credential and use
// that to resolve the authentication challenge. If anything goes wrong, resolve
// the challenge with nil, which continues without a credential, which causes the
// connection to fail.
trust = [protectionSpace serverTrust];
if (trust == NULL) {
assert(NO);
} else {
// Just Ignore Self Signed Certs
SecTrustResultType result;
//This takes the serverTrust object and checkes it against your keychain
SecTrustEvaluate(protectionSpace.serverTrust, &result);
//if we want to ignore invalid server for certificates, we just accept the server
credential = [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust];
if(result == kSecTrustResultProceed ||
// result == kSecTrustResultConfirm || // Deprecated - Remove?
result == kSecTrustResultUnspecified) {
[challenge.sender useCredential:credential forAuthenticationChallenge: challenge];
}
}
[protocol resolveAuthenticationChallenge:challenge withCredential:credential];
}
//... more ways to resolve challenge
}
I tried various way for achieving this but all in vein.
But 1 simple trick worked.
To force my web page to re-authenticate, I would add below at the end of the request url.
&r=1100910192 **(timestamp)**.
So instead of using the cached authenticated credential it infact re-authenticated.
In other cases just adding a # at the end of the request worked:
[NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://localhost:8080/some.json#"]]
I hope it helped.

NSURLSession with backgroundSessionConfiguration can not work with user certificate

I found a wired thing about NSURLSession when using background session configuration.
We use a self asigned certificate when contact with server, an implement:
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.taskDidReceiveAuthenticationChallenge) {
disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
NSLog(#"ServerTrust:%#", task.originalRequest.URL);
} else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
if (self.clientCertCredential && [challenge previousFailureCount] == 0) {
credential = self.clientCertCredential;
disposition = NSURLSessionAuthChallengeUseCredential;
NSLog(#"ClientCert:%#", task.originalRequest.URL);
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
When using defaultSessionConfiguration it works perfect, but when I change the session configuration to background session configration, This delegate method will be called in a loop, an none of other delegate method will be called, and this request will never complete.
Here is the console output:
2014-08-11 15:36:01.204 OneBox[1736:a413] ServerTrust:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents
2014-08-11 15:36:01.232 OneBox[1736:1413] ClientCert:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents
2014-08-11 15:36:02.068 OneBox[1736:8c03] ServerTrust:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents
2014-08-11 15:36:02.076 OneBox[1736:1413] ClientCert:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents
2014-08-11 15:36:12.728 OneBox[1736:1413] ServerTrust:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents
2014-08-11 15:36:12.735 OneBox[1736:1413] ClientCert:https://demo.mycompany.com/api/v1/files/351/1/9cc69106455d11599a08ed978fbdbe1d/contents
OK I know this is an old question but I've been banging my head over the walls on this issue also, so hoping this will help somebody, here is my solution to the problem.
To be fair, my "solution" feels more like a convoluted workaround to me than a proper solution, but at least it works.
The short answer is that the key to make it work is to use NSURLProtectionSpace to set a default permanent credential for all sessions. This prevents the delegate from being called when presented with a challenge of type NSURLAuthenticationMethodClientCertificate.
The long answer follows below.
In your code, this will not work :
credential = self.clientCertCredential;
disposition = NSURLSessionAuthChallengeUseCredential;
//
// Redacted for clarity
//
completionHandler(disposition, credential);
Because in a background session, the delegate cannot have access to self.clientCertCredential (God only knows why).
However I found out that the background session will not try to call the delegate if you have previously defined a default credential in an NSURLProtectionSpace.
So scratch all of your else if block and instead do the following :
NSURLProtectionSpace *space = [NSURLProtectionSpace
initWithHost:#"your_address"
port:your_port
protocol:#"https"
realm:#"your_realm"
authenticationMethod:NSURLAuthenticationMethodClientCertificate];
[[NSURLCredentialStorage sharedCredentialStorage]
setDefaultCredential:self.clientCertCredential
forProtectionSpace:space];
If the host, port and realm parameters match exactly those of your server, then when the challenge is presented to the background session, the challenge.protectionSpace will find the default credential automagically.
In order for it to work, this code will need to be executed before you try to make any request with the background session. You can do it whenever you load the client certificate into self.clientCertCredential for instance.
But beware !!! There is one more subtlety here. Whenever you do so, make sure you load the certificate using the persistence option NSURLCredentialPersistencePermanent. Otherwise it will not work.
One last note. Depending on your use case, the drawback of using this hack is that you might find yourself with a bunch of permanently persisted credentials if several NSURLProtectionSpaces. You might then have to do some housekeeping after setting defaultCredential for the NSURLCredentialStorage class. This is beyond the scope of this answer, but the class has some convenience methods such as -removeCredential:forProtectionSpace: which are documented here https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLCredentialStorage_Class/

iOS - programmatically trusting CA (certificate authority)

I'm trying around with an app that is to securely connect from an iPhone client to a TLS server via sockets/streams for general data-exchange. For that purpose I set up an own CA with the mac keychain-tool and included the certificat in the code bundle.
Now my app should trust any server certificate issued by that CA. (I do not care how other apps treat those certs, I assume that they will not trust it because of the sandbox.)
I have found several similar issues online but seem to have gotten something wrong.
Connecting with the server seems to work fine if I drag & drop the CA certificate into the Simulator and manually accept to trust it.
However, when I try to establish trust for the CA cert programmatically my connection attempts later to the server are refused, despite the code below does not generate errors.
Therefore I must have got the certificate implementation part wrong... Any ideas?
Many thanks in advance!
NSString* certPath = [[NSBundle mainBundle] pathForResource:#"MyTestCA2" ofType:#"cer"]; //cer = CA certificate
NSData* certData = [NSData dataWithContentsOfFile:certPath];
SecCertificateRef cert;
if( [certData length] ) {
cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
if( cert != NULL ) {
CFStringRef certSummary = SecCertificateCopySubjectSummary(cert);
NSString* summaryString = [[NSString alloc] initWithString:(__bridge NSString*)certSummary];
NSLog(#"CERT SUMMARY: %#", summaryString);
certSummary = nil;
} else {
NSLog(#" *** ERROR *** trying to create the SSL certificate from data located at %#, but failed", certPath);
}
}
OSStatus err = noErr;
CFTypeRef result;
NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kSecClassCertificate, kSecClass,
cert, kSecValueRef,
nil];
err = SecItemAdd((__bridge CFDictionaryRef)dict, &result);
if(err!=noErr) NSLog(#"error while importing");
if (err==errSecDuplicateItem) NSLog(#"Cert already installed");
NSLog(#":%i",(int)err);
assert(err==noErr||err==errSecDuplicateItem); // accept no errors other than duplicate
err = noErr;
SecTrustRef trust;
err = SecTrustCreateWithCertificates(cert, SecPolicyCreateBasicX509() ,&trust);
assert(err==noErr);
err = noErr;
CFMutableArrayRef newAnchorArray = CFArrayCreateMutable(kCFAllocatorDefault,0,&kCFTypeArrayCallBacks);
CFArrayAppendValue(newAnchorArray,cert);
err = SecTrustSetAnchorCertificates(trust, newAnchorArray);
assert(err==noErr);
SecTrustResultType trustResult;
err=SecTrustEvaluate(trust,&trustResult);
assert(err==noErr);
cert=nil;
I did not try to run the partial code, but I have some code (provided below) that I know works. I use it to trust my internal CA.
err=SecTrustEvaluate(trust,&trustResult);
assert(err==noErr);
trustResult is what you are interested in, not the err return from SecTrustEvaluate. err tells you if the API call succeeded/failed; it does not tell you the result of the trust evaluation.
I think you have two strategies here. First is to look for "success" in trustResult with values kSecTrustResultProceed or kSecTrustResultUnspecified. Its "success" because its not "prompt", its not "try to recover" and its not "failure".
The second strategy is "not failure" in trustResult with values kSecTrustResultDeny, kSecTrustResultFatalTrustFailure or kSecTrustResultOtherError. That is, as long as trustResult is not one of those values, then proceed as success. Ignore prompting the user to trust a certificate because they will not understand the prompt and "tap through".
Below is the code I use in NSURLConnection delegate's -didReceiveAuthenticationChallenge:. It expects an ASN.1/DER encoded certificate (named ca-cert.der). It uses Strategy 1 described above. If you use the code in the #ifdef 0, then its using Strategy 2.
I think Apple's Overriding TLS Chain Validation Correctly, Apple's Tech Note TN2232, HTTPS Server Trust Evaluation and Apple's Technical Q&A QA1360, Describing the kSecTrustResultUnspecified error might be useful to you.
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:
(NSURLAuthenticationChallenge *)challenge
{
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
return [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust]
forAuthenticationChallenge: challenge];
if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust])
{
do
{
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
NSCAssert(serverTrust != nil, #"serverTrust is nil");
if(nil == serverTrust)
break; /* failed */
NSData* caCert = [NSData dataWithContentsOfFile:#"ca-cert.der"];
NSCAssert(caCert != nil, #"caCert is nil");
if(nil == caCert)
break; /* failed */
SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
NSCAssert(caRef != nil, #"caRef is nil");
if(nil == caRef)
break; /* failed */
NSArray* caArray = [NSArray arrayWithObject:(__bridge id)(caRef)];
NSCAssert(caArray != nil, #"caArray is nil");
if(nil == caArray)
break; /* failed */
OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
NSCAssert(errSecSuccess == status, #"SecTrustSetAnchorCertificates failed");
if(!(errSecSuccess == status))
break; /* failed */
SecTrustResultType result = -1;
status = SecTrustEvaluate(serverTrust, &result);
if(!(errSecSuccess == status))
break; /* failed */
NSLog(#"Result: %d", result);
/* https://developer.apple.com/library/ios/technotes/tn2232/_index.html */
/* https://developer.apple.com/library/mac/qa/qa1360/_index.html */
/* kSecTrustResultUnspecified and kSecTrustResultProceed are success */
if(result != kSecTrustResultUnspecified && result != kSecTrustResultProceed)
break; /* failed */
#if 0
/* Treat kSecTrustResultConfirm and kSecTrustResultRecoverableTrustFailure as success */
/* since the user will likely tap-through to see the dancing bunnies */
if(result == kSecTrustResultDeny || result == kSecTrustResultFatalTrustFailure || result == kSecTrustResultOtherError)
break; /* failed to trust cert (good in this case) */
#endif
// The only good exit point
return [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust]
forAuthenticationChallenge: challenge];
} while(0);
}
// Bad dog
return [[challenge sender] cancelAuthenticationChallenge: challenge];
}
Despite all efforts I have not been able to duplicate with NS/CF-Streams, what jww obviously has managed with NSURLConnection. (As before when drag-dropping the CA-cert on the simulator and accepting it as trusted, the TLS stream works fine, but doing it programmatically does not succeed.)
Therefore I've done a little more search on the topic and actually found an additional post by aeternusrahl that was answered by Rob Napier:Post
...in which the following blog entry from Heath Borders is cited:
Blog
For everyone that has a similar issue as me, I can very much recommend those links – they provide good insight!
Without replicating the entire content of those links: Rob Napier says '...there is, as far as I've ever discovered, only one trusted anchor list in that keychain, shared by everyone. This doesn't help you a lot with socket.io, because it doesn't give you access to its NSURLConnection delegate methods. You'd have to modify socket.io to accept a trust anchor.'
As all authors above have outstanding reputation and I am pretty new to iOS programming, I would not dare to disagree with either one! But since I am having a really hard time consolidating the posts, I would very much appreciate any clarification on preconditions in the posts I might have accidentally skipped.
For me it seems, jww does not consider disabling automatic TLS validation necessary when using own root-certs (see his answer to my comment on his first post). Then, Heath Borders/Rob Napier, seem to suggest that disabling certificate chain validation in ssl settings is necessary for sockets (again if I got it right.)
Basically I see the following possible explanations:
A) jww is referring to NSURLConnections only, whose delegate seems more powerful than the one you're stuck with when working with NS/CF streams
B) the situation has changed since the post from Rob Napier / the blog from Heath Borders
C) I got everything entirely wrong and apologize in advance in that case!
While A) seems somewhat more probable I'm kind of hoping for B)...
I'd be very thankful for additional insight!
PS. I hope placing the above as an answer does not violate any rules... It's certainly not the correct / complete answer, but starting a entirely new question seems neither useful and unfortunately the text is too long for any comment. If there is a better way to include additional information (as the above new links) while listing points that are still unclear, let me know.

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