Skip to content

Commit b104f6c

Browse files
authored
Merge pull request #528 from stytchauth/change-encryption-key-logic
Remove logic around isProtectedDataAvailable
2 parents ed926f6 + 4806f62 commit b104f6c

File tree

8 files changed

+85
-38
lines changed

8 files changed

+85
-38
lines changed

Sources/StytchCore/KeychainClient/KeychainClient.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ protocol KeychainClient: AnyObject {
88
func valueExistsForItem(item: KeychainItem) -> Bool
99
func setValueForItem(value: KeychainItem.Value, item: KeychainItem) throws
1010
func removeItem(item: KeychainItem) throws
11-
func onProtectedDataDidBecomeAvailable()
1211
}
1312

1413
extension KeychainClient {

Sources/StytchCore/KeychainClient/KeychainClientImplementation.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ final class KeychainClientImplementation: KeychainClient {
1919
private init() {
2020
queue = DispatchQueue(label: "StytchKeychainClientQueue")
2121
queue.setSpecific(key: queueKey, value: ())
22+
loadEncryptionKey()
2223
}
2324

24-
func onProtectedDataDidBecomeAvailable() {
25+
func loadEncryptionKey() {
2526
try? safelyEnqueue {
2627
encryptionKey = try? getEncryptionKey()
2728
}

Sources/StytchCore/KeychainClient/KeychainItem.swift

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ struct KeychainItem {
1313
}
1414

1515
var getQuery: [CFString: Any] {
16-
baseQuery
17-
.merging([
18-
kSecReturnData: true,
19-
kSecReturnAttributes: true,
20-
kSecMatchLimit: kSecMatchLimitAll,
21-
kSecAttrSynchronizable: kSecAttrSynchronizableAny,
22-
]) { $1 }
16+
var query = baseQuery.merging([
17+
kSecReturnData: true,
18+
kSecReturnAttributes: true,
19+
kSecMatchLimit: kSecMatchLimitAll,
20+
kSecAttrSynchronizable: kSecAttrSynchronizableAny,
21+
]) { $1 }
22+
23+
if kind == .encryptionKey {
24+
query[kSecUseAuthenticationUI] = kSecUseAuthenticationUISkip
25+
}
26+
return query
2327
}
2428

2529
func insertQuery(value: Value) -> CFDictionary {
@@ -42,6 +46,13 @@ struct KeychainItem {
4246
if let accessControl = try? value.accessPolicy?.accessControl {
4347
querySegment[kSecAttrAccessControl] = accessControl
4448
}
49+
50+
// Make the encryption key available after first unlock
51+
if kind == .encryptionKey {
52+
querySegment[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlock
53+
// or use kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly if you do not want sync
54+
}
55+
4556
return querySegment
4657
}
4758
}

Sources/StytchCore/StytchClientType.swift

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,16 @@ extension StytchClientType {
8282
}
8383
}
8484
}
85-
if UIApplication.shared.isProtectedDataAvailable {
86-
keychainClient.onProtectedDataDidBecomeAvailable()
87-
defaultStartupFlow()
88-
} else {
89-
NotificationCenter.default.addObserver(forName: UIApplication.protectedDataDidBecomeAvailableNotification, object: nil, queue: nil) { _ in
90-
keychainClient.onProtectedDataDidBecomeAvailable()
91-
defaultStartupFlow()
85+
#endif
86+
87+
Task {
88+
do {
89+
try await StartupClient.start(clientType: Self.clientType)
90+
try? await EventsClient.logEvent(parameters: .init(eventName: "client_initialization_success"))
91+
} catch {
92+
try? await EventsClient.logEvent(parameters: .init(eventName: "client_initialization_failure"))
9293
}
9394
}
94-
#else
95-
defaultStartupFlow()
96-
#endif
9795
}
9896

9997
// swiftlint:disable:next identifier_name large_tuple
@@ -141,15 +139,4 @@ extension StytchClientType {
141139
}
142140
}
143141
}
144-
145-
private func defaultStartupFlow() {
146-
Task {
147-
do {
148-
try await StartupClient.start(clientType: Self.clientType)
149-
try? await EventsClient.logEvent(parameters: .init(eventName: "client_initialization_success"))
150-
} catch {
151-
try? await EventsClient.logEvent(parameters: .init(eventName: "client_initialization_failure"))
152-
}
153-
}
154-
}
155142
}

Stytch/DemoApps/StytchBiometrics/ViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class ViewController: UIViewController {
1515

1616
override func viewDidLoad() {
1717
super.viewDidLoad()
18-
StytchClient.configure(configuration: .init(publicToken: ""))
18+
StytchClient.configure(configuration: .init(publicToken: "public-token-test-81b5f118-b6d3-49ff-9f56-a037a6e65176"))
1919

2020
StytchClient.sessions.onSessionChange
2121
.receive(on: DispatchQueue.main)

Stytch/DemoApps/StytchUIDemo/ContentView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class ContentViewModel: ObservableObject {
109109
}
110110

111111
let configuration: StytchUIClient.Configuration = .init(
112-
stytchClientConfiguration: .init(publicToken: "public-token-test-..."),
112+
stytchClientConfiguration: .init(publicToken: "public-token-test-81b5f118-b6d3-49ff-9f56-a037a6e65176"),
113113
products: [.otp, .oauth, .passwords, .emailMagicLinks, .biometrics],
114114
navigation: Navigation(closeButtonStyle: .close(.right)),
115115
oauthProviders: [.apple, .thirdParty(.google)],

Tests/StytchCoreTests/KeychainClient+Mock.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import Foundation
66
public var keychainDateCreatedOffsetInMinutes = 0
77

88
class KeychainClientMock: KeychainClient {
9-
func onProtectedDataDidBecomeAvailable() {}
10-
119
var encryptionKey: SymmetricKey? {
1210
do {
1311
return SymmetricKey(data: try Current.cryptoClient.dataWithRandomBytesOfCount(256))

Tests/StytchCoreTests/KeychainClientTestCase.swift

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,71 @@ final class KeychainClientTestCase: BaseTestCase {
5656
func testKeychainEncryptionKeyItem() {
5757
let item: KeychainItem = .init(kind: .encryptionKey, name: "encryptionKey")
5858

59+
// Helper to create a KeychainItem.Value for a given string
5960
let itemValueForKey: (String) -> KeychainItem.Value = { value in
60-
.init(data: .init(value.utf8), account: ENCRYPTEDUSERDEFAULTSKEYNAME, label: nil, generic: nil, accessPolicy: nil)
61+
.init(
62+
data: .init(value.utf8),
63+
account: ENCRYPTEDUSERDEFAULTSKEYNAME,
64+
label: nil,
65+
generic: nil,
66+
accessPolicy: nil
67+
)
6168
}
69+
70+
// Verify the base query used to fetch the encryption key.
71+
// Expected fields:
72+
// - service ("svce") set to "encryptionKey"
73+
// - class ("genp") for generic password
74+
// - return data and attributes
75+
// - match limit all
76+
// - synchronizable any
77+
// - no authentication UI (skip prompts)
6278
XCTAssertEqual(
6379
item.getQuery as CFDictionary,
64-
["svce": "encryptionKey", "class": "genp", "m_Limit": "m_LimitAll", "r_Data": 1, "r_Attributes": 1, "nleg": 1, "sync": "syna"] as CFDictionary
80+
[
81+
"svce": "encryptionKey",
82+
"class": "genp",
83+
"m_Limit": "m_LimitAll",
84+
"r_Data": 1,
85+
"r_Attributes": 1,
86+
"nleg": 1,
87+
"sync": "syna",
88+
"u_AuthUI": "u_AuthUIS",
89+
] as CFDictionary
6590
)
91+
92+
// Verify the update segment for an existing encryption key value.
93+
// Expected fields:
94+
// - account set to ENCRYPTEDUSERDEFAULTSKEYNAME
95+
// - value data stored as bytes
96+
// - pdmn = "ck", meaning kSecAttrAccessibleAfterFirstUnlock
6697
XCTAssertEqual(
6798
item.updateQuerySegment(for: itemValueForKey("value")) as CFDictionary,
68-
["acct": ENCRYPTEDUSERDEFAULTSKEYNAME, "v_Data": Data("value".utf8)] as CFDictionary
99+
[
100+
"acct": ENCRYPTEDUSERDEFAULTSKEYNAME,
101+
"v_Data": Data("value".utf8),
102+
"pdmn": "ck",
103+
] as CFDictionary
69104
)
105+
106+
// Verify the insert query for a new encryption key value.
107+
// Expected fields:
108+
// - account set to ENCRYPTEDUSERDEFAULTSKEYNAME
109+
// - service set to "encryptionKey"
110+
// - class = generic password
111+
// - value data stored as bytes
112+
// - nleg = 1 (data protection keychain)
113+
// - pdmn = "ck" (AfterFirstUnlock accessibility)
70114
XCTAssertEqual(
71115
item.insertQuery(value: itemValueForKey("new_value")) as CFDictionary,
72-
["acct": ENCRYPTEDUSERDEFAULTSKEYNAME, "svce": "encryptionKey", "class": "genp", "v_Data": Data("new_value".utf8), "nleg": 1] as CFDictionary
116+
[
117+
"acct": ENCRYPTEDUSERDEFAULTSKEYNAME,
118+
"svce": "encryptionKey",
119+
"class": "genp",
120+
"v_Data": Data("new_value".utf8),
121+
"nleg": 1,
122+
"pdmn": "ck", // AfterFirstUnlock
123+
] as CFDictionary
73124
)
74125
}
75126

0 commit comments

Comments
 (0)