The documentation on SecPKCS12Import states the following:
[…] You can then use the Keychain Services API (see Keychain Services
Reference) to put the identities and associated certificates in the
keychain.
This means that the items returned in the “items” argument (3rd argument of that function) should not be automatically added to the keychain. However, I have found that those items are automatically added to the keychain when using that function. If I try to add them using SecItemAdd, I get errSecDuplicateItem.
Is this a bug or should it be this way? Why are the items automatically added?
Here is some sample code:
NSDictionary *options = [[NSDictionary alloc] initWithObjectsAndKeys:#"password", (id)kSecImportExportPassphrase, nil];
CFArrayRef items_ = NULL;
OSStatus ret = SecPKCS12Import((CFDataRef)pkcs12data /* get this from somewhere … */, (CFDictionaryRef)options, &items_);
If you use that code and then open Keychain Access, you’ll see that the certificate and the private key have been added to the keychain.
Regards,
David.
It seems like Apple's documentation may be out of date for that link (SecPKCS12Import), because this link https://developer.apple.com/library/ios/qa/qa1745/_index.html mentions that "reading in a PKCS#12-formatted blob and then importing the contents of the blob into the app's keychain using the function SecPKCS12Import..."
Going by the document revision dates, QA1745 is more recent than the Certificate, Key, and Trust Services Reference.
Is this a bug or should it be this way?
It isn't a bug, the documentation is only incorrect. Well, it is correct for iOS, it is just incorrect for macOS.
Why are the items automatically added?
This is caused by the way how this function is implemented in macOS. The comments in the implementation reveal the cause:
// SecPKCS12Import is implemented on Mac OS X in terms of the existing
// SecKeychainItemImport API, which supports importing items into a
// specified keychain with initial access control settings for keys.
SecPKCS12Import in macOS is just a wrapper around SecKeychainItemImport and as the function name implies, this function imports into a keychain. This also explains the following code:
if (!importKeychain) {
// SecKeychainItemImport requires a keychain, so use default
status = SecKeychainCopyDefault(&importKeychain);
}
On iOS the function is implemented standalone and not as a wrapper since SecKeychainItemImport isn't even available on iOS.
But there is a way round that if that is a problem for you. Many people work around it by creating a temporary keychain, which will never be visible to the system or the user (and thus also not be visible to apps or in Keychain Access), of course, this will work too, but is a bit of an ugly hack.
Better: Use SecItemImport and as import format use kSecFormatPKCS12, then you also get the parsed identities but nothing is imported anywhere unless you request that.
SecPKCS12Import does NOT add items to the keychain. However, it WILL look in the keychain to see if imported items are already there. If it finds existing items, they will be returned for the SecIdentityRef (SecCertificateRef and SecKeyRef). This is why you can get errSecDuplicateItem when calling SecItemAdd after calling SecPKCS12Import.
When debugging, you might want to remove everything in your keychain using code like this:
void _EraseKeychain()
{
NSMutableArray *toDelete = [NSMutableArray array];
NSArray *classes = #[(__bridge id)kSecClassCertificate,
(__bridge id)kSecClassKey,
(__bridge id)kSecClassIdentity,
(__bridge id)kSecClassInternetPassword,
(__bridge id)kSecClassGenericPassword];
NSMutableDictionary *query = [NSMutableDictionary dictionary];
query[(id)kSecClass] = (__bridge id)kSecClassIdentity;
query[(id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
query[(id)kSecReturnPersistentRef] = #YES;
id class;
for( class in classes )
{
query[(__bridge id)kSecClass] = class;
CFTypeRef items = nil;
OSStatus result = SecItemCopyMatching((__bridge CFDictionaryRef)query, &items);
if( result == errSecSuccess )
{
[toDelete addObjectsFromArray:(__bridge NSArray*)items];
CFRelease(items);
}
}
id deleteRef;
for( deleteRef in toDelete )
{
NSString *objectKind = #"unknown";
if( CFGetTypeID(deleteRef) == CFDataGetTypeID() )
{
objectKind = [[NSString alloc] initWithUTF8String:(char *)[(__bridge NSData*)deleteRef bytes]];
}
NSDictionary *delRequest = #{(id)kSecValuePersistentRef:deleteRef};
OSStatus deleteResult = SecItemDelete((__bridge CFDictionaryRef)delRequest);
if( deleteResult == errSecSuccess )
NSLog(#"Deleted item(%#) with persistent ref %#", objectKind, deleteRef);
else if( deleteResult == errSecItemNotFound )
NSLog(#"Already deleted item(%#) with persistent ref %#", objectKind, deleteRef);
else
NSLog(#"Can't delete keychain item(%#) with persistent ref %#", objectKind, deleteRef);
}
}
Related
How can I access the Bundle Seed ID/Team ID/App Identifier Prefix string programmatically? (These are all the same thing as far as I can tell).
I am using the UICKeychainStore keychain wrapper to persist data across several applications. Each of these applications has a shared keychain access group in their entitlement plists, and share the same provisioning profile. By default, the keychain services use the first access group in the plist as the access group to save data to. This looks like "AS234SDG.com.myCompany.SpecificApp" when I debug UICKeychainStore. I would like to set the access group to "AS234SDG.com.myCompany.SharedStuff", but I can't seem to locate how to get the "AS234SDG" string of the access group programmatically, and would like to avoid hard-coding it if possible.
Info.plist can have your own information and if you write a value with $(AppIdentifierPrefix), it is replaced to the real app identifier prefix at building phase.
So, try this:
In your Info.plist, add an info about app identifier prefix.
<key>AppIdentifierPrefix</key>
<string>$(AppIdentifierPrefix)</string>
You can then retrieve it programmatically with Objective-C:
NSString *appIdentifierPrefix =
[[NSBundle mainBundle] objectForInfoDictionaryKey:#"AppIdentifierPrefix"];
and with Swift:
let appIdentifierPrefix =
Bundle.main.infoDictionary!["AppIdentifierPrefix"] as! String
Note that appIdentifierPrefix ends with a period; e.g. AS234SDG.
You can programmatically retrieve the Bundle Seed ID by looking at the access group attribute (i.e. kSecAttrAccessGroup) of an existing KeyChain item. In the code below, I look up for an existing KeyChain entry and create one if it doesn't not exist. Once I have a KeyChain entry, I extract the access group information from it and return the access group's first component separated by "." (period) as the Bundle Seed ID.
+ (NSString *)bundleSeedID {
NSString *tempAccountName = #"bundleSeedID";
NSDictionary *query = #{
(__bridge NSString *)kSecClass : (__bridge NSString *)kSecClassGenericPassword,
(__bridge NSString *)kSecAttrAccount : tempAccountName,
(__bridge NSString *)kSecAttrService : #"",
(__bridge NSString *)kSecReturnAttributes: (__bridge NSNumber *)kCFBooleanTrue,
};
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecItemNotFound)
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status != errSecSuccess) {
return nil;
}
status = SecItemDelete((__bridge CFDictionaryRef)query); // remove temp item
NSDictionary *dict = (__bridge_transfer NSDictionary *)result;
NSString *accessGroup = dict[(__bridge NSString *)kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:#"."];
NSString *bundleSeedID = [[components objectEnumerator] nextObject];
return bundleSeedID;
}
Here is the Swift version of #David H answer:
static func bundleSeedID() -> String? {
let queryLoad: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "bundleSeedID" as AnyObject,
kSecAttrService as String: "" as AnyObject,
kSecReturnAttributes as String: kCFBooleanTrue
]
var result : AnyObject?
var status = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
if status == errSecItemNotFound {
status = withUnsafeMutablePointer(to: &result) {
SecItemAdd(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
}
if status == noErr {
if let resultDict = result as? [String: Any], let accessGroup = resultDict[kSecAttrAccessGroup as String] as? String {
let components = accessGroup.components(separatedBy: ".")
return components.first
}else {
return nil
}
} else {
print("Error getting bundleSeedID to Keychain")
return nil
}
}
This is a good question but to achieve what you were intended to do, there could have been a solution
that does not require to retrieve the Bundle Seed ID.
From this article, about the same keychain wrapper you're using:
By default it will pick the first access-group specified in your
Entitlements.plist when writing and will search across all
access-groups when none is specified.
The key will then be search in all groups where access is granted.
So to solve your problem, you could add access group of all your bundle apps into your entitlements.plist instead of using a "shared stuff" group, put $(CFBundleIdentifier) as your first keychain group (your keychain wrapper will then write in this group) and you're all set
If you search in Xcode on your team's ID then you will see that this value is hosted in the build settings under the key DEVELOPMENT_TEAM.
You can retrieve this key by putting in your Info.plist file:
<key>DEVELOPMENT_TEAM</key>
<string>$(DEVELOPMENT_TEAM)</string>
Make sure to put this in every target's Info.plist file where you want to retrieve it using this code:
let teamID = Bundle.main.infoDictionary!["DEVELOPMENT_TEAM"] as! String
This solution will give you the team ID without the dot suffix.
The solution in https://stackoverflow.com/a/28714850/2743633 worked for me only to get the team ID from the main app target. It would not retrieve the team ID when doing the same for a Share Extension target.
I want to store a local player's high score in the iOS game. What is the best way to approach it? Should I use NSUserDefaults in this case? Is it safe enough to store a high score?
Thanks in advance.
NSUserDefaults is not correct place to store this kind of information. The best way is to hide the user score in Keychain, so that noone can crack it (perhaps the game score is relevant to your monetization idea).
Other good thing is that Keychain is seamless syncable via iCloud, "it's just works".
The best way to store player's high score is to use following code in your project.
Following method will help you to save some value in Keychain:
- (void) setValue: (NSData *) value forAccount: (NSString *) account service: (NSString *) service {
NSDictionary *searchDict = #{
(__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword,
(__bridge id) kSecMatchLimit: (__bridge id) kSecMatchLimitOne,
(__bridge id) kSecAttrService: service,
(__bridge id) kSecAttrAccount: account,
(__bridge id) kSecReturnData: #YES,
};
CFDataRef keyData = NULL;
SecItemCopyMatching ((__bridge CFDictionaryRef) searchDict, (CFTypeRef *) &keyData);
if (keyData) {
CFRelease (keyData);
NSDictionary *removeDict = #{
(__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword,
(__bridge id) kSecAttrService: service,
(__bridge id) kSecAttrAccount: account,
};
SecItemDelete ((__bridge CFDictionaryRef) removeDict);
}
if (value) {
NSDictionary *writeDict = #{
(__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword,
(__bridge id) kSecAttrService: service,
(__bridge id) kSecAttrAccount: account,
(__bridge id) kSecValueData: value,
};
SecItemAdd ((__bridge CFDictionaryRef) writeDict, NULL);
}
}
where value is your user score archived into NSData
to pack integer into NSData:
NSUInteger score = <some number>;
NSData *dataValue = [NSData dataWithBytes:&score length:sizeof(score)];
to unpack:
NSUInteger score;
[dataValue getBytes:&score length:sizeof(score)];
account - is the name of your value, for example #"userScore", or score for certain user #"ForzenHeart_Score"
service - is the name of your application, you can use your bundle ID [[NSBundle mainBundle] bundleIdentifier]
To get saved data from Keychain you use this method:
- (NSData *) valueForAccount: (NSString *) account service: (NSString *) service {
NSDictionary *searchDict = #{
(__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword,
(__bridge id) kSecMatchLimit: (__bridge id) kSecMatchLimitOne,
(__bridge id) kSecAttrService: service,
(__bridge id) kSecAttrAccount: account,
(__bridge id) kSecReturnData: #YES,
};
CFDataRef keyData = NULL;
SecItemCopyMatching ((__bridge CFDictionaryRef) searchDict, (CFTypeRef *) &keyData);
return (__bridge_transfer NSData *) keyData;
}
And don't forget to wipe all user related data when you have to, simply by passing nil as value parameter for -(void)setValue... method listed above.
NSUserDefaults is not safe because the defaults are actually keyed into a .plist file stored locally on the device in your application folder. This is the directory: .../YourApp.app/Library/Preferences/appBundleName.plist. This can very easily be modified and viewed with tools on and off the device, and no jail-break is required for off-device viewing. As other's have suggested, use the iOS Keychain for it's added security. Also, here is an open-source library for secure defaults in iOS: Secure-NSUserDefaults.
To answer your question directly, it is not safe enough for any sensitive data that you do not want the user modifying indirectly. NSUserDefaults can easily be accessed and modified with little effort. Do not store encrypted data in NSUserDefaults because that too can easily be accessed as well. The attacker won't easily be able to decrypt the data but they can still access it nonetheless with ease. The iOS Keychain is also not very secure either because the contents can be dumped easily too but not decrypted. For your purposes though, the iOS Keychain is fine.
NSUSerDefaults in never safe.
Either use iOS Keychain or upload the data to your server (the most reliable way)
No NSUserDefaults is not safe.You can use Keychain.
The contents of NSUserDefaults are stored in plain text. They can be accessed and modified with tools like iExplorerwihout the jailbreak.
If that is not the issue you can go for NSUserDefaults
Generally, NSUserDefaults should only contain settings that can be changed by the user. Internal data of your app should not be stored in NSUserDefaults.
Use iOS Keychain to store really secure data.
Alternatively, you could use a cryptographic algorithm (e.g. AES) to encrypt the data you store in NSUserDefaults, or save it in a dictionary and write it to a file.
Do not use NSUserDefaults to hold anything locally. It is not secure. You should store score in the keychain where it is encrypted, there's small wrapper class that makes saving username/password into the keychain very easy:
Click here to Know more
I often choose coredata something like these situations. There are lots of handy & safe framework about this. Nowadays I use MagicalRecord framework. It's useful and commonly used framework.
You just create your model and decare magical record framework to your AppDelegate.m file. And anywhere you want save data properly.
The documentation for Keychain Services is horribly incomplete and I keep getting unhelpful errors when I try to use the SecItem*() functions. Currently I'm trying to delete an identity I've previously added to the keychain:
// Identity ref is a persistent reference to the identity I want to delete.
NSData *identityRef = ...
NSDictionary *query = #{ (id)kSecClass: (id)kSecClassIdentity,
(id)kSecValuePersistentRef: identityRef };
OSStatus status = SecItemDelete((CFDictionaryRef)query);
// Fails with errSecParam (-50) under iOS 6
// Fails with errSecNotAvailable (-25291) under iOS 7
However, the required (and recommended) parameters for each of the various security item classes are not documented anywhere as far as I can tell. What should I be specifying in order to successfully work with identities in the keychain?
EDIT
I have also tried using kSecMatchItemList as documented:
NSDictionary *query = #{ (id)kSecClass: (id)kSecClassIdentity,
(id)kSecMatchItemList: #[identityRef] };
OSStatus status = SecItemDelete((CFDictionaryRef)query);
// Fails with errSecParam (-50)
I have also tried specifying the suggested primary keys from this SO question:
NSDictionary *attrs = nil;
NSDictionary *attrsQuery = #{ (id)kSecClass: (id)kSecClassIdentity,
(id)kSecValuePersistentRef: identityRef };
SecItemCopyMatching(attrsQuery, (CFTypeRef *)&attrs);
NSDictionary *query = #{ (id)kSecClass: (id)kSecClassIdentity,
(id)kSecAttrCertificateType: attrs[(id)kSecAttrCertificateType],
(id)kSecAttrIssuer: attrs[(id)kSecAttrIssuer],
(id)kSecAttrSerialNumber: attrs[(id)kSecAttrSerialNumber],
(id)kSecAttrApplicationLabel: attrs[(id)kSecAttrApplicationLabel],
(id)kSecAttrApplicationTag: attrs[(id)kSecAttrApplicationTag],
(id)kSecAttrKeyType: attrs[(id)kSecAttrKeyType],
(id)kSecAttrKeySizeInBits: attrs[(id)kSecAttrKeySizeInBits],
(id)kSecAttrEffectiveKeySize: attrs[(id)kSecAttrEffectiveKeySize] };
OSStatus status = SecItemDelete(query);
// Still fails with errSecParam (-50)
It appears I was overspecifying the item to delete. If you include the kSecClass key in the query when deleting an identity from the keychain, Keychain Services gets confused. This code works:
NSData *identityRef = ...
NSDictionary *query = #{ (id)kSecValuePersistentRef: identityRef };
OSStatus status = SecItemDelete((CFDictionaryRef)query); // Success!
Keychain can be seen as a database with many tables (kSecClass).
Since you're using kSecClassIdentity this "table" has two primary keys which are kSecClassKey and kSecClassCertificate.
You should always specify those values when you do operations with an entry. In your case your query dictionary is missing those values.
You can check this SO post for more information on primary keys of keychain classes
What makes a keychain item unique (in iOS)?
I want to add some data in the Keychain in iOS. I am not worried about the security just wanted to store some string permanently some where(Keychain) which can be consistent even if user uninstalled the application. I am not storing any password, all the example in the Web are only showing how to store the password. I am planning to use the kSecClassKey attribute to store the string. Please guide me in the correct direction. Any sample code will be very helpful.
I've used SFHF Keychain lib ( https://github.com/kamiro/SFHFKeychainUtils ). It's really easy to use and works fine.
Here an example to use
NSString* username = #"myValue1";
NSString* service = #"com.organization.appname.myValue1";
NSString* valueToStore = #"....";
// Add/Update valute to keychain
NSError* anyStoringError = NULL;
BOOL stored = [SFHFKeychainUtils storeUsername:username andPassword:valueToStore forServiceName:service updateExisting:YES error:&anyStoringError];
// Get value from keychain
NSString *storedValue = [SFHFKeychainUtils getPasswordForUsername:username andServiceName:service error:&anyStoringError];
// Remove value from keychain
BOOL valueRemoved = [SFHFKeychainUtils deleteItemForUsername:username andServiceName:service error:&anyStoringError];
Hope it helps
I´m developing a couple of iOS applications and i need to share a element between them, that i want to store in the keychain.
This element is used in a complex login process with 3 or 4 steps, in each one i need to read the value from the keychain, to do this i used the code bellow:
- (NSString *)installationToken
{
KeychainItemWrapper *kw = [[KeychainItemWrapper alloc] initWithIdentifier:#"uuid" accessGroup:#"yyyyy.xxxxxxxxxxx"];
if (![kw objectForKey:(NSString*)kSecAttrAccount] || [[kw objectForKey:(NSString*)kSecAttrAccount] isEqualToString:#""]) {
NSString *result;
CFUUIDRef uuid;
CFStringRef uuidStr;
uuid = CFUUIDCreate(NULL);
assert(uuid != NULL);
uuidStr = CFUUIDCreateString(NULL, uuid);
assert(uuidStr != NULL);
result = [NSString stringWithFormat:#"%#", uuidStr];
assert(result != nil);
CFRelease(uuidStr);
CFRelease(uuid);
[kw setObject:result forKey:(NSString*)kSecAttrAccount];
return result;
} else {
return [kw objectForKey:(NSString*)kSecAttrAccount];
}
}
This all works well in almost every device but in some, users are complaining. So, i checked what my server is receiving, and saw that different values are being sent.
I checked the code and in no other place i'm acessing/emptying this keychain element, what can be wrong with this? For the majority of devices this works like a charm but for some reason, in some devices, they aren't storing or retrieving well from the keychain.
The problem happens in different invocation in the same application.
If you are using Apples' sample code for KeyChainWrapper, then main problem is sometimes randomly, SecItemCopyMatching fails and then the sample code has resetKeychainItem which will basically reset your keychain.
if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
{
// Stick these default values into Keychain if nothing found.
[self resetKeychainItem];
}
In our app, we noticed similar problems, and so now we are using
https://github.com/carlbrown/PDKeychainBindingsController to do all keyChain related functionality. Now it works very well.