/ SecurityTool / macOS / fvunlock.m
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