Finding out if the device is locked, from a Notification Widget - ios

I'd like to know if the device is locked when I'm loading my Notification/Today widget, so I can show the widget appropriately. (it's financial, and we don't want to show balances on a locked phone)
On devices with TouchID, I can just try to access the Keychain, and if I get
errSecInteractionNotAllowed
back, it's locked. All good. This doesn't work on devices without touchID (but with a PIN). I've found a few things, which recommend using
[[UIApplication sharedApplication] protectedDataAvailable]
However I don't have [UIApplication sharedApplication] in a widget.
Any ideas where and how to do this? I just need a yes/no: is the device locked.
Thanks
[UPDATE: here's the code I have]
Getting the filename:
+ (NSString *)lockedDeviceFilename {
NSURL *directoryUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:USER_DEFAULTS_GROUP_NAME];
return [directoryUrl.path stringByAppendingPathComponent:#"security.dummy"];
}
Writing / creating the file (in the app, not the extension:
NSError *error = nil;
NSString *documentPath = [FOOStorageGatekeeper lockedDeviceFilename];
[[NSFileManager defaultManager] removeItemAtPath:documentPath error:&error];
BOOL created = [[NSFileManager defaultManager] createFileAtPath:documentPath
contents:[#"super secret file contents. we only care about the permissions" dataUsingEncoding:NSUTF8StringEncoding]
attributes:#{NSFileProtectionKey : NSFileProtectionComplete}];
Reading:
BOOL isReadable = [[NSFileManager defaultManager] fileExistsAtPath:[FOOStorageGatekeeper lockedDeviceFilename]];
NSLog(#"isReadable? %#", isReadable ? #"YES" : #"NO");
It's always able to read the file, even on a TouchID device with the screen locked. If I look at the attributes, it shows the NSFileProtectionKey is set to NSFileProtectionComplete... but I can STILL READ IT :(
Update: found it. Marking Ian's answer as correct

Create a file with NSFileProtectionComplete while your app is running and then attempt to access it from your extension. If you can't access it, the screen is locked.
[[NSFileManager defaultManager] createFileAtPath:someFilePath
contents:[#"Lock screen test." dataUsingEncoding:NSUTF8StringEncoding]
attributes:#{NSFileProtectionKey: NSFileProtectionComplete}];
EDIT: Final steps included to complete solution and consolidate answers. (Remaining work provided by Nic Wise.)
NSData *data = [NSData dataWithContentsOfURL:[FOOStorageGatekeeper lockedDeviceUrl] options: NSDataReadingMappedIfSafe error:&error];
if (error != nil && error.code == 257) {
NSLog(#"**** the keychain appears to be locked, using the file method");
return YES;
}
The other method, using errSecInteractionNotAllowed also works, but only for TouchID devices.
I found the answer (indirectly) here (rego with the iOS dev program most likely needed)

Finally, after 3-4 days of looking, found the answer. It was more in how I was reading the result back. Ian is right: I need to create the file using createFileAtPath, but then read it back using
NSData *data = [NSData dataWithContentsOfURL:[FOOStorageGatekeeper lockedDeviceUrl] options: NSDataReadingMappedIfSafe error:&error];
if (error != nil && error.code == 257) {
NSLog(#"**** the keychain appears to be locked, using the file method");
return YES;
}
The other method, using errSecInteractionNotAllowed also works, but only for TouchID devices.
I found the answer (indirectly) here (rego with the iOS dev program most likely needed)

I tried that and my file was always readable (in lock screen or not).
I found this document :
https://www.apple.com/business/docs/iOS_Security_Guide.pdf
It appeared that the files are locked 10 seconds after the device is locked.
Knowing that, you can create the files from the extensions, and it seems to work.

Related

NSFileManager startDownloadingUbiquitousItemAtURL not working correctly on iOS14

I have an App in the AppStore since 2013 and it always worked flawlessly. It creates and syncs files on iCloud.
Since iOS14 startDownloadingUbiquitousItemAtURL only downloads a fraction of the existing files. If I start the App in the simulator and iOS14 (and of course on the real devices as well). If I start the App in a simulator using iOS12 or 13 it works as expected.
I don't find anything on the web regarding possible changes in the startDownloadingUbiquitousItemAtURL method.
Here's the code in question:
...
BOOL tt = [[NSFileManager defaultManager] startDownloadingUbiquitousItemAtURL:cloudFileUrl error:&error] ;
if (error != nil)
{
NSLog(#"ERROR Loading %#", cloudFileUrl) ;
}
...
tt is true and error is nil, so it seems the sync process has started correctly
However, If I try to access the files with
NSArray *dirContent = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:cloudFileUrl includingPropertiesForKeys:[[NSArray alloc] initWithObjects:NSURLNameKey, nil] options:NSDirectoryEnumerationSkipsHiddenFiles error:&error];
The the array dirContent contains only 3 or 4 elements, even though the folder contains 10 files.
Any idea what the problem could be? I have opened a bug with Apple as well.
Ok, the solution ist that this call does not read some files when they are binary. Alleged text files were found.
NSArray *dirContent = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:cloudFileUrl includingPropertiesForKeys:[[NSArray alloc] initWithObjects:NSURLNameKey, nil] options:NSDirectoryEnumerationSkipsHiddenFiles error:&error];
I've replaced the call with this
NSArray *dirContent = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:cloudFileUrl includingPropertiesForKeys:[[NSArray alloc] initWithObjects:NSURLNameKey, nil] options:0 error:&error];
and now it works again.

How Can I Get A List of Available System Sounds in iOS?

I found this question, but it's a bit aged. It looks like that directory access is no longer available for the example app they recommend.
The Apple documentation doesn't seem to have what I need, either.
What I need is to be able to list the built-in sounds (not provide my own), and allow a user of my app to choose one to play.
Sounds simple enough, eh?
UPDATE:
Here is the relevant code in the example app mentioned below:
NSURL *directoryURL = [NSURL URLWithString:#"/System/Library/Audio/UISounds"];
NSArray *keys = [NSArray arrayWithObject:NSURLIsDirectoryKey];
NSDirectoryEnumerator *enumerator = [fileManager
enumeratorAtURL:directoryURL
includingPropertiesForKeys:keys
options:0
errorHandler:^(NSURL *url, NSError *error) {
// Handle the error.
// Return YES if the enumeration should continue after the error.
return YES;
}];
for (NSURL *url in enumerator) {
NSError *error;
NSNumber *isDirectory = nil;
if (! [url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {
// handle error
}
else if (! [isDirectory boolValue]) {
[audioFileList addObject:url];
}
}
The problem is that the enumerator is always empty. I suspect this may be a security/sandbox issue.
Reading up on this, you can find code to get a list of system sound files - but only on a "jail break" device.
Based on this note on Apple's docs (link), it sounds like (sorry for the pun) we won't have much luck in trying to access the "internal" sound clips:
Note System-supplied alert sounds and system-supplied user-interface sound effects are not available to your iOS application. For example, using the kSystemSoundID_UserPreferredAlert constant as a parameter to the AudioServicesPlayAlertSound function will not play anything.

How to make iOS application tamper-evident?

I am working on a project (mobile app) where I need to monitor adversary actions. So, my question is how can I make iOS app tamper-evident?
e.g.
Whenever any adversary try to tamper code then system should alert admin for these actions
and block that adversary
If user tries to install app on rooted device then system can detect that.
System should able to monitor adversary actions.
I found solution for android like ProGuard, SafetyNet but did not found anything for iOS.
I've used this JailBreak detection in one of my project.
With this, you can prevent the possibility.
if ([DTTJailbreakDetection isJailbroken]) {
// your custom activity and business logic here
}
Also, In precise you can use the below snippet:
BOOL isJailbroken()
{
#if !(TARGET_IPHONE_SIMULATOR)
if ([[NSFileManager defaultManager] fileExistsAtPath:#"/Applications/Cydia.app"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/Library/MobileSubstrate/MobileSubstrate.dylib"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/bin/bash"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/usr/sbin/sshd"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/etc/apt"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/private/var/lib/apt/"] ||
[[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:#"cydia://package/com.example.package"]]) {
return YES;
}
FILE *f = NULL ;
if ((f = fopen("/bin/bash", "r")) ||
(f = fopen("/Applications/Cydia.app", "r")) ||
(f = fopen("/Library/MobileSubstrate/MobileSubstrate.dylib", "r")) ||
(f = fopen("/usr/sbin/sshd", "r")) ||
(f = fopen("/etc/apt", "r"))) {
fclose(f);
return YES;
}
fclose(f);
NSError *error;
NSString *stringToBeWritten = #"This is a test.";
[stringToBeWritten writeToFile:#"/private/jailbreak.txt" atomically:YES encoding:NSUTF8StringEncoding error:&error];
[[NSFileManager defaultManager] removeItemAtPath:#"/private/jailbreak.txt" error:nil];
if(error == nil)
{
return YES;
}
#endif
return NO;
}
Also , Obfuscation in iOS - objective C you can use this open source-library and for Methods & Classes.
Apart from detecting jailbroken device, and obfuscating code (as #itechnician mentioned), you can:
Detect if debugger is attached: https://developer.apple.com/library/content/qa/qa1361/_index.html
Check the load commands in Mach-O header to check if there's anything injected
Check code integrity
Anyway, all of these can be easily bypassed when on jailbroken device (even the check if it's jailbroken). The best way is to use multiple techniques including obfuscation, to make tampering as hard as possible (so it's not worth it). But I'm not sure if you could make fully tamper-proof app.
You might find these links useful:
https://www.coredump.gr/articles/ios-anti-debugging-protections-part-1/
https://www.raywenderlich.com/45645/ios-app-security-analysis-part-1
http://resources.infosecinstitute.com/ios-application-security-part-31-problem-using-third-party-libraries-securing-apps/
This book is a bit old, but still useful: http://shop.oreilly.com/product/0636920023234.do
Here are opensource ObjC obfuscators/string encryptors:
https://github.com/Polidea/ios-class-guard
https://github.com/FutureWorkshops/Objc-Obfuscator
https://github.com/pjebs/Obfuscator-iOS
I think you're looking something like ixguard

iOS Background Transfer - com.apple.nsurlsessiond folder full of tmp files

We've written a media application that allows you to get a list of latest videos as json list using BACKGROUND FETCH
then it uses BACKGROUND TRANSFER to tell iOS to download the video one by one and go back to sleep and to wake the app when its done.
It does all that but we've noticed that Space Usage is growing and growing.
We added code to clear all downloaded videos but space usage stayed hi in settings.
We downloaded the app folders using Xcode > Organizer> Devices and found the BACKGROUND TRANSFER tmp folder was dull of tmp files.
Shouldn't these be getting cleared out
This is in general the code I use.
I think the main is I attach multiple DownloadTask(can be up to 30) to one background session. files vary in size from movies to pdfs.
NSURLSession * backgroundSession_ = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
backgroundSession_ = [NSURLSession sessionWithConfiguration:urlSessionConfigurationBACKGROUND_
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
NSOperationQueue *mainQueue_ = [NSOperationQueue mainQueue];
NSURLSessionDownloadTask * downloadTask_ = [backgroundSession_ downloadTaskWithURL:url_];
downloadStarted_ = TRUE;
[downloadTask_ resume];
Try something like this before returning from didFinishDownloadingToURL:
// App's Documents directory path
NSString *docsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)firstObject];
// Creating the path for the downloaded file
docsPath = [docsPath stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
// Moving the file from temp location to App's Documents directory
[[NSFileManager defaultManager] moveItemAtPath:location.path toPath:docsPath error:NULL];
The documentation states that you should "move the file to a permanent location in your app’s sandbox container directory before returning from this delegate method" (perhaps the Documents directory).
The temp files that gets cleared out after you return from didFinishDownloadingToURL (or if the download failed) - at the OS's discretion (usually under memory pressure).
I have the same issue but in a bit different circumstances:
on older devices (iPhone 4S or older) the app is usually killed during the background fetching by the OS. Probably to free memory. In this case the tmp files are retained (and untracked). Next time the app has an opportunity to fetch, new files are created... and this cycle goes on and on till the user recognises the app uses 4gb of storage space - and deletes it.
I haven't found the perfect solution yet - even after I set the background configuration's -NSURLSessionConfiguration URLCache to a custom one (documentation says it's nil by default) with a path the same directory (defaultCacheDir/com.apple.nsurlsessiond/...) was used - but made a cleanup method and use it when I sure there is no download in progress.
+ (BOOL)clearCache:(NSError * __autoreleasing *)error
{
__block BOOL successOnLegacyPath = NO;
__block NSError *errorOnLegacyPath = nil;
NSString *cacheDirPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSArray *allSubPaths = [[NSFileManager defaultManager] subpathsAtPath:cacheDirPath];
if (!allSubPaths) {
NSLog(#"No subpaths of cache:\n%#", cacheDirPath);
} else {
[allSubPaths enumerateObjectsUsingBlock:^(NSString *subpath, NSUInteger idx, BOOL *stop) {
static NSString * const kNSURLSessionPathComponent = #"nsurlsession"; // this is a non-documented way, Uncle Apple can change the path at any time
if ([subpath containsString:kNSURLSessionPathComponent]) {
successOnLegacyPath = [[NSFileManager defaultManager] removeItemAtPath:[cacheDirPath stringByAppendingPathComponent:subpath]
error:&errorOnLegacyPath];
if (!successOnLegacyPath) {
NSLog(#"Error while deleting cache subpath:\n%#\nError:\n%#", subpath, errorOnLegacyPath);
}
// First we find is the root > bail out
*stop = YES;
}
}];
}
if (!successOnLegacyPath && !errorOnLegacyPath) {
// Couldn't find the nsurlsession's cache directory
if (error) *error = [NSError errorWithDomain:NSCocoaErrorDomain
code:NSFileNoSuchFileError
userInfo:nil];
// OR
successOnLegacyPath = YES;
}
return successOnLegacyPath;
}
This is not a solution and this is recommended to use if no download is in progress. Haven't tested what's happening if there are running downloads and trying to delete the tmp files.
Even if I find a solution, the previously created tmp files still remain untracked so those need to be deleted by a method like this.
Btw, this seems to be the same question - without conclusion.

writeToFile:atomically: does not work on iOS 6.1.3?

I have a plist (NSDictionary) which I intended can be changed by user data input. Here is what I have done.
NSString *path = (the path for the plist)
NSMutableDictionary *plistFile = [[NSDictionary dictionaryWithContentOfFile:path] mutableCopy];
[plistFile setObject:(an NSString object) forKey:(an NSString key)];
[plistFile writeToFile:path atomically:YES];
So this is what I have coded. It works well on iPad 3 (new iPad) (iOS 6.1.2) and my XCode (4.6) simulator (iOS 6). However, it does not work on my iPad mini (iOS 6.1.3). I have found the problem which is in the last step. When I wrote
BOOL success = [plistFile writeToFile:path atomically:YES];
NSLog(#"%#",#(success));
The console always prints 0 which means it does not succeed. But on my iPad 3 and simulator it prints 1, which means success.
That is all I can describe because there is no exception being thrown out or other output. By the way, my iPad 3 (on which it works) is jailbroken but the iPad mini is not. Nevertheless, I use my developer account to codesign on both devices. Can anyone help me? Or else can anyone point to me a new solution?
Try to use
NSError * error = nil;
BOOL success = [plistFile writeToFile:path options:NSDataWritingAtomic error:&error];
NSLog(#"Success = %d, error = %#", success, error);
And then check what error occures. Maybe it can helps you.
And you can write only to the document directory. You may have some troubles with your path.
I can't see from your code if you have done this, but your path needs to be the sandboxed app directory.
NSArray* pathList = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString* path = [[NSString alloc] initWithFormat:#"%#", [pathList objectAtIndex:0]];

Resources