Does Firebase use the keychain in iOS applications? - ios

I have a function to check if my application has a password saved into keychain of the app when its is opened.
My function is added in my first ViewController and is this:
- (NSString *)tapGetButton{
NSDictionary *info = [[NSBundle mainBundle] infoDictionary];
NSString *version = [info objectForKey:#"CFBundleIdentifier"];
NSDictionary *query = #{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: version,
(__bridge id)kSecReturnData : (__bridge id)kCFBooleanTrue,
(__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne
};
CFDataRef token = nil;
// 検索
OSStatus err = SecItemCopyMatching((__bridge CFDictionaryRef)query,
(CFTypeRef *)&token);
NSString *accessToken = nil;
// success
if (err == errSecSuccess) {
NSLog(#"Success: get token");
}
// not found
else if (err == errSecItemNotFound) {
NSLog(#"NotFound: get token");
}
else {
NSLog(#"Error: get token");
}
return accessToken;
}
This funcitons works fine, I tested this and return correctly the password and username that I saved. But my problem appear when start the application then get this, and I find the problem. When I start the application, in the didFinishLaunchingWithOptions of the delegate is executed the init code to configure Firebase:
[FIRApp configure];
If I delete this code, the values from keychain is returned correctly, but if I add this code, the value returned is the number 1 or in other cases a long number...
Is Firebase using the keychain? There is other form of initialize Firebase?
Thanks!
EDIT: I'm not using Firebase to store the accounts or password, I'm using firebase to share events...

Firebase iOS SDK uses keychain a lot. You could inspect source code. For example here
I would recommend you to define your own service and account values to identify your data more precisely:
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "for example, token name",
kSecAttrService as String: "for example, token provider name",
]

Related

Error code -25308 (errSecInteractionNotAllowed) when accessing shared keychain

I'm developing a react-native app using https://github.com/oblador/react-native-keychain to save and get things from keychain.
The things stored in keychain (and some other things) should be accessible from all our apps. So in xcode under capabilities I have activated an app-group "group.something.test".
I'm using setGenericPassword function of react-native-keychain like this:
await Keychain.setGenericPassword(
'a username',
'a password',
{
service: 'a userId',
accessible: Keychain.ACCESSIBLE.ALWAYS,
accessGroup: 'group.something.test',
accessControl: Keychain.ACCESS_CONTROL.DEVICE_PASSCODE
}
);
In the native module this will store it to the keychain something like this:
SecAccessControlRef sacRef = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleAlways,
kSecAccessControlDevicePasscode,
&error);
NSDictionary *attributes = attributes = #{
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassGenericPassword),
(__bridge NSString *)kSecAttrService: service,
(__bridge NSString *)kSecAttrAccount: username,
(__bridge NSString *)kSecValueData: [password dataUsingEncoding:NSUTF8StringEncoding],
(__bridge NSString *)kSecAttrAccessControl] = (__bridge id)sacRef,
(__bridge NSString *)kSecAttrAccessGroup] = accessGroup;
};
...
OSStatus osStatus = SecItemAdd((__bridge CFDictionaryRef) attributes, NULL);
...
And keychain items are retrieved in the following way:
await Keychain.getGenericPassword({ service: 'a userId' });
Which will will run the following native code:
NSDictionary *query = #{
(__bridge NSString *)kSecClass: (__bridge id)(kSecClassGenericPassword),
(__bridge NSString *)kSecAttrService: service,
(__bridge NSString *)kSecReturnAttributes: (__bridge id)kCFBooleanTrue,
(__bridge NSString *)kSecReturnData: (__bridge id)kCFBooleanTrue,
(__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne,
(__bridge NSString *)kSecUseOperationPrompt:#"Authenticate to retrieve secret"
};
CFTypeRef foundTypeRef = NULL;
OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef) query, (CFTypeRef*)&foundTypeRef);
This works fine in the first app, I can store and retrieve an item from the keychain.
When I deploy a second app through xcode by just changing Display name and Bundle identifier to something different it also works. I can retrieve the key saved by the first app.
However if I now go back to the first app and try to retrieve the keychain item osStatus is -25308 (errSecInteractionNotAllowed).
If I uninstall the second app, it will start working in the first app again.
What am I doing wrong? Is there something more I need to do in the project settings?
I have also tried with keychain group capability, having the same keychain group as the first index in the keychain groups array, using that id as kSecAttrAccessGroup.

