Created
January 7, 2026 14:05
-
-
Save ChiChou/e1a6760745229d525a890dde475c014c to your computer and use it in GitHub Desktop.
iOS KeyChain ACL parser. Code written with the help of AI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #import <Foundation/Foundation.h> | |
| #import <Security/Security.h> | |
| #import "Priv.h" | |
| #define kACMKeyAclParamBioCatacombUUID "pbioc" | |
| #define kACMKeyAclConstraintBio "cbio" | |
| #define kACMKeyAclConstraintPolicy "cpo" | |
| #define kACMKeyAclConstraintUserPasscode "cup" | |
| #define kACMKeyAclConstraintWatch "cwtch" | |
| #define kACMPolicyDeviceOwnerAuthentication "DeviceOwnerAuthentication" | |
| #define kAKSKeyOpDecrypt "od" | |
| #define kAKSKeyOpEncrypt "oe" | |
| #define kAKSKeyOpDelete "odel" | |
| #define kAKSKeyOpComputeKey "ock" | |
| #define kAKSKeyOpSign "osgn" | |
| #define kAKSKeyOpAttest "oa" | |
| #define kACMKeyAclParamKofN "pkofn" | |
| #define kAKSKeyOpKEMDecapsulate "okd" | |
| void dump(SecAccessControlRef r) { | |
| if (!r) return; | |
| NSMutableArray *flags = [NSMutableArray array]; | |
| // 1. Check Application Password (stored at the top level as 'prp') | |
| if (SecAccessControlGetRequirePassword(r)) { | |
| [flags addObject:@"kSecAccessControlApplicationPassword"]; | |
| } | |
| SecAccessConstraintRef c = SecAccessControlGetConstraints(r); | |
| if (c) { | |
| NSDictionary *dict = (__bridge NSDictionary *)c; | |
| // 2. Determine which operation key to inspect. | |
| // If PrivateKeyUsage is active, constraints are duplicated across osgn, ock, and okd. | |
| // We can check 'osgn' as the representative for PrivateKeyUsage constraints. | |
| BOOL isPrivateKey = (dict[@kAKSKeyOpAttest] != nil); | |
| if (isPrivateKey) { | |
| [flags addObject:@"kSecAccessControlPrivateKeyUsage"]; | |
| } | |
| // Identify the container for the actual logic flags (UserPresence, Bio, etc.) | |
| // Case A: Standard (Decrypt 'od') | |
| // Case B: PrivateKey (Sign 'osgn') | |
| // Case C: Single Constraint (Top level 'cpo', 'cup', etc. - though the C code usually nests them) | |
| NSDictionary *opDict = dict[@kAKSKeyOpDecrypt]; | |
| if (isPrivateKey) { | |
| opDict = dict[@kAKSKeyOpSign]; | |
| } | |
| // If no nested opDict, the constraints might be at the top level (constraints_count == 1 case) | |
| if (!opDict || ![opDict isKindOfClass:[NSDictionary class]]) { | |
| opDict = dict; | |
| } | |
| // 3. Parse Constraints from the identified dictionary | |
| // Device Owner Authentication / UserPresence | |
| NSString *policy = opDict[@kACMKeyAclConstraintPolicy]; | |
| if ([policy isEqualToString:@kACMPolicyDeviceOwnerAuthentication]) { | |
| [flags addObject:@"kSecAccessControlUserPresence"]; | |
| } else if (policy) { | |
| [flags addObject:[NSString stringWithFormat:@"Policy: %@", policy]]; | |
| } | |
| // Passcode | |
| if (opDict[@kACMKeyAclConstraintUserPasscode]) { | |
| [flags addObject:@"kSecAccessControlDevicePasscode"]; | |
| } | |
| // Biometrics | |
| NSDictionary *cbio = opDict[@kACMKeyAclConstraintBio]; | |
| if (cbio) { | |
| // BiometryCurrentSet has both pbioc (catacomb) and pbioh (hash/history) | |
| // BiometryAny usually just has pbioc | |
| [flags addObject:(cbio.count > 1 ? @"kSecAccessControlBiometryCurrentSet" : @"kSecAccessControlBiometryAny")]; | |
| } | |
| // Companion (Watch) | |
| if (opDict[@kACMKeyAclConstraintWatch]) { | |
| [flags addObject:@"kSecAccessControlCompanion"]; | |
| } | |
| // Logic Operators (pkofn) | |
| NSNumber *pkofn = opDict[@kACMKeyAclParamKofN]; | |
| if (pkofn) { | |
| // k-of-n: if k=1, it's an OR. If k=N, it's an AND. | |
| if (pkofn.intValue == 1) { | |
| [flags addObject:@"kSecAccessControlOr"]; | |
| } else { | |
| [flags addObject:@"kSecAccessControlAnd"]; | |
| } | |
| } | |
| CFShow(c); | |
| } | |
| NSLog(@"parsed flags: %@", flags); | |
| CFRelease(r); | |
| } | |
| int main(int argc, const char * argv[]) { | |
| CFErrorRef error = NULL; | |
| CFAllocatorRef alloc = kCFAllocatorDefault; | |
| CFTypeRef protection = kSecAttrAccessibleAfterFirstUnlock; | |
| // 1. Basic Presence & Biometrics | |
| @autoreleasepool { | |
| puts("--- kSecAccessControlUserPresence ---"); | |
| dump(SecAccessControlCreateWithFlags(alloc, protection, kSecAccessControlUserPresence, &error)); | |
| } | |
| @autoreleasepool { | |
| puts("--- kSecAccessControlBiometryAny ---"); | |
| dump(SecAccessControlCreateWithFlags(alloc, protection, kSecAccessControlBiometryAny, &error)); | |
| } | |
| @autoreleasepool { | |
| puts("--- kSecAccessControlBiometryCurrentSet ---"); | |
| dump(SecAccessControlCreateWithFlags(alloc, protection, kSecAccessControlBiometryCurrentSet, &error)); | |
| } | |
| // 2. Device & Companion | |
| @autoreleasepool { | |
| puts("--- kSecAccessControlDevicePasscode ---"); | |
| dump(SecAccessControlCreateWithFlags(alloc, protection, kSecAccessControlDevicePasscode, &error)); | |
| } | |
| @autoreleasepool { | |
| puts("--- kSecAccessControlCompanion (Watch) ---"); | |
| dump(SecAccessControlCreateWithFlags(alloc, protection, kSecAccessControlCompanion, &error)); | |
| } | |
| // 3. Logic Operators (Or / And) | |
| @autoreleasepool { | |
| puts("--- kSecAccessControlUserPresence | kSecAccessControlOr (Standard UserPresence) ---"); | |
| dump(SecAccessControlCreateWithFlags(alloc, protection, kSecAccessControlDevicePasscode | kSecAccessControlBiometryAny | kSecAccessControlOr, &error)); | |
| } | |
| @autoreleasepool { | |
| puts("--- kSecAccessControlDevicePasscode | kSecAccessControlBiometryAny | kSecAccessControlAnd ---"); | |
| dump(SecAccessControlCreateWithFlags(alloc, protection, kSecAccessControlDevicePasscode | kSecAccessControlBiometryAny | kSecAccessControlAnd, &error)); | |
| } | |
| // 4. Usage Flags | |
| @autoreleasepool { | |
| puts("--- kSecAccessControlPrivateKeyUsage ---"); | |
| dump(SecAccessControlCreateWithFlags(alloc, protection, kSecAccessControlPrivateKeyUsage, &error)); | |
| } | |
| @autoreleasepool { | |
| puts("--- kSecAccessControlApplicationPassword ---"); | |
| dump(SecAccessControlCreateWithFlags(alloc, protection, kSecAccessControlApplicationPassword, &error)); | |
| } | |
| // 5. Complex Combinations | |
| @autoreleasepool { | |
| puts("--- Combined: PrivateKeyUsage | BiometryAny | DevicePasscode | And ---"); | |
| dump(SecAccessControlCreateWithFlags(alloc, protection, | |
| kSecAccessControlPrivateKeyUsage | kSecAccessControlBiometryAny | kSecAccessControlDevicePasscode | kSecAccessControlAnd, &error)); | |
| } | |
| return EXIT_SUCCESS; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment