iOS NSURLConnection with SSL: Accepting an expired self-signed certificate - ios

I have a shipped app that uses the following code to secure an SSL connection using a self-signed certificate that is shipped with the app.
- (void) connection:(NSURLConnection *)conn willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(#"didReceiveAuthenticationChallenge %# FAILURES=%d", [[challenge protectionSpace] authenticationMethod], (int)[challenge previousFailureCount]);
/* Setup */
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];
/* Build up the trust anchor using our root cert */
int err;
SecTrustResultType trustResult = 0;
err = SecTrustSetAnchorCertificates(trust, certs);
if (err == noErr) {
err = SecTrustEvaluate(trust,&trustResult);
}
CFRelease(trust); // OK, now we're done with it
// 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];
} else {
NSLog(#"Trust evaluation failed for service root certificate");
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
Unfortunately, I made a huge oversight. SSL certificates expire. So when the expiry date passes I'm assuming the app is going to stop working properly! There's nothing I can do for the current version of the app - that's going to stop working soon.
I need to release an update and in order to avoid this in the future I would like to allow the self-signed certificate even if it has expired.
How do I modify my code above to trust the certificate even if it has expired?

NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:Nil];
...
...
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
if([challenge.protectionSpace.host isEqualToString:#"mydomain.com"]){
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
}
}

Related

Certificate pinning in Xcode

I got below code for certificate pinning in Android
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
.add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
.add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
.add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
.build();
How do i achieve same task in IOS using NSURLSession method?
Got some reference code here
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));
NSString *cerPath = [[NSBundle mainBundle] pathForResource:#"MyLocalCertificate" ofType:#"cer"];
NSData *localCertData = [NSData dataWithContentsOfFile:cerPath];
if ([remoteCertificateData isEqualToData:localCertData]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
EDIT PART
I got below solution, which delegate function is called automatically in NSURLSession, can anyone explain how it will work ? ALSO Need to send multiplier certificate how do i do it?
(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler
{
NSString *authMethod = [[challenge protectionSpace] authenticationMethod];
if ([authMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
} else {
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));
NSString *cerPath = [[NSBundle mainBundle] pathForResource:#"MyLocalCertificate" ofType:#"cer"];
NSData *localCertData = [NSData dataWithContentsOfFile:cerPath];
NSURLCredential *credential;
if ([remoteCertificateData isEqualToData:localCertData]) {
credential = [NSURLCredential credentialForTrust:serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
NSLog(#"Finished Challenge");
}
}
The if block skips certificate pinning if the authentication method is NSURLAuthenticationMethodServerTrust. I'm not quite sure why you'd do that---you'll have to look at the source where you got this code snippet and see what it's requirements are.
If the authentication method is anything else, the else block does certificate pinning.
The variable serverTrust is sent to the SSL transaction state from the server. The main thing here is that it has a chain of certificates that authenticate the server. In the next line, certificate is set to the leaf certificate in the chain, i.e. the server's certificate.
remoteCertificateData is essentially a big binary blob representing the information in the certificate. The call to CFBridgingRelease is needed for memory management (all the CFxxx functions are C/C++ functions, not Objective-C, and the memory management is a little bit more complicated than normal).
localCertData is a binary blob of the information in the local copy of the certificate. Note that iOS apps are (more or less) a collection of files including the executable as well as various resources, etc. As part of the build process, you would arrange for a copy of the server's certificate to be included in thes collection (NSBundle) of files. The cerPath variable is set to the file path of the local copy of the certificate.
Finally, we check to see if the two binary blobs are equal. If not, then the certificate from the server is bogus and we don't proceed with the request.
I'm not completely sure what you mean by "Need to send multiplier certificate". Judging from the Java code you reference I am assuming what you mean is that you want to compare the server certificate with multiple local certificates. In that case, something (roughly) like the following (note: untested code):
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));
BOOL match = NO;
NSURLCredential *credential;
for (NSString *path in [[NSBundle mainBundle] pathsForResourcesOfType:#"cer" inDirectory:#"."]) {
NSData *localCertData = [NSData dataWithContentsOfFile:path];
if ([remoteCertificateData isEqualToData:localCertData]) {
credential = [NSURLCredential credentialForTrust:serverTrust];
match = YES;
break;
}
}
if (match) {
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
} else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
NSLog(#"Finished Challenge");

Intermittent SSL error in iOS for NSURLConnection

I am connecting to a server with a custom SSL which is no longer a valid SSL Certificate. I have updated my info.plist to allow arbitrary and added code to bypass challenge on NSURLConnection delegate.
-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSLog(#"willSendRequestForAuthenticationChallenge");
BOOL trusted = NO;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSString *thePath = [[NSBundle mainBundle] pathForResource:#"cert" ofType:#"der"];
NSData *certData = [[NSData alloc] initWithContentsOfFile:thePath];
CFDataRef certDataRef = (__bridge_retained CFDataRef)certData;
SecCertificateRef cert = SecCertificateCreateWithData(NULL, certDataRef);
SecPolicyRef policyRef = SecPolicyCreateBasicX509();
SecCertificateRef certArray[1] = { cert };
CFArrayRef certArrayRef = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
SecTrustSetAnchorCertificates(serverTrust, certArrayRef);
SecTrustResultType trustResult;
SecTrustEvaluate(serverTrust, &trustResult);
trusted = (trustResult == kSecTrustResultUnspecified);
CFRelease(certArrayRef);
CFRelease(policyRef);
CFRelease(cert);
CFRelease(certDataRef);
}
if (trusted) {
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
} else {
[challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
}
}
However, intermittently happening, I am getting the SSL error. It is not calling the delegate willSendRequestForAuthenticationChallenge instead going directly to didFailWithError delegate.
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
Your code is subtly wrong. The cert is trusted if the result status is either:
kSecTrustResultUnspecified
kSecTrustResultProceed
Additionally, for an expired cert, the code above should see a kSecTrustResultRecoverableTrustFailure error unless you call:
SecTrustSetOptions(serverTrust,kSecTrustOptionAllowExpired);
However, allowing expired certificates is strongly discouraged.
Unless by "no longer valid", you mean that it violates the minimum requirements for iOS 9, in which case yes, you'll get exactly the behavior you described when running on iOS 9, if compiled against the iOS 9 SDK. For more info, do a Google search for App Transport Security.

NSURLAuthenticationMethodServerTrust challenge called every time

Situation: I am building an enterprise App which can be used for sharing Pictures etc. It was using HTTP and now I moved to HTTPS as it is going public. I have the client certificates (.cer) bundled in the App.
I have followed through most of the discussions here and the post by Eskimo on AppDev forum (https://devforums.apple.com/message/737087#737087) and here (http://www.techrepublic.com/blog/software-engineer/use-https-certificate-handling-to-protect-your-ios-app/). But my problem is that my App has multiple calls to the same HTTPS server and for each time I keep getting the NSURLAuthenticationMethodServerTrust challenge. Is this normal? 'coz for each challenge I lose close to 4 seconds of transaction time and my pictures take time to load.
Also, please see the NSURLAuthenticationMethodDefault challenge IF condition comment, really not sure why that is going this way.
So to sum up.
1. Do I have to check certificates and verify for every HTTPS call?
2. Why is the Authentication challenge (NSURLAuthenticationMethodDefault) not working if I put inside the IF block.
Any help here would be appreciated.
-(BOOL) shouldTrustProtectionSpace:(NSURLProtectionSpace *) protectionSpace{
NSString *certPath = [[NSBundle mainBundle] pathForResource:#"file.something.com" ofType:#"cer"];
NSData *certData = [[NSData alloc] initWithContentsOfFile:certPath];
CFDataRef certDataRef = (__bridge_retained CFDataRef)certData;
SecCertificateRef cert = SecCertificateCreateWithData(NULL,certDataRef);
CFArrayRef certArrayRef = CFArrayCreate(NULL, (void *)&cert,1,NULL);
SecTrustRef serverTrust = protectionSpace.serverTrust;
SecTrustSetAnchorCertificates(serverTrust, certArrayRef);
SecTrustResultType trustResult;
SecTrustEvaluate(serverTrust, &trustResult);
if ( trustResult == kSecTrustResultRecoverableTrustFailure){
NSLog(#"kSecTrustResultRecoverableTrustFailure");
CFDataRef errDataRef = SecTrustCopyExceptions(serverTrust);
SecTrustSetExceptions(serverTrust, errDataRef);
SecTrustEvaluate(serverTrust, &trustResult);
}
if ( trustResult == kSecTrustResultUnspecified){
NSLog(#"In Trust: kSecTrustResultUnspecified");
}
if ( trustResult == kSecTrustResultProceed){
NSLog(#"In Trust: kSecTrustResultProceed");
}
return trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed;
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{
NSLog(#"didReceiveChallenge");
if ([self shouldTrustProtectionSpace:[challenge protectionSpace]]) {
SecTrustRef trust = [[challenge protectionSpace] serverTrust];
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(trust, 0);
NSData *serverCertificateData = (__bridge NSData *)SecCertificateCopyData(certificate);
NSURL *bundledCertURL = [[NSBundle mainBundle] URLForResource:#"file.something.com" withExtension:#"cer"];
NSData *bundledCertData = [[NSData alloc] initWithContentsOfURL:bundledCertURL];
BOOL whatWeExpected = [serverCertificateData isEqual:bundledCertData];
if (whatWeExpected == TRUE ){
NSLog(#"As Expected");
NSURLCredential *cred = [[NSURLCredential alloc] initWithTrust:trust];
[[challenge sender] useCredential:cred forAuthenticationChallenge:challenge];
}
}
if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodDefault]) {
NSLog(#"NSURLAuthenticationMethodDefault");
}
//This piece of code I put it outside the above IF for now else I never get
//the NSURLAuthenticationMethodDefault challenge?????
NSURLCredential *newCredential;
newCredential = [NSURLCredential credentialWithUser:userName
password:passWord
persistence:NSURLCredentialPersistenceNone];
completionHandler(NSURLSessionAuthChallengeUseCredential, newCredential);
}

SecTrustSetAnchorCertificates with client certificate

I am developing iOS Application. We have custom certificate authority with self-signed ca-cert. Certification authority issues certificates both for users and for https server too. I would like to create iOS application which can authenticate https server using ca certificate and also can communicate to https server using client certificate. I already have the code for communicating with https server using client certificate, but I need to have ca-certificate imported to system keyring. I would like to have ca certificate hard-coded into application. My code looks like this:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
bool result=NO;
if ([protectionSpace authenticationMethod] == NSURLAuthenticationMethodServerTrust) {
result= YES;
} else if([protectionSpace authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {
result = YES;
}
return result;
}
- (BOOL)shouldTrustProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
CFDataRef certDataRef = (__bridge_retained CFDataRef)self.rootCertData;
SecCertificateRef cert = SecCertificateCreateWithData(NULL, certDataRef);
SecTrustRef serverTrust = protectionSpace.serverTrust;
CFArrayRef certArrayRef = CFArrayCreate(NULL, (void *)&cert, 1, NULL);
SecTrustSetAnchorCertificates(serverTrust, certArrayRef);
SecTrustResultType trustResult;
SecTrustEvaluate(serverTrust, &trustResult);
return trustResult == kSecTrustResultUnspecified;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSLog(#"Did receive auth challange %#",[challenge debugDescription]);
NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
NSString *authMethod = [protectionSpace authenticationMethod];
if(authMethod == NSURLAuthenticationMethodServerTrust ) {
NSLog(#"Verifying The Trust");
NSURLCredential* cred=[NSURLCredential credentialForTrust:[protectionSpace serverTrust]];
if ([self shouldTrustProtectionSpace:challenge.protectionSpace]) {
[[challenge sender] useCredential:cred forAuthenticationChallenge:challenge];
NSLog(#"OK");
} else {
NSLog(#"FAILED");
[challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
}
}
if(authMethod == NSURLAuthenticationMethodClientCertificate ) {
NSLog(#"Trying Certificate");
.....
Everything works like a charm, until server does not require client certificate. At this moment I will receive error The certificate for this server is invalid and execution will never reach point
NSLog(#"Trying Certificate");
When I have .der ca-cert loaded into system keyring, everything works, even client certificate is sent to server, and server can recognize user. I thing that
SecTrustSetAnchorCertificates(serverTrust, certArrayRef);
affects somehow trust, cause when I skip this call, I can simply do:
[[challenge sender] useCredential:cred forAuthenticationChallenge:challenge];
without any error, but I'm not able to verify certificate in this case.
What am I doing wrong?
Big thanks,
Adam
Have a look at this code
https://github.com/dirkx/Security-Pinning-by-CA
which does both by fairly carefully keeping the two trust blocks separate (which you seem to be mixing).

NSUrlConnection sendAsynchronousRequest and self-signed certificates

I'm writing some API code that does http requests, and I've been using [NSUrlConnection:sendAsynchronousRequest:queue:completionHandler] for the calls, as it makes it pretty easy to write simple handlers, and also so that I don't have to have different classes with different delegates for each call.
The problem that I'm having is that it seems that the only way to accept self-signed certificates is to have a delegate that implements a couple of functions saying that the certificate is okay. Is there any way to do this with the asynchronous method that uses blocks?
No, but the delegate calls are not all that hard. This is the code you need:
1) Make this a file static
static CFArrayRef certs;
2) DO this in your initialize:
// I had a crt certificate, needed a der one, so found this site:
// http://fixunix.com/openssl/537621-re-der-crt-file-conversion.html
// and did this from Terminal: openssl x509 -in crt.crt -outform der -out crt.der
NSString *path = [[NSBundle mainBundle] pathForResource:#"<your name>" ofType:#"der"];
assert(path);
NSData *data = [NSData dataWithContentsOfFile:path];
assert(data);
SecCertificateRef rootcert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)data);
if(rootcert) {
const void *array[1] = { rootcert };
certs = CFArrayCreate(NULL, array, 1, &kCFTypeArrayCallBacks);
CFRelease(rootcert); // for completeness, really does not matter
} else {
NSLog(#"BIG TROUBLE - ROOT CERTIFICATE FAILED!");
}
3) Then add this method:
- (void)connection:(NSURLConnection *)conn didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
#pragma unused(conn)
// NSLog(#"didReceiveAuthenticationChallenge %# FAILURES=%zd", [[challenge protectionSpace] authenticationMethod], (ssize_t)[challenge previousFailureCount]);
/* Setup */
NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
assert(protectionSpace);
SecTrustRef trust = [protectionSpace serverTrust];
assert(trust);
CFRetain(trust); // Don't know when ARC might release protectionSpace
NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];
BOOL trusted = NO;
OSStatus err;
SecTrustResultType trustResult = 0;
err = SecTrustSetAnchorCertificates(trust, certs);
if (err == noErr) {
err = SecTrustEvaluate(trust, &trustResult);
if(err == noErr) {
// http://developer.apple.com/library/mac/#qa/qa1360/_index.html
switch(trustResult) {
case kSecTrustResultProceed:
case kSecTrustResultConfirm:
case kSecTrustResultUnspecified:
trusted = YES;
break;
}
}
}
CFRelease(trust);
// Return based on whether we decided to trust or not
if (trusted) {
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
} else {
NSLog(#"Trust evaluation failed");
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}

Resources