Nic Wise
Nic Wise

Reputation: 8129

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

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

Upvotes: 5

Views: 2447

Answers (3)

user3492842
user3492842

Reputation: 71

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.

Upvotes: 1

Ian MacDonald
Ian MacDonald

Reputation: 14030

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)

Upvotes: 8

Nic Wise
Nic Wise

Reputation: 8129

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)

Upvotes: 2

Related Questions