I am using sha1WithRSAEncryption (i.e. not MD5 as per SecTrustEvaluate returns kSecTrustResultRecoverableTrustFailure on iOS 5) server certs generated with a simple ca->subca->server lineage.
openssl req -new -x509 -subj /CN=ca/O=1002 -nodes -keyout /dev/stdout -set_serial 4 -days 3650 > ca-1002.pem
openssl req -new -subj /CN=sub-ca/O=1002 -nodes -keyout subca-1002.pem -set_serial 5 -days 3650 | openssl x509 -CA ca-1002.pem -req -set_serial 1 >> subca-1002.pem
openssl req -new -subj /CN=localhost:2002/O=1002 -nodes -keyout cert-1002.pem -set_serial 6 -days 3650 | openssl x509 -CA subca-1002.pem -req -set_serial 1 >> cert-1002.pem
for the server; with a chain for the servers SSLCACertificateFile and a DER file to be read in by the iPhone/iPad app later.
cat ca-1002.pem subca-1002.pem > chain-1002.pem
openssl x509 -in ca-1002.pem -outform DER -out ca-1002.der
I make sure I get a say in the evaluation with:
-(BOOL)connection:(NSURLConnection *)aConnection
canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)space
{
DLog(@" authenticationMethod: %@",[space authenticationMethod]);
// nil/not set - rely on the keychain, set to an empty array indicates
// we know what we're doing -and accept anything - otherwise a list
// of certs gotten from reading in DER files.
//
NSArray *acceptableCAs = [delegate sslAcceptableServerCACertificates];
if ([[space authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) {
NSArray *acceptableCAs = [delegate sslAcceptableServerCACertificates];
return acceptableCAs != nil ? YES : NO;
}
// Client cert stuff snipped.
// the keychain is used.
return NO;
}
So far so good.
-(void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSURLProtectionSpace * space = [challenge protectionSpace];
if ([space.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
NSArray *acceptableCAs = [delegate sslAcceptableServerCACertificates];
NSURLCredential *newCredential;
if (acceptableCAs == nil) { ... }
// We'll leave this to the keychain by doing nothing. (works splendidly)
else if ([acceptableCAs count] == 0) { }
// We'll accept 'anything' - or in this case - exactly this server.
// (this works just fine).
works - but when I specific 1 or more certs explicity - things fail:
else {
SecTrustRef secTrustRef = challenge.protectionSpace.serverTrust;
SecTrustResultType result;
NSMutableArray * serverChain = [[NSMutableArray alloc] init];
for(long i = 0; i < SecTrustGetCertificateCount(secTrustRef); i++) {
SecCertificateRef cr = SecTrustGetCertificateAtIndex(secTrustRef, i);
DLog(@"Server cert %04ld: %@", i, cr);
DLog(@" %@", [SimpleCertificate sha1fingerprint:cr]);
[serverChain addObject:(__bridge id)(cr)];
}
for(NSUInteger i = 0; i < [acceptableCAs count]; i++) { .. dump these too }
SecTrustSetAnchorCertificatesOnly(secTrustRef, YES);
SecTrustSetAnchorCertificates(secTrustRef, (__bridge CFArrayRef)acceptableCAs);
// SecTrustSetAnchorCertificatesOnly(secTrustRef, NO);
// assert(SecTrustCreateWithCertificates((__bridge CFTypeRef)(serverChain), SecPolicyCreateSSL(YES, nil), &secTrustRef) == noErr);
// assert(SecTrustCreateWithCertificates((__bridge CFTypeRef)(serverChain), SecPolicyCreateSSL(NO, nil), &secTrustRef) == noErr);
// assert(SecTrustCreateWithCertificates((__bridge CFTypeRef)(serverChain), SecPolicyCreateBasicX509(), &secTrustRef) == noErr);
// assert(SecTrustSetVerifyDate(secTrustRef, ...);
assert(SecTrustEvaluate(secTrustRef, &result) == noErr);
DLog(@"SecTrustEvaluate gives us: %lu", result);
....
I get the error below. SecTrustEvaluate always gives me back a 5 (and sometimes a 4).
Have tried various suggestions; such as date shifting, verified it is sha1 and not md5, etc.
athAgstPrSp:] [Line 68] authenticationMethod: NSURLAuthenticationMethodServerTrust
didRecAChlg:] [Line 98] authenticationMethod: NSURLAuthenticationMethodServerTrust
didRecAChlg:] [Line 143] Server cert 0000: <cert(0xd637ce0) s: localhost:2002 i: sub-ca>
didRecAChlg:] [Line 144] 8D:21:95:20:11:D0:9C:27:86:56:B3:0B:A6:F7:A2:CF:4C:F6:67:64
didRecAChlg:] [Line 143] Server cert 0001: <cert(0xd638710) s: sub-ca i: ca>
didRecAChlg:] [Line 144] 30:58:8E:F9:B8:29:5E:84:46:E3:0B:98:0D:B0:28:23:3D:7E:86:A3
didRecAChlg:] [Line 143] Server cert 0002: <cert(0xd638af0) s: ca i: ca>
didRecAChlg:] [Line 144] 8E:A2:D9:13:AF:8B:AE:82:1C:39:E9:79:72:98:2A:B4:C0:17:0D:F0
didRecAChlg:] [Line 150] CA cert 0000: <cert(0xf0397f0) s: ca i: ca>
didRecAChlg:] [Line 151] 8E:A2:D9:13:AF:8B:AE:82:1C:39:E9:79:72:98:2A:B4:C0:17:0D:F0
didRecAChlg:] [Line 159] SecTrustEvaluate gives us: 5
Note - I am trying to avoid the manual compare with the SHA1 of the main answer of: Does SecTrustEvaluate() look for root certificates in the application keychain? as to rely as cleanly as possible on the keychain and what not. As I have several other use cases where a chipcard and a keychain is involved. Hostnames in above example match. In above - the server 001..003 certs match 1:1 what is seen by openssl s_client; and the CA cert is indeed the DER file provided (and identical to server flashed CA cert 002). We tried also setting SubjectAltNames on CA's and clients; and did not see that make a difference (as suggested by SecTrustEvaluate always returns kSecTrustResultRecoverableTrustFailure with SecPolicyCreateSSL) which makes sense as wee know that manually importing them in keychain/profile makes the very same certs work perfectly fine as default keychain objects.
Any and all suggestions would be welcome. Looking for a clean way to say in the NSURLConnectionDelegate to either accept the keychain, to accept 'anything' or to just accept something specific.
Thanks,
Dw.