Skip to content

MASTG-DEMO-0043: Uses of kSecAccessControlUserPresence with r2

Download MASTG-DEMO-0043 IPA Open MASTG-DEMO-0043 Folder Build MASTG-DEMO-0043 IPA

Sample

The following sample correctly uses the Keychain API for local authentication (SecAccessControlCreateWithFlags) but it uses the kSecAccessControlUserPresence flag which allows fallback to device passcode when biometric authentication fails or isn't yet configured.

MastgTest.swift
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import SwiftUI
import LocalAuthentication
import Security

struct MastgTest {

  static func mastgTest(completion: @escaping (String) -> Void) {
    let account = "com.mastg.sectoken"
    let tokenData = "8767086b9f6f976g-a8df76".data(using: .utf8)!

    // 1. Store the token in the Keychain with ACL flags
    // 1a. Create an accesscontrol object requiring user presence
    guard let accessControl = SecAccessControlCreateWithFlags(
      nil,
      kSecAttrAccessibleWhenUnlocked,
      .userPresence,
      nil
    ) else {
      completion("❌ Failed to create access control")
      return
    }

    // 1b. Build your additem query
    // Optional: you may provide a customized context to alter the default config, e.g. to set a "reuse duration".
    // See https://developer.apple.com/documentation/localauthentication/accessing-keychain-items-with-face-id-or-touch-id#Optionally-Provide-a-Customized-Context
    // Keychain services automatically makes use of the LocalAuthentication framework, even if you don't provide one.
    //
    //  let context = LAContext()
    //  context.touchIDAuthenticationAllowableReuseDuration = 10
    let storeQuery: [String: Any] = [
      kSecClass as String:          kSecClassGenericPassword,
      kSecAttrAccount as String:    account,
      kSecValueData as String:      tokenData,
      kSecAttrAccessControl as String: accessControl,
      // kSecUseAuthenticationContext as String: context,
    ]

    // Before adding, we delete any existing item
    SecItemDelete(storeQuery as CFDictionary)
    let storeStatus = SecItemAdd(storeQuery as CFDictionary, nil)
    guard storeStatus == errSecSuccess else {
      completion("❌ Failed to store token in Keychain (status \(storeStatus))")
      return
    }

    // 2. Now let's retrieve the token
    // Optional: you may provide a context with a localized reason.
    // See https://developer.apple.com/documentation/localauthentication/accessing-keychain-items-with-face-id-or-touch-id#Provide-a-Prompt-When-Reading-the-Item
    // Keychain services will use LAContext to prompt the user even if you don't provide one.
    //
    // let context = LAContext()
    // context.localizedReason = "Access your token from the keychain"
    let fetchQuery: [String: Any] = [
      kSecClass as String:         kSecClassGenericPassword,
      kSecAttrAccount as String:   account,
      kSecReturnData as String:    true,
      kSecMatchLimit as String:    kSecMatchLimitOne,
      //kSecUseAuthenticationContext as String: context,
    ]

    var result: CFTypeRef?
    let fetchStatus = SecItemCopyMatching(fetchQuery as CFDictionary, &result)

    if fetchStatus == errSecSuccess,
       let data = result as? Data,
       let token = String(data: data, encoding: .utf8) {
      completion("✅ Retrieved token: \(token)")
    } else {
      completion("❌ Authentication failed or token inaccessible (status \(fetchStatus))")
    }
  }
}

Steps

  1. Unzip the app package and locate the main binary file ( Exploring the App Package), which in this case is ./Payload/MASTestApp.app/MASTestApp.
  2. Run run.sh.
biometricAuthenticationFallback.r2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
e asm.bytes=false
e scr.color=false
e asm.var=false

?e Print xrefs to \'Run analysis\"
aaa

?e Print xrefs to \'SecAccessControlCreateWithFlags\"
axt @ sym.imp.SecAccessControlCreateWithFlags

?e

?e Print disassembly around \"SecAccessControlCreateWithFlags\" in the function
pdf @  0x100004194 | grep -C 5 "SecAccessControlCreateWithFlags"
run.sh
1
2
#!/bin/bash
r2 -q -i biometricAuthenticationFallback.r2 -A MASTestApp > output.asm

Observation

output.asm
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Print xrefs to 'Run analysis"
Print xrefs to 'SecAccessControlCreateWithFlags"
sym.MASTestApp.MastgTest.createAccessControl.Sec.Ref.d_n 0x100004194 [CALL:--x] bl sym.imp.SecAccessControlCreateWithFlags

Print disassembly around "SecAccessControlCreateWithFlags" in the function
           0x100004180      mov x19, x0
           0x100004184      add x3, sp, 0x10
           0x100004188      mov x0, 0
           0x10000418c      mov x1, x19
           0x100004190      mov w2, 1
           0x100004194      bl sym.imp.SecAccessControlCreateWithFlags
       ┌─< 0x100004198      cbz x0, 0x1000041ac
          0x10000419c      mov x20, x0
          0x1000041a0      mov x0, x19                               ; void *instance
          0x1000041a4      bl sym.imp.objc_release                   ; void objc_release(void *instance)
      ┌──< 0x1000041a8      b 0x100004310

The output reveals the use of SecAccessControlCreateWithFlags(allocator, protection, flags, error) in the app. In this demo, we focus on the flags argument because it specifies the Access Control. flags is a third argument of the function, so it's at x2/w2 register. By looking at the output, we can see that w2 register holds value of 1.

mov w2, 1
bl sym.imp.SecAccessControlCreateWithFlags

The flags is an enum of SecAccessControlCreateFlags. 1 corresponds with kSecAccessControlUserPresence (see LAPublicDefines.h). This means that the app invokes SecAccessControlCreateWithFlags(..., kSecAccessControlUserPresence), which means it falls back to device's passcode authentication.

Evaluation

The test fails because the output shows references to biometric verification that falls back to device's passcode authentication, specifically kSecAccessControlUserPresence.

Since this data requires protection with biometrics, it's recommended to use the kSecAccessControlBiometryCurrentSet or kSecAccessControlBiometryAny flags instead, being kSecAccessControlBiometryCurrentSet the most secure.