iOS Keychain Services: Only Specific Values Allowed for Ksecattrgeneric Key

iOS Keychain Services: only specific values allowed for kSecAttrGeneric Key?

Okay, I found the solution in this blog post Keychain duplicate item when adding password

To sum it up, the issue is that the GenericKeychain sample app uses the value stored in kSecAttrGeneric key as the identifier for the keychain item when in fact that is not what the API uses to determine a unique keychain item. The keys you need to set with unique values are the kSecAttrAccount key and/or the kSecAttrService key.

You can rewrite the initilizer of KeychainItemWrapper so you don't need to change any other code by changing these lines:

Change:

[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrGeneric];

to:

[genericPasswordQuery setObject:identifier forKey:(id)kSecAttrAccount];

and change:

[keychainItemData setObject:identifier forKey:(id)kSecAttrGeneric];

to:

[keychainItemData setObject:identifier forKey:(id)kSecAttrAccount];

Or, you could do what I did and write a new initilizer that takes both of the identifying keys:

Edit: For people using ARC (you should be nowadays) check nycynik's answer for all the correct bridging notation

- (id)initWithAccount:(NSString *)account service:(NSString *)service accessGroup:(NSString *) accessGroup;
{
if (self = [super init])
{
NSAssert(account != nil || service != nil, @"Both account and service are nil. Must specifiy at least one.");
// Begin Keychain search setup. The genericPasswordQuery the attributes kSecAttrAccount and
// kSecAttrService are used as unique identifiers differentiating keychain items from one another
genericPasswordQuery = [[NSMutableDictionary alloc] init];

[genericPasswordQuery setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

[genericPasswordQuery setObject:account forKey:(id)kSecAttrAccount];
[genericPasswordQuery setObject:service forKey:(id)kSecAttrService];

// The keychain access group attribute determines if this item can be shared
// amongst multiple apps whose code signing entitlements contain the same keychain access group.
if (accessGroup != nil)
{
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[genericPasswordQuery setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
}

// Use the proper search constants, return only the attributes of the first match.
[genericPasswordQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
[genericPasswordQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnAttributes];

NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];

NSMutableDictionary *outDictionary = nil;

if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
{
// Stick these default values into keychain item if nothing found.
[self resetKeychainItem];

//Adding the account and service identifiers to the keychain
[keychainItemData setObject:account forKey:(id)kSecAttrAccount];
[keychainItemData setObject:service forKey:(id)kSecAttrService];

if (accessGroup != nil)
{
#if TARGET_IPHONE_SIMULATOR
// Ignore the access group if running on the iPhone simulator.
//
// Apps that are built for the simulator aren't signed, so there's no keychain access group
// for the simulator to check. This means that all apps can see all keychain items when run
// on the simulator.
//
// If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the
// simulator will return -25243 (errSecNoAccessForItem).
#else
[keychainItemData setObject:accessGroup forKey:(id)kSecAttrAccessGroup];
#endif
}
}
else
{
// load the saved data from Keychain.
self.keychainItemData = [self secItemFormatToDictionary:outDictionary];
}

[outDictionary release];
}

return self;
}

Hope this helps someone else out!

What makes a keychain item unique (in iOS)?

The primary keys are as follows (derived from open source files from Apple, see Schema.m4, KeySchema.m4 and SecItem.cpp):

  • For a keychain item of class kSecClassGenericPassword, the primary key is the combination of
    kSecAttrAccount and kSecAttrService.
  • For a keychain item of class kSecClassInternetPassword, the primary key is the combination of kSecAttrAccount, kSecAttrSecurityDomain, kSecAttrServer, kSecAttrProtocol, kSecAttrAuthenticationType, kSecAttrPort and kSecAttrPath.
  • For a keychain item of class kSecClassCertificate, the primary key is the combination of kSecAttrCertificateType, kSecAttrIssuer and kSecAttrSerialNumber.
  • For a keychain item of class kSecClassKey, the primary key is the combination of kSecAttrApplicationLabel, kSecAttrApplicationTag, kSecAttrKeyType,
    kSecAttrKeySizeInBits, kSecAttrEffectiveKeySize, and the creator, start date and end date which are not exposed by SecItem yet.
  • For a keychain item of class kSecClassIdentity I haven't found info on the primary key fields in the open source files, but as an identity is the combination of a private key and a certificate, I assume the primary key is the combination of the primary key fields for kSecClassKey and kSecClassCertificate.

As each keychain item belongs to a keychain access group, it feels like the keychain access group (field kSecAttrAccessGroup) is an added field to all these primary keys.

fetching all items in keychain in one shot?

I believe you're slightly misreading that example. In GenericKeychain, they're not fetching "all items." They're fetching just one item and its one value. A keychain item is made up of properties, which are semi-public, and a "value" which is protected. Reading out of the keychain is fantastically expensive (much, much slower than reading a file off of disk; it's really shockingly slow). So the example is avoiding re-reading it when not needed. But it's not reading the entire keychain; just the one item that stores its data.

Access all (user) accounts inside Keychain

Use kSecMatchLimitAll to get all values in your query dictionary for kSecMatchLimit

 (__bridge id)kSecMatchLimitAll, (__bridge id)kSecMatchLimit 

It will fetch all passwords in keychain for kSecClassGenericPassword.You can use other keychain classes as well like this

NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnAttributes,
(__bridge id)kSecMatchLimitAll, (__bridge id)kSecMatchLimit,
(__bridge id)kSecClassGenericPassword, (__bridge id)kSecClass, //change your class in query
nil];

