I have 2 Mac apps. My requirement is to create a RSA asymmetric private key and store it to the default login key chain from App 1. Now when App 2 tries to retrieve this key it should be able to retrieve this without any login prompt (Key chain asks for login when apps(non owner) try to access it).
In order to solve this the first solution I tried is to edit the ACL (Access Control List)while creating the key and include the apps which tries to access this key. This works as well. However, this can’t be used since the TrustedApplication is created using SecTrustedApplicationCreateFromPath API. This API is deprecated in 10.15 and there is no way to create a SecTrustedApplication and we will not be able to use this anymore. Apple has not mentioned an alternative. It seems Apple no more wants users to manipulate the ACL from within an application without asking for the user prompt since Catalina(10.15).
So the solution which I ended up implementing is Keychain sharing and I followed the Apple documentation:
https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps?language=objc
App 1 creates the key and saves the private key in keychain. App 2 tries to access it and generate public key from it.
I have created the Keychain access group in both the app and ensured that the App identifier prefix is the same for both these apps.
Attributes for Key creation from App 1:
NSString *accessGroup = [NSString stringWithFormat:@"%@com.sample.keychaingroup",appIdentifierPrefix];
NSDictionary* attributes = @{
(id)kSecAttrLabel: @"Sample Private Key",
(id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecAttrKeySizeInBits: @(2048),
(id)kSecAttrAccessGroup: accessGroup,
(id)kSecPrivateKeyAttrs:
@{
(id)kSecAttrApplicationTag: tag,
(id)kSecAttrIsPermanent: @YES,
(id)kSecAttrIsExtractable : @YES,
(id)kSecAttrDescription: @"Sample Key Desc",
},
}
And key is created using:
SecKeyRef privateKey = SecKeyCreateRandomKey((_bridge CFDictionaryRef)attributes, &error);
The creation part works perfectly fine and I can see the key(Sample Private Key) in the keychain.
Now to access this from App 2, I make use of the following:
NSDictionary *queryDict = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecReturnRef: @YES,
(id)kSecAttrApplicationTag: same tag used above,
(id)kSecUseDataProtectionKeychain: @YES,
};
OSStatus status = SecItemCopyMatching((_bridge CFDictionaryRef)queryDict, (CFTypeRef *)&fullKey);
Here I always receive the status as -25300, which means the item is not found in the keychain.
The kSecUseDataProtectionKeychain is used here because as per Apple documentation it is needed for Keychain sharing to work in Mac.
Here I tried n number of permutation and combinations for the query, but never ended up in succeeding. If I remove the kSecUseDataProtectionKeychain, it works as earlier. It shows the prompt when we try to access the public key. We get the fullKey value, but following method will show the prompt. I also tried including the access group while querying, but none of it helped.
publicKey = SecKeyCopyPublicKey(fullKey);
I can't exactly figure out what is going wrong at my end, and feeling kind of stuck here. If anyone has any insights to this, please share your thoughts.
In order to solve this the first solution I tried is to edit the ACL (Access Control List)while creating the key and include the apps which tries to access this key. This works as well. However, this can’t be used since the TrustedApplication is created using SecTrustedApplicationCreateFromPath API. This API is deprecated in 10.15 and there is no way to create a SecTrustedApplication and we will not be able to use this anymore. Apple has not mentioned an alternative. It seems Apple no more wants users to manipulate the ACL from within an application without asking for the user prompt since Catalina(10.15).
So the solution which I ended up implementing is Keychain sharing and I followed the Apple documentation:
https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps?language=objc
App 1 creates the key and saves the private key in keychain. App 2 tries to access it and generate public key from it.
I have created the Keychain access group in both the app and ensured that the App identifier prefix is the same for both these apps.
Attributes for Key creation from App 1:
NSString *accessGroup = [NSString stringWithFormat:@"%@com.sample.keychaingroup",appIdentifierPrefix];
NSDictionary* attributes = @{
(id)kSecAttrLabel: @"Sample Private Key",
(id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecAttrKeySizeInBits: @(2048),
(id)kSecAttrAccessGroup: accessGroup,
(id)kSecPrivateKeyAttrs:
@{
(id)kSecAttrApplicationTag: tag,
(id)kSecAttrIsPermanent: @YES,
(id)kSecAttrIsExtractable : @YES,
(id)kSecAttrDescription: @"Sample Key Desc",
},
}
And key is created using:
SecKeyRef privateKey = SecKeyCreateRandomKey((_bridge CFDictionaryRef)attributes, &error);
The creation part works perfectly fine and I can see the key(Sample Private Key) in the keychain.
Now to access this from App 2, I make use of the following:
NSDictionary *queryDict = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecReturnRef: @YES,
(id)kSecAttrApplicationTag: same tag used above,
(id)kSecUseDataProtectionKeychain: @YES,
};
OSStatus status = SecItemCopyMatching((_bridge CFDictionaryRef)queryDict, (CFTypeRef *)&fullKey);
Here I always receive the status as -25300, which means the item is not found in the keychain.
The kSecUseDataProtectionKeychain is used here because as per Apple documentation it is needed for Keychain sharing to work in Mac.
Here I tried n number of permutation and combinations for the query, but never ended up in succeeding. If I remove the kSecUseDataProtectionKeychain, it works as earlier. It shows the prompt when we try to access the public key. We get the fullKey value, but following method will show the prompt. I also tried including the access group while querying, but none of it helped.
publicKey = SecKeyCopyPublicKey(fullKey);
I can't exactly figure out what is going wrong at my end, and feeling kind of stuck here. If anyone has any insights to this, please share your thoughts.