Apple keychain store client identity so only my application could access it

让人想犯罪 __ 提交于 2019-12-02 16:57:20

问题


Aim

I need to store client identity on OS X application in secure way in such way that only my application could access it. No prompting asking for permissions.

Problem

Problem appeared immediately when I was trying to store client identity. Here is code sample (what have I tied so far):

- (BOOL)saveClientIdentity:(SecIdentityRef)clientIdentity error:(NSError**) error
{
    NSDictionary *attributes = @{
        (__bridge id)kSecAttrAccessible:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly,
        (__bridge id)kSecValueRef:(__bridge id)clientIdentity,
        (__bridge id)kSecAttrApplicationTag:[kMyKeychainAttrApplicationTag dataUsingEncoding: NSUTF8StringEncoding],
        (__bridge id)kSecAttrAccessGroup:kMyKeychainAttrAccessGroup
    };

    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
    // status == -25299
    …
}

I'm constantly getting code -25299 and tool expalint the problem:

$ security error -25299
Error: 0xFFFF9D2D -25299 The specified item already exists in the keychain.

So it tries override global client identity (I never successful written a client identity for this application so there shouldn't be such conflict) and I don't what to do that. It have to be private for only this application.

I verified what happens for respective loading code. It loads my developer identity and I do not want that.

- (SecIdentityRef)clientIdentity
{
    NSDictionary *attributes =
    @{
      (__bridge id)kSecClass:(__bridge id)kSecClassIdentity,
      (__bridge id)kSecAttrAccessible:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly,
      (__bridge id)kSecAttrApplicationTag:[kMyKeychainAttrApplicationTag dataUsingEncoding: NSUTF8StringEncoding],
      (__bridge id)kSecAttrAccessGroup:kMyKeychainAttrAccessGroup
      };

    CFTypeRef universalResult = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)attributes, &universalResult);
    SecIdentityRef result = (SecIdentityRef)universalResult;
    if (result)
    {
        CFAutorelease(result);
    }
    if (status != noErr)
    {
        NSLog(@"Failed to load client identity: %@", NSErrorFromStatusErrorCode(status));
    }
    return result;
}

Notes

I need to use same code for iOS, but here should be no problem since by default iOS keychain is not shared between applications.


回答1:


I've found nice solution. The trick is to create custom key-chain and than store client identity in that key chain.

So basically there are tree steps.

  1. First create or open custom key-chain:

    NSString *keychainpath  = self.customKeychainPath;
    unsigned char password[SHA_DIGEST_LENGTH];
    GenerateCustomKeychainPassword(password);
    OSStatus status = SecKeychainCreate(keychainpath.UTF8String,
                                        SHA_DIGEST_LENGTH,
                                        password,
                                        NO,
                                        NULL,
                                        &customKeychain);
    
    if (status == errSecDuplicateKeychain)
    {
        status = SecKeychainOpen(keychainpath.UTF8String, &customKeychain);
        if (status == errSecSuccess)
        {
            status = SecKeychainUnlock(customKeychain,
                                       SHA_DIGEST_LENGTH,
                                       password,
                                       TRUE);
    
            if (status != errSecSuccess)
            {
                NSLog(@"%s Failed to unlock custom keychain: %@",
                           __PRETTY_FUNCTION__, NSErrorFromStatusErrorCode(status));
            }
        }
    }
    else if (status != errSecSuccess)
    {
        NSLog(@"%s Failed to unlock custom keychain: %@",
                   __PRETTY_FUNCTION__, NSErrorFromStatusErrorCode(status));
    }
    
  2. Than add client identity to that key-chain

    OSStatus status = errSecSuccess;
    
    CFTypeRef  persistent_ref = NULL;
    NSDictionary *dict = @{
                           (id)kSecValueRef:(id)secItem,
                           (id)kSecReturnPersistentRef:(id)kCFBooleanTrue,
    #if !TARGET_OS_IPHONE
                           (id)kSecUseKeychain:(__bridge id)customKeychain,
    #endif
                           };
    status = SecItemAdd((CFDictionaryRef)dict, &persistent_ref);
    NSCAssert(status != errSecParam, @"Wrong contents of dictionary");
    if (status == errSecDuplicateItem)
    {
        NSLog(@"%s Item: %@ already exists", __PRETTY_FUNCTION__, secItem);
        return NULL;
    }
    return (CFDataRef)persistent_ref;
    
  3. And to read item from a keychain (persistent_ref can be stored in user defaults)

    NSDictionary *dict = @{
                           (id)kSecClass:(__bridge id)itemType,//kSecClassIdentity,
                           (id)kSecReturnRef:(id)kCFBooleanTrue,
                           (id)kSecValuePersistentRef:persistantRef,
    #if !TARGET_OS_IPHONE
                           (id)kSecUseKeychain:(__bridge id)customKeychain,
    #endif
                           };
    
    OSStatus status =  SecItemCopyMatching((CFDictionaryRef)dict, &result);
    NSCAssert(status != errSecParam, @"Invalid arguments");
    
    return result;
    



回答2:


I've had a lot of success with SSKeychain, which was recently deprecated in favor of SAMKeychain. It works for both iOS and Mac so it should address your cross-platform issue too.



来源:https://stackoverflow.com/questions/38771668/apple-keychain-store-client-identity-so-only-my-application-could-access-it

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