CFTypeRef result = NULL;
SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
NSLog(@"%@", (__bridge id)result);
if (result != NULL) CFRelease(result);

EDIT : For delting all keys of your app you can use

+(void)deleteAllKeychainItems{

NSArray *secItemClasses = @[(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecClassInternetPassword,
(__bridge id)kSecClassCertificate,
(__bridge id)kSecClassKey,
(__bridge id)kSecClassIdentity];
for (id secItemClass in secItemClasses) {
NSDictionary *spec = @{(__bridge id)kSecClass:secItemClass};
SecItemDelete((__bridge CFDictionaryRef)spec);
}

}

It will delete all keychain items including all accounts password or values associated.

Why SecItemAdd return me -50 (invalid params)

The error is occurring because of kSecValueRef, as per Apple's guideline kSecValueRef accepts a cryptographic key which can be generated through SecKeyRef, Please find below,

    NSData* tag = [@"com.example.keys.mykey" dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* attributes =
@{ (id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecAttrKeySizeInBits: @2048,
(id)kSecPrivateKeyAttrs:
@{ (id)kSecAttrIsPermanent: @YES,
(id)kSecAttrApplicationTag: tag,
},
};
CFErrorRef error = NULL;
SecKeyRef privateKey = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes,
&error);
SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey);
NSDictionary* addquery = @{ (id)kSecValueRef: (__bridge id)publicKey,
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: tag,
};
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)addquery, NULL);

For more info please refer Storing Keys in the Keychain

How to add another password into iOS Keychain?

I doubt if that can be done.... at max you can create two username with the second userame havinge "_passcode" attached to it and save the passcode to it...

for eg:

username: ankit
password: 1234

save this to keychain

and then again save another pair using

username: ankit_passcode
passcode: 4321

you can then retrieve it using the username provided...

hoping this helps.

Xamarin.iOS Keychain hangs when trying to query for a key storing NSData

I've fixed the problem i was having, it seems to have been some kind of race condition that was locking the app as the call to save into keychain was being done on the main thread. Normally this call is very quick and never had a problem locking anything up in the past, but something in the iOS 12.1 update changed that. In any case, I simply explicitly ran code saving the encryption password into keychain in a background thread and that fixed the problem:

Task.Run(() =>
{
var keychain = new KeyChain();
keychain.SetValueForKey("securedvalue", "securedvaluekey");
}).ConfigureAwait(false);


Related Topics



Leave a reply



Submit