Skip to content

Commit 4ccb7f1

Browse files
committed
Refactor iOS access control and Keychain handling; add RN Swift module & ObjC bridge; update deps and lockfiles
- Adjust AccessControlResolver flags and security level (remove .userPresence from biometry flags, use .devicePasscode for device passcode, mark secureEnclave policy) - Disable Secure Enclave usage for generic password items in HybridSensitiveInfo (always false) and remove token handling from KeychainManager - Improve authentication context handling in KeychainManager (attach LAContext and localizedFallbackTitle to Sec query) - Add SensitiveInfo React Native Swift module and Objective-C extern bridge (SensitiveInfoModule.swift / .m) - Bump example and root devDependencies (babel, bob, monorepo-config, lefthook) and regenerate yarn.lock / example/Podfile.lock (SensitiveInfo -> 5.6.0)
1 parent 3decfdf commit 4ccb7f1

File tree

9 files changed

+589
-46
lines changed

9 files changed

+589
-46
lines changed

example/ios/Podfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2321,7 +2321,7 @@ PODS:
23212321
- React-perflogger (= 0.82.1)
23222322
- React-utils (= 0.82.1)
23232323
- SocketRocket
2324-
- SensitiveInfo (5.5.8):
2324+
- SensitiveInfo (5.6.0):
23252325
- boost
23262326
- DoubleConversion
23272327
- fast_float
@@ -2658,10 +2658,10 @@ SPEC CHECKSUMS:
26582658
ReactAppDependencyProvider: a45ef34bb22dc1c9b2ac1f74167d9a28af961176
26592659
ReactCodegen: 878add6c7d8ff8cea87697c44d29c03b79b6f2d9
26602660
ReactCommon: 804dc80944fa90b86800b43c871742ec005ca424
2661-
SensitiveInfo: 43398c28c0f14495b190af5d397a889e38799c82
2661+
SensitiveInfo: e5d1904ea8b2d848a593d01bfd7c3beac5cd304b
26622662
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
26632663
Yoga: 689c8e04277f3ad631e60fe2a08e41d411daf8eb
26642664

26652665
PODFILE CHECKSUM: 20594f1555ab577d46d74105f279caee68095c24
26662666

2667-
COCOAPODS: 1.16.2
2667+
COCOAPODS: 1.15.2

