My app acts like a VPN client to an IPSec VPN server (strongSwan).
I have used NEVPNManager to setup the VPN profile. The code is something like this:
#define KEY_PASSWORD #"password"
#define PASSWORD #"password"
#define VPN_SERVER #"10.1.1.1"
#define KEY_USERNAME #"username"
#define USERNAME #"myusername"
#define KEY_SHARED_SECRET #"sharedSecret"
#define SHARED_SECRET #"thisisthesecretekey"
#define LOCAL_IDENTIFIER #"myserver.com.client"
#define REMOTE_IDENTIFIER #"myserver.com.server"
-(void) setupVPNProfile {
NEVPNProtocolIKEv2 *protocol = [[NEVPNProtocolIKEv2 alloc] init];
protocol.username = USERNAME;
NSData *passwdRef = [self getData:KEY_PASSWORD];
if (passwdRef == nil) {
[self storeData:KEY_PASSWORD data:[PASSWORD dataUsingEncoding:NSUTF8StringEncoding]];
passwdRef = [self getData:PASSWORD];
NSLog(#"passwdRef: %#", [[NSString alloc] initWithData:passwdRef encoding:NSUTF8StringEncoding]);
}
protocol.passwordReference = passwdRef;
protocol.serverAddress = VPN_SERVER;
protocol.authenticationMethod = NEVPNIKEAuthenticationMethodSharedSecret;
NSData *sharedSecretRef = [self getData:KEY_SHARED_SECRET];
if (sharedSecretRef == nil) {
[self storeData:KEY_SHARED_SECRET data:[SHARED_SECRET dataUsingEncoding:NSUTF8StringEncoding]];
sharedSecretRef = [self getData:KEY_SHARED_SECRET];
NSLog(#"sharedSecretRef: %#", [[NSString alloc] initWithData:sharedSecretRef encoding:NSUTF8StringEncoding]);
}
protocol.sharedSecretReference = sharedSecretRef;
protocol.localIdentifier = LOCAL_IDENTIFIER;
protocol.remoteIdentifier = REMOTE_IDENTIFIER;
protocol.useExtendedAuthentication = YES;
protocol.disconnectOnSleep = NO;
self.manager.protocolConfiguration = protocol;
self.manager.enabled = YES;
}
#pragma mark - Keychain methods
- (void) storeData: (NSString * )key data:(NSData *)data {
NSLog(#"Store Data");
NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
[dict setObject:#"VPN" forKey:(__bridge id)kSecAttrService];
[dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
[dict setObject:data forKey:(__bridge id)kSecValueData];
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dict, NULL);
if(errSecSuccess != status) {
NSLog(#"Unable add item with key =%# error:%d",key,(int)status);
}
}
- (NSData *) getData: (NSString *)key {
NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
[dict setObject:#"VPN" forKey:(__bridge id)kSecAttrService];
[dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
[dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnPersistentRef];
CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)dict,&result);
if( status != errSecSuccess) {
NSLog(#"Unable to fetch item for key %# with error:%d",key,(int)status);
return nil;
}
NSData *resultData = (__bridge NSData *)result;
return resultData;
}
- (BOOL) removeData: (NSString *) key {
NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
[dict setObject:#"VPN" forKey:(__bridge id)kSecAttrService];
[dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)dict);
if( status != errSecSuccess) {
NSLog(#"Unable to fetch item for key %# with error:%d",key,(int)status);
return NO;
}
return YES;
}
Here values like username, password server-address are stored in MACROS defined above. Per the required design, password and sharedSecret is to be stored as Persistent KeyChain items.
In this setup, how can I gracefully change values like username, password, serverAddress, sharedSecret?
What I have tried so far:
I changed the values like server address, username in the code. I
used storeData functions to overwrite the password and sharedSecret
persistent keychain items. However when I ran the app after making
these changes I wasn't able to connect to new VPN server. Please
note that I didn't make any mistake with the new values I had to
use. I double checked before updating. Please also note that I was
able to connect to VPN server when I created a .mobileconfig file
out of those new params. Its just that my VPN client app wasn't
able to connect to server anymore. It would try connecting and would
disconnect again. Uninstalling the app had no effect as well.
Setting manager.protocolConfiguration = nil. Doing this has no effect. The installed protocolConfiguration stays intact.
I do not want to remove the VPN profile by calling manager removePreferencesWithCompletionHandler:] since the user would see the VPN related pop-up again when he tries to connect to VPN server.
If anyone has done this before please help.
Thanks.
To change VPN account you should be able to simply change the VPN account parameters and then start VPNManager with the new values. You don't need to stop the currently running VPN Session and you certainly don't need to delete the profile, just call NEVPNManager.shared.locadFromPreferences, make the changes, then .saveToPreferences and startVPNTunnel as normal.
Since you're not able to get VPN tunnel to start even with the known good credentials using variables instead of macros, then you are probably passing values incorrectly from the keychain or not what you expect. I would compare every parameter using print statements, first with your current working solution that uses macros then with the non-working keychain code.
Related
I'm using this website to generate public and private key
http://travistidwell.com/jsencrypt/demo/ and I'm trying to figure it out how to create SecKeyRef from the private key
I've found this project which looks very promising but it doesn't work for me.
Here is my code which try to create the SecKeyRef
NSString* publicKey = #"MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHKzKc/6vphvntLiP1r/YvxjSLolPeDeOCy48ao5ymwNU2Nqbfeir/qHqbqSAhclAO8TGq8QIpE5ObAKNp2j01pu8Cu9AqwdtZ6EZa/NYahfITKS8iYGs6cHzk2LGw8AqFOEJqHrW/xR8MOS1J765KeZOBCSrWZ5Ag/lpb5jxiDlAgMBAAE=";
[[RSA sharedInstance] setPublicKey:publicKey];
and
- (BOOL)setPublicKey: (NSString *)keyAsBase64 {
NSData *extractedKey =
[[NSData alloc] initWithBase64EncodedString:keyAsBase64 options:0];
/* Load as a key ref */
OSStatus error = noErr;
CFTypeRef persistPeer = NULL;
NSData * refTag = [self.serverPublicIdentifier dataUsingEncoding:NSUTF8StringEncoding];
NSMutableDictionary * keyAttr = [[NSMutableDictionary alloc] init];
[keyAttr setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[keyAttr setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
[keyAttr setObject:refTag forKey:(__bridge id)kSecAttrApplicationTag];
/* First we delete any current keys */
error = SecItemDelete((__bridge CFDictionaryRef) keyAttr);
[keyAttr setObject:extractedKey forKey:(__bridge id)kSecValueData];
[keyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnPersistentRef];
error = SecItemAdd((__bridge CFDictionaryRef) keyAttr, (CFTypeRef *)&persistPeer);
if (persistPeer == nil || ( error != noErr && error != errSecDuplicateItem)) {
NSLog(#"Problem adding public key to keychain");
return FALSE;
}
CFRelease(persistPeer);
serverPublicRef = nil;
/* Now we extract the real ref */
[keyAttr removeAllObjects];
/*
[keyAttr setObject:(id)persistPeer forKey:(id)kSecValuePersistentRef];
[keyAttr setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef];
*/
[keyAttr setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[keyAttr setObject:refTag forKey:(__bridge id)kSecAttrApplicationTag];
[keyAttr setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
[keyAttr setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
// Get the persistent key reference.
error = SecItemCopyMatching((__bridge CFDictionaryRef)keyAttr, (CFTypeRef *)&serverPublicRef);
if (serverPublicRef == nil || ( error != noErr && error != errSecDuplicateItem)) {
NSLog(#"Error retrieving public key reference from chain");
return FALSE;
}
return TRUE;
}
I'm getting serverPublicRef == nil but the error is 0 (which is ok.)
The kSecClassKey does not have a kSecValueData field. You should look into using SecPKCS12Import().
Some related questions:
How to create private key from file in Objective C?
How to make a valid p12 file to be correctly imported by SecPKCS12Import
Can anybody tell me where is the miastake? I have no idea now.
//Get a query Dic
+ (NSMutableDictionary *)keychainQueryDictionary:(NSString *)service
{
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kSecClassGenericPassword,(__bridge id)kSecClass,
service, (__bridge id)kSecAttrService,
service, (__bridge id)kSecAttrAccount,
(__bridge id)kSecAttrAccessibleAfterFirstUnlock,(__bridge id)kSecAttrAccessible,
nil];
}
//Here i save some data to keychain
+ (OSStatus)saveData:(id)data service:(NSString *)serviceIdentify
{
NSMutableDictionary *keychainQuery = [self keychainQueryDictionary:serviceIdentify];
SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];
return SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL);
}
//delete opration
+ (OSStatus)deleteData:(NSString *)serviceIdentify
{
NSMutableDictionary *keychainQuery = [self keychainQueryDictionary:serviceIdentify];
return SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
}
//Here i try to update one item in keychian, but i get an error -50, but i have no idea where is the wrong param
+ (OSStatus)updataData:(id)data service:(NSString *)serviceIdentify
{
NSMutableDictionary *keychainQuery = [self keychainQueryDictionary:serviceIdentify];
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[keychainQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
CFDataRef keyData = NULL;
OSStatus status = errSecNotAvailable;
if (SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
NSMutableDictionary *queryDict = nil;
NSDictionary *ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
queryDict = [NSMutableDictionary dictionaryWithDictionary:ret];
[queryDict setObject:[keychainQuery objectForKey:(__bridge id)kSecClass] forKey:(__bridge id)kSecClass];
NSMutableDictionary *attributesToUpdate = [self keychainQueryDictionary:serviceIdentify];
[attributesToUpdate setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];
status = SecItemUpdate((__bridge CFDictionaryRef)queryDict,(__bridge CFDictionaryRef)attributesToUpdate);
}
return status;
}
//read opration
+ (id)loadData:(NSString *)serviceIdentify
{
id ret = nil;
NSMutableDictionary *keychainQuery = [self keychainQueryDictionary:serviceIdentify];
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[keychainQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
}
return ret;
}
-50 is errSecParam. I think you have the parameters flipped for SecItemUpdate. The first param is the search query to find the matching item that needs to be updated. This probably should be created by calling your keychainQueryDictionary method. The second parameter should contain the change - in your example this is the new data for kSecValueData like you are doing, but the dictionary generally doesn't include any of search attributes. In your example it looks like it also contains all the search attributes again which will cause an errSecParam.
As a separate point, also note that any values in the keychainQuery dictionary that are not applicable to the update will also cause errSecParam. If these values are present they can simply be removed, for example:
CFDictionaryRemoveValue(keychainQuery, kSecReturnData);
CFDictionaryRemoveValue(keychainQuery, kSecMatchLimit);
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 am using apple provided keychainwrapper sample code to store NSDictionary data which i get in my application authorization. I am receiving errSecParam (-50) as error code from SecItemAdd API.
Below is the code for keychainwrapper.m
#import "KeychainItemWrapper.h"
#import "SynthesizeSingleton.h"
#import <Security/Security.h>
#interface KeychainItemWrapper (PrivateMethods)
/*
The decision behind the following two methods (secItemFormatToDictionary and dictionaryToSecItemFormat) was
to encapsulate the transition between what the detail view controller was expecting (NSString *) and what the
Keychain API expects as a validly constructed container class.
*/
- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert;
- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert;
// Updates the item in the keychain, or adds it if it doesn't exist.
- (void)writeToKeychain;
#end
#implementation KeychainItemWrapper
{
NSMutableDictionary *keychainItemData; // The actual keychain item data backing store.
NSMutableDictionary *genericPasswordQuery; // A placeholder for the generic keychain item query used to locate the item.
}
SYNTHESIZE_SINGLETON_FOR_CLASS(KeychainItemWrapper);
#pragma mark singleton implementation
+(KeychainItemWrapper *) sharedInstance
{
return [self sharedKeychainItemWrapper];
}
- (id) init
{
self = [super init];
if (self) {
// Do Nothing
}
return self;
}
- (void)createKeychainItemWithIdentifier: (NSString *)identifier accessGroup:(NSString *) accessGroup;
{
// Begin Keychain search setup. The genericPasswordQuery leverages the special user
// defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
// items which may be included by the same application.
genericPasswordQuery = [[NSMutableDictionary alloc] init];
[genericPasswordQuery setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
[genericPasswordQuery setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
// 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:(__bridge id)kSecAttrAccessGroup];
#endif
}
// Use the proper search constants, return only the attributes of the first match.
[genericPasswordQuery setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecMatchLimitOne];
[genericPasswordQuery setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnAttributes];
NSDictionary *tempQuery = [NSDictionary dictionaryWithDictionary:genericPasswordQuery];
CFMutableDictionaryRef outDictionary = NULL;
if (!SecItemCopyMatching((__bridge CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
{
// Stick these default values into keychain item if nothing found.
[self resetKeychainItem];
// Add the generic attribute and the keychain access group.
[keychainItemData setObject:identifier forKey:(__bridge id)kSecAttrGeneric];
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:(__bridge id)kSecAttrAccessGroup];
#endif
}
}
else
{
// load the saved data from Keychain.
keychainItemData = [self secItemFormatToDictionary:(__bridge NSDictionary *)outDictionary];
}
if(outDictionary) CFRelease(outDictionary);
}
- (void)setObject:(id)inObject forKey:(id)key
{
if (inObject == nil) return;
id currentObject = [keychainItemData objectForKey:key];
if (![currentObject isEqual:inObject])
{
[keychainItemData setObject:inObject forKey:key];
[self writeToKeychain];
}
}
- (id)objectForKey:(id)key
{
return [keychainItemData objectForKey:key];
}
- (void)resetKeychainItem
{
OSStatus junk = noErr;
if (!keychainItemData)
{
keychainItemData = [[NSMutableDictionary alloc] init];
}
else if (keychainItemData)
{
NSMutableDictionary *tempDictionary = [self dictionaryToSecItemFormat:keychainItemData];
junk = SecItemDelete((__bridge CFDictionaryRef)tempDictionary);
NSAssert( junk == noErr || junk == errSecItemNotFound, #"Problem deleting current dictionary." );
}
// Default attributes for keychain item.
[keychainItemData setObject:#"" forKey:(__bridge id)kSecAttrAccount];
[keychainItemData setObject:#"" forKey:(__bridge id)kSecAttrLabel];
[keychainItemData setObject:#"" forKey:(__bridge id)kSecAttrDescription];
// Default data for keychain item.
[keychainItemData setObject:#"" forKey:(__bridge id)kSecValueData];
// [keychainItemData setObject:[NSData data] forKey:(__bridge id)kSecValueData];
}
- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert
{
// The assumption is that this method will be called with a properly populated dictionary
// containing all the right key/value pairs for a SecItem.
// Create a dictionary to return populated with the attributes and data.
NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
// Add the Generic Password keychain item class attribute.
[returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
// Convert the NSString to NSData to meet the requirements for the value type kSecValueData.
// This is where to store sensitive data that should be encrypted.
NSString *passwordString = [dictionaryToConvert objectForKey:(__bridge id)kSecValueData];
[returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData];
return returnDictionary;
}
- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert
{
// The assumption is that this method will be called with a properly populated dictionary
// containing all the right key/value pairs for the UI element.
// Create a dictionary to return populated with the attributes and data.
NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
// Add the proper search key and class attribute.
[returnDictionary setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
[returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
// Acquire the password data from the attributes.
CFDataRef passwordData = NULL;
if (SecItemCopyMatching((__bridge CFDictionaryRef)returnDictionary, (CFTypeRef *)&passwordData) == noErr)
{
// Remove the search, class, and identifier key/value, we don't need them anymore.
[returnDictionary removeObjectForKey:(__bridge id)kSecReturnData];
// Add the password to the dictionary, converting from NSData to NSString.
NSString *password = [[NSString alloc] initWithBytes:[(__bridge NSData *)passwordData bytes] length:[(__bridge NSData *)passwordData length]
encoding:NSUTF8StringEncoding];
[returnDictionary setObject:password forKey:(__bridge id)kSecValueData];
}
else
{
// Don't do anything if nothing is found.
NSAssert(NO, #"Serious error, no matching item found in the keychain.\n");
}
if(passwordData) CFRelease(passwordData);
return returnDictionary;
}
- (void)writeToKeychain
{
CFDictionaryRef attributes = NULL;
NSMutableDictionary *updateItem = nil;
OSStatus result;
if (SecItemCopyMatching((__bridge CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr)
{
// First we need the attributes from the Keychain.
updateItem = [NSMutableDictionary dictionaryWithDictionary:(__bridge NSDictionary *)attributes];
// Second we need to add the appropriate search key/values.
[updateItem setObject:[genericPasswordQuery objectForKey:(__bridge id)kSecClass] forKey:(__bridge id)kSecClass];
// Lastly, we need to set up the updated attribute list being careful to remove the class.
NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData];
[tempCheck removeObjectForKey:(__bridge id)kSecClass];
#if TARGET_IPHONE_SIMULATOR
// Remove 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).
//
// The access group attribute will be included in items returned by SecItemCopyMatching,
// which is why we need to remove it before updating the item.
[tempCheck removeObjectForKey:(__bridge id)kSecAttrAccessGroup];
#endif
// An implicit assumption is that you can only update a single item at a time.
result = SecItemUpdate((__bridge CFDictionaryRef)updateItem, (__bridge CFDictionaryRef)tempCheck);
NSAssert( result == noErr, #"Couldn't update the Keychain Item." );
}
else
{
// No previous item found; add the new one.
result = SecItemAdd((__bridge CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);
NSAssert( result == noErr, #"Couldn't add the Keychain Item." );
}
if(attributes) CFRelease(attributes);
}
#end
While using this..
KeychainItemWrapper *secClientIDMapping = [KeychainItemWrapper sharedInstance];
[secClientIDMapping createKeychainItemWithIdentifier:#"com.xxx.ClientID" accessGroup:nil];
NSString *error;
NSData *dictionaryRep = [NSPropertyListSerialization dataFromPropertyList:clientIDMapping format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
[secClientIDMapping setObject:dictionaryRep forKey:#"com.xxx.ClientID"];
Couldn't solve it from morning. Basically everywhere I am getting how to store a string but not a dictionary object.
As no body answered this question I am posting it myself as i found the way to it. When adding to keychain if we are passing other than NSString like NSData we need to serialize the data before adding to keychain.
Here is the answer..
NSDictionary *secTokenDic = [dictionaryToConvert objectForKey:(__bridge id)kSecValueData];
NSString *error;
NSData *dictionaryRep = [NSPropertyListSerialization dataFromPropertyList:secTokenDic format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
[returnDictionary setObject:dictionaryRep forKey:(__bridge id)kSecValueData];
And similarly when retrieving need to deserialize the NSData. Hope this helps somebody else who is struggling with similar problem.
I have two keys, public and private, that are both stored in SecKeyRef-variables. For simplicity's sake, let's start with the public one. What I wanna do is export it to an NSData object. For that, there is an almost famous code snippet provide by Apple, which is here:
- (NSData *)getPublicKeyBits {
OSStatus sanityCheck = noErr;
NSData * publicKeyBits = nil;
NSMutableDictionary * queryPublicKey = [[NSMutableDictionary alloc] init];
// Set the public key query dictionary.
[queryPublicKey setObject:(id)kSecClassKey forKey:(id)kSecClass];
[queryPublicKey setObject:publicTag forKey:(id)kSecAttrApplicationTag];
[queryPublicKey setObject:(id)kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnData];
// Get the key bits.
sanityCheck = SecItemCopyMatching((CFDictionaryRef)queryPublicKey, (CFTypeRef *)&publicKeyBits);
if (sanityCheck != noErr)
{
publicKeyBits = nil;
}
[queryPublicKey release];
return publicKeyBits;
}
I have Xcode 4.6.2, however, and the code appears erroneous ("__bridge" is added before each conversion to id). The new version looks like this:
- (NSData *)getPublicKeyBitsFromKey:(SecKeyRef)givenKey {
OSStatus sanityCheck = noErr;
NSData * publicKeyBits = nil;
NSMutableDictionary * queryPublicKey = [[NSMutableDictionary alloc] init];
// Set the public key query dictionary.
[queryPublicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[queryPublicKey setObject:publicTag forKey:(__bridge id)kSecAttrApplicationTag];
[queryPublicKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
[queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnData];
// Get the key bits.
sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef)queryPublicKey, (CFTypeRef *)&publicKeyBits);
if (sanityCheck != noErr)
{
publicKeyBits = nil;
}
return publicKeyBits;
}
There are still two errors, though:
use of undeclared identifier 'publicTag'
Cast of an indirect pointer to an Objective-C pointer to 'CFTypeRef ' (aka 'const void *') is disallowed with ARC
Now, I hope that after your help, the first issue will no longer be a problem, for I do not want to build a query or whatnot to extract the key from the keychain. I have it in a variable and I wish to extract it from there. The variable's name is givenPublicKey, and that's the key I wish to convert to NSData.
So, how would I go about doing this and solving this ARC-issue?
Follow-up: How can I export a private key to NSData, since I've read several time that the function I'm trying to work with only works for public keys.
use of undeclared identifier 'publicTag'
The publicTag is just some unique identifier added to the Keychain items. In the CryptoExercise sample project it is defined as
#define kPublicKeyTag "com.apple.sample.publickey"
static const uint8_t publicKeyIdentifier[] = kPublicKeyTag;
NSData *publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];
Cast of an indirect pointer to an Objective-C pointer to 'CFTypeRef ' (aka 'const void *') is disallowed with ARC
This can be solved by using a temporary CFTypeRef variable:
CFTypeRef result;
sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef)queryPublicKey, &result);
if (sanityCheck == errSecSuccess) {
publicKeyBits = CFBridgingRelease(result);
}
I do not want to build a query or whatnot to extract the key from the keychain. I have it in a variable and I wish to extract it from there ...
As far as I know, you have to store the SecKeyRef to the Keychain temporarily. SecItemAdd
has the option to return the added item as data. From the documentation:
To obtain the data of the added item as an object of type CFDataRef,
specify the return type key kSecReturnData with a value of
kCFBooleanTrue.
Putting all that together, the following code should do what you want:
- (NSData *)getPublicKeyBitsFromKey:(SecKeyRef)givenKey {
static const uint8_t publicKeyIdentifier[] = "com.your.company.publickey";
NSData *publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];
OSStatus sanityCheck = noErr;
NSData * publicKeyBits = nil;
NSMutableDictionary * queryPublicKey = [[NSMutableDictionary alloc] init];
[queryPublicKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[queryPublicKey setObject:publicTag forKey:(__bridge id)kSecAttrApplicationTag];
[queryPublicKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
// Temporarily add key to the Keychain, return as data:
NSMutableDictionary * attributes = [queryPublicKey mutableCopy];
[attributes setObject:(__bridge id)givenKey forKey:(__bridge id)kSecValueRef];
[attributes setObject:#YES forKey:(__bridge id)kSecReturnData];
CFTypeRef result;
sanityCheck = SecItemAdd((__bridge CFDictionaryRef) attributes, &result);
if (sanityCheck == errSecSuccess) {
publicKeyBits = CFBridgingRelease(result);
// Remove from Keychain again:
(void)SecItemDelete((__bridge CFDictionaryRef) queryPublicKey);
}
return publicKeyBits;
}
I hope that this works, I cannot test it at the moment.
Follow-up: How can I export a private key to NSData, since I've read several time that the function I'm trying to work with only works for public keys.
I don't know.