4

I am trying to follow the apple docs for dealing with client p12 certificates here:

https://developer.apple.com/library/ios/documentation/Security/Conceptual/CertKeyTrustProgGuide/iPhone_Tasks/iPhone_Tasks.html#//apple_ref/doc/uid/TP40001358-CH208-SW13

I have successfully loaded a .p12 cert from the file system:

- (SecIdentityRef)getClientCertificate:(NSString *) certificatePath {
    SecIdentityRef identity = nil;
    NSData *PKCS12Data = [NSData dataWithContentsOfFile:certificatePath];

    CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data;
    CFStringRef password = CFSTR("password");
    const void *keys[] = { kSecImportExportPassphrase };
    const void *values[] = { password };
    CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
    OSStatus securityError = SecPKCS12Import(inPKCS12Data, options, &items);
    CFRelease(options);
    CFRelease(password);
    if (securityError == errSecSuccess) {
        NSLog(@"Success opening p12 certificate. Items: %ld", CFArrayGetCount(items));
        CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
        identity = (SecIdentityRef) CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
    } else {
        NSLog(@"Error opening Certificate.");
    }

    return identity;
}

I then get the certificate for that identity:

- (CFArrayRef)getCertificate:(SecIdentityRef) identity {
    SecCertificateRef certificate = nil;

    SecIdentityCopyCertificate(identity, &certificate);
    SecCertificateRef certs[1] = { certificate };



    CFArrayRef array = CFArrayCreate(NULL, (const void **) certs, 1, NULL);

    SecPolicyRef myPolicy = SecPolicyCreateBasicX509();
    SecTrustRef myTrust;

    OSStatus status = SecTrustCreateWithCertificates(array, myPolicy, &myTrust);
    if (status == noErr) {
        NSLog(@"No Err creating certificate");
    } else {
        NSLog(@"Possible Err Creating certificate");
    }
    return array;
}

But what I really want to do is store the certificate (or the identity) in my apps keychain so I am not reading it from the file system.

A couple of questions:

  1. Which am I supposed to store? The certificate or the identity?
  2. How do I store it and retrieve it?

The link above talks about 'Getting and Using Persistent Keychain References' which is very confusing to me.

It also talks about 'Finding a Certificate In the Keychain', but it mentions using the name of the certificate to find it. I am not sure where the 'name' comes from.

lostintranslation
  • 23,756
  • 50
  • 159
  • 262
  • Can you help me with the above case, I am also trying to do the same thing. how you have implemented this in your project? – Wolverine Feb 19 '19 at 07:38

3 Answers3

3
  1. Which am I supposed to store? The certificate or the identity?

It depends on what you are doing, and whether you need the private key on your device for authentication. A SecIdentityRef contains the certificate and private key. If you are using the .p12 file for authentication, then you likely want to store and use the full identity. If you only need the certificate, then I wouldn't be loading the full .p12 onto the drive in the first place, as it contains the private key.

  1. How do I store it and retrieve it?

I would recommend storing your identity (or certificate) in the keychain, and using kSecAttrLabel as a unique reference for querying.

The documentation you need to look at is Storing an Identity in the Keychain, which directs you to Storing a Certificate in the Keychain and outlines some minor differences required between storing an identity and certificate.

This is done as follows (adapted from the links above):

Save to Keychain

// Create a query (with unique label for reference later)
NSDictionary* addquery = @{ (id)kSecValueRef:   (__bridge id)identity,
                            (id)kSecClass:      (id)kSecClassIdentity,
                            (id)kSecAttrLabel:  @"My Identity",
                           };

// Add the identity to the keychain
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addquery, NULL);
if (status != errSecSuccess) {
    // Handle the error
}

Load from Keychain

// Query the keychain for your identity
NSDictionary *getquery = @{ (id)kSecClass:     (id)kSecClassIdentity,
                            (id)kSecAttrLabel: @"My Identity",
                            (id)kSecReturnRef: @YES,
                            };

// Retrieve the identity from the keychain
SecIdentityRef identity = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)getquery,
                                      (CFTypeRef *)&identity);
if (status != errSecSuccess) { <# Handle error #> }
else                         { <# Use identity #> }

if (identity) { CFRelease(identity); } // After you are done with it

As RyanR mentioned, you can also create a persistent reference to the keychain item once it has been saved, and then save that to file. I would recommend adding [kSecReturnPersistentRef][3] to your addquery to achieve this.

Jordan Johnson
  • 669
  • 3
  • 16
2

I can't think of a good reason to store the certificate in the keychain, although I'm sure there might be some. I store just the identity (which is the private key portion) in the keychain. To make it easier to find the identity in the keychain you generate a persistent reference to it (See listing 2-3 in the link), and then save that persistent reference in the filesystem for your app. The persistent ref is just a CFDataRef, which you can toll free bridge to an NSData object and then easily save/load. When you want the private key for crypto/whatever, you use that persistent reference to load the identity from the keychain (see listing 2-4 in the link). I'd post some code for you but I'm in the process of rebuilding my development machine right now and don't have Xcode installed just yet.

RyanR
  • 7,728
  • 1
  • 25
  • 39
  • Thanks! I have been trying to save/retrieve the persistent reference to/from NSUserDefaults with no luck. Wondering if I can write that to NSUserDefaults, and if so, if I am doing it correctly. – lostintranslation Jun 02 '15 at 15:51
  • I would advise against storing that reference in NSUserDefaults, it just isn't the right place for it and can be finicky. Just use the [-writeToFile:atomically:](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/index.html#//apple_ref/occ/instm/NSData/writeToFile:atomically:) method – RyanR Jun 02 '15 at 16:37
0

Here's a Swift version that writes the SecIdentity to the Keychain, and reads it back:

func addP12ToKeychain(secIdentity:SecIdentity) throws {
        let keychainAddQuery: [String: Any] = [
            // The online example in the Apple doc includes the
            // kSecClass key, but this always failed for me. 
            // When I exclude it, the write works; not sure why.
            // kSecClass as String: kSecClassIdentity,
            kSecValueRef as String: secIdentity,
            kSecAttrLabel as String: "identifierForP12Cert"
        ]
        
        let addResult = SecItemAdd(keychainAddQuery as CFDictionary, nil)
        if addResult != errSecSuccess {
            // You can look up the error # here:
            // https://www.osstatus.com/
            // For ex, errSecInternal = -26276
            throw EtimsError.runtimeError("Err in addP12ToKeychain, error # \(addResult)")
        }
    } // addP12ToKeychain

func getP12FromKeychain() throws -> SecIdentity {
        // Note that the fetch works when you include the kSecClass key,
        // unlike the write.
        let keychainFetchQuery: [String: Any] = [
            kSecClass as String: kSecClassIdentity,
            kSecAttrLabel as String: "identifierForP12Cert",
            kSecReturnRef as String: kCFBooleanTrue!
        ]

        var item: CFTypeRef?
        let fetchResult = SecItemCopyMatching(keychainFetchQuery as CFDictionary, &item)
        guard fetchResult == errSecSuccess else {
            throw EtimsError.runtimeError("Err in getP12FromKeychain, error # \(fetchResult)")
        }

        let secIdentity = item as! SecIdentity
        return secIdentity
    } // getP12FromKeychain
James Toomey
  • 5,635
  • 3
  • 37
  • 41