5 // Copyright © 2019 Apple. All rights reserved.
8 #import <Foundation/Foundation.h>
11 #import <DiskManagement/DiskManagement.h>
12 #import "PreloginUserDb.h"
13 #import <LocalAuthentication/LAContext+Private.h>
14 #import <SoftLinking/SoftLinking.h>
16 #import <Security/Authorization.h>
17 #import <Security/AuthorizationTagsPriv.h>
18 #import <libaks_filevault.h>
22 SOFT_LINK_FRAMEWORK(Frameworks, LocalAuthentication)
23 SOFT_LINK_FRAMEWORK(PrivateFrameworks, DiskManagement)
24 SOFT_LINK_FRAMEWORK(Frameworks, DiskArbitration)
25 SOFT_LINK_FRAMEWORK(PrivateFrameworks, APFS)
27 SOFT_LINK_CLASS(LocalAuthentication, LAContext)
28 SOFT_LINK_CLASS(DiskManagement, DMManager)
29 SOFT_LINK_CLASS(DiskManagement, DMAPFS)
30 SOFT_LINK_FUNCTION(APFS, APFSVolumeGetUnlockRecord, soft_APFSVolumeGetUnlockRecord, errno_t, (const char *disk, uuid_t wrecUUID, CFDataRef *data), (disk, wrecUUID, data))
31 SOFT_LINK_FUNCTION(DiskArbitration, DADiskMount, soft_DADiskMount, void, ( DADiskRef disk, CFURLRef __nullable path, DADiskMountOptions options, DADiskMountCallback __nullable callback, void * __nullable context), (disk, path, options, callback, context ))
32 SOFT_LINK_FUNCTION(DiskArbitration, DADiskUnmount, soft_DADiskUnmount, void, ( DADiskRef disk, DADiskUnmountOptions options, DADiskUnmountCallback __nullable callback, void * __nullable context), (disk, options, callback, context ))
33 SOFT_LINK_FUNCTION(DiskArbitration, DADissenterGetStatusString, soft_DADissenterGetStatusString, CFStringRef __nullable, ( DADissenterRef dissenter ), ( dissenter ))
34 SOFT_LINK_FUNCTION(DiskManagement, DMUnlocalizedTechnicalErrorString, soft_DMUnlocalizedTechnicalErrorString, NSString *, ( DMDiskErrorType inError ), ( inError ))
35 SOFT_LINK_FUNCTION(DiskArbitration, DASessionCreate, soft_DASessionCreate, DASessionRef __nullable, ( CFAllocatorRef __nullable allocator ), ( allocator ))
36 SOFT_LINK_FUNCTION(DiskArbitration, DADissenterGetStatus, soft_DADissenterGetStatus, DAReturn, ( DADissenterRef dissenter ), ( dissenter ))
37 SOFT_LINK_FUNCTION(DiskArbitration, DASessionSetDispatchQueue, soft_DASessionSetDispatchQueue, void, ( DASessionRef session, dispatch_queue_t __nullable queue ), ( session, queue ))
39 static NSString *kVekItemName = @"SecureAccessToken";
40 static NSString *kGUIDItemName = @"GeneratedUID";
41 static NSString *kAuthenticationAuthority = @"AuthenticationAuthority";
42 static NSString *kIsAdmintemName = @"Admin";
43 static NSString *kSCUnlockDataItemName = @"FVTokenSecret";
44 static NSString *kSCEnforcementItemName = @"SmartCardEnforcement";
45 static NSString *kSCUacItemName = @"userAccountControl";
47 static NSString *kLongNameItemName = @"RealName";
48 static NSString *kUidItemName = @"UID";
49 static NSString *kVekFile = @"%@/%@/var/db/secureaccesstoken.plist";
50 static NSString *kUsersFile = @"%@/%@/var/db/AllUsersInfo.plist";
52 static NSString *kUsersGUID = @"UserIdent";
53 static NSString *kUsersNameSection = @"UserNamesData";
54 static NSString *kUsersSection = @"CryptoUsers";
57 @interface PreloginUserDb : NSObject
61 - (BOOL) loadWithError:(NSError **)error;
63 - (NSArray<NSDictionary *> *) users;
64 - (NSArray<NSDictionary *> *) users:(NSString *)volumeUuid;
68 typedef void (^AIRDBDACommonCompletionHandler)(DADissenterRef dissenter);
69 static void _commonDACompletionCallback(DADiskRef disk, DADissenterRef dissenter, void *context)
71 AIRDBDACommonCompletionHandler handler = (__bridge AIRDBDACommonCompletionHandler)context;
75 @implementation PreloginUserDb {
78 NSMutableDictionary<NSString*, NSMutableArray*> *_dbDataDict; // NSDictionary indexed by volume UUID (NSString*)
79 NSMutableDictionary<NSString*, NSString*> *_dbVolumeGroupMap;
80 dispatch_queue_t _queue;
85 if ((self = [super init])) {
86 _queue = dispatch_queue_create("com.apple.PLUDB", DISPATCH_QUEUE_SERIAL);
88 os_log_error(AUTHD_LOG, "Failed to create queue");
92 _diskMgr = [[getDMManagerClass() alloc] init];
94 os_log_error(AUTHD_LOG, "Failed to get DM");
98 _daSession = (__bridge_transfer id)soft_DASessionCreate(kCFAllocatorDefault);
100 os_log_error(AUTHD_LOG, "Failed to get DA");
104 soft_DASessionSetDispatchQueue((__bridge DASessionRef _Nullable)_daSession, _queue);
105 [_diskMgr setDefaultDASession:(__bridge DASessionRef _Nullable)(_daSession)];
110 - (BOOL)loadWithError:(NSError **)err
112 // get all preboot volumes
113 NSArray* prebootVolumes = [self allPrebootVolumes];
114 if (prebootVolumes.count == 0) {
115 os_log_error(AUTHD_LOG, "Failed to get preboot volumes for Prelogin userDB");
117 *err = [NSError errorWithDomain:@"com.apple.authorization" code:-1000 userInfo:@{ NSLocalizedDescriptionKey : @"Failed to get preboot volumes for Prelogin userDB"}];
122 NSUUID *uuid = [self currentRecoveryVolumeUUID];
123 os_log_info(AUTHD_LOG, "Current Recovery Volume UUID: %{public}@", uuid);
125 _dbDataDict = [NSMutableDictionary new];
126 _dbVolumeGroupMap = [NSMutableDictionary new];
127 [self processPrebootVolumes:prebootVolumes currentRecoveryVolumeUUID:uuid];
129 if (_dbDataDict.count == 0 && uuid != nil) {
130 os_log(AUTHD_LOG, "No admins found. Try to load all preboot partitions");
131 _dbDataDict = [NSMutableDictionary new];
132 [self processPrebootVolumes:prebootVolumes currentRecoveryVolumeUUID:nil]; // load admins from ALL preboot partitions
141 - (NSArray<NSDictionary *> *)users
143 return [self users:nil];
146 - (NSArray<NSDictionary *> *) users:(NSString *)requestedUuid
148 if (!_dbDataDict.allValues) {
151 if (requestedUuid && !_dbDataDict[requestedUuid]) {
152 NSString *realUuid = _dbVolumeGroupMap[requestedUuid];
154 os_log_info(AUTHD_LOG, "Requested volume %{public}@ was not found and is not volumeGroup", requestedUuid);
155 NSArray *keys = [_dbVolumeGroupMap allKeysForObject:requestedUuid];
156 for(NSString *uuid in keys) {
157 if (_dbDataDict[uuid]) {
163 os_log_info(AUTHD_LOG, "Requested volumeGroup %{public}@ was not found", requestedUuid);
164 return nil; // no users for requested partition and no mapping for VolumeGroup or vice versa
167 os_log_info(AUTHD_LOG, "Requested volume %{public}@ has no users, trying volume %{public}@", requestedUuid, realUuid);
168 requestedUuid = realUuid;
171 NSMutableArray *allUsers = [NSMutableArray new];
172 for (NSString *uuid in _dbDataDict) {
173 if (requestedUuid && ![requestedUuid isEqualToString:uuid]) {
174 os_log_info(AUTHD_LOG, "Requested volume %{public}@ so ignoring volume %{public}@", requestedUuid, uuid);
177 [allUsers addObjectsFromArray:_dbDataDict[uuid]];
181 #pragma mark - Private Methods
183 - (void)processPrebootVolumes:(NSArray*)prebootVolumes currentRecoveryVolumeUUID:(NSUUID *)currentRecoveryVolumeUUID
185 // process each preboot volume
186 for (id prebootVolume in prebootVolumes) {
188 // mount the preboot volume. If it fails it could be already mounted. Try to get mountPoint anyway.
189 Boolean mounted = [self mountPrebootVolume:prebootVolume];
191 // get a mount point of the preboot volume
192 NSString* mountPoint = [self mountPointForPrebootVolume:prebootVolume];
197 // process the preboot volume
198 NSDirectoryEnumerator *dirEnumerator = [[NSFileManager defaultManager] enumeratorAtURL:[NSURL fileURLWithPath:mountPoint isDirectory:YES] includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants errorHandler:nil];
199 for (NSURL *url in dirEnumerator) {
201 [[NSFileManager defaultManager] fileExistsAtPath:url.path isDirectory:&isDir];
203 os_log_info(AUTHD_LOG, "Skipping file %{public}@ (not a directory)", url.path);
207 NSUUID* volumeUUID = [[NSUUID alloc] initWithUUIDString:url.lastPathComponent]; // the dir has the name as UUID
209 os_log_info(AUTHD_LOG, "Ignoring folder %{public}@ (not UUID)", url);
213 if (currentRecoveryVolumeUUID && ![currentRecoveryVolumeUUID isEqualTo:volumeUUID]) {
214 os_log_info(AUTHD_LOG, "The preboot volume skipped: %{public}@ (not the currentRecoveryVolumeUUID %{public}@)", url, currentRecoveryVolumeUUID);
218 [self processVolumeData:volumeUUID mountPoint:mountPoint];
221 // unmount the preboot volume
223 [self unmountPrebootVolume:prebootVolume];
228 #define kEFISystemVolumeUUIDVariableName "SystemVolumeUUID"
229 - (NSUUID *)currentRecoveryVolumeUUID
232 NSString * const LANVRAMNamespaceStartupManager = @"5EEB160F-45FB-4CE9-B4E3-610359ABF6F8";
234 NSString *key = [NSString stringWithFormat:@"%@:%@", LANVRAMNamespaceStartupManager, @kEFISystemVolumeUUIDVariableName];
236 io_registry_entry_t match = IORegistryEntryFromPath(kIOMasterPortDefault, "IODeviceTree:/options");
238 CFTypeRef entry = IORegistryEntryCreateCFProperty(match, (__bridge CFStringRef)key, kCFAllocatorDefault, 0);
239 IOObjectRelease(match);
243 if (CFGetTypeID(entry) == CFDataGetTypeID())
244 data = CFBridgingRelease(entry);
249 os_log_info(AUTHD_LOG, "Current boot volume: %{public}@", data);
252 return [[NSUUID alloc] initWithUUIDBytes:data.bytes];
258 - (NSArray *)allPrebootVolumes
260 NSMutableArray* result = [NSMutableArray new];
262 DMAPFS* dmAPFS = [[getDMAPFSClass() alloc] initWithManager:_diskMgr];
264 for (id tmp in _diskMgr.disks) {
265 DADiskRef diskRef = (__bridge DADiskRef)(tmp);
266 os_log_info(AUTHD_LOG, "Found disk %{public}@", diskRef);
269 DMDiskErrorType diskErr = [dmAPFS isPrebootVolume:diskRef prebootRole:&preboot];
271 os_log(AUTHD_LOG, "Failed to determine preboot state for %{public}@: %{public}@", diskRef, soft_DMUnlocalizedTechnicalErrorString(diskErr));
275 os_log_info(AUTHD_LOG, "Not a preboot volume: %{public}@", diskRef);
279 id prebootVolume = CFBridgingRelease([_diskMgr copyBooterDiskForDisk:diskRef error:&diskErr]);
281 os_log_info(AUTHD_LOG, "Found APFS preboot %{public}@", prebootVolume);
282 [result addObject:prebootVolume];
284 os_log_error(AUTHD_LOG, "Failed to copy preboot for disk %{public}@, err: %{public}@", diskRef, soft_DMUnlocalizedTechnicalErrorString(diskErr));
291 - (BOOL)mountPrebootVolume:(id)preboot
293 __block BOOL success = NO;
294 dispatch_semaphore_t sem = dispatch_semaphore_create(0);
295 AIRDBDACommonCompletionHandler completionHandler = ^(DADissenterRef dissenter) {
296 success = (dissenter == NULL);
297 if (dissenter != NULL) {
298 os_log(AUTHD_LOG, "Failed to mount preboot volume %{public}@ (status: 0x%x, reason: \"%{public}@\").", preboot, soft_DADissenterGetStatus(dissenter), soft_DADissenterGetStatusString(dissenter));
300 dispatch_semaphore_signal(sem);
302 soft_DADiskMount((__bridge DADiskRef _Nonnull)(preboot), NULL, kDADiskMountOptionDefault, _commonDACompletionCallback, (__bridge void * _Nullable)(completionHandler));
303 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
307 - (NSString *)mountPointForPrebootVolume:(id)preboot
309 DMDiskErrorType diskErr;
310 NSString* result = [_diskMgr mountPointForDisk:(__bridge DADiskRef _Nonnull)(preboot) error:&diskErr];
312 os_log_info(AUTHD_LOG, "Mounted preboot partition %{public}@ at %{public}@", preboot, result);
314 os_log_error(AUTHD_LOG, "Failed to get preboot mount point: %{public}@", soft_DMUnlocalizedTechnicalErrorString(diskErr));
319 - (void)unmountPrebootVolume:(id)preboot
321 soft_DADiskUnmount((__bridge DADiskRef _Nonnull)(preboot), kDADiskUnmountOptionDefault, nil, nil);
322 os_log_info(AUTHD_LOG, "Preboot partition unmounted: %{public}@", preboot);
325 - (NSString *)deviceNodeForVolumeWithUUID:(NSUUID *)volumeUuid diskRef:(DADiskRef *)diskRef
327 DMDiskErrorType diskErr;
328 DADiskRef localDiskRef = [_diskMgr copyDiskForVolumeUUID:volumeUuid.UUIDString error:&diskErr];
330 os_log_error(AUTHD_LOG, "Failed to find disk with volume %{public}@: %{public}@", volumeUuid, soft_DMUnlocalizedTechnicalErrorString(diskErr));
334 *diskRef = localDiskRef;
337 os_log_info(AUTHD_LOG, "Found disk %{public}@ with volume UUID %{public}@", localDiskRef, volumeUuid);
338 NSString* deviceNode = [self deviceNodeForDisk:localDiskRef];
339 CFRelease(localDiskRef);
343 - (NSString *)deviceNodeForDisk:(DADiskRef)diskRef
345 DMDiskErrorType diskErr;
346 NSString *deviceNode = [_diskMgr deviceNodeForDisk:diskRef error:&diskErr];
348 os_log_error(AUTHD_LOG, "Failed to find device node for disk %{public}@: %{public}@", diskRef, soft_DMUnlocalizedTechnicalErrorString(diskErr));
351 os_log_info(AUTHD_LOG, "Device node found: %{public}@", deviceNode);
355 - (NSData *)loadVEKforVolumeWithUUID:(NSUUID *)volumeUuid mountPoint:(NSString *)mountPoint
357 NSString *vekPath = [NSString stringWithFormat:kVekFile, mountPoint, volumeUuid.UUIDString];
358 NSDictionary *vekDict = [NSDictionary dictionaryWithContentsOfFile:vekPath];
359 NSData *vek = vekDict[kVekItemName];
361 os_log_error(AUTHD_LOG, "Failed to load DiskToken from %{public}@", vekPath);
364 os_log_info(AUTHD_LOG, "Loaded DiskToken from %{public}@", vekPath);
369 - (NSData *)loadKEKforUuid:(NSString *)userUuid deviceNode:(NSString *)deviceNode
371 NSUUID *nsUuid = [[NSUUID alloc] initWithUUIDString:userUuid];
373 [nsUuid getUUIDBytes:uuid];
375 errno_t err = soft_APFSVolumeGetUnlockRecord(deviceNode.UTF8String, uuid, &dataCF);
377 os_log_error(AUTHD_LOG, "Failed to find SecureToken on device node %{public}@ and UUID %{public}@ (%d)", deviceNode, userUuid, err);
380 os_log_info(AUTHD_LOG, "Loaded SecureToken from device node %{public}@", deviceNode);
382 NSData *kek = CFBridgingRelease(dataCF);
386 - (NSDictionary *)loadUserDatabaseForVolumeUUID:(NSUUID *)volumeUuid mountPoint:(NSString *)mountPoint
388 NSString *usersPath = [NSString stringWithFormat:kUsersFile, mountPoint, volumeUuid.UUIDString];
389 NSDictionary *users = [NSDictionary dictionaryWithContentsOfFile:usersPath];
390 if (users.count == 0) {
391 os_log_error(AUTHD_LOG, "Failed to find user records in file %{public}@", usersPath);
394 os_log_debug(AUTHD_LOG, "Loaded %lu user records from file %{public}@", (unsigned long)users.count, usersPath);
398 - (void)processVolumeData:(NSUUID *)volumeUuid mountPoint:(NSString *)mountPoint
400 os_log_info(AUTHD_LOG, "Processing volume data: %{public}@", volumeUuid);
401 NSData *vek = [self loadVEKforVolumeWithUUID:volumeUuid mountPoint:mountPoint];
406 DADiskRef cfDiskRef = NULL;
407 NSString* deviceNode = [self deviceNodeForVolumeWithUUID:volumeUuid diskRef:&cfDiskRef];
408 id diskRef = CFBridgingRelease(cfDiskRef);
412 NSString *volumeGroupUuid;
413 DMAPFS* dmAPFS = [[getDMAPFSClass() alloc] initWithManager:_diskMgr];
414 DMDiskErrorType diskErr = [dmAPFS volumeGroupForVolume:(__bridge DADiskRef _Nonnull)(diskRef) id:&volumeGroupUuid];
415 if (diskErr != kDiskErrorNoError || volumeGroupUuid == nil) {
416 os_log_error(AUTHD_LOG, "Error %d while trying to get volume group for %{public}@", diskErr, volumeUuid);
418 if ([volumeUuid.UUIDString isEqualTo:volumeGroupUuid]) {
419 NSArray *systemVolumeDisks = nil;
420 diskErr = [dmAPFS disksForVolumeGroup:volumeGroupUuid volumeDisks:nil systemVolumeDisks:&systemVolumeDisks dataVolumeDisks:nil userVolumeDisks:nil container:nil];
421 if (diskErr != kDiskErrorNoError || systemVolumeDisks == nil) {
422 os_log_error(AUTHD_LOG, "Error %d while trying to get volume group disks for %{public}@", diskErr, volumeGroupUuid);
424 // There should be only one systemVolume, but the API returns an array so we'll process as many as it wants to give us
425 for (id tmp in systemVolumeDisks) {
426 DADiskRef systemVolumeDiskRef = (__bridge DADiskRef)(tmp);
427 NSString *systemVolumeUuid = nil;
428 diskErr = [dmAPFS volumeUUIDForVolume:systemVolumeDiskRef UUID:&systemVolumeUuid];
429 if (diskErr != kDiskErrorNoError || systemVolumeUuid == nil) {
430 os_log_error(AUTHD_LOG, "Error %d while trying to get volume uuid disks for some system volumes of group %{public}@", diskErr, volumeGroupUuid);
432 os_log(AUTHD_LOG, "Volume %{public}@ belongs to the group %{public}@", systemVolumeUuid, volumeGroupUuid);
433 _dbVolumeGroupMap[systemVolumeUuid] = volumeGroupUuid;
440 NSDictionary *users = [self loadUserDatabaseForVolumeUUID:volumeUuid mountPoint:mountPoint];
441 for (NSString *userName in users) {
442 NSDictionary *userData = users[userName];
443 os_log_debug(AUTHD_LOG, "Processing user: %{public}@", userData);
444 NSString *userGuid = userData[kGUIDItemName];
445 if (userGuid == nil) {
446 os_log_error(AUTHD_LOG, "Failed to find GUID for user %{public}@", userName);
449 NSData* kek = [self loadKEKforUuid:userGuid deviceNode:deviceNode];
451 os_log_error(AUTHD_LOG, "Failed to find SecureToken for user %{public}@", userName);
455 NSArray *aauthority = userData[kAuthenticationAuthority];
456 NSMutableDictionary *dict = @{}.mutableCopy;
458 dict[@PLUDB_SCPAIR] = aauthority;
459 os_log_debug(AUTHD_LOG, "Using authority: %{public}@", aauthority);
463 struct aks_fv_param_s params = {};
464 aks_fv_blob_state_s verifier_state = {};
465 struct aks_fv_data_s kekData = { .data = (void *)kek.bytes, .len = kek.length };
467 int res = aks_fv_get_blob_state(¶ms, &kekData, &verifier_state);
469 os_log_error(AUTHD_LOG, "Blob state failed: %x", res);
472 owner = ((verifier_state.flags & aks_fv_state_is_owner) == aks_fv_state_is_owner);
475 dict[@PLUDB_USERNAME] = userName;
476 dict[@PLUDB_GUID] = userGuid;
477 dict[@PLUDB_ADMIN] = userData[kIsAdmintemName];
478 dict[@PLUDB_KEK] = kek;
479 dict[@PLUDB_VEK] = vek;
480 dict[@PLUDB_DNODE] = deviceNode;
481 dict[@PLUDB_OWNER] = @(owner);
483 if ([userData.allKeys containsObject:kSCUnlockDataItemName]) {
484 dict[@PLUDB_SCUNLOCK_DATA] = userData[kSCUnlockDataItemName];
486 if ([userData.allKeys containsObject:kSCEnforcementItemName]) {
487 dict[@PLUDB_SCUNLOCK_DATA] = userData[kSCEnforcementItemName];
489 if ([userData.allKeys containsObject:kSCUnlockDataItemName]) {
490 dict[@PLUDB_SCUNLOCK_DATA] = userData[kSCUnlockDataItemName];
492 if ([userData.allKeys containsObject:kSCUacItemName]) {
493 dict[@PLUDB_SCUAC] = userData[kSCUacItemName];
495 if ([userData.allKeys containsObject:kLongNameItemName]) {
496 dict[@PLUDB_LUSERNAME] = userData[kLongNameItemName];
499 NSMutableArray *array = _dbDataDict[volumeUuid.UUIDString];
501 array = [NSMutableArray new];
503 os_log_error(AUTHD_LOG, "Failed to create users array");
506 _dbDataDict[volumeUuid.UUIDString] = array;
509 os_log_info(AUTHD_LOG, "Prelogin UserDB added entry: %{public}@", dict);
510 [array addObject:dict];
516 OSStatus preloginudb_copy_userdb(const char *uuid, UInt32 flags, CFArrayRef *output)
519 return errAuthorizationBadAddress;
521 static PreloginUserDb *database;
522 static OSStatus loadError = errAuthorizationSuccess;
523 static dispatch_once_t onceToken;
525 dispatch_once(&onceToken, ^{
527 os_log_info(AUTHD_LOG, "Going to load User DB");
529 database = [[PreloginUserDb alloc] init];
531 loadError = errAuthorizationInvalidSet;
534 if ([database loadWithError:&error]) {
535 loadError = (int)error.code;
544 os_log_debug(AUTHD_LOG, "Processing user db for volume %{public}s with flags %d", uuid, flags);
546 *output = CFBridgingRetain([database users:uuid ? [NSString stringWithUTF8String:uuid] : nil]);
547 return errAuthorizationSuccess;