fvunlock.m   [plain text]


//
//  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:@"%@:%@", LANVRAMNamespaceStartupManager, @kEFISystemVolumeUUIDVariableName];
    
    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 %d", status);
        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 %{public}@", 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 %d)", 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 %d when trying to set the SmartCard enforcement override\n", retval);
            } else {
                fprintf(stdout, "SmartCard enforcement is temporarily turned off for the next boot\n");
            }
            break;
        case kAuthorizationOverrideOperationReset:
            if (retval != noErr) {
                fprintf(stderr, "Error %d when trying to reset the SmartCard enforcement override\n", retval);
            } else {
                fprintf(stdout, "SmartCard enforcement override was reset\n");
            }
            break;
        case kAuthorizationOverrideOperationQuery:
            if (retval != noErr) {
                fprintf(stderr, "Error %d when trying to get the SmartCard enforcement state\n", retval);
            } else {
                fprintf(stdout, "SmartCard enforcement is%s temporarily turned off for the next boot\n", enabled ? "" : " not");
            }
            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 %s was not recognized\n", argv[1]);
        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 %s is not supported\n", argv[1]);
        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