Skip to content

Commit 2686232

Browse files
committed
feat(Auth) WIP Keychain Sharing
feat(Auth): WIP New method for keychain migration feat(Auth): WIP Keychain, use userDefaults to store access group Fix formatting and import Make access group optional for AWSCognitoSecureStoragePreferences Allow customers to specify if they want migration to happen Make secureStoragePreferences non-optional with default value Make AccessGroup an enum Integration tests added for keychain sharing Added MockLegacyStore _getAll() stub Fix yet another MockKeychainStore stub Fix auth integration tests for watchOS
1 parent 0d15595 commit 2686232

File tree

16 files changed

+592
-18
lines changed

16 files changed

+592
-18
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
10+
public enum AccessGroup {
11+
case named(String, migrateKeychainItemsOfUserSession: Bool)
12+
case unnamed(migrateKeychainItemsOfUserSession: Bool)
13+
14+
public init(name: String?, migrateKeychainItemsOfUserSession: Bool = false) {
15+
if let name = name {
16+
self = .named(name, migrateKeychainItemsOfUserSession: migrateKeychainItemsOfUserSession)
17+
} else {
18+
self = .unnamed(migrateKeychainItemsOfUserSession: migrateKeychainItemsOfUserSession)
19+
}
20+
}
21+
}

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin+Configure.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,21 @@ extension AWSCognitoAuthPlugin {
177177
}
178178

179179
private func makeCredentialStore() -> AmplifyAuthCredentialStoreBehavior {
180-
AWSCognitoAuthCredentialStore(authConfiguration: authConfiguration)
180+
let accessGroupName: String?
181+
let shouldMigrate: Bool
182+
switch secureStoragePreferences?.accessGroup {
183+
case .named(let name, let migrateKeychainItemsOfUserSession):
184+
accessGroupName = name
185+
shouldMigrate = migrateKeychainItemsOfUserSession
186+
case .unnamed(let migrateKeychainItemsOfUserSession):
187+
accessGroupName = nil
188+
shouldMigrate = migrateKeychainItemsOfUserSession
189+
default:
190+
accessGroupName = nil
191+
shouldMigrate = false
192+
}
193+
return AWSCognitoAuthCredentialStore(authConfiguration: authConfiguration, accessGroup: accessGroupName,
194+
migrateKeychainItemsOfUserSession: shouldMigrate)
181195
}
182196