example/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
"react-native": "0.82.1"
1515
},
1616
"devDependencies": {
17-
"@babel/core": "^7.28.4",
18-
"@babel/preset-env": "^7.28.3",
17+
"@babel/core": "^7.28.5",
18+
"@babel/preset-env": "^7.28.5",
1919
"@babel/runtime": "^7.28.4",
2020
"@react-native-community/cli": "20.0.2",
2121
"@react-native-community/cli-platform-android": "20.0.2",
@@ -24,8 +24,8 @@
2424
"@react-native/metro-config": "0.82.1",
2525
"@react-native/typescript-config": "0.82.1",
2626
"@types/react": "19.1.1",
27-
"react-native-builder-bob": "^0.40.13",
28-
"react-native-monorepo-config": "^0.2.2"
27+
"react-native-builder-bob": "^0.40.14",
28+
"react-native-monorepo-config": "^0.3.0"
2929
},
3030
"engines": {
3131
"node": ">=22"

ios/AccessControlResolver.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,19 @@ struct AccessControlResolver {
4242
static func resolve(_ preference: String?) -> AccessControlConfig {
4343
switch preference {
4444
case "secureEnclaveBiometry":
45-
// Strongest: Secure Enclave + Biometric
45+
// Strongest: Secure Enclave + Biometric (current set)
4646
return AccessControlConfig(
4747
preference: "secureEnclaveBiometry",
48-
flags: [.biometryCurrentSet, .userPresence],
48+
flags: [.biometryCurrentSet],
4949
secureEnclave: true,
5050
biometric: true,
51-
securityLevel: "biometry"
51+
securityLevel: "secureEnclave"
5252
)
5353

5454
case "biometryCurrentSet":
5555
return AccessControlConfig(
5656
preference: "biometryCurrentSet",
57-
flags: [.biometryCurrentSet, .userPresence],
57+
flags: [.biometryCurrentSet],
5858
secureEnclave: false,
5959
biometric: true,
6060
securityLevel: "biometry"
@@ -63,7 +63,7 @@ struct AccessControlResolver {
6363
case "biometryAny":
6464
return AccessControlConfig(
6565
preference: "biometryAny",
66-
flags: [.biometryAny, .userPresence],
66+
flags: [.biometryAny],
6767
secureEnclave: false,
6868
biometric: true,
6969
securityLevel: "biometry"
@@ -72,7 +72,7 @@ struct AccessControlResolver {
7272
case "devicePasscode":
7373
return AccessControlConfig(
7474
preference: "devicePasscode",
75-
flags: [.userPresence],
75+
flags: [.devicePasscode],
7676
secureEnclave: false,
7777
biometric: false,
7878
securityLevel: "deviceCredential"

ios/HybridSensitiveInfo.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,15 +159,15 @@ struct HybridSensitiveInfo {
159159
try await ensureAuthenticationIfNeeded(config: config, prompt: authenticationPrompt)
160160

161161
let now = currentTimestamp()
162-
let secureEnclaveEnabled = config.secureEnclave && isSecureEnclaveAvailable()
162+
// Generic password items cannot opt into Secure Enclave directly; Keychain falls back to biometry.
163+
let secureEnclaveEnabled = false
163164
let metadata = config.metadata(timestamp: now, secureEnclaveActive: secureEnclaveEnabled)
164165

165166
let keychain = keychain(for: service)
166167
try keychain.set(
167168
key: key,
168169
value: value,
169170
accessControl: control,
170-
useSecureEnclave: secureEnclaveEnabled,
171171
metadata: metadata
172172
)
173173

ios/KeychainManager.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Security
22
import Foundation
3+
import LocalAuthentication
34

45
/**
56
* KeychainManager.swift
@@ -51,7 +52,6 @@ struct KeychainManager {
5152
* - Parameter key: Unique key within service
5253
* - Parameter value: Value to store (encrypted by Keychain)
5354
* - Parameter accessControl: Access control policy (nil for software-only)
54-
* - Parameter useSecureEnclave: Requests Secure Enclave token (if available)
5555
* - Parameter metadata: Additional metadata stored alongside value
5656
* - Throws: KeychainError if operation fails
5757
*
@@ -70,7 +70,6 @@ struct KeychainManager {
7070
key: String,
7171
value: String,
7272
accessControl: SecAccessControl?,
73-
useSecureEnclave: Bool,
7473
metadata: KeychainItemMetadata?
7574
) throws {
7675
let valueData = value.data(using: .utf8) ?? Data()
@@ -86,10 +85,6 @@ struct KeychainManager {
8685
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly
8786
}
8887

89-
if useSecureEnclave {
90-
addQuery[kSecAttrTokenID as String] = kSecAttrTokenIDSecureEnclave
91-
}
92-
9388
if let encodedMetadata = metadata?.encoded() {
9489
addQuery[kSecAttrGeneric as String] = encodedMetadata
9590
}
@@ -158,6 +153,9 @@ struct KeychainManager {
158153
query[kSecUseDataProtectionKeychain as String] = true
159154

160155
if let prompt = prompt {
156+
let context = LAContext()
157+
context.localizedFallbackTitle = prompt.cancel
158+
query[kSecUseAuthenticationContext as String] = context
161159
query[kSecUseOperationPrompt as String] = prompt.localizedReason
162160
}
163161

ios/SensitiveInfoModule.m

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#import <React/RCTBridgeModule.h>
2+
3+
@interface RCT_EXTERN_MODULE(SensitiveInfo, NSObject)
4+
5+
RCT_EXTERN_METHOD(setItem:(NSString *)key
6+
value:(NSString *)value
7+
options:(NSDictionary *)options
8+
resolver:(RCTPromiseResolveBlock)resolver
9+
rejecter:(RCTPromiseRejectBlock)rejecter)
10+
11+
RCT_EXTERN_METHOD(getItem:(NSString *)key
12+
options:(NSDictionary *)options
13+
resolver:(RCTPromiseResolveBlock)resolver
14+
rejecter:(RCTPromiseRejectBlock)rejecter)
15+
16+
RCT_EXTERN_METHOD(hasItem:(NSString *)key
17+
options:(NSDictionary *)options
18+
resolver:(RCTPromiseResolveBlock)resolver
19+
rejecter:(RCTPromiseRejectBlock)rejecter)
20+
21+
RCT_EXTERN_METHOD(deleteItem:(NSString *)key
22+
options:(NSDictionary *)options
23+
resolver:(RCTPromiseResolveBlock)resolver
24+
rejecter:(RCTPromiseRejectBlock)rejecter)
25+
26+
RCT_EXTERN_METHOD(getAllItems:(NSDictionary *)options
27+
resolver:(RCTPromiseResolveBlock)resolver
28+
rejecter:(RCTPromiseRejectBlock)rejecter)
29+
30+
RCT_EXTERN_METHOD(clearService:(NSDictionary *)options
31+
resolver:(RCTPromiseResolveBlock)resolver
32+
rejecter:(RCTPromiseRejectBlock)rejecter)
33+
34+
RCT_EXTERN_METHOD(getSupportedSecurityLevels:(RCTPromiseResolveBlock)resolver
35+
rejecter:(RCTPromiseRejectBlock)rejecter)
36+
37+
@end

0 commit comments

Comments
 (0)