I want to get a dictionary of attributes of Keychain, but I got an NSArray with 1 element of NSDictionary. Here's my code of getting attributes:
NSMutableDictionary *queryDictionary = [KeychainQueryDictionaryWithServiceAndIdentifier(serviceName, identifier) mutableCopy];
queryDictionary[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
queryDictionary[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)queryDictionary, (CFTypeRef *)&result);
if (status != errSecSuccess) {
NSLog(#"Unable to fetch account info with identifier \"%#\" (Error %li)", identifier, (long int)status);
return nil;
}
id ret = (NSDictionary *)CFBridgingRelease(result);
And here's some infomation from console
Why ret is an NSArray type?
queryDictionary[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
Fixed it!
Related
I'm trying to use Keychain Services to save a value, which would persist even if a user reinstalls the app. So I check if an item exists using SecItemCopyMatching, which returns errSecItemNotFound the first time and add a new item using SecItemAdd, which returns errSecSuccess, but the value of _attrs is nil. Also, when the code is called second time, the SecItemCopyMatching still returns errSecItemNotFound, as if SecItemAdd wasn't called. So what that could be related to?
CFMutableDictionaryRef _attrs = nil;
NSString* key = #"<unique key>";
NSMutableDictionary* query = [NSMutableDictionary dictionary];
query[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
query[(__bridge id)kSecAttrLabel] = key;
query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
query[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
OSStatus err = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef*)&_attrs);
if (err == errSecSuccess) {
return YES;
}
NSString* str = #"<some data>";
if (err == errSecItemNotFound) {
query[(__bridge id)kSecValueData] = NSData_from_string(string_from_NSString(str));
query[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAlways;
err = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef*)&_attrs);
assert(err == errSecSuccess);
}
You are re-using query for your call to SecItemAdd, and the kSecMatchLimit value being present in the dictionary for this function is breaking it. You should remove this key before calling SecItemAdd.
It's also worth noting that [str dataUsingEncoding:NSUTF8StringEncoding] might be a better choice than whatever NSData_from_string(string_from_NSString(str)) is, depending on what you're doing.
i am newbie in iOS Development and i want to store my Application UUID in KeyChain so For any Time my Application UUID remaining same i do R&D on it and Find a code from this Site i mess StackOver Flow the Code is like as
+(NSUUID *)persistentIdentifierForVendor
{
static NSString * const kKeyChainVendorID = #"co.cwbrn.PersistentIdentifier";
static NSString * const kKeyChainVendorIDAccessGroup = #"<AppIdentifier>.<keychain-access-group-identifier>";
// First, check NSUserDefaults so that we're not hitting the KeyChain every single time
NSString *uuidString = [[NSUserDefaults standardUserDefaults] stringForKey:kKeyChainVendorIDGroup];
BOOL vendorIDMissingFromUserDefaults = (uuidString == nil || uuidString.length == 0);
if (vendorIDMissingFromUserDefaults) {
// Check to see if a UUID is stored in the KeyChain
NSDictionary *query = #{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: kKeyChainVendorID,
(__bridge id)kSecAttrService: kKeyChainVendorID,
(__bridge id)kSecAttrAccessGroup: kKeyChainVendorIDAccessGroup,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne,
(__bridge id)kSecReturnAttributes: (__bridge id)kCFBooleanTrue
};
CFTypeRef attributesRef = NULL;
OSStatus result = SecItemCopyMatching((__bridge CFDictionaryRef)query, &attributesRef);
if (result == noErr) {
// There is a UUID, so try to retrieve it
NSDictionary *attributes = (__bridge_transfer NSDictionary *)attributesRef;
NSMutableDictionary *valueQuery = [NSMutableDictionary dictionaryWithDictionary:attributes];
[valueQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[valueQuery setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
CFTypeRef passwordDataRef = NULL;
OSStatus result = SecItemCopyMatching((__bridge CFDictionaryRef)valueQuery, &passwordDataRef);
if (result == noErr) {
NSData *passwordData = (__bridge_transfer NSData *)passwordDataRef;
uuidString = [[NSString alloc] initWithBytes:[passwordData bytes]
length:[passwordData length]
encoding:NSUTF8StringEncoding];
}
}
}
// Failed to read the UUID from the KeyChain, so create a new UUID and store it
if (uuidString == nil || uuidString.length == 0) {
// Generate the new UIID
CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
uuidString = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuidRef);
CFRelease(uuidRef);
// Now store it in the KeyChain
NSDictionary *query = #{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount: kKeyChainVendorID,
(__bridge id)kSecAttrService: kKeyChainVendorID,
(__bridge id)kSecAttrAccessGroup: kKeyChainVendorIDAccessGroup,
(__bridge id)kSecAttrLabel: #"",
(__bridge id)kSecAttrDescription: #"",
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleAfterFirstUnlock,
(__bridge id)kSecValueData: [uuidString dataUsingEncoding:NSUTF8StringEncoding]
};
OSStatus result = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
if (result != noErr) {
NSLog(#"ERROR: Couldn't add to the Keychain. Result = %ld; Query = %#", result, query);
return nil;
}
}
// Save UUID to NSUserDefaults so that we can avoid the KeyChain next time
if (vendorIDMissingFromUserDefaults) {
[[NSUserDefaults standardUserDefaults] setObject:uuidString forKey:kKeyChainVendorIDGroup];
}
return [[NSUUID alloc] initWithUUIDString:uuidString];
}
But i want to Know that here What is kKeyChainVendorID and kKeyChainVendorIDAccessGroup here it use is like as
static NSString * const kKeyChainVendorID = #"co.cwbrn.PersistentIdentifier";
static NSString * const kKeyChainVendorIDAccessGroup = #"<AppIdentifier>.<keychain-access-group-identifier>";
For My application how i Get two Value like as kKeyChainVendorID and kKeyChainVendorIDAccessGroup??Please Give me Solution for that and in my Xcode 5.0 Version error are Occurred in line
NSString *uuidString = [[NSUserDefaults standardUserDefaults] stringForKey:kKeyChainVendorIDGroup];
Erro is:- Replace kKeyChainVendorIDGroup with kKeyChainVendorID. Can i replace it then it is Work or not Please Give me Solution For my Both Question
Thanks in advance. and thanks to nelico that post answer in stack overflow.
Here i Post My Own Answer. I get answer From this Link
http://objectivecwithsuraj.blogspot.in/2014/01/unique-identifier-uuid-ios.html
i use FDKeyChain and Write Following Code to Save UUID In KeyChain
Just Define Two String like as
static NSString * const KeychainItem_Service = #"FDKeychain";
static NSString * const KeychainItem_UUID = #"Local";
and For Get UUID I write as
uniqueIdentifier=[self generateUUID];
-(NSString *)generateUUID {
NSString *CFUUID = nil;
if (![FDKeychain itemForKey: KeychainItem_UUID
forService: KeychainItem_Service
error: nil]) {
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
CFUUID = (NSString *)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid));
[FDKeychain saveItem: CFUUID
forKey: KeychainItem_UUID
forService: KeychainItem_Service
error: nil];
} else {
CFUUID = [FDKeychain itemForKey: KeychainItem_UUID
forService: KeychainItem_Service
error: nil];
}
return CFUUID;
}
Hope it Help to SomeOne. and if i do some fault in my answer then please give me solution. Thanks For reply.
I am at a loss here, I create a keychain query, add the item if it does not already exist, then I try and update kSecValueData with a test string and it returns error code -50, which means there was an error with one or more parameters I entered...
NSString *initial = #"";
NSData *initData = [initial dataUsingEncoding:NSUTF8StringEncoding];
//Create Search Dictionary For Phone Number...
NSDictionary *secPhoneItem = #{ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecReturnData : (__bridge id)kCFBooleanTrue,
(__bridge id)kSecValueData : initData
};
//Check to see if keychain already exists by using secItemCopyMatching and associated status code
OSStatus PhoneCheckStatus = SecItemCopyMatching((__bridge CFDictionaryRef)secPhoneItem, NULL);
//Check Status Code Phone
if (PhoneCheckStatus == errSecItemNotFound) //If Phone Keychain Item Does Not already Exist
{
//Add Phone Number To Keychain
SecItemAdd((__bridge CFDictionaryRef)secPhoneItem, NULL);
}
//Update Phone Number to String
NSString *string = #"Test String";
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *attributesForUpdate = #{
(__bridge id)kSecValueData : data
};
OSStatus news = SecItemUpdate((__bridge CFDictionaryRef)secPhoneItem, (__bridge CFDictionaryRef)attributesForUpdate);
NSLog(#"Update Status Code: %ld", news);
If anyone knows why or can shed some info, the only lead I have right now from Apples Documentation is that you can only pass real attributes into secItemUpdate(), not "meta" attributes.
So after rereading the documentation, I found out that the key-value pair (__bridge id)kSecReturnData : (__bridge id)kCFBooleanTrue cannot be used in the secItemUpdate()' query parameter. To fix my problem and help better refine the search, I added the key-value pair(__bridge id)kSecAttrDescription : someUniqueData` to the search query along with the class item specification then making my attributes dictionary returned status 0: SUCCESS!!!
Key and certificate Attribute Update-----------
for the normal SecItemUpdate code gave two error:-25300(item not found),-50(trying to update more than one item in one update method)
here is the code for update method:
//Search Dictionary.....
NSMutableDictionary *searchDict = [[NSMutableDictionary alloc] init];
NSData *privateKeyTag = [NSData dataWithBytes:[keyIdentifier UTF8String] length:keyIdentifier.length];
[searchDict setObject:privateKeyTag forKey:(__bridge id)kSecAttrApplicationTag];
[searchDict setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[searchDict setObject:(__bridge id)(kSecAttrKeyTypeRSA) forKey:(__bridge id<NSCopying>)(kSecAttrKeyType)];
[searchDict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[searchDict setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id<NSCopying>)(kSecReturnData)];
status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDict, (CFTypeRef*)&item);
if (status != errSecSuccess)
{//your code for error handling }
//dictionary for the attribute that are going to update in key of certificate
//if youwant to update your passward the add the passward attribute
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,kSecAttrAccessible, nil];
/*removing the some of the from the searching dictionary*/
[searchDict removeObjectForKey:(__bridge id)kSecReturnData];
[searchDict removeObjectForKey:(__bridge id)kSecMatchLimit];
//Creating the Array for every item in the keychain that is cert of key as Search Dictionary
NSArray *secItemclasses= #[(__bridge id)kSecClassKey,(__bridge id)kSecClassCertificate];
for (id secitemclass in secItemclasses) {
//updating the key as well as certificate attribute....//
status = SecItemUpdate((__bridge CFDictionaryRef)searchDict,(__bridge CFDictionaryRef)dict);
}
if(status != errSecSuccess)
I've been getting some odd reports for my app where the application settings stored into the NSUserDefaults is being cleared. The reports have all been on iOS 7s
I know you can manually clear the NSUserDefaults by either uninstalling or making a call to
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
But are there any other known causes for an app to clear its settings ?
If you don't want to get your data deleted, then you should use KeyChain to store values. A good way to get started: Using KeyChain
Below I am providing an example code how to store and get data back from the KeyChain
Importing required frameworks
#import <Security/Security.h>
Storing values to KeyChain
NSString *key = #"Full Name";
NSString *value = #"Steve Jobs";
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
NSString *service = [[NSBundle mainBundle] bundleIdentifier];
NSDictionary *secItem = #{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService : service,
(__bridge id)kSecAttrAccount : key,
(__bridge id)kSecValueData : valueData,
};
CFTypeRef result = NULL;
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)secItem, &result);
if (status == errSecSuccess)
{
NSLog(#"Successfully stored the value");
}
else{
NSLog(#"Failed to store the value with code: %ld", (long)status);
}
Getting values back from KeyChain
NSString *keyToSearchFor = #"Full Name";
NSString *service = [[NSBundle mainBundle] bundleIdentifier];
NSDictionary *query = #{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService : service,(__bridge id)kSecAttrAccount : keyToSearchFor,
(__bridge id)kSecReturnAttributes : (__bridge id)kCFBooleanTrue, };
CFDictionaryRef valueAttributes = NULL;
OSStatus results = SecItemCopyMatching((__bridge CFDictionaryRef)query,
(CFTypeRef *)&valueAttributes);
NSDictionary *attributes =
(__bridge_transfer NSDictionary *)valueAttributes;
if (results == errSecSuccess){
NSString *key, *accessGroup, *creationDate, *modifiedDate, *service;
key = attributes[(__bridge id)kSecAttrAccount];
accessGroup = attributes[(__bridge id)kSecAttrAccessGroup]; creationDate = attributes[(__bridge id)kSecAttrCreationDate]; modifiedDate = attributes[(__bridge id)kSecAttrModificationDate]; service = attributes[(__bridge id)kSecAttrService];
NSLog(#"Key = %#\n \ Access Group = %#\n \
Creation Date = %#\n \
Modification Date = %#\n \
Service = %#", key, accessGroup, creationDate, modifiedDate, service);
}
else
{
NSLog(#"Error happened with code: %ld", (long)results);
}
It is possible to store a NSDictionary in the iPhone keychain, using KeychainItemWrapper (or without)?
If it's not possible, have you another solution?
You must properly serialize the NSDictionary before storing it into the Keychain.
Using:
[dic description]
[dic propertyList]
you will end up with a NSDictionary collection of only NSString objects. If you want to maintain the data types of the objects, you can use NSPropertyListSerialization.
KeychainItemWrapper *keychain = [[KeychainItemWrapper alloc] initWithIdentifier:#"arbitraryId" accessGroup:nil]
NSString *error;
//The following NSData object may be stored in the Keychain
NSData *dictionaryRep = [NSPropertyListSerialization dataFromPropertyList:dictionary format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
[keychain setObject:dictionaryRep forKey:kSecValueData];
//When the NSData object object is retrieved from the Keychain, you convert it back to NSDictionary type
dictionaryRep = [keychain objectForKey:kSecValueData];
NSDictionary *dictionary = [NSPropertyListSerialization propertyListFromData:dictionaryRep mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error];
if (error) {
NSLog(#"%#", error);
}
The NSDictionary returned by the second call to NSPropertyListSerialization will maintain original data types within the NSDictionary collection.
Using the KeychainItemWrapper dependency requires modifying the library/sample code to accept NSData as the encrypted payload, which is not future proof. Also, doing the NSDictionary > NSData > NSString conversion sequence just so that you can use KeychainItemWrapper is inefficient: KeychainItemWrapper will convert your string back to NSData anyway, to encrypt it.
Here's a complete solution that solves the above by utilizing the keychain library directly. It is implemented as a category so you use it like this:
// to store your dictionary
[myDict storeToKeychainWithKey:#"myStorageKey"];
// to retrieve it
NSDictionary *myDict = [NSDictionary dictionaryFromKeychainWithKey:#"myStorageKey"];
// to delete it
[myDict deleteFromKeychainWithKey:#"myStorageKey"];
and here's the Category:
#implementation NSDictionary (Keychain)
-(void) storeToKeychainWithKey:(NSString *)aKey {
// serialize dict
NSString *error;
NSData *serializedDictionary = [NSPropertyListSerialization dataFromPropertyList:self format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
// encrypt in keychain
if(!error) {
// first, delete potential existing entries with this key (it won't auto update)
[self deleteFromKeychainWithKey:aKey];
// setup keychain storage properties
NSDictionary *storageQuery = #{
(id)kSecAttrAccount: aKey,
(id)kSecValueData: serializedDictionary,
(id)kSecClass: (id)kSecClassGenericPassword,
(id)kSecAttrAccessible: (id)kSecAttrAccessibleWhenUnlocked
};
OSStatus osStatus = SecItemAdd((CFDictionaryRef)storageQuery, nil);
if(osStatus != noErr) {
// do someting with error
}
}
}
+(NSDictionary *) dictionaryFromKeychainWithKey:(NSString *)aKey {
// setup keychain query properties
NSDictionary *readQuery = #{
(id)kSecAttrAccount: aKey,
(id)kSecReturnData: (id)kCFBooleanTrue,
(id)kSecClass: (id)kSecClassGenericPassword
};
NSData *serializedDictionary = nil;
OSStatus osStatus = SecItemCopyMatching((CFDictionaryRef)readQuery, (CFTypeRef *)&serializedDictionary);
if(osStatus == noErr) {
// deserialize dictionary
NSString *error;
NSDictionary *storedDictionary = [NSPropertyListSerialization propertyListFromData:serializedDictionary mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error];
if(error) {
NSLog(#"%#", error);
}
return storedDictionary;
}
else {
// do something with error
return nil;
}
}
-(void) deleteFromKeychainWithKey:(NSString *)aKey {
// setup keychain query properties
NSDictionary *deletableItemsQuery = #{
(id)kSecAttrAccount: aKey,
(id)kSecClass: (id)kSecClassGenericPassword,
(id)kSecMatchLimit: (id)kSecMatchLimitAll,
(id)kSecReturnAttributes: (id)kCFBooleanTrue
};
NSArray *itemList = nil;
OSStatus osStatus = SecItemCopyMatching((CFDictionaryRef)deletableItemsQuery, (CFTypeRef *)&itemList);
// each item in the array is a dictionary
for (NSDictionary *item in itemList) {
NSMutableDictionary *deleteQuery = [item mutableCopy];
[deleteQuery setValue:(id)kSecClassGenericPassword forKey:(id)kSecClass];
// do delete
osStatus = SecItemDelete((CFDictionaryRef)deleteQuery);
if(osStatus != noErr) {
// do something with error
}
[deleteQuery release];
}
}
#end
In fact, you can modify it easily to store any kind of serializable object in the keychain, not just a dictionary. Just make an NSData representation of the object you want to store.
Made few minor changes to Dts category. Converted to ARC and using NSKeyedArchiver to store custom objects.
#implementation NSDictionary (Keychain)
-(void) storeToKeychainWithKey:(NSString *)aKey {
// serialize dict
NSData *serializedDictionary = [NSKeyedArchiver archivedDataWithRootObject:self];
// encrypt in keychain
// first, delete potential existing entries with this key (it won't auto update)
[self deleteFromKeychainWithKey:aKey];
// setup keychain storage properties
NSDictionary *storageQuery = #{
(__bridge id)kSecAttrAccount: aKey,
(__bridge id)kSecValueData: serializedDictionary,
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlocked
};
OSStatus osStatus = SecItemAdd((__bridge CFDictionaryRef)storageQuery, nil);
if(osStatus != noErr) {
// do someting with error
}
}
+(NSDictionary *) dictionaryFromKeychainWithKey:(NSString *)aKey {
// setup keychain query properties
NSDictionary *readQuery = #{
(__bridge id)kSecAttrAccount: aKey,
(__bridge id)kSecReturnData: (id)kCFBooleanTrue,
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword
};
CFDataRef serializedDictionary = NULL;
OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef)readQuery, (CFTypeRef *)&serializedDictionary);
if(osStatus == noErr) {
// deserialize dictionary
NSData *data = (__bridge NSData *)serializedDictionary;
NSDictionary *storedDictionary = [NSKeyedUnarchiver unarchiveObjectWithData:data];
return storedDictionary;
}
else {
// do something with error
return nil;
}
}
-(void) deleteFromKeychainWithKey:(NSString *)aKey {
// setup keychain query properties
NSDictionary *deletableItemsQuery = #{
(__bridge id)kSecAttrAccount: aKey,
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll,
(__bridge id)kSecReturnAttributes: (id)kCFBooleanTrue
};
CFArrayRef itemList = nil;
OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef)deletableItemsQuery, (CFTypeRef *)&itemList);
// each item in the array is a dictionary
NSArray *itemListArray = (__bridge NSArray *)itemList;
for (NSDictionary *item in itemListArray) {
NSMutableDictionary *deleteQuery = [item mutableCopy];
[deleteQuery setValue:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
// do delete
osStatus = SecItemDelete((__bridge CFDictionaryRef)deleteQuery);
if(osStatus != noErr) {
// do something with error
}
}
}
#end
Encoding : [dic description]
Decoding : [dic propertyList]
You can store anything, you just need to serialize it.
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dictionary];
You should be able to store that data in the keychain.
I found that the keychain wrapper only wants strings. Not even NSData. So to store a dictionary you'll have to do as Bret suggested, but with an extra step to convert the NSData serialization to a string. Like this:
NSString *error;
KeychainItemWrapper *keychain = [[KeychainItemWrapper alloc] initWithIdentifier:MY_STRING accessGroup:nil];
NSData *dictionaryRep = [NSPropertyListSerialization dataFromPropertyList:dictToSave format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
NSString *xml = [[NSString alloc] initWithBytes:[dictionaryRep bytes] length:[dictionaryRep length] encoding:NSUTF8StringEncoding];
[keychain setObject:xml forKey:(__bridge id)(kSecValueData)];
Reading it back:
NSError *error;
NSString *xml = [keychain objectForKey:(__bridge id)(kSecValueData)];
if (xml && xml.length) {
NSData *dictionaryRep = [xml dataUsingEncoding:NSUTF8StringEncoding];
dict = [NSPropertyListSerialization propertyListWithData:dictionaryRep options:NSPropertyListImmutable format:nil error:&error];
if (error) {
NSLog(#"%#", error);
}
}
I added access group support and simulator safety to Amols solution:
//
// NSDictionary+SharedKeyChain.h
// LHSharedKeyChain
//
#import <Foundation/Foundation.h>
#interface NSDictionary (SharedKeyChain)
/**
* Returns a previously stored dictionary from the KeyChain.
*
* #param key NSString The name of the dictionary. There can be multiple dictionaries stored in the KeyChain.
* #param accessGroup NSString Access group for shared KeyChains, set to nil for no group.
*
* #return NSDictionary A dictionary that has been stored in the Keychain, nil if no dictionary for the key and accessGroup exist.
*/
+ (NSDictionary *)dictionaryFromKeychainWithKey:(NSString *)key accessGroup:(NSString *)accessGroup;
/**
* Deletes a previously stored dictionary from the KeyChain.
*
* #param key NSString The name of the dictionary. There can be multiple dictionaries stored in the KeyChain.
* #param accessGroup NSString Access group for shared KeyChains, set to nil for no group.
*/
+ (void)deleteFromKeychainWithKey:(NSString *)key accessGroup:(NSString *)accessGroup;
/**
* Save dictionary instance to the KeyChain. Any previously existing data with the same key and accessGroup will be overwritten.
*
* #param key NSString The name of the dictionary. There can be multiple dictionaries stored in the KeyChain.
* #param accessGroup NSString Access group for shared KeyChains, set to nil for no group.
*/
- (void)storeToKeychainWithKey:(NSString *)key accessGroup:(NSString *)accessGroup;
#end
//
// NSDictionary+SharedKeyChain.m
// LHSharedKeyChain
//
#import "NSDictionary+SharedKeyChain.h"
#implementation NSDictionary (SharedKeyChain)
- (void)storeToKeychainWithKey:(NSString *)key accessGroup:(NSString *)accessGroup;
{
// serialize dict
NSData *serializedDictionary = [NSKeyedArchiver archivedDataWithRootObject:self];
// encrypt in keychain
// first, delete potential existing entries with this key (it won't auto update)
[NSDictionary deleteFromKeychainWithKey:key accessGroup:accessGroup];
// setup keychain storage properties
NSDictionary *storageQuery = #{
(__bridge id)kSecAttrAccount: key,
#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
(__bridge id)kSecAttrAccessGroup: accessGroup,
#endif
(__bridge id)kSecValueData: serializedDictionary,
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlocked
};
OSStatus status = SecItemAdd ((__bridge CFDictionaryRef)storageQuery, nil);
if (status != noErr)
{
NSLog (#"%d %#", (int)status, #"Couldn't save to Keychain.");
}
}
+ (NSDictionary *)dictionaryFromKeychainWithKey:(NSString *)key accessGroup:(NSString *)accessGroup;
{
// setup keychain query properties
NSDictionary *readQuery = #{
(__bridge id)kSecAttrAccount: key,
#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
(__bridge id)kSecAttrAccessGroup: accessGroup,
#endif
(__bridge id)kSecReturnData: (id)kCFBooleanTrue,
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword
};
CFDataRef serializedDictionary = NULL;
OSStatus status = SecItemCopyMatching ((__bridge CFDictionaryRef)readQuery, (CFTypeRef *)&serializedDictionary);
if (status == noErr)
{
// deserialize dictionary
NSData *data = (__bridge NSData *)serializedDictionary;
NSDictionary *storedDictionary = [NSKeyedUnarchiver unarchiveObjectWithData:data];
return storedDictionary;
}
else
{
NSLog (#"%d %#", (int)status, #"Couldn't read from Keychain.");
return nil;
}
}
+ (void)deleteFromKeychainWithKey:(NSString *)key accessGroup:(NSString *)accessGroup;
{
// setup keychain query properties
NSDictionary *deletableItemsQuery = #{
(__bridge id)kSecAttrAccount: key,
#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
(__bridge id)kSecAttrAccessGroup: accessGroup,
#endif
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll,
(__bridge id)kSecReturnAttributes: (id)kCFBooleanTrue
};
CFArrayRef itemList = nil;
OSStatus status = SecItemCopyMatching ((__bridge CFDictionaryRef)deletableItemsQuery, (CFTypeRef *)&itemList);
// each item in the array is a dictionary
NSArray *itemListArray = (__bridge NSArray *)itemList;
for (NSDictionary *item in itemListArray)
{
NSMutableDictionary *deleteQuery = [item mutableCopy];
[deleteQuery setValue:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
// do delete
status = SecItemDelete ((__bridge CFDictionaryRef)deleteQuery);
if (status != noErr)
{
NSLog (#"%d %#", (int)status, #"Couldn't delete from Keychain.");
}
}
}
#end