2

I have imported a self-signed X509 certificate into my iPhone by simply emailing the certificate.pem to it and installing it on the device. Now I would like to verify a particular certificate in my iOS application that must be signed using the aforementioned certificate. Basically, the imported certificate acts as root certificate for a CA.

Does the imported certificate get stored in the Keychain?

How can I programmatically validate another certificate based on the imported one? (the second certificate is only valid if it is signed by the before imported CA certificate)

Does anyone have some experienced with these scenarios?

Thanks in advance!

Chris
  • 3,057
  • 5
  • 37
  • 63

1 Answers1

3

1) yes - it sits in your keychain.

2) you verify it using the trust SecTrustCreateWithCertificates(), SecTrustEvaluate() against either all certificates or just your own.

3) If you verified it against a wide smattering of certs you can optionally lookup your own cert in the keychain; get the DER; calculate its SHA1 and compare this to a SHA1 which is hardcoded in your code.

The code is something like below.

NSMutableArray *serverChain = -- array with what you want to check
NSMutableArray *trustedCertRefs = <your-hardcoded-certs>;

SecTrustRef noHostTrustRef = NULL;
OSErr status = SecTrustCreateWithCertificates((__bridge CFArrayRef)serverChain,
                                 SecPolicyCreateSSL(NO, nil), &noHostTrustRef);

if (status != noErr) {
    NSLog(@"SecTrustCreateWithCertificates failed: %hd", status);
    [[challenge sender] cancelAuthenticationChallenge:challenge];
}


status = SecTrustSetAnchorCertificates(noHostTrustRef,
                         (__bridge CFArrayRef)trustedCertRefs);
if (status != noErr) {
    NSLog(@"SecTrustSetAnchorCertificates failed: %hd", status);
    [[challenge sender] cancelAuthenticationChallenge:challenge];
}

status = SecTrustEvaluate(noHostTrustRef, &result);
if (status != noErr) {
    NSLog(@"SecTrustEvaluate failed: %hd", status);
    [[challenge sender] cancelAuthenticationChallenge:challenge];
}
CFRelease(noHostTrustRef);

/* From SecTrust.h:
 *
 * SecTrustResultType results have two dimensions.  They specify both whether 
 * evaluation suceeded and whether this is because of a user decision.  
 *
 * In practice the commonly expected result is kSecTrustResultUnspecified,
 * which indicates a positive result that wasn't decided by the user.  
 *
 * The common failure is kSecTrustResultRecoverableTrustFailure, which means a
 * negative result.  kSecTrustResultProceed and kSecTrustResultDeny are the
 * positive and negative result respectively when decided by the user.  User
 *  decisions are persisted through the use of SecTrustCopyExceptions() and
 * SecTrustSetExceptions().  Finally kSecTrustResultFatalTrustFailure is a
 * negative result that should not be circumvented.  In fact only in the case
 * of kSecTrustResultRecoverableTrustFailure should a user ever be asked.
 */
switch (result) {
    case kSecTrustResultProceed: // 1
    case kSecTrustResultConfirm: // 2
    case kSecTrustResultUnspecified: // 4
        return YES
        break;
    case kSecTrustResultRecoverableTrustFailure:  // 5
    case kSecTrustResultDeny: // 3
    case kSecTrustResultFatalTrustFailure: // 6
    case kSecTrustResultOtherError: // 7
    case kSecTrustResultInvalid: // 0
    default:
        return NO:
        break;
}
[[challenge sender] cancelAuthenticationChallenge:challenge];

or if you get a trust chain, say from the network stack which is already verified against the keychain (and thus against your certs) - then you can extract the certs; do a SecCertificateCopyData() on them; and then SHA1 that NSData to compare to your hardcoded sha1 as to ensure it is verified against exactly that one.

NSGod
  • 22,699
  • 3
  • 58
  • 66
Dirk-Willem van Gulik
  • 7,566
  • 2
  • 35
  • 40
  • Thanks! Exactly what I was looking for. By the way, is it also possible to get/extract the entire certificate from the keychain? (For example, if you need it as NSData in your code out of the keychain but it's deployed using apple's iPhone configuration utility or any other MDM?) – Chris Jul 20 '12 at 17:59
  • 1
    Yes - below is the code I use for that. It is actually a bit more if you need it from a presistent identifier. Sorry for the formatting - but comments are a bit hard on code. Feel free to create a question so I can properly cut-and-paste. CFDictionaryCreate(), SecItemCopyMatching(),check SecIdentityGetTypeID() cast, CFDataRef der = SecCertificateCopyData(cert); const unsigned char * ptr = CFDataGetBytePtr(der); long len = CFDataGetLength(der); d2i_X509(&x509,&ptr,len); _sha1 = [(__bridge NSData *)der sha1]; _der = (__bridge_transfer NSData *) der; CFRelease(der); – Dirk-Willem van Gulik Jul 22 '12 at 10:39
  • I have tried your code (and Apple's example code) but it always crashes on `SecTrustEvaluate`. New post: http://stackoverflow.com/questions/11608847/always-exc-bad-access-on-sectrustevaluate – Chris Jul 23 '12 at 08:26
  • I was able to load an identity from a .p12 data file and use the above to get a public key for encrypting data. I didn't need to call `SecTrustSetAnchorCertificates` though, only `SecTrustEvaluate` before `SecTrustCopyPublicKey`. Thanks! – Matt Connolly May 08 '13 at 11:49
  • Aye - that depends on the policy. See https://github.com/dirkx/Security-Pinning-by-CA for a much more complete example - which is very explicit about this aspect. – Dirk-Willem van Gulik May 14 '13 at 12:32