I have a really strange behavior in an iOS App.
I switched from iOS 6 to iOS 7. In iOS 6 everything worked perfectly.
- (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
[searchDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
[searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrGeneric];
[searchDictionary setObject:encodedIdentifier forKey:(__bridge id)kSecAttrAccount];
[searchDictionary setObject:serviceName forKey:(__bridge id)kSecAttrService];
return searchDictionary;
}
- (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
[searchDictionary setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[searchDictionary setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
CFDataRef dataRef;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary,
(CFTypeRef *)&dataRef);
if (status != errSecSuccess) {
#ifdef DEBUG
NSLog(#"%s - No OSStatus errSecSuccess. Caused by SecItemCopyMatching", __PRETTY_FUNCTION__);
#endif
return nil;
}
NSData *result = (__bridge_transfer NSData *)dataRef;
return result;
}
When the App starts the - (NSData *)searchKeychainCopyMatching:(NSString *)identifier function loads the values from the keychain. Everything works fine for a while. But after about 15 successful value requests I get an error.
OSStatus Code -34018
The SecItemCopyMatching function returns that error code. The documentation says
#result A result code. See "Security Error Codes" (SecBase.h).
But looking in the SecBase.h there are only these OSStatus codes specified.
enum
{
errSecSuccess = 0, /* No error. */
errSecUnimplemented = -4, /* Function or operation not implemented. */
errSecIO = -36, /*I/O error (bummers)*/
errSecOpWr = -49, /*file already open with with write permission*/
errSecParam = -50, /* One or more parameters passed to a function where not valid. */
errSecAllocate = -108, /* Failed to allocate memory. */
errSecUserCanceled = -128, /* User canceled the operation. */
errSecBadReq = -909, /* Bad parameter or invalid state for operation. */
errSecInternalComponent = -2070,
errSecNotAvailable = -25291, /* No keychain is available. You may need to restart your computer. */
errSecDuplicateItem = -25299, /* The specified item already exists in the keychain. */
errSecItemNotFound = -25300, /* The specified item could not be found in the keychain. */
errSecInteractionNotAllowed = -25308, /* User interaction is not allowed. */
errSecDecode = -26275, /* Unable to decode the provided data. */
errSecAuthFailed = -25293, /* The user name or passphrase you entered is not correct. */
};
The values doesn't get overridden, already checked.
And last but not least the search dictionary:
Edit - new info
I was debugging the whole day and I found some news. I'm downloading a Zip-File containing an executable Bundle. This is a In-House App so no worries about point 2.7 and 2.8 in the review guidelines. After successfully loading the bundle the entitlements error appears.
NSBundle *bundle = nil;
NSError *error = nil;
bundle = [[NSBundle alloc] initWithPath:bundlePath];
if (!bundle) {
return nil;
}
// Here i can access the keychain as usually
[bundle loadAndReturnError:&error];
// Well here it suddenly doesn't work anymore
// error is also nil
Well the bundle code inside does not use the keychain. May be this is some kind of security logic? Any clues?
This error indicates a problem with your app's entitlements. Found this: The cause is often that the App Identifier Prefix in the app's entitlements doesn't match the App Identifier Prefix in the provisioning profile.
To verify, use the codesign tool to view your app's entitlements:
codesign -d --entitlements - MyApp.app/
Then, compare the App Identifier Prefix to that in the provisioning profile:
cat MyApp.app/embedded.mobileprovision
Related
I have this method
- (NSString *)bundleSeedID:(NSError **)error __attribute__((annotate("oclint:suppress"))) {
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge NSString *)kSecClassGenericPassword, (__bridge NSString *)kSecClass,
BUNDLE_SEED_ID, kSecAttrAccount,
EMPTY_STRING, kSecAttrService,
(__bridge NSString *)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, (__bridge NSString *)kSecAttrAccessible,
(id)kCFBooleanTrue, kSecReturnAttributes,
nil];
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecItemNotFound) {
status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
}
if (status != errSecSuccess) {
if (error) *error = [ErrorUtils bundleSeedErrorWithCode:(int)status];
return nil;
}
NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:(__bridge NSString *)kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:#"."];
NSString *bundleSeedID = [[components objectEnumerator] nextObject];
CFRelease(result);
return bundleSeedID;
}
and I don't understand why sometimes result is null.
Note: This code is always being executed in main thread without any asynchronous call
Can you please help me understand why is this happening? This code is only executed when I open the app.
I know I could check if result is null and then I won't CFRelease(result); but it's not expected to happen this. Once the app crashes, if I open it again, everything is ok.
Thanks in advance 🙏
You are very likely launching in the background when the device is locked. While the device is locked, you don't have access to certain protected data in the keychain; you should see this in the error message.
You do appear to be passing kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly to the creation, it's possible that you added kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly to your app after creating the keychain items (or that you don't pass this during creation elsewhere).
Your code checks for errSecItemNotFound, but you should be checking for errSecDataNotAvailable if I recall correctly to detect this problem. (You can also check UIApplication's `isProtectedDataAvailable.) You shouldn't try to create an item in that case of course; you just need an elegant way to fail.
It's fairly rare to be launched before first unlock, but that's also possible, and the keychain won't be available. Pending Bluetooth connections, for example, can cause this as I recall. I don't believe that push notifications can launch the app in that state, but it's possible.
In any case, you need to be checking for unexpected errors and handling a nil value.
I'm trying to make a custom TunnelProvider network extension by starting with the XCode template for the TunnelProvider and then adding the code to the host app in order to configure it and start it.
I am using an instance of NETunnelProviderManager to configure it, and when I call saveToPreferencesWithCompletionHandler: I get success (error = 0). However, when I call startVPNTunnelAndReturnError: on the (non-zero) connection I always get the below error:
Error Domain=NEVPNErrorDomain Code=1 "(null)"
I have read through all of the related Apple documentation as well as tried to make my program look as close as possible to the SimpleTunnel test program, however I cannot determine why I am getting this "1" (which seems to indicate a configuration issue).
I've seen a few other people with this same problem in posts around the net, but no solutions.
I have the special entitlements required and I know that is not the issue since after using the proper provisioning profile I was able to see the popup confirming I want to add a VPN when I run the app, and then it get's added in Settings under VPN.
Here is my code in case it is relevant:
NETunnelProviderManager * man = [[NETunnelProviderManager alloc] init];
NETunnelProviderProtocol *protocol = [[NETunnelProviderProtocol alloc] init];
[protocol setServerAddress:#"aaa.bbb.ccc.ddd"]; // not actual value
[protocol setUsername:#"testuser"];
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
NSData *data = [[NSData alloc] init];
[dictionary setObject:#"UUID" forKey:(id)kSecAttrService];
[dictionary setObject:data forKey:(id)kSecValueData];
[dictionary setObject:(__bridge id)kSecAttrAccessibleAlways forKey:(id)kSecAttrAccessible];
[dictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(id)kSecClass];
[dictionary setObject:(__bridge id)kCFBooleanTrue forKey:(id)kSecReturnPersistentRef];
CFTypeRef passwordRef = nil;
OSStatus delStatus = SecItemDelete((__bridge CFDictionaryRef)dictionary);
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dictionary, &passwordRef);
[protocol setPasswordReference:(__bridge NSData * _Nullable)(passwordRef)];
man.protocolConfiguration = protocol;
man.localizedDescription = #"My VPN";
man.onDemandEnabled = false;
man.enabled = true;
[man saveToPreferencesWithCompletionHandler:^(NSError *err) {
NSLog(#"saved preferences: error = %#", err);
[man.connection startVPNTunnelAndReturnError:&err];
NSLog(#"after start tunnel: error = %#", err);
}];
I discovered that if I call loadFromPreferencesWithCompletionHandler: before trying to start the tunnel (but after saveToPreferencesWithCompletionHandler), this error goes away I am able to get things proceed a little farther.
However I run into more problems after that. So for the time being I am going to analyze the SimpleTunnel program a bit more.
I've been using KeychainItemWrapper just fine. But since I've updated my phone to iOS 9, it doesn't store the sessionID for some reason.
+ (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier
{
NSMutableDictionary *dictionary = [self setupSearchDirectoryForIdentifier:identifier];
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
[dictionary setObject:valueData forKey:(__bridge id)kSecValueData];
// Protect the keychain entry so it's only valid when the device is unlocked at least once.
[dictionary setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
// **THIS LINE OF CODE RETURNS -34108**
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dictionary, NULL);
// If the addition was successful, return. Otherwise, attempt to update existing key or quit (return NO).
if (status == errSecSuccess) {
return YES;
} else if (status == errSecDuplicateItem){
return [self updateKeychainValue:value forIdentifier:identifier];
} else {
return NO; **The call returns here...**
}
}
Anybody know whats going on?
EDIT
Weirdest thing: it only happens from time to time and always in debug mode.
EDIT2
As this only occurs in debug mode, there are two work arounds that I usually do depending on the type of variable:
- Always keep the last valid variable loaded from the keychain locally (for instance a sessionID) and use it as a backup when in debug mode
- Ignore invalid value(s) if possible when in debug (in this case I would add an additional control variable to allow/disallow these invalid value(s))
(use #ifdef DEBUG to check if you're in debug mode)
According to Apple there is no solution right now.
https://forums.developer.apple.com/thread/4743
How can I detect if an iOS app is installed for the first time or was deleted and re-installed?
I do use NSUserDefaults when the app is running. However, when the app is deleted, I believe all associated data is deleted.
My goal is to do SMS verification of the users phone number only when the app was installed for the first time on the device.
If for some reason, the app was deleted and re-installed, I want to avoid redoing the SMS verification.
What else can I do? Can I store some metadata related to my app which is not deleted when the app itself is deleted on the device?
Any standard pattern to follow?
You can do this by storing a value in the user's keychain. It will persist even if the app is deleted and thus you can tell if the app is a new install or a reinstall. Add another value to the user defaults for comparison, if both values exist the app has been installed and executed at least once. If neither values exist, it's a new, first time install. If just the keychain value exists, it's a fresh reinstall.
You can use keychain and keychain does store values after we uninstall the app.
Apple has provided KeyChainItemWrapper class in their GenericKeyChain (sample code)
Try this code of storing your device code in keychain.....and keychain value remains the same even if app is deleted:
static NSString *serviceName = #"com.mycompany.myAppServiceName";
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSData *passwordData = [self searchKeychainCopyMatching:#"device--code"];
if (passwordData) {
NSString *password = [[NSString alloc] initWithData:passwordData
encoding:NSUTF8StringEncoding];
NSLog(#"pasword:=%#",password);
}
else{
[self createKeychainValue:devicecode forIdentifier:#"device--code"];
}
//[self deleteKeychainValue:#"device--code"];
return YES;
}
- (void)deleteKeychainValue:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
SecItemDelete((CFDictionaryRef)searchDictionary);
}
- (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
[searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
[searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrGeneric];
[searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrAccount];
[searchDictionary setObject:serviceName forKey:(id)kSecAttrService];
return searchDictionary;
}
- (NSData *)searchKeychainCopyMatching:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
// Add search attributes
[searchDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
// Add search return types
[searchDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
NSData *result = nil;
// OSStatus status =
SecItemCopyMatching((CFDictionaryRef)searchDictionary,(__bridge CFTypeRef *)&result);
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchDictionary,
(void *)&result);
return result;
}
- (BOOL)createKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier {
NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[dictionary setObject:passwordData forKey:(id)kSecValueData];
OSStatus status = SecItemAdd((CFDictionaryRef)dictionary, NULL);
if (status == errSecSuccess) {
return YES;
}
return NO;
}
Source
There is no way to accomplish this with iOS alone, but you could achieve this by having a server with a database, and the first time the app is run you send the device's UDID to your server; if the server already has the UDID, then the app was already installed.
Of course this solution does require the user have an internet connection.
I think, you send UDID to icloud Apple to store it. If app install first time. Icloud iphone not found, else It will have UDID. You need't server.
I'm using this method in order to retrieve a saved value (and using SecItemAdd to add it originally):
+ (NSData *)passwordDataForService:(NSString *)service
account:(NSString *)account error:(NSError **)error {
CFTypeRef result = NULL;
NSMutableDictionary *query = [self _queryForService:service account:account];
[query setObject:(__bridge id)kCFBooleanTrue
forKey:(__bridge id)kSecReturnData];
[query setObject:(__bridge id)kSecMatchLimitOne
forKey:(__bridge id)kSecMatchLimit];
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
if (status != noErr && error != NULL) {
*error = [NSError errorWithDomain:kSSKeychainErrorDomain code:status
userInfo:nil];
return nil;
}
return (__bridge_transfer NSData *)result;
}
This code is working fine for most users, but a small percentage of my users (< 1%) are experiencing results indicating that either the read or write here is failing. My code unfortunately swallows any errors (i.e. doesn't log them anywhere when they occur) so I can't tell why it's failing out in the world, and I can't reproduce the problem at all on any of my development devices.
Does anyone know of any security/permissions settings that can be enabled on an iOS device that could cause SecItemAdd or SecItemCopyMatching to fail? I've tried turning on passcode locking, but that seems to have no effect.