Skip to content

Commit 358f03b

Browse files
committed
(ios) Bug fix: upgrade mechanism had minor bug that could affect developers when switching back-and-forth between old and new versions of the app.
1 parent 8a42ae1 commit 358f03b

File tree

1 file changed

+65
-94
lines changed

1 file changed

+65
-94
lines changed

phoenix-ios/phoenix-ios/security/Keychain.swift

Lines changed: 65 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -63,60 +63,81 @@ class Keychain {
6363
return mixins
6464
}
6565

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+
) {
6872
let mixins = commonMixins()
6973

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.
7077
var value: Data? = nil
7178
do {
7279
value = try SystemKeychain.readItem(
7380
account : oldAccount,
74-
accessGroup : accessGroup,
81+
accessGroup : oldAccessGroup,
7582
mixins : mixins
7683
)
7784
} 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)")
8086
}
8187

8288
guard let value else {
89+
// Nothing to migrate
8390
return
8491
}
8592

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.
87101
do {
88-
dstExists = try SystemKeychain.itemExists(
102+
try SystemKeychain.deleteItem(
89103
account : newAccount,
90-
accessGroup : accessGroup,
91-
mixins : mixins
104+
accessGroup : newAccessGroup
92105
)
93106
} 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)")
96108
}
97109

98-
if dstExists {
99-
log.debug("keychain.delete: \(oldAccount)")
100-
} else {
110+
if oldAccessGroup == newAccessGroup {
101111
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))")
114114
}
115115

116116
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+
)
118139
} catch {
119-
log.error("keychain.delete(acct: \(oldAccount), grp: \(accessGroup)): error: \(error)")
140+
log.error("keychain.delete(acct: \(oldAccount), grp: \(oldAccessGroup)): error: \(error)")
120141
}
121142
}
122143

@@ -198,66 +219,12 @@ class Keychain {
198219
let oldAccessGroup = AccessGroup.appOnly.value
199220
let newAccessGroup = AccessGroup.appAndExtensions.value
200221

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+
)
261228
}
262229

263230
private static func performMigration_toBuild92(
@@ -281,10 +248,12 @@ class Keychain {
281248
runWhenProtectedDataAvailable(completionPublisher) {
282249

283250
for key in Key.allCases {
251+
let accessGroup = key.accessGroup.value
284252
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
288257
)
289258
}
290259
}
@@ -332,10 +301,12 @@ class Keychain {
332301
let newId = walletId.standardKeyId
333302

334303
for key in Key.allCases {
304+
let accessGroup = key.accessGroup.value
335305
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
339310
)
340311
}
341312
}

0 commit comments

Comments
 (0)