//
// fvunlock.m
// SecurityTool
#import <Foundation/Foundation.h>
#import "fvunlock.h"
#import "security_tool.h"
#import "Security/AuthorizationPriv.h"
#import <LocalAuthentication/LAContext+Private.h>
#import <Security/AuthorizationTagsPriv.h>
#import <SoftLinking/SoftLinking.h>
#import <os/log.h>
#if TARGET_OS_OSX && TARGET_CPU_ARM64
SOFT_LINK_FRAMEWORK(Frameworks, LocalAuthentication)
SOFT_LINK_CLASS(LocalAuthentication, LAContext)
NSUUID *currentRecoveryVolumeUUID(void);
static Boolean isInFVUnlock()
{
return YES;
// temporary solution until we find a better way
// return getenv("__OSINSTALL_ENVIRONMENT") != NULL;
}
#define kEFISystemVolumeUUIDVariableName "SystemVolumeUUID"
NSUUID *currentRecoveryVolumeUUID()
{
NSData *data;
NSString * const LANVRAMNamespaceStartupManager = @"5EEB160F-45FB-4CE9-B4E3-610359ABF6F8";
NSString *key = [NSString stringWithFormat:@"
io_registry_entry_t match = IORegistryEntryFromPath(kIOMasterPortDefault, "IODeviceTree:/options");
if (match) {
CFTypeRef entry = IORegistryEntryCreateCFProperty(match, (__bridge CFStringRef)key, kCFAllocatorDefault, 0);
IOObjectRelease(match);
if (entry)
{
if (CFGetTypeID(entry) == CFDataGetTypeID())
data = CFBridgingRelease(entry);
else
CFRelease(entry);
}
}
if (data) {
return [[NSUUID alloc] initWithUUIDBytes:data.bytes];
} else {
return nil;
}
}
static Boolean verifyUser()
{
// first check if policy was already satisfied
__block Boolean verified = NO;
dispatch_semaphore_t ds = dispatch_semaphore_create(0);
LAContext *ctx = [[getLAContextClass() alloc] init];
[ctx evaluatePolicy:LAPolicyUserAuthenticationWithPasscodeRecovery options:@{ @(LAOptionNotInteractive) : @YES } reply:^(NSDictionary *result, NSError *error) {
if (result) {
verified = YES;
}
dispatch_semaphore_signal(ds);
}];
dispatch_semaphore_wait(ds, DISPATCH_TIME_FOREVER);
if (verified) {
// user was already authenticated, no need to prompt
return YES;
}
// we need to prompt for the credentials
char buf[50];
printf("Enter admin's username: ");
if (fgets(buf, sizeof(buf), stdin) == NULL) {
// no input
os_log_error(OS_LOG_DEFAULT, "Unable to acquire username");
return NO;
}
char *temp = getpass("Password: ");
NSString *username = [[NSString stringWithUTF8String:buf] stringByReplacingOccurrencesOfString:@"\n" withString:@""];
NSString *password = [NSString stringWithUTF8String:temp];
if (username.length == 0 || password.length == 0) {
// no credentials
os_log_error(OS_LOG_DEFAULT, "Unable to get the credentials");
return NO;
}
// get the user GUID
CFArrayRef usersCf;
OSStatus status = AuthorizationCopyPreloginUserDatabase(NULL, 0, &usersCf);
if (status) {
// cannot get AIR
os_log_error(OS_LOG_DEFAULT, "AIR failed with error return NO;
}
NSArray *users = CFBridgingRelease(usersCf);
Boolean found = NO;
for (NSDictionary *record in users) {
if (![username isEqualToString:record[@PLUDB_USERNAME]]) {
continue;
}
if (![record[@PLUDB_ADMIN] isEqual:@YES]) {
// admins only
continue;
}
found = YES;
NSString *userGuid = record[@PLUDB_GUID];
if (userGuid) {
ctx = [[getLAContextClass() alloc] init];
NSDictionary *cred = @{
@kLACredentialKeyUserGuid : userGuid,
@kLACredentialKeyPassword : password,
};
NSError *error;
NSData *credData = [NSKeyedArchiver archivedDataWithRootObject:cred requiringSecureCoding:YES error:&error];
if (credData == nil) {
os_log_error(OS_LOG_DEFAULT, "NSKeyedArchiver failed with error continue; // try another user
}
if ([ctx setCredential:credData type:LACredentialTypeRecoveryData error:&error]) {
return YES; // user was verified
}
}
}
os_log_error(OS_LOG_DEFAULT, "Unable to verify user (found return NO;
}
static int enforcementWorker(const char operation, NSUUID *volumeUuid)
{
// first ensure we are in a Recovery
if (!isInFVUnlock()) {
fprintf(stderr, "This command is available only when booted to Recovery\n");
return -1;
}
// then authenticate user
if (!verifyUser()) {
fprintf(stderr, "Unable to verify an administrator\n");
return -3;
}
// then call authd
Boolean enabled = false;
OSStatus retval = AuthorizationHandlePreloginOverride(volumeUuid.UUIDString.UTF8String, operation, &enabled);
switch (operation) {
case kAuthorizationOverrideOperationSet:
if (retval != noErr) {
fprintf(stderr, "Error } else {
fprintf(stdout, "SmartCard enforcement is temporarily turned off for the next boot\n");
}
break;
case kAuthorizationOverrideOperationReset:
if (retval != noErr) {
fprintf(stderr, "Error } else {
fprintf(stdout, "SmartCard enforcement override was reset\n");
}
break;
case kAuthorizationOverrideOperationQuery:
if (retval != noErr) {
fprintf(stderr, "Error } else {
fprintf(stdout, "SmartCard enforcement is }
break;
default:
fprintf(stderr, "Unsupported operation\n");
break;
}
return retval;
}
static int skipScEnforcement(int argc, char * const *argv)
{
NSUUID *systemVolumeUuid = [[NSUUID UUID] initWithUUIDString:[[NSString alloc] initWithUTF8String:argv[1]]];
if (!systemVolumeUuid) {
fprintf(stderr, "System volume UUID return -4;
}
// look if system volume exists at all
CFArrayRef cfUsers;
OSStatus status = AuthorizationCopyPreloginUserDatabase(argv[1], 0, &cfUsers);
NSArray *users = CFBridgingRelease(cfUsers);
if (status) {
fprintf(stderr, "System volume error\n");
return -5;
}
if (users.count == 0) {
fprintf(stderr, "System volume with UUID return -6;
}
if (!strcmp("set", argv[2])) {
return enforcementWorker(kAuthorizationOverrideOperationSet, systemVolumeUuid);
} else if (!strcmp("reset", argv[2])) {
return enforcementWorker(kAuthorizationOverrideOperationReset, systemVolumeUuid);
} else if (!strcmp("status", argv[2])) {
return enforcementWorker(kAuthorizationOverrideOperationQuery, systemVolumeUuid);
}
return SHOW_USAGE_MESSAGE;
}
int fvunlock(int argc, char * const *argv) {
int result = SHOW_USAGE_MESSAGE;
require_quiet(argc > 3, out); // three arguments needed
@autoreleasepool {
if (!strcmp("skip-sc-enforcement", argv[1])) {
result = skipScEnforcement(argc - 1, argv + 1);
}
}
out:
return result;
}
#endif