@@ -60,8 +60,8 @@ struct KeychainQueryParameters {
60
60
/// `kSecUseAuthenticationUI` (iOS/macOS): Controls how authentication UI is presented during secure operations.
61
61
var authenticationUIBehavior : String ?
62
62
63
- /// `kSecAttrAccessControl ` (iOS/macOS): Specifies access control settings (e.g., biometrics, passcode).
64
- var accessControlSettings : SecAccessControl ?
63
+ /// `accessControlFlags ` (iOS/macOS): Specifies access control settings (e.g., biometrics, passcode).
64
+ var accessControlFlags : String ?
65
65
}
66
66
67
67
/// Represents the response from a keychain operation.
@@ -88,6 +88,52 @@ class FlutterSecureStorage {
88
88
default : return kSecAttrAccessibleWhenUnlocked
89
89
}
90
90
}
91
+
92
+ /// Parses a string of comma-separated access control flags into SecAccessControlCreateFlags.
93
+ private func parseAccessControlFlags( _ flagString: String ? ) -> SecAccessControlCreateFlags {
94
+ guard let flagString = flagString else { return [ ] }
95
+ var flags : SecAccessControlCreateFlags = [ ]
96
+ let flagList = flagString. split ( separator: " , " ) . map { $0. trimmingCharacters ( in: . whitespaces) }
97
+ for dirtyFlag in flagList {
98
+ let flag = dirtyFlag. trimmingCharacters ( in: CharacterSet ( charactersIn: " [] " ) )
99
+
100
+ switch flag {
101
+ case " userPresence " :
102
+ flags. insert ( . userPresence)
103
+ case " biometryAny " :
104
+ flags. insert ( . biometryAny)
105
+ case " biometryCurrentSet " :
106
+ flags. insert ( . biometryCurrentSet)
107
+ case " devicePasscode " :
108
+ flags. insert ( . devicePasscode)
109
+ case " or " :
110
+ flags. insert ( . or)
111
+ case " and " :
112
+ flags. insert ( . and)
113
+ case " privateKeyUsage " :
114
+ flags. insert ( . privateKeyUsage)
115
+ case " applicationPassword " :
116
+ flags. insert ( . applicationPassword)
117
+ default :
118
+ continue
119
+ }
120
+ }
121
+ return flags
122
+ }
123
+
124
+ /// Creates an access control object based on the provided parameters.
125
+ private func createAccessControl( params: KeychainQueryParameters ) -> SecAccessControl ? {
126
+ guard let accessibilityLevel = params. accessibilityLevel else { return nil }
127
+ let protection = parseAccessibleAttr ( accessibilityLevel)
128
+ let flags = parseAccessControlFlags ( params. accessControlFlags)
129
+ var error : Unmanaged < CFError > ?
130
+ let accessControl = SecAccessControlCreateWithFlags ( nil , protection, flags, & error)
131
+ if let error = error? . takeRetainedValue ( ) {
132
+ print ( " Error creating access control: \( error. localizedDescription) " )
133
+ return nil
134
+ }
135
+ return accessControl
136
+ }
91
137
92
138
/// Constructs a keychain query dictionary from the given parameters.
93
139
private func baseQuery( from params: KeychainQueryParameters ) -> [ CFString : Any ] {
@@ -108,14 +154,6 @@ class FlutterSecureStorage {
108
154
query [ kSecAttrService] = service
109
155
}
110
156
111
- if let isSynchronizable = params. isSynchronizable {
112
- query [ kSecAttrSynchronizable] = isSynchronizable
113
- }
114
-
115
- if let accessibilityLevel = params. accessibilityLevel {
116
- query [ kSecAttrAccessible] = parseAccessibleAttr ( accessibilityLevel)
117
- }
118
-
119
157
if let shouldReturnData = params. shouldReturnData {
120
158
query [ kSecReturnData] = shouldReturnData
121
159
}
@@ -152,8 +190,15 @@ class FlutterSecureStorage {
152
190
query [ kSecUseAuthenticationUI] = authenticationUIBehavior
153
191
}
154
192
155
- if let accessControlSettings = params. accessControlSettings {
156
- query [ kSecAttrAccessControl] = accessControlSettings
193
+ if let accessControl = createAccessControl ( params: params) {
194
+ query [ kSecAttrAccessControl] = accessControl
195
+ } else {
196
+ if let accessibilityLevel = params. accessibilityLevel {
197
+ query [ kSecAttrAccessible] = parseAccessibleAttr ( accessibilityLevel)
198
+ }
199
+ if let isSynchronizable = params. isSynchronizable {
200
+ query [ kSecAttrSynchronizable] = isSynchronizable
201
+ }
157
202
}
158
203
159
204
#if os(macOS)
@@ -172,11 +217,6 @@ class FlutterSecureStorage {
172
217
}
173
218
174
219
private func validateQueryParameters( params: KeychainQueryParameters ) throws {
175
- // Accessibility and access control
176
- if params. accessibilityLevel != nil , params. accessControlSettings != nil {
177
- throw OSSecError ( status: errSecParam, message: " Cannot use kSecAttrAccessible and kSecAttrAccessControl together. " )
178
- }
179
-
180
220
// Match limit
181
221
if params. resultLimit == 1 , params. shouldReturnData == true {
182
222
throw OSSecError ( status: errSecParam, message: " Cannot use kSecMatchLimitAll when expecting a single result with kSecReturnData. " )
0 commit comments