Skip to content

Commit c2fc412

Browse files
iykazrjiRobChangCA
andauthored
fix: ensure session persistence on IOS (#1457)
* feat: update logo (#1454) * fix: update keychain implementation to declear variables earlier during run * feat: use struct for keychain utilities as opposed to class --------- Co-authored-by: Rob <30500864+RobChangCA@users.noreply.github.com>
1 parent 51b0ec2 commit c2fc412

File tree

2 files changed

+80
-17
lines changed

2 files changed

+80
-17
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import Foundation
2+
import Security
3+
import CryptoKit
4+
5+
let ACCOUNT_KIT_EPHEMERAL_PRIVATE_KEY = "accountKitEphemeralPrivateKey"
6+
7+
let save_ephemeral_private_key_query: [String: Any] = [
8+
kSecClass as String: kSecClassGenericPassword,
9+
kSecAttrAccount as String: ACCOUNT_KIT_EPHEMERAL_PRIVATE_KEY,
10+
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
11+
]
12+
13+
let get_ephemeral_private_key_query: [String: Any] = [
14+
kSecClass as String: kSecClassGenericPassword,
15+
kSecAttrAccount as String: ACCOUNT_KIT_EPHEMERAL_PRIVATE_KEY,
16+
kSecReturnData as String: true,
17+
kSecMatchLimit as String: kSecMatchLimitOne
18+
]
19+
20+
let delete_ephemeral_private_key_query: [String: Any] = [
21+
kSecClass as String: kSecClassGenericPassword,
22+
kSecAttrAccount as String: ACCOUNT_KIT_EPHEMERAL_PRIVATE_KEY
23+
]
24+
25+
26+
struct PrivateKeyChainUtilities {
27+
@available(*, unavailable) private init() {}
28+
29+
static func savePrivateKeyToKeychain(_ privateKey: P256.KeyAgreement.PrivateKey) {
30+
let keyData = privateKey.rawRepresentation
31+
32+
var _saveEphemeralPrivateKeyQuery = save_ephemeral_private_key_query
33+
_saveEphemeralPrivateKeyQuery.updateValue(keyData, forKey: kSecValueData as String)
34+
35+
SecItemDelete(_saveEphemeralPrivateKeyQuery as CFDictionary)
36+
SecItemAdd(_saveEphemeralPrivateKeyQuery as CFDictionary, nil)
37+
}
38+
39+
static func getPrivateKeyFromKeychain() -> P256.KeyAgreement.PrivateKey? {
40+
var result: AnyObject?
41+
let status = SecItemCopyMatching(get_ephemeral_private_key_query as CFDictionary, &result)
42+
43+
if status == errSecSuccess, let keyData = result as? Data {
44+
do {
45+
return try P256.KeyAgreement.PrivateKey(rawRepresentation: keyData)
46+
} catch {
47+
print("❌ Failed to decode private key: \(error)")
48+
return nil
49+
}
50+
}
51+
52+
return nil
53+
}
54+
55+
static func deletePrivateKeyFromKeychain() {
56+
SecItemDelete(delete_ephemeral_private_key_query as CFDictionary)
57+
}
58+
}

account-kit/rn-signer/ios/implementation/NativeTEKStamperImpl.swift

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
import CryptoKit
23
import Foundation
34

@@ -11,22 +12,28 @@ enum StamperError: Error {
1112
@objc public class NativeTEKStamperImpl: NSObject {
1213
// TODO: we probably want to keep this longer term somewhere, because the RN session manager will
1314
// hold on to the bundle and try to recreate a session if a user is still logged in
14-
var ephemeralPrivateKey: P256.KeyAgreement.PrivateKey? = nil;
15+
var ephemeralPrivateKey: P256.KeyAgreement.PrivateKey? = nil
1516

1617
// These can be ephemeral and held in memory because the session manager will handle re-authenticating
1718
var apiPublicKey: P256.Signing.PublicKey? = nil;
1819
var apiPrivateKey: P256.Signing.PrivateKey? = nil;
1920

2021
@objc public func create() async throws -> NSString {
21-
if (ephemeralPrivateKey == nil) {
22-
ephemeralPrivateKey = P256.KeyAgreement.PrivateKey()
23-
}
24-
let targetPublicKey = try ephemeralPrivateKey!.publicKey.toString(representation: .x963)
22+
var _ephemeralPrivateKey: P256.KeyAgreement.PrivateKey? = PrivateKeyChainUtilities.getPrivateKeyFromKeychain()
2523

24+
if (_ephemeralPrivateKey == nil) {
25+
_ephemeralPrivateKey = P256.KeyAgreement.PrivateKey()
26+
PrivateKeyChainUtilities.savePrivateKeyToKeychain(_ephemeralPrivateKey!)
27+
}
28+
29+
let targetPublicKey = try _ephemeralPrivateKey!.publicKey.toString(representation: .x963)
30+
ephemeralPrivateKey = _ephemeralPrivateKey
31+
2632
return NSString(string: targetPublicKey)
2733
}
2834

2935
@objc public func clear() {
36+
PrivateKeyChainUtilities.deletePrivateKeyFromKeychain()
3037
ephemeralPrivateKey = nil
3138
apiPublicKey = nil
3239
apiPrivateKey = nil
@@ -41,18 +48,17 @@ enum StamperError: Error {
4148
}
4249
}
4350

44-
@objc public func injectCredentialBundle(bundle: NSString) async throws -> ObjCBool {
45-
if let ephemeralPrivateKey = ephemeralPrivateKey {
46-
let (bundlePrivateKey, bundlePublicKey) = try AuthManager.decryptBundle(encryptedBundle: bundle as String, ephemeralPrivateKey: ephemeralPrivateKey)
51+
@objc public func injectCredentialBundle(bundle: NSString) async throws -> ObjCBool {
52+
if let ephemeralPrivateKey = ephemeralPrivateKey {
53+
let (bundlePrivateKey, bundlePublicKey) = try AuthManager.decryptBundle(encryptedBundle: bundle as String, ephemeralPrivateKey: ephemeralPrivateKey)
54+
apiPublicKey = bundlePublicKey
55+
apiPrivateKey = bundlePrivateKey
4756

48-
apiPublicKey = bundlePublicKey
49-
apiPrivateKey = bundlePrivateKey
50-
51-
return true;
52-
} else {
53-
throw StamperError.notInitialized
54-
}
55-
}
57+
return true
58+
} else {
59+
throw StamperError.notInitialized
60+
}
61+
}
5662

5763
// TODO: we should use the turnkey stamper for all of this, but we need it published as a pod
5864
// and it shouldn't require use_frameworks!
@@ -70,7 +76,6 @@ enum StamperError: Error {
7076

7177
let signatureHex = signature.derRepresentation.toHexString()
7278

73-
print(apiPublicKey.compressedRepresentation.toHexString())
7479
let stamp: [String: Any] = [
7580
"publicKey": apiPublicKey.compressedRepresentation.toHexString(),
7681
"scheme": "SIGNATURE_SCHEME_TK_API_P256",

0 commit comments

Comments
 (0)