Accesing secure item in Keychain sometimes returns error -25308 (errSecInteractionNotAllowed) in iOS

I'm finding that sometimes I get that error when trying to get a secure item I previously succesfully stored by using Keychain API. I have found it when the app running in my device was in background state and I locked the screen. The device hasn't lock code set, and this is the function I'm calling:
+ (NSString *)findValueForKey:(NSString *)keyStr
{
NSString *valueStr = #"";
if ((keyStr != nil) && (![keyStr isEqualToString:#""])) {
NSString *service = [[NSBundle mainBundle] bundleIdentifier];
NSDictionary *query = #{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService : service,
(__bridge id)kSecAttrAccount : keyStr,
(__bridge id)kSecReturnData : (__bridge id)kCFBooleanTrue};
CFDataRef cfValue = NULL;
OSStatus results = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&cfValue);
if ([self checkIfNoError:results]) {
valueStr = [[NSString alloc] initWithData:(__bridge_transfer NSData *)cfValue encoding:NSUTF8StringEncoding];
}
else {
NSLog(#"%#", [self getErrorMessageForStatus:results]);
}
}
return valueStr;
}
and the item was stored by calling this method:
+ (BOOL)storeInKeychainWithKey:(NSString *)keyStr withValueStr:(NSString *)valueStr
{
if ((keyStr != nil) && (![keyStr isEqualToString:#""]) &&
(valueStr != nil) && (![valueStr isEqualToString:#""])) {
NSData *valueData = [valueStr dataUsingEncoding:NSUTF8StringEncoding];
NSString *service = [[NSBundle mainBundle] bundleIdentifier];
NSDictionary *secItem = #{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService : service,
(__bridge id)kSecAttrAccount : keyStr,
(__bridge id)kSecValueData : valueData};
CFTypeRef result = NULL;
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)secItem, &result);
NSLog(#"%#", [self getErrorMessageForStatus:status]);
return [self checkIfItem:status];
}
else {
return NO;
}
}
I thought that keychain items were always accessible in iOS... this post seems to be about something similar but I'm not sure if it is is deprecated and how should I solve this...
Thanks in advance
We had the same issue, #AppsDev, and the post you mention is accurate. We solved it by keeping the keychain as a last resort for things we need available even if we uninstall / reinstall the app.
We now loop back on the app defaults (in Swift 3 that would be UserDefaults.standard) to keep that info handy while the install lifecycle doesn't reach the "uninstalled" stage.
If uninstalled, next time is installed we go to the keychain (by definition, a just installed app isn't in background so it won't fail). With the data retrieved we refresh the app defaults and, from there, we just use the app defaults.

Private data in shared keychain

I'm struggling with iOS keychain and i can't seem to find any good documentation.
Anyway, I have two apps, and basically all i want to do is share some data in the keychain and keep some data private so that the other app cannot access it.
I've tried to implement the KeychainItemWrapper provided by Apple, but this is simply not working. I have no issue sharing data, but if i don't set an access group, the data is still shared. I working with a device not the simulator, which could lead to the same problems.
Here is my code
App 1 :
KeychainItemWrapper *item = [[KeychainItemWrapper alloc] initWithIdentifier:#"SharedKeyChainApp" accessGroup:nil];
[item setObject:#"MyAccount" forKey:(__bridge id)kSecAttrAccount];
[item setObject:#"SecureValue" forKey:(__bridge id)kSecValueData];
App 2 :
KeychainItemWrapper *keychain = [[KeychainItemWrapper alloc] initWithIdentifier:#"SharedKeyChainApp" accessGroup:nil];
NSString *data = [keychain objectForKey:(__bridge id)kSecValueData];
NSLog(#"data is : %#",data); //Prints "data is : SecureValue"
If I remove in project properties my keychain group in one or the other app, it won't print anything. But obviously i'm not able to share data between those two apps anymore.
Thanks
If it's a shared keychain then it's shared. All the data in it will be accessible to any other app which can access the keychain.
You could:
Create 2 keychain. One shared and one private. Sharable stuff goes in shared, private stuff goes in private.
Encrypt data you won't want to to share with others.
I'd probably go with the first. IMHO, KeychainItemWrapper is pretty poor as grab and use code. It's old code which provides little in the way of functionality. I'm attaching a quick and dirty bit of code I wrote to play with and test out using the key chain functionality without KeychainItemWrapper. In this case I was playing with items both in "app" and "Security" to create some shared and non-shared items. You can't really tell that here since it's just some test code and sharing is under Targets->Capabilities->Keychain Sharing.
- (void)viewDidLoad {
[super viewDidLoad];
// [self removeKeychainItem];
// [self addKeychainItem];
[self searchForKeychainItems];
}
- (void)searchForKeychainItems {
[self log:#"\n\n EXISTING KEYCHAIN ITEM(S)"];
NSDictionary *query = #{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll,
(__bridge id)kSecReturnData: (__bridge id)kCFBooleanTrue, // returns password
(__bridge id)kSecReturnAttributes: (__bridge id)kCFBooleanTrue, // returns rest of data
// (__bridge id)kSecAttrAccessGroup: #"AAAAAAAAAA.com.foo.Security"
// (__bridge id)kSecAttrAccessGroup: #"AAAAAAAAAA.com.foo.app"
};
OSStatus resultCode;
CFArrayRef *searchResults = nil;
resultCode = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&searchResults);
NSArray *foo = CFBridgingRelease(searchResults);
[self log:[NSString stringWithFormat:#"Search result code: %d", (int)resultCode]];
[self log:[NSString stringWithFormat:#"Search Results: %#", foo]];
NSDictionary *keychainItem = foo[0];
NSString *password = [[NSString alloc] initWithData:[keychainItem objectForKey:(__bridge id)kSecValueData] encoding:NSUTF8StringEncoding];
[self log:[NSString stringWithFormat:#"password is `%#`", password]];
}
- (void)addKeychainItem {
[self log:#"\n\n ADDING KEYCHAIN ITEM"];
NSDictionary *genericDataDictionary = #{#"authState": #"1",
#"lastAuthDate": #"2/11/2014",
#"otherCrap": #"poo"};
NSData *encodedGenericData = [NSKeyedArchiver archivedDataWithRootObject:genericDataDictionary];
NSData *encodedPassword = [#"secret" dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *query = #{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrCreator: #"MyCom",
(__bridge id)kSecAttrComment: #"keychain tests",
(__bridge id)kSecAttrService: #"Credentials",
(__bridge id)kSecAttrAccount: #"username",
(__bridge id)kSecValueData: encodedPassword,
(__bridge id)kSecAttrGeneric: encodedGenericData,
(__bridge id)kSecAttrAccessGroup: #"AAAAAAAAAA.com.foo.Security"
};
OSStatus result;
result = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
NSLog(#"Add status code: %d", (int)result);
}
- (void)removeKeychainItem {
[self log:#"\n\n REMOVING KEYCHAIN ITEM"];
NSDictionary *query = #{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
// (__bridge id)kSecAttrCreator: #"MyCom",
// (__bridge id)kSecAttrService: #"Credentials",
(__bridge id)kSecAttrComment: #"New Keychain standards Test Item",
// (__bridge id)kSecAttrAccount: #"username",
// (__bridge id)kSecValueData: [#"password" dataUsingEncoding:NSUTF8StringEncoding],
// (__bridge id)kSecAttrGeneric: encodedGenericData
// (__bridge id)kSecAttrAccessGroup: #"AAAAAAAAAA.com.foo.Security"
};
OSStatus resultsCode;
resultsCode = SecItemDelete((__bridge CFDictionaryRef)query);
NSLog(#"Delete results code: %d", (int)resultsCode);
}
- (void)log:(NSString *)text {
self.textView.text = [[self.textView.text stringByAppendingString:text] stringByAppendingString:#"\n"];
}
One thing to be aware of is that you need to add both a private and a shared access group. The private access group should match the app's bundle ID and come before the shared one.
<dict>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)<APP BUNDLE ID></string>
<string>$(AppIdentifierPrefix)com.mybusiness.shared</string>
</array>
</dict>
If you only add a single named access group then this will be the default for all keychain items effectively sharing everything.

Using SecItemUpdate in Keychain Services

I have the following code to create a keychain item in the keychain:
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
[dict setObject: (__bridge id) kSecClassGenericPassword forKey: (__bridge id) kSecClass];
[dict setObject: MYKEY forKey: (__bridge id) kSecAttrService];
[dict setObject: #"0" forKey: (__bridge id) kSecValueData];
SecItemAdd ((__bridge CFDictionaryRef) dict, NULL);
Which works fine. Can anyone give the syntax for what exactly to put for SecItemUpdate if I want to change this item?
UPDATE: with the following:
NSMutableDictionary *query = [NSMutableDictionary dictionary];
NSMutableDictionary *attributesToUpdate = [NSMutableDictionary dictionary];
[query setObject: (__bridge id) kSecClassGenericPassword forKey: (__bridge id) kSecClass];
[query setObject: MYKEY forKey: (__bridge id) kSecAttrService];
[query setObject: (id) kCFBooleanTrue forKey: (__bridge id) kSecReturnData];
NSString *numberOfBalloonsString = [NSString stringWithFormat:#"%d", numberOfBalloonsUsed];
NSData *numberOfBalloonsData = [numberOfBalloonsString dataUsingEncoding:NSUTF8StringEncoding];
[attributesToUpdate setObject: numberOfBalloonsData forKey:(__bridge id)kSecValueData];
OSStatus error = SecItemUpdate ((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef) attributesToUpdate);
NSLog(#"Error #: %ld", error);
I'm getting the error code -50 =
One or more parameters passed to the function were not valid.
SecItemUpdate is terribly documented.
The query parameter of SecItemUpdate is documented as a query (as used in other functions) as well as the vague statement: "Specify the items whose values you wish to change". This seems to imply that you must include the existing attribute value in this dictionary that you want to change but I don't think you do. I've found you can use the same query you use to get attributes for the item you want to update.
The attributes parameter should be the result of SecItemCopyMatching with the kSecValueData key and value added and any attributes changed.
A late answer, but an answer nonetheless:
I've been struggling with updating items in the keychain as well, my context was a little different though.
What happened:
I could add a keychain item with success (using SecItemAdd), but calling SecItemUpdate on the same item failed with the notorious errSecParam -50.
What was even worse; if the keychain item already existed (hence I called SecItemUpdate immediately), the update went through with no problems at all.
I've got absolutely no idea as of why that happened...
How I fixed it:
Quite simple actually, I just removed "params" until the big bad -50 was satisfied. This happened when I removed the kSecClass from the dictionary I retrieved from kSecItemCopyMatching.
Here's my code:
// If the item already exists, we update it instead
if (SecItemCopyMatching((__bridge CFDictionaryRef)self.searchQueryDict, (CFTypeRef *)&foundItem) == errSecSuccess) {
NSMutableDictionary *updateDict = (__bridge NSMutableDictionary *)foundItem;
[updateDict addEntriesFromDictionary:dictToSave];
[updateDict removeObjectForKey:(__bridge id)kSecClass];
OSStatus updateSuccess = SecItemUpdate((__bridge CFDictionaryRef)self.updateQueryDict,
(__bridge CFDictionaryRef)updateDict);
NSAssert(updateSuccess == errSecSuccess, #"Couldn't save the dirty info to the keychain, might want to log the updateSuccess (%d)", updateSuccess);
}
As a reference I used the following dictionaries
self.searchQueryDict contained:
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword
(__bridge id)kSecAttrService : service
(__bridge id)kSecAttrGeneric : [identifier dataUsingEncoding:NSUTF8StringEncoding]
(__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne
(__bridge id)kSecReturnAttributes : (__bridge id)kCFBooleanTrue
(__bridge id)kSecReturnData : (__bridge id)kCFBooleanTrue
self.updateQueryDict contained:
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService : service,
(__bridge id)kSecAttrGeneric : [identifier dataUsingEncoding:NSUTF8StringEncoding]
dictToSave should contain the values (in the correct format) which needs to change
Removing the kSecClass fixed the problem for me.

secItemCopyMatching returns nil data

First off, I have watched the WWDC 2013 session on protecting secrets with the keychain. I want to do a basic passcode store. Watched the whole video, but found what I needed in the first 10 minutes of the video. It seems straightforward, but I don't completely understand how the data encoding and retrieval works.
PROBLEM: after secItemCopyMatching, I check my NSData object to make sure it is not nil before converting it to a NSString. Problem is, it is always nil. Below is how I'm saving the keychain entry or update, followed by how I'm retrieving it. Any help and explanation would be very much appreciated.
UPDATE (EDITED):
Fruity Geek, thanks for the response. I've updated my code below using __bridge. My problem now boils down to, am I storing and retrieving the password correctly? Have I got both wrong or just one or the other? My NSData instance is always nil. I am checking returns codes and my SecItemAdd and SecItemUpdate (when the keychaing entry exists) are working correctly. I can't seem to retrieve the string value of the data (passcode) stored to compare it with the passcode entered by the user. Appreciate the help guys and gals. Here is what I am doing now:
UPDATE #2: (Edited with Fruity Geek's answers and final working version. My edits only include changes to the code below.)
Set keychain entry:
NSData *secret = [_backupPassword dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *query = #{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: twServiceName,
(__bridge id)kSecAttrAccount: twAccountName,
(__bridge id)kSecValueData: secret,
};
OSStatus status =
SecItemAdd((__bridge CFDictionaryRef)query, NULL);
if (status == errSecDuplicateItem) {
// this item exists in the keychain already, update it
query = #{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: twServiceName,
(__bridge id)kSecAttrAccount: twAccountName,
};
NSDictionary *changes = #{
(__bridge id)kSecValueData: secret,
};
status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)changes);
}
Retrieve password from keychain:
NSDictionary *query = #{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: twServiceName,
(__bridge id)kSecAttrAccount: twAccountName,
(__bridge id)kSecReturnData: #YES,
};
NSData *data = NULL;
CFTypeRef dataTypeRef = (__bridge CFTypeRef)data;
OSStatus status =
SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);
NSData *data = (__bridge NSData *)dataTypeRef;
NSString *passcode = #"none";
if (status == errSecSuccess) {
// we found a keychain entry, set the passcode
if (data)
passcode = [NSString stringWithUTF8String:[data bytes]];
}
twServiceName and twAccountName are static NSStrings.
As I said, I don't quite what I am doing with __bridge or CFTypeRef. I looked through apples docs, numerous posts here and other sites, but keychain and these terms are brand new to me and I'm still trying to figure it out. Hoping someone here can point out my error and help me understand. Thanks in advance for the help.
iOS 7 / Xcode 5
You don't own any of the Core Foundation objects (you didn't create or copy them) and you don't want to retain or release them, so CFBridgingRelease and CFBridgingRetainis incorrect. Use (__bridge id) instead whenever you want to cast to an Objective-C object.
(__bridge id)kSecAttrService
when should you use __bridge vs. CFBridgingRelease/CFBridgingRetain?
Your data variable and dataTypeRef are two distinct pointers. Only the dataTypeRef was filled with data in SecItemCopyMatching. Cast your CFTypeRef to NSData after it has been populated by SecItemCopyMatching so your data isn't always nil
CFTypeRef dataTypeRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);
NSData *data = (__bridge NSData *)dataTypeRef;
You should look more closely at the OSStatus returned by all your SecItem functions calls. There are many possible return codes that are not success. In your case, you are detecting a duplicate item in SecItemAdd - then updating it to the exact same item (doing nothing). Instead, you should try retrieving it first using SecItemCopyMatching. If no match is found, use SecItemAdd. If a match was found, use SecItemUpdate.
The example code from Apple is terrible, not written for ARC and confusing, but it exists. In particular, the writeToKeychain method is what you need. https://developer.apple.com/library/ios/documentation/Security/Conceptual/keychainServConcepts/iPhoneTasks/iPhoneTasks.html#//apple_ref/doc/uid/TP30000897-CH208-SW1

Resources