fvunlock.m
1 // 2 // fvunlock.m 3 // SecurityTool 4 5 #import <Foundation/Foundation.h> 6 7 #import "fvunlock.h" 8 #import "security_tool.h" 9 #import "Security/AuthorizationPriv.h" 10 #import <LocalAuthentication/LAContext+Private.h> 11 #import <Security/AuthorizationTagsPriv.h> 12 #import <SoftLinking/SoftLinking.h> 13 #import <os/log.h> 14 15 #if TARGET_OS_OSX && TARGET_CPU_ARM64 16 17 SOFT_LINK_FRAMEWORK(Frameworks, LocalAuthentication) 18 SOFT_LINK_CLASS(LocalAuthentication, LAContext) 19 20 NSUUID *currentRecoveryVolumeUUID(void); 21 22 static Boolean isInFVUnlock() 23 { 24 return YES; 25 // temporary solution until we find a better way 26 // return getenv("__OSINSTALL_ENVIRONMENT") != NULL; 27 } 28 29 #define kEFISystemVolumeUUIDVariableName "SystemVolumeUUID" 30 NSUUID *currentRecoveryVolumeUUID() 31 { 32 NSData *data; 33 NSString * const LANVRAMNamespaceStartupManager = @"5EEB160F-45FB-4CE9-B4E3-610359ABF6F8"; 34 35 NSString *key = [NSString stringWithFormat:@"%@:%@", LANVRAMNamespaceStartupManager, @kEFISystemVolumeUUIDVariableName]; 36 37 io_registry_entry_t match = IORegistryEntryFromPath(kIOMasterPortDefault, "IODeviceTree:/options"); 38 if (match) { 39 CFTypeRef entry = IORegistryEntryCreateCFProperty(match, (__bridge CFStringRef)key, kCFAllocatorDefault, 0); 40 IOObjectRelease(match); 41 42 if (entry) 43 { 44 if (CFGetTypeID(entry) == CFDataGetTypeID()) 45 data = CFBridgingRelease(entry); 46 else 47 CFRelease(entry); 48 } 49 } 50 51 if (data) { 52 return [[NSUUID alloc] initWithUUIDBytes:data.bytes]; 53 } else { 54 return nil; 55 } 56 } 57 58 static Boolean verifyUser() 59 { 60 // first check if policy was already satisfied 61 __block Boolean verified = NO; 62 dispatch_semaphore_t ds = dispatch_semaphore_create(0); 63 LAContext *ctx = [[getLAContextClass() alloc] init]; 64 [ctx evaluatePolicy:LAPolicyUserAuthenticationWithPasscodeRecovery options:@{ @(LAOptionNotInteractive) : @YES } reply:^(NSDictionary *result, NSError *error) { 65 if (result) { 66 verified = YES; 67 } 68 dispatch_semaphore_signal(ds); 69 }]; 70 dispatch_semaphore_wait(ds, DISPATCH_TIME_FOREVER); 71 if (verified) { 72 // user was already authenticated, no need to prompt 73 return YES; 74 } 75 76 // we need to prompt for the credentials 77 char buf[50]; 78 printf("Enter admin's username: "); 79 if (fgets(buf, sizeof(buf), stdin) == NULL) { 80 // no input 81 os_log_error(OS_LOG_DEFAULT, "Unable to acquire username"); 82 return NO; 83 } 84 char *temp = getpass("Password: "); 85 NSString *username = [[NSString stringWithUTF8String:buf] stringByReplacingOccurrencesOfString:@"\n" withString:@""]; 86 NSString *password = [NSString stringWithUTF8String:temp]; 87 88 if (username.length == 0 || password.length == 0) { 89 // no credentials 90 os_log_error(OS_LOG_DEFAULT, "Unable to get the credentials"); 91 return NO; 92 } 93 94 // get the user GUID 95 CFArrayRef usersCf; 96 OSStatus status = AuthorizationCopyPreloginUserDatabase(NULL, 0, &usersCf); 97 if (status) { 98 // cannot get AIR 99 os_log_error(OS_LOG_DEFAULT, "AIR failed with error %d", status); 100 return NO; 101 } 102 NSArray *users = CFBridgingRelease(usersCf); 103 Boolean found = NO; 104 for (NSDictionary *record in users) { 105 if (![username isEqualToString:record[@PLUDB_USERNAME]]) { 106 continue; 107 } 108 if (![record[@PLUDB_ADMIN] isEqual:@YES]) { 109 // admins only 110 continue; 111 } 112 found = YES; 113 NSString *userGuid = record[@PLUDB_GUID]; 114 if (userGuid) { 115 ctx = [[getLAContextClass() alloc] init]; 116 NSDictionary *cred = @{ 117 @kLACredentialKeyUserGuid : userGuid, 118 @kLACredentialKeyPassword : password, 119 }; 120 NSError *error; 121 NSData *credData = [NSKeyedArchiver archivedDataWithRootObject:cred requiringSecureCoding:YES error:&error]; 122 if (credData == nil) { 123 os_log_error(OS_LOG_DEFAULT, "NSKeyedArchiver failed with error %{public}@", error); 124 continue; // try another user 125 } 126 127 if ([ctx setCredential:credData type:LACredentialTypeRecoveryData error:&error]) { 128 return YES; // user was verified 129 } 130 } 131 } 132 133 os_log_error(OS_LOG_DEFAULT, "Unable to verify user (found %d)", found); 134 return NO; 135 } 136 137 static int enforcementWorker(const char operation, NSUUID *volumeUuid) 138 { 139 // first ensure we are in a Recovery 140 if (!isInFVUnlock()) { 141 fprintf(stderr, "This command is available only when booted to Recovery\n"); 142 return -1; 143 } 144 145 // then authenticate user 146 if (!verifyUser()) { 147 fprintf(stderr, "Unable to verify an administrator\n"); 148 return -3; 149 } 150 151 // then call authd 152 Boolean enabled = false; 153 OSStatus retval = AuthorizationHandlePreloginOverride(volumeUuid.UUIDString.UTF8String, operation, &enabled); 154 switch (operation) { 155 case kAuthorizationOverrideOperationSet: 156 if (retval != noErr) { 157 fprintf(stderr, "Error %d when trying to set the SmartCard enforcement override\n", retval); 158 } else { 159 fprintf(stdout, "SmartCard enforcement is temporarily turned off for the next boot\n"); 160 } 161 break; 162 case kAuthorizationOverrideOperationReset: 163 if (retval != noErr) { 164 fprintf(stderr, "Error %d when trying to reset the SmartCard enforcement override\n", retval); 165 } else { 166 fprintf(stdout, "SmartCard enforcement override was reset\n"); 167 } 168 break; 169 case kAuthorizationOverrideOperationQuery: 170 if (retval != noErr) { 171 fprintf(stderr, "Error %d when trying to get the SmartCard enforcement state\n", retval); 172 } else { 173 fprintf(stdout, "SmartCard enforcement is%s temporarily turned off for the next boot\n", enabled ? "" : " not"); 174 } 175 break; 176 default: 177 fprintf(stderr, "Unsupported operation\n"); 178 break; 179 } 180 181 return retval; 182 } 183 184 static int skipScEnforcement(int argc, char * const *argv) 185 { 186 NSUUID *systemVolumeUuid = [[NSUUID UUID] initWithUUIDString:[[NSString alloc] initWithUTF8String:argv[1]]]; 187 if (!systemVolumeUuid) { 188 fprintf(stderr, "System volume UUID %s was not recognized\n", argv[1]); 189 return -4; 190 } 191 // look if system volume exists at all 192 CFArrayRef cfUsers; 193 OSStatus status = AuthorizationCopyPreloginUserDatabase(argv[1], 0, &cfUsers); 194 NSArray *users = CFBridgingRelease(cfUsers); 195 if (status) { 196 fprintf(stderr, "System volume error\n"); 197 return -5; 198 } 199 if (users.count == 0) { 200 fprintf(stderr, "System volume with UUID %s is not supported\n", argv[1]); 201 return -6; 202 } 203 204 if (!strcmp("set", argv[2])) { 205 return enforcementWorker(kAuthorizationOverrideOperationSet, systemVolumeUuid); 206 } else if (!strcmp("reset", argv[2])) { 207 return enforcementWorker(kAuthorizationOverrideOperationReset, systemVolumeUuid); 208 } else if (!strcmp("status", argv[2])) { 209 return enforcementWorker(kAuthorizationOverrideOperationQuery, systemVolumeUuid); 210 } 211 212 return SHOW_USAGE_MESSAGE; 213 } 214 215 int fvunlock(int argc, char * const *argv) { 216 int result = SHOW_USAGE_MESSAGE; 217 require_quiet(argc > 3, out); // three arguments needed 218 @autoreleasepool { 219 if (!strcmp("skip-sc-enforcement", argv[1])) { 220 result = skipScEnforcement(argc - 1, argv + 1); 221 } 222 } 223 224 out: 225 return result; 226 } 227 228 #endif