183197
private func makeLegacyKeychainStore(service: String) -> KeychainStoreBehavior {

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/AWSCognitoAuthPlugin.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public final class AWSCognitoAuthPlugin: AWSCognitoAuthPluginBehavior {
3535
/// The user network preferences for timeout and retry
3636
let networkPreferences: AWSCognitoNetworkPreferences?
3737

38+
/// The user secure storage preferences for access group
39+
let secureStoragePreferences: AWSCognitoSecureStoragePreferences?
40+
3841
@_spi(InternalAmplifyConfiguration)
3942
internal(set) public var jsonConfiguration: JSONValue?
4043

@@ -43,15 +46,14 @@ public final class AWSCognitoAuthPlugin: AWSCognitoAuthPluginBehavior {
4346
return "awsCognitoAuthPlugin"
4447
}
4548

46-
/// Instantiates an instance of the AWSCognitoAuthPlugin.
47-
public init() {
48-
self.networkPreferences = nil
49-
}
50-
51-
/// Instantiates an instance of the AWSCognitoAuthPlugin with custom network preferences
49+
/// Instantiates an instance of the AWSCognitoAuthPlugin with optionally custom network
50+
/// preferences and custom secure storage preferences
5251
/// - Parameters:
5352
/// - networkPreferences: network preferences
54-
public init(networkPreferences: AWSCognitoNetworkPreferences) {
53+
/// - secureStoragePreferences: secure storage preferences
54+
public init(networkPreferences: AWSCognitoNetworkPreferences? = nil,
55+
secureStoragePreferences: AWSCognitoSecureStoragePreferences = AWSCognitoSecureStoragePreferences()) {
5556
self.networkPreferences = networkPreferences
57+
self.secureStoragePreferences = secureStoragePreferences
5658
}
5759
}

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/CredentialStorage/AWSCognitoAuthCredentialStore.swift

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ struct AWSCognitoAuthCredentialStore {
1313

1414
// Credential store constants
1515
private let service = "com.amplify.awsCognitoAuthPlugin"
16+
private let sharedService = "com.amplify.awsCognitoAuthPluginShared"
1617
private let sessionKey = "session"
1718
private let deviceMetadataKey = "deviceMetadata"
1819
private let deviceASFKey = "deviceASF"
@@ -25,14 +26,31 @@ struct AWSCognitoAuthCredentialStore {
2526
private var isKeychainConfiguredKey: String {
2627
"\(userDefaultsNameSpace).isKeychainConfigured"
2728
}
29+
private var accessGroupKey: String {
30+
"\(userDefaultsNameSpace).accessGroup"
31+
}
2832

2933
private let authConfiguration: AuthConfiguration
3034
private let keychain: KeychainStoreBehavior
3135
private let userDefaults = UserDefaults.standard
36+
private let accessGroup: String?
3237

33-
init(authConfiguration: AuthConfiguration, accessGroup: String? = nil) {
38+
init(authConfiguration: AuthConfiguration, accessGroup: String? = nil, migrateKeychainItemsOfUserSession: Bool = false) {
3439
self.authConfiguration = authConfiguration
35-
self.keychain = KeychainStore(service: service, accessGroup: accessGroup)
40+
self.accessGroup = accessGroup
41+
if let accessGroup {
42+
self.keychain = KeychainStore(service: sharedService, accessGroup: accessGroup)
43+
} else {
44+
self.keychain = KeychainStore(service: service)
45+
}
46+
47+
if migrateKeychainItemsOfUserSession {
48+
try? migrateKeychainItemsToAccessGroup()
49+
} else {
50+
try? clearOnFirstTimeChangingAccessGroup()
51+
}
52+
53+
try? saveStoredAccessGroup()
3654

3755
if !userDefaults.bool(forKey: isKeychainConfiguredKey) {
3856
try? clearAllCredentials()
@@ -182,6 +200,93 @@ extension AWSCognitoAuthCredentialStore: AmplifyAuthCredentialStoreBehavior {
182200
private func clearAllCredentials() throws {
183201
try keychain._removeAll()
184202
}
203+
204+
private func retrieveStoredAccessGroup() throws -> String? {
205+
return userDefaults.string(forKey: accessGroupKey)
206+
}
207+
208+
private func saveStoredAccessGroup() throws {
209+
if let accessGroup {
210+
userDefaults.set(accessGroup, forKey: accessGroupKey)
211+
} else {
212+
userDefaults.removeObject(forKey: accessGroupKey)
213+
}
214+
}
215+
216+
private func migrateKeychainItemsToAccessGroup() throws {
217+
let oldAccessGroup = try? retrieveStoredAccessGroup()
218+
let oldKeychain: KeychainStoreBehavior
219+
220+
if oldAccessGroup == accessGroup {
221+
log.verbose("[AWSCognitoAuthCredentialStore] Stored access group is the same as current access group, aborting migration")
222+
return
223+
}
224+
225+
if let oldAccessGroup {
226+
oldKeychain = KeychainStore(service: sharedService, accessGroup: oldAccessGroup)
227+
} else {
228+
oldKeychain = KeychainStore(service: service)
229+
}
230+
231+
let authCredentialStoreKey = generateSessionKey(for: authConfiguration)
232+
let authCredentialData: Data
233+
let awsCredential: AmplifyCredentials
234+
do {
235+
authCredentialData = try oldKeychain._getData(authCredentialStoreKey)
236+
awsCredential = try decode(data: authCredentialData)
237+
} catch {
238+
log.verbose("[AWSCognitoAuthCredentialStore] Could not retrieve previous credentials in keychain under old access group, nothing to migrate")
239+
try? clearAllCredentials()
240+
return
241+
}
242+
243+
guard awsCredential.areValid() else {
244+
log.verbose("[AWSCognitoAuthCredentialStore] Credentials found are not valid (expired) in old access group keychain, aborting migration")
245+
try? clearAllCredentials()
246+
return
247+
}
248+
249+
let oldItems : [(key: String, value: Data)]
250+
do {
251+
oldItems = try oldKeychain._getAll()
252+
} catch {
253+
log.error("[AWSCognitoAuthCredentialStore] Error getting all items from keychain under old access group, aborting migration")
254+
try? clearAllCredentials()
255+
return
256+
}
257+
258+
if oldItems.isEmpty {
259+
log.verbose("[AWSCognitoAuthCredentialStore] No items in keychain under old access group, aborting migration")
260+
try? clearAllCredentials()
261+
return
262+
}
263+
264+
for item in oldItems {
265+
do {
266+
try keychain._set(item.value, key: item.key)
267+
} catch {
268+
log.error("[AWSCognitoAuthCredentialStore] Error migrating one of the items, aborting migration: \(error)")
269+
try? clearAllCredentials()
270+
return
271+
}
272+
}
273+
274+
do {
275+
try oldKeychain._removeAll()
276+
} catch {
277+
log.error("[AWSCognitoAuthCredentialStore] Error deleting all items from keychain under old access group after migration")
278+
}
279+
}
280+
281+
private func clearOnFirstTimeChangingAccessGroup() {
282+
let oldAccessGroup = try? retrieveStoredAccessGroup()
283+
284+
if oldAccessGroup == accessGroup {
285+
return
286+
} else {
287+
try? clearAllCredentials()
288+
}
289+
}
185290

186291
}
187292

@@ -205,3 +310,11 @@ private extension AWSCognitoAuthCredentialStore {
205310
}
206311

207312
}
313+
314+
extension AWSCognitoAuthCredentialStore: DefaultLogger {
315+
public static var log: Logger {
316+
Amplify.Logging.logger(forNamespace: String(describing: self))
317+
}
318+
319+
public nonisolated var log: Logger { Self.log }
320+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Foundation
9+
import Amplify
10+
11+
public struct AWSCognitoSecureStoragePreferences {
12+
13+
/// The access group that the keychain will use for auth items
14+
public let accessGroup: AccessGroup?
15+
16+
public let migrateKeychainItemsOfUserSession: Bool
17+
18+
public init(accessGroup: AccessGroup? = nil, migrateKeychainItemsOfUserSession: Bool = true) {
19+
self.accessGroup = accessGroup
20+
self.migrateKeychainItemsOfUserSession = migrateKeychainItemsOfUserSession
21+
}
22+
}

AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/ActionTests/CredentialStore/MockCredentialStoreBehavior.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@ class MockKeychainStoreBehavior: KeychainStoreBehavior {
1515
typealias VoidHandler = () -> Void
1616

1717
let data: String
18+
let allData: [(key: String, value: Data)]
1819
let removeAllHandler: VoidHandler?
20+
let mockKey: String = "mockKey"
1921

2022
init(data: String,
2123
removeAllHandler: VoidHandler? = nil) {
2224
self.data = data
2325
self.removeAllHandler = removeAllHandler
26+
self.allData = [(key: mockKey, value: Data(data.utf8))]
2427
}
2528

2629
func _getString(_ key: String) throws -> String {
@@ -41,4 +44,8 @@ class MockKeychainStoreBehavior: KeychainStoreBehavior {
4144
func _removeAll() throws {
4245
removeAllHandler?()
4346
}
47+
48+
func _getAll() throws -> [(key: String, value: Data)] {
49+
return allData
50+
}
4451
}

AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/DefaultConfig.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,10 @@ struct MockLegacyStore: KeychainStoreBehavior {
366366
func _removeAll() throws {
367367

368368
}
369+
370+
func _getAll() throws -> [(key: String, value: Data)] {
371+
return []
372+
}
369373

370374
}
371375

AmplifyPlugins/Auth/Tests/AuthHostApp/AuthHostApp.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@
216216
B43C26C827BC9D54003F3BF7 /* AuthConfirmSignUpTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthConfirmSignUpTests.swift; sourceTree = "<group>"; };
217217
B43C26C927BC9D54003F3BF7 /* AuthResendSignUpCodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthResendSignUpCodeTests.swift; sourceTree = "<group>"; };
218218
B4B9F45628F47B7B004F346F /* amplify-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "amplify-ios"; path = ../../../..; sourceTree = "<group>"; };
219+
E2A7D1732C5D76CB00B06999 /* AuthHostApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AuthHostApp.entitlements; sourceTree = "<group>"; };
220+
E2A7D1742C5D774200B06999 /* AuthWatchApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AuthWatchApp.entitlements; sourceTree = "<group>"; };
219221
/* End PBXFileReference section */
220222

221223
/* Begin PBXFrameworksBuildPhase section */
@@ -303,6 +305,7 @@
303305
485CB53127B614CE006CCEC7 = {
304306
isa = PBXGroup;
305307
children = (
308+
E2A7D1742C5D774200B06999 /* AuthWatchApp.entitlements */,
306309
485CB5C627B62C5C006CCEC7 /* Packages */,
307310
485CB53C27B614CE006CCEC7 /* AuthHostApp */,
308311
485CB5A027B61E04006CCEC7 /* AuthIntegrationTests */,
@@ -328,6 +331,7 @@
328331
485CB53C27B614CE006CCEC7 /* AuthHostApp */ = {
329332
isa = PBXGroup;
330333
children = (
334+
E2A7D1732C5D76CB00B06999 /* AuthHostApp.entitlements */,
331335
681DFEA728E747B80000C36A /* AsyncTesting */,
332336
485CB53D27B614CE006CCEC7 /* AuthHostAppApp.swift */,
333337
485CB53F27B614CE006CCEC7 /* ContentView.swift */,
@@ -1135,6 +1139,7 @@
11351139
buildSettings = {
11361140
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
11371141
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
1142+
CODE_SIGN_ENTITLEMENTS = AuthHostApp/AuthHostApp.entitlements;
11381143
CODE_SIGN_STYLE = Automatic;
11391144
CURRENT_PROJECT_VERSION = 1;
11401145
DEVELOPMENT_ASSET_PATHS = "\"AuthHostApp/Preview Content\"";
@@ -1168,6 +1173,7 @@
11681173
buildSettings = {
11691174
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
11701175
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
1176+
CODE_SIGN_ENTITLEMENTS = AuthHostApp/AuthHostApp.entitlements;
11711177
CODE_SIGN_STYLE = Automatic;
11721178
CURRENT_PROJECT_VERSION = 1;
11731179
DEVELOPMENT_ASSET_PATHS = "\"AuthHostApp/Preview Content\"";
@@ -1245,6 +1251,7 @@
12451251
buildSettings = {
12461252
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
12471253
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
1254+
CODE_SIGN_ENTITLEMENTS = AuthWatchApp.entitlements;
12481255
CODE_SIGN_STYLE = Automatic;
12491256
CURRENT_PROJECT_VERSION = 1;
12501257
DEVELOPMENT_ASSET_PATHS = "";
@@ -1275,6 +1282,7 @@
12751282
buildSettings = {
12761283
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
12771284
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
1285+
CODE_SIGN_ENTITLEMENTS = AuthWatchApp.entitlements;
12781286
CODE_SIGN_STYLE = Automatic;
12791287
CURRENT_PROJECT_VERSION = 1;
12801288
DEVELOPMENT_ASSET_PATHS = "";
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>keychain-access-groups</key>
6+
<array>
7+
<string>$(AppIdentifierPrefix)com.aws.amplify.auth.AuthHostAppShared</string>
8+
<string>$(AppIdentifierPrefix)com.aws.amplify.auth.AuthHostAppShared2</string>
9+
</array>
10+
</dict>
11+
</plist>

AmplifyPlugins/Auth/Tests/AuthHostApp/AuthIntegrationTests/AWSAuthBaseTest.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ class AWSAuthBaseTest: XCTestCase {
3030
var amplifyOutputsFile =
3131
"testconfiguration/AWSCognitoAuthPluginIntegrationTests-amplify_outputs"
3232
let credentialsFile = "testconfiguration/AWSCognitoAuthPluginIntegrationTests-credentials"
33+
let keychainAccessGroup = "94KV3E626L.com.aws.amplify.auth.AuthHostAppShared"
34+
let keychainAccessGroup2 = "94KV3E626L.com.aws.amplify.auth.AuthHostAppShared2"
35+
let keychainAccessGroupWatch = "W3DRXD72QU.com.amazon.aws.amplify.swift.AuthWatchAppShared"
36+
let keychainAccessGroupWatch2 = "W3DRXD72QU.com.amazon.aws.amplify.swift.AuthWatchAppShared2"
3337

3438
var amplifyConfiguration: AmplifyConfiguration!
3539
var amplifyOutputs: AmplifyOutputsData!

0 commit comments

Comments
 (0)