Is it possible to update the value of the attribute kSecAttrAccessible
of existing items in the Keychain? It seems that it cannot be changed after the item was added to the Keychain. The following steps back up my assumption.
Add a new item to the Keychain:
NSData *encodedIdentifier = [@"BUNDLE_IDENTIFIER"
dataUsingEncoding:NSUTF8StringEncoding];
NSData *encodedPassword = [@"PASSWORD"
dataUsingEncoding:NSUTF8StringEncoding];
// Construct a Keychain item
NSDictionary *keychainItem =
[NSDictionary dictionaryWithObjectsAndKeys:
kSecClassGenericPassword, kSecClass,
encodedIdentifier, kSecAttrGeneric,
encodedIdentifier, kSecAttrService,
@"USERNAME", kSecAttrAccount,
kSecAttrAccessibleWhenUnlocked, kSecAttrAccessible,
encodedPassword, kSecValueData
nil];
// Add item to Keychain
OSStatus addItemStatus = SecItemAdd((CFDictionaryRef)keychainItem, NULL);
At a later time, change the attribute kSecAttrAccessible
from kSecAttrAccessibleWhenUnlocked
to kSecAttrAccessibleAfterFirstUnlock
:
NSData *encodedIdentifier = [@"BUNDLE_IDENTIFIER"
dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassGenericPassword, kSecClass,
encodedIdentifier, kSecAttrGeneric,
encodedIdentifier, kSecAttrService,
nil];
NSDictionary *updatedAttributes =
[NSDictionary dictionaryWithObject:kSecAttrAccessibleAfterFirstUnlock
forKey:kSecAttrAccessible];
OSStatus updateItemStatus = SecItemUpdate((CFDictionaryRef)query,
(CFDictionaryRef)updatedAttributes);
The problem with this approach is that updateItemStatus
always results in the status errSecUnimplemented
.
I think it should be possible to update the value of kSecAttrAccessible
because requirements of applications change. What if an application added ten items to the Keychain in the past without specifying the protection class with kSecAttrAccessible
. The Keychain implicitly assigns new items the value kSecAttrAccessibleWhenUnlocked
if the protection class is not set explicitly by the developer. Later, the developer needs to change the protection class to kSecAttrAccessibleAfterFirstUnlock
because the application must access it in the background (Multitasking). How can the developer accomplish that?
There is already a thread in the Apple Developer Forums, but it has not yielded an answer yet: https://devforums.apple.com/thread/87646?tstart=0
After opening a support incident at Apple Developer Technical Support (ADTS), I received a reply that answers this question. SecItemUpdate()
requires the Keychain item's data via the attribute kSecValueData
to perform the update of the attribute kSecAttrAccessible
. According to ADTS, this constraint is currently not documented in the reference documentation.
NSData *encodedIdentifier = [@"BUNDLE_IDENTIFIER"
dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
kSecClassGenericPassword, kSecClass,
encodedIdentifier, kSecAttrGeneric,
encodedIdentifier, kSecAttrService,
nil];
// Obtain the Keychain item's data via SecItemCopyMatching()
NSData *itemData = ...;
NSDictionary *updatedAttributes =
[NSDictionary dictionaryWithObjectsAndKeys:
kSecAttrAccessibleAfterFirstUnlock, kSecAttrAccessible,
(CFDataRef)itemData, kSecValueData,
nil];
OSStatus updateItemStatus = SecItemUpdate((CFDictionaryRef)query,
(CFDictionaryRef)updatedAttributes);
// updateItemStatus should have the value errSecSuccess