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.
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.
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)); }
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;
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;
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