@@ -63,60 +63,81 @@ class Keychain {
63
63
return mixins
64
64
}
65
65
66
- private static func migrateKey( oldAccount: String , newAccount: String , accessGroup: String ) {
67
-
66
+ private static func migrateKey(
67
+ oldAccount : String ,
68
+ oldAccessGroup : String ,
69
+ newAccount : String ,
70
+ newAccessGroup : String
71
+ ) {
68
72
let mixins = commonMixins ( )
69
73
74
+ // Step 1 of 4:
75
+ // - Read the OLD keychain item.
76
+ // - If it exists, then we need to migrate it to the new location.
70
77
var value : Data ? = nil
71
78
do {
72
79
value = try SystemKeychain . readItem (
73
80
account : oldAccount,
74
- accessGroup : accessGroup ,
81
+ accessGroup : oldAccessGroup ,
75
82
mixins : mixins
76
83
)
77
84
} catch {
78
- log. error ( " keychain.readItem(acct: \( oldAccount) , grp: \( accessGroup) ): error: \( error) " )
79
- return
85
+ log. error ( " keychain.read(acct: \( oldAccount) , grp: \( oldAccessGroup) ): error: \( error) " )
80
86
}
81
87
82
88
guard let value else {
89
+ // Nothing to migrate
83
90
return
84
91
}
85
92
86
- var dstExists = false
93
+ // Step 2 of 4:
94
+ // - Delete the NEW keychain item.
95
+ // - It shouldn't exist, but if it does it will cause an error on the next step.
96
+ //
97
+ // Important:
98
+ // Keychain items do NOT get cleared when an app is deleted on iOS.
99
+ // So it's possible, for example, that the lockingKey still exists from a previous install.
100
+ // If we don't overwrite this old value, then it will be out-of-sync with the SecurityFile.
87
101
do {
88
- dstExists = try SystemKeychain . itemExists (
102
+ try SystemKeychain . deleteItem (
89
103
account : newAccount,
90
- accessGroup : accessGroup,
91
- mixins : mixins
104
+ accessGroup : newAccessGroup
92
105
)
93
106
} catch {
94
- log. error ( " keychain.exists(acct: \( newAccount) , grp: \( accessGroup) ): error: \( error) " )
95
- return
107
+ log. error ( " keychain.delete(acct: \( newAccount) , grp: \( newAccessGroup) ): error: \( error) " )
96
108
}
97
109
98
- if dstExists {
99
- log. debug ( " keychain.delete: \( oldAccount) " )
100
- } else {
110
+ if oldAccessGroup == newAccessGroup {
101
111
log. debug ( " keychain.move: \( oldAccount) => \( newAccount) " )
102
-
103
- do {
104
- try SystemKeychain . addItem (
105
- value : value,
106
- account : newAccount,
107
- accessGroup : accessGroup,
108
- mixins : mixins
109
- )
110
- } catch {
111
- log. error ( " keychain.add(acct: \( newAccount) , grp: \( accessGroup) ): error: \( error) " )
112
- return
113
- }
112
+ } else {
113
+ log. debug ( " keychain.move: ( \( oldAccount) , \( oldAccessGroup) => ( \( newAccount) , \( newAccessGroup) ) " )
114
114
}
115
115
116
116
do {
117
- try SystemKeychain . deleteItem ( account: oldAccount, accessGroup: accessGroup)
117
+ // Step 3 of 4:
118
+ // - Copy the OLD keychain item to the NEW location.
119
+ // - If this step fails, we do NOT advance to step 4.
120
+ try SystemKeychain . addItem (
121
+ value : value,
122
+ account : newAccount,
123
+ accessGroup : newAccessGroup,
124
+ mixins : mixins
125
+ )
126
+ } catch {
127
+ log. error ( " keychain.add(acct: \( newAccount) , grp: \( newAccessGroup) ): error: \( error) " )
128
+ return // <- important safety mechanism
129
+ }
130
+
131
+ // Step 4 of 4:
132
+ // - Delete the OLD keychain item.
133
+ // - This prevents any duplicate migration attempts in the future.
134
+ do {
135
+ try SystemKeychain . deleteItem (
136
+ account : oldAccount,
137
+ accessGroup : oldAccessGroup
138
+ )
118
139
} catch {
119
- log. error ( " keychain.delete(acct: \( oldAccount) , grp: \( accessGroup ) ): error: \( error) " )
140
+ log. error ( " keychain.delete(acct: \( oldAccount) , grp: \( oldAccessGroup ) ): error: \( error) " )
120
141
}
121
142
}
122
143
@@ -198,66 +219,12 @@ class Keychain {
198
219
let oldAccessGroup = AccessGroup . appOnly. value
199
220
let newAccessGroup = AccessGroup . appAndExtensions. value
200
221
201
- var mixins = [ String: Any] ( )
202
- mixins [ kSecAttrAccessible as String ] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
203
-
204
- // Step 1 of 4:
205
- // - Read the OLD keychain item.
206
- // - If it exists, then we need to migrate it to the new location.
207
- var savedKey : SymmetricKey ? = nil
208
- do {
209
- savedKey = try SystemKeychain . readItem (
210
- account : account,
211
- accessGroup : oldAccessGroup, // <- old location
212
- mixins : mixins
213
- )
214
- } catch {
215
- log. error ( " keychain.read(account: keychain, group: nil): error: \( error) " )
216
- }
217
-
218
- if let lockingKey = savedKey {
219
- // The OLD keychain item exists, so we're going to migrate it.
220
-
221
- var migrated = false
222
- do {
223
- // Step 2 of 4:
224
- // - Delete the NEW keychain item.
225
- // - It shouldn't exist, but if it does it will cause an error on the next step.
226
- try SystemKeychain . deleteItem (
227
- account : account,
228
- accessGroup : newAccessGroup // <- new location
229
- )
230
- } catch {
231
- log. error ( " keychain.delete(account: keychain, group: shared): error: \( error) " )
232
- }
233
- do {
234
- // Step 3 of 4:
235
- // - Copy the OLD keychain item to the NEW location.
236
- // - If this step fails, an exception is thrown, and we do NOT advance to step 4.
237
- try SystemKeychain . storeItem (
238
- value : lockingKey,
239
- account : account,
240
- accessGroup : newAccessGroup, // <- new location
241
- mixins : mixins
242
- )
243
- migrated = true
244
-
245
- // Step 4 of 4:
246
- // - Finally, delete the OLD keychain item.
247
- // - This prevents any duplicate migration attempts in the future.
248
- try SystemKeychain . deleteItem (
249
- account : account,
250
- accessGroup : oldAccessGroup // <- old location
251
- )
252
-
253
- } catch {
254
- if !migrated {
255
- log. error ( " keychain.store(account: keychain, group: shared): error: \( error) " )
256
- } else {
257
- log. error ( " keychain.delete(account: keychain, group: private): error: \( error) " )
258
- }
259
- }
260
- }
222
+ migrateKey (
223
+ oldAccount: account,
224
+ oldAccessGroup: oldAccessGroup,
225
+ newAccount: account,
226
+ newAccessGroup: newAccessGroup
227
+ )
261
228
}
262
229
263
230
private static func performMigration_toBuild92(
@@ -281,10 +248,12 @@ class Keychain {
281
248
runWhenProtectedDataAvailable ( completionPublisher) {
282
249
283
250
for key in Key . allCases {
251
+ let accessGroup = key. accessGroup. value
284
252
self . migrateKey (
285
- oldAccount : key. deprecatedValue,
286
- newAccount : key. account ( KEYCHAIN_DEFAULT_ID) ,
287
- accessGroup : key. accessGroup. value
253
+ oldAccount : key. deprecatedValue,
254
+ oldAccessGroup : accessGroup,
255
+ newAccount : key. account ( KEYCHAIN_DEFAULT_ID) ,
256
+ newAccessGroup : accessGroup
288
257
)
289
258
}
290
259
}
@@ -332,10 +301,12 @@ class Keychain {
332
301
let newId = walletId. standardKeyId
333
302
334
303
for key in Key . allCases {
304
+ let accessGroup = key. accessGroup. value
335
305
migrateKey (
336
- oldAccount : key. account ( oldId) ,
337
- newAccount : key. account ( newId) ,
338
- accessGroup : key. accessGroup. value
306
+ oldAccount : key. account ( oldId) ,
307
+ oldAccessGroup : accessGroup,
308
+ newAccount : key. account ( newId) ,
309
+ newAccessGroup : accessGroup
339
310
)
340
311
}
341
312
}
0 commit comments