iOS app SSL .p12 Authentication - bad certificate error (-9825)

五迷三道 提交于 2019-12-21 05:21:11

问题


Updates

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?

Known information

  • I know that the app is finding .p12 certificate because when I fudge the path to the .p12 cert, I receive an error saying it cannot find the certificate which I don't normally see otherwise.
  • I know the password I am providing for the certificate file is correct because when I fudge the password, I receive an error regarding the password I don't normally see otherwise.
  • I don't think certificate trust is an issue because if I remove my server's CA from the iOS keychain, I get an error in the console specifically stating the server cannot be trusted. After adding the CA back, this error no longer occurs, but I get same error as above (-9825)

Others tests

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

TL;DR

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!


回答1:


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.




回答2:


In some cases handshake may fail because server also asks intermediate and root certificates.

To fix it do the following:

Authentication challenge part:

    OSStatus status = extractIdentityAndTrust(inP12data, &myIdentity, &myTrust, password);

    SecCertificateRef myCertificate;
    SecIdentityCopyCertificate(myIdentity, &myCertificate);

    CFIndex count = SecTrustGetCertificateCount(myTrust);

    NSMutableArray* myCertificates = [NSMutableArray arrayWithCapacity:count];
    if (count > 1) {
        for (int i = 1; i < count; ++i) {
            [myCertificates addObject:(__bridge id)SecTrustGetCertificateAtIndex(myTrust, i)];
        }
    }

    NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:myCertificates persistence: NSURLCredentialPersistenceForSession];

Extracting method

OSStatus extractIdentityAndTrust(CFDataRef inPKCS12Data,
                             SecIdentityRef *outIdentity,
                             SecTrustRef *outTrust,
                             CFStringRef keyPassword) {

OSStatus securityError = errSecSuccess;


const void *keys[] =   { kSecImportExportPassphrase };
const void *values[] = { keyPassword };
CFDictionaryRef optionsDictionary = NULL;

/* Create a dictionary containing the passphrase if one
 was specified.  Otherwise, create an empty dictionary. */
optionsDictionary = CFDictionaryCreate(
                                       NULL, keys,
                                       values, (keyPassword ? 1 : 0),
                                       NULL, NULL);

CFArrayRef items = NULL;
securityError = SecPKCS12Import(inPKCS12Data,
                                optionsDictionary,
                                &items);

if (securityError == 0) {
    CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0);
    const void *tempIdentity = NULL;
    tempIdentity = CFDictionaryGetValue (myIdentityAndTrust,
                                         kSecImportItemIdentity);
    CFRetain(tempIdentity);
    *outIdentity = (SecIdentityRef)tempIdentity;
    const void *tempTrust = NULL;
    tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);

    CFRetain(tempTrust);
    *outTrust = (SecTrustRef)tempTrust;
}

if (optionsDictionary)
    CFRelease(optionsDictionary);

if (items)
    CFRelease(items);

return securityError;  } 

Please note that loop from 1 (not 0) is not a mistake. First cert is already added to "myIdentify", so only other certs should be passed as certificates, otherwise most likely you'll receive an error during handshake because of duplicating.



来源:https://stackoverflow.com/questions/21512761/ios-app-ssl-p12-authentication-bad-certificate-error-9825

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!