问题
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.
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;
回答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