Edit 2/6/14:
I created a local Apache Tomcat server to test SSL w/ certificate authentication. I was successful! Everything works as expected, using both of my approaches below. (MKNetworkKit, and custom code). While this does tell me my code is working, my original issue is still not solved. I updated the title of the question to more specifically reflect the issue. Does anyone know if SAP Portal needs special settings to accept certificates from an iOS app? Remember, I was able to successfully authenticate using Safari mobile after importing the CA and .p12 into the shared keychain, I was only unsuccessful in code (which I now know the code works, just not with the portal).
I am creating a very simple iOS7 Cordova 3.2 application with a custom plugin to get data from an SSL web service by providing only a .p12 certificate for authentication (no basic auth or other user credentials needed). I am performing all tests on a physical iPad (no simulator). The web service lives on a development SAP NetWeaver portal box using a self signed certificate. For now, I imported the server's CA into the iOS keychain to avoid certificate trust errors. For testing purposes, my .p12 certificate is bundled locally inside the app, in the root of the mainBundle.
When trying to connect to the web service I get the follow error in the console:
CFNetwork SSLHandshake failed (-9825)
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9825)
Error Domain=NSURLErrorDomain Code=-1205 "The server “myhostremoved.com” did not accept the certificate." UserInfo=0x14e99000 {NSErrorFailingURLStringKey=https://myhostremoved.com/sslwebservice/, NSErrorFailingURLKey=https://myhostremoved.com/sslwebservice/, NSLocalizedDescription=The server “myhostremoved.com” did not accept the certificate., NSUnderlyingError=0x14d9b1d0 "The server “myhostremoved.com” did not accept the certificate.", NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x14d94720>}
According to Apple's documentation site, the -9825 error referes to a bad certificate.
There are many questions on SO related to what I am trying to do, but none specifically pertaining to the error I am seeing. I approached the development of the code in two different ways.
First I tried to use code already on SO, adapting it to my use case. See code below:
- (void)startConnection:(CDVInvokedUrlCommand*)command {
NSDictionary *options = [command.arguments objectAtIndex:0];
NSURL *serverURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@", [options objectForKey:@"host"]]];//hostname provided by Cordova plugin, but could just as easily be hardcoded here
NSMutableURLRequest *connectionRequest = [NSMutableURLRequest requestWithURL:serverURL];
NSURLConnection *connection = nil;
connection = [[NSURLConnection alloc] initWithRequest:connectionRequest delegate:self startImmediately:YES];
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
// gets a certificate from local resources
NSString *thePath = [[NSBundle mainBundle] pathForResource:@"mycert" ofType:@"p12"];
NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath];
CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data;
SecIdentityRef identity;
// extract the ideneity from the certificate
[self extractIdentity :inPKCS12Data :&identity];
SecCertificateRef certificate = NULL;
SecIdentityCopyCertificate (identity, &certificate);
const void *certs[] = {certificate};
CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL);
// create a credential from the certificate and ideneity, then reply to the challenge with the credential
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
- (OSStatus)extractIdentity:(CFDataRef)inP12Data :(SecIdentityRef*)identity {
OSStatus securityError = errSecSuccess;
CFStringRef password = CFSTR("MyCertPassw0rd");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import(inP12Data, options, &items);
if (securityError == 0) {
CFDictionaryRef ident = CFArrayGetValueAtIndex(items,0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue(ident, kSecImportItemIdentity);
*identity = (SecIdentityRef)tempIdentity;
}
if (options) {
CFRelease(options);
}
return securityError;
}
On my second approach, I tried to use the MKNetworkKit library, which abstracts away alot of the code needed to interface with the certificate. All you need to do is provide the path to the certificate and the password. Again, I get the same error as above. This code is below.
- (void)startConnection:(CDVInvokedUrlCommand*)command {
NSDictionary *options = [command.arguments objectAtIndex:0];
NSURL *serverURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@", [options objectForKey:@"host"]]];//hostname provided by Cordova plugin, but could just as easily be hardcoded here
MKNetworkEngine *engine = [[MKNetworkEngine alloc] initWithHostName:serverURL customHeaderFields:nil];
MKNetworkOperation *op = [engine operationWithPath:nil params:nil httpMethod:@"GET" ssl:YES];
NSString *thePath = [[NSBundle mainBundle] pathForResource:@"mycert" ofType:@"p12"];
[op setShouldContinueWithInvalidCertificate:YES];
op.clientCertificate = thePath;
op.clientCertificatePassword = @"MyCertPassw0rd";
[op addCompletionHandler:^(MKNetworkOperation *operation) {
NSLog(@"[operation responseData]-->>%@", [operation responseString]);
}errorHandler:^(MKNetworkOperation *errorOp, NSError* err) {
NSLog(@"MKNetwork request error : %@", [err localizedDescription]);
}];
[engine enqueueOperation:op];
}
I get the same error using both approaches. Any ideas?
When providing basic auth in code (bypassing certificate authentication), everything works as expected and I receive no errors.
I have also tried all the same steps above, but using a .pfx file instead of my .p12 certificate, same errors.
The SSL service works in mobile Safari. I imported the .p12 certificate into my keychain via the iPhone Configuration utility, and tested the web service via mobile safari. Everything works as expected, no errors occur (no trust errors either), and I receive the expected output.
I am also able to successfully test the web service on my desktop using the rest-client utility
I am trying to authenticate to an SSL web service using only a .p12 certificate in objective-c. I know the server and web service both work with the cert for auth, but I keep getting errors when I try to establish the connection in objective-c, so something must be wrong in my code. Please help!
I finally was able to resolve this problem. I found the answer here http://oso.com.pl/?p=207&lang=en
It looks like the certificate was being sent twice, causing an error on the server.
Incase the above link dies, I found the same solution already on SO (but for a different question and error).
When using Client Certificate Authentication, why do I keep getting NSURLErrorDomain Code=-1206?
Ultimately, changing this:
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:(__bridge NSArray*)certsArray persistence:NSURLCredentialPersistenceNone];
to this:
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:nil persistence:NSURLCredentialPersistenceNone];
solved my issue. It would be nice if anyone could offer more insight into this issue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With