importSwiftUIimportLocalAuthenticationimportSecuritystructMastgTest{staticfuncmastgTest(completion:@escaping(String)->Void){letaccount="com.mastg.sectoken"lettokenData="8767086b9f6f976g-a8df76".data(using:.utf8)!//1.StorethetokenintheKeychainwithACLflags//1a.Createanaccess‐controlobjectrequiringuserpresenceguardletaccessControl=SecAccessControlCreateWithFlags(nil,kSecAttrAccessibleWhenUnlocked,.userPresence,nil)else{completion("❌ Failed to create access control")return}//1b.Buildyouradd‐itemquery//Optional:youmayprovideacustomizedcontexttoalterthedefaultconfig,e.g.toseta"reuse duration".//Seehttps://developer.apple.com/documentation/localauthentication/accessing-keychain-items-with-face-id-or-touch-id#Optionally-Provide-a-Customized-Context//KeychainservicesautomaticallymakesuseoftheLocalAuthenticationframework,evenifyoudon't provide one.////letcontext=LAContext()//context.touchIDAuthenticationAllowableReuseDuration=10letstoreQuery:[String:Any]=[kSecClassasString:kSecClassGenericPassword,kSecAttrAccountasString:account,kSecValueDataasString:tokenData,kSecAttrAccessControlasString:accessControl,//kSecUseAuthenticationContextasString:context,]//Beforeadding,wedeleteanyexistingitemSecItemDelete(storeQueryasCFDictionary)letstoreStatus=SecItemAdd(storeQueryasCFDictionary,nil)guardstoreStatus==errSecSuccesselse{completion("❌ Failed to store token in Keychain (status \(storeStatus))")return}//2.Nowlet's retrieve the token//Optional:youmayprovideacontextwithalocalizedreason.//Seehttps://developer.apple.com/documentation/localauthentication/accessing-keychain-items-with-face-id-or-touch-id#Provide-a-Prompt-When-Reading-the-Item//KeychainserviceswilluseLAContexttoprompttheuserevenifyoudon't provide one.////letcontext=LAContext()//context.localizedReason="Access your token from the keychain"letfetchQuery:[String:Any]=[kSecClassasString:kSecClassGenericPassword,kSecAttrAccountasString:account,kSecReturnDataasString:true,kSecMatchLimitasString:kSecMatchLimitOne,//kSecUseAuthenticationContextasString:context,]varresult:CFTypeRef?letfetchStatus=SecItemCopyMatching(fetchQueryasCFDictionary,&result)iffetchStatus==errSecSuccess,letdata=resultas?Data,lettoken=String(data:data,encoding:.utf8){completion("✅ Retrieved token: \(token)")}else{completion("❌ Authentication failed or token inaccessible (status \(fetchStatus))")}}}
constAccessControlFlags={kSecAccessControlUserPresence:1<<0,kSecAccessControlBiometryAny:1<<1,kSecAccessControlBiometryCurrentSet:1<<3,kSecAccessControlDevicePasscode:1<<4,kSecAccessControlWatch:1<<5,kSecAccessControlOr:1<<14,kSecAccessControlAnd:1<<15,kSecAccessControlPrivateKeyUsage:1<<30,kSecAccessControlApplicationPassword:1<<31,};Interceptor.attach(Module.getExportByName(null,'SecAccessControlCreateWithFlags'),{/* func SecAccessControlCreateWithFlags( _ allocator: CFAllocator?, _ protection: CFTypeRef, _ flags: SecAccessControlCreateFlags, _ error: UnsafeMutablePointer<Unmanaged<CFError>?>? ) -> SecAccessControl? */onEnter(args){constflags=args[2]constflags_description=parseAccessControlFlags(flags)console.log(`\SecAccessControlCreateWithFlags(..., 0x${flags.toString(16)}) called with ${flags_description}\n`)// Use an arrow function so that `this` remains the same as in onEnterconstprintBacktrace=(maxLines=8)=>{console.log("\nBacktrace:");letbacktrace=Thread.backtrace(this.context,Backtracer.ACCURATE).map(DebugSymbol.fromAddress);for(leti=0;i<Math.min(maxLines,backtrace.length);i++){console.log(backtrace[i]);}}printBacktrace();}});functionparseAccessControlFlags(value){constresult=[];for(const[name,bit]ofObject.entries(AccessControlFlags)){if((value&bit)===bit){result.push(name);}}returnresult;}
SecAccessControlCreateWithFlags(...,0x1)calledwithkSecAccessControlUserPresenceBacktrace:0x102cc0198MASTestApp!specializedstaticMastgTest.createAccessControl()0x102cc04b0MASTestApp!specializedstaticMastgTest.storeTokenInKeychain(secretToken:)0x102cc1b98MASTestApp!closure#1 in closure #1 in closure #1 in ContentView.body.getter0x19908bc54SwiftUI!partialapplyforclosure#1 in closure #2 in ContextMenuBridge.contextMenuInteraction(_:willPerformPreviewActionForMenuWith:animator:)0x198f986d0SwiftUI!partialapplyforspecializedthunkfor@callee_guaranteed()->(@outA,@error@ownedError)0x1996657c4SwiftUI!specializedstaticMainActor.assumeIsolated<A>(_:file:line:)0x1996321a8SwiftUI!ButtonAction.callAsFunction()0x198b14c2cSwiftUI!partialapplyforimplicitclosure#2 in implicit closure #1 in PlatformItemListButtonStyle.makeBody(configuration:)
The output reveals the use of SecAccessControlCreateWithFlags in the app and lists all used flags.
The test fails because the output shows the runtime use of SecAccessControlCreateWithFlags(..., kSecAccessControlUserPresence) which allows for a fallback to passcode authentication.