Skip to content
This repository was archived by the owner on Oct 10, 2025. It is now read-only.

Commit f1601bb

Browse files
authored
Prevent Crash for Device Swapping Users via Regeneration of PK (#40)
* Delete PK from Secure Enclave in the case that it fails * Bump versions
1 parent 3ca78cd commit f1601bb

File tree

3 files changed

+76
-59
lines changed

3 files changed

+76
-59
lines changed

MagicSDK.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44
Pod::Spec.new do |s|
55
s.name = 'MagicSDK'
6-
s.version = '10.1.0'
6+
s.version = '10.1.1'
77
s.summary = 'Magic IOS SDK'
88

99
s.description = <<-DESC

Sources/MagicSDK/Core/Relayer/Types/DPop.swift

Lines changed: 64 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -17,66 +17,72 @@ func base64UrlEncoded(_ data: Data) -> String {
1717
return b64
1818
}
1919

20-
func createJwt() -> String?{
21-
var error: Unmanaged<CFError>?
2220

23-
do {
24-
let privateKey = try retrieveKeyFromKeyChain()
25-
26-
// Get the public key.
27-
let publicKey = privateKey.publicKey
28-
29-
// Get the raw representation of the public key.
30-
let rawPublicKey = publicKey.rawRepresentation
31-
32-
// Extract the x and y coordinates.
33-
let xCoordinateData = rawPublicKey[1..<33]
34-
let yCoordinateData = rawPublicKey[33..<65]
35-
36-
// If you need base64-encoded strings for JWK:
37-
let xCoordinateBase64 = base64UrlEncoded(xCoordinateData)
38-
let yCoordinateBase64 = base64UrlEncoded(yCoordinateData)
39-
// Convert the public key to JWK format.
40-
// construct headers
41-
var headers: [String: Any] = ["typ": "dpop+jwt", "alg": "ES256"]
42-
headers["jwk"] = [
43-
"kty": "EC",
44-
"crv": "P-256",
45-
"x": xCoordinateBase64,
46-
"y": yCoordinateBase64
47-
] as [String : Any]
48-
49-
let headersData = try JSONSerialization.data(withJSONObject: headers)
50-
let headersB64 = base64UrlEncoded(headersData)
51-
52-
53-
// construct claims
54-
let iat = Int(Date().timeIntervalSince1970)
55-
let jti = UUID().uuidString.lowercased()
56-
57-
let claims: [String: Any] = ["iat": iat, "jti": jti]
58-
let claimsData = try JSONSerialization.data(withJSONObject: claims)
59-
let claimsB64 = base64UrlEncoded(claimsData)
60-
61-
/// sign
62-
let signingInput = headersB64 + "." + claimsB64
63-
let signingInputData = signingInput.data(using: .utf8)!
64-
65-
guard let signature = try? privateKey.signature(for: signingInputData) else {
66-
// This can happen when the [secure enclave biometrics](https://developer.apple.com/forums/thread/682162?answerId=726099022#726099022) have changed such as when an app is auto-installed on a new device
67-
return nil
21+
func createJwt() -> String? {
22+
var attempts = 0
23+
24+
while attempts < 3 { // Attempt the retry flow at a maximum of 3 times before giving up
25+
do {
26+
let privateKey = try retrieveKeyFromKeyChain()
27+
28+
// Get the public key.
29+
let publicKey = privateKey.publicKey
30+
31+
// Get the raw representation of the public key.
32+
let rawPublicKey = publicKey.rawRepresentation
33+
34+
// Extract the x and y coordinates.
35+
let xCoordinateData = rawPublicKey[1..<33]
36+
let yCoordinateData = rawPublicKey[33..<65]
37+
38+
// If you need base64-encoded strings for JWK:
39+
let xCoordinateBase64 = base64UrlEncoded(xCoordinateData)
40+
let yCoordinateBase64 = base64UrlEncoded(yCoordinateData)
41+
42+
// Convert the public key to JWK format.
43+
// construct headers
44+
var headers: [String: Any] = ["typ": "dpop+jwt", "alg": "ES256"]
45+
headers["jwk"] = [
46+
"kty": "EC",
47+
"crv": "P-256",
48+
"x": xCoordinateBase64,
49+
"y": yCoordinateBase64
50+
] as [String : Any]
51+
52+
let headersData = try JSONSerialization.data(withJSONObject: headers)
53+
let headersB64 = base64UrlEncoded(headersData)
54+
55+
// construct claims
56+
let iat = Int(Date().timeIntervalSince1970)
57+
let jti = UUID().uuidString.lowercased()
58+
59+
let claims: [String: Any] = ["iat": iat, "jti": jti]
60+
let claimsData = try JSONSerialization.data(withJSONObject: claims)
61+
let claimsB64 = base64UrlEncoded(claimsData)
62+
63+
/// sign
64+
let signingInput = headersB64 + "." + claimsB64
65+
66+
guard let signingInputData = signingInput.data(using: .utf8),
67+
let signature = try? privateKey.signature(for: signingInputData) else {
68+
// This can happen when the [secure enclave biometrics](https://developer.apple.com/forums/thread/682162?answerId=726099022#726099022)
69+
// have changed such as when an app is auto-installed on a new device, ergo delete previously created key and try again
70+
try deleteKeyFromKeyChain()
71+
attempts += 1
72+
continue
73+
}
74+
75+
let signatureB64 = base64UrlEncoded(signature.rawRepresentation)
76+
let jwt = signingInput + "." + signatureB64
77+
78+
return jwt
79+
80+
} catch {
81+
// Handle error (log, throw, or break as necessary)
82+
print("An error occurred: \(error)")
83+
break
6884
}
69-
70-
let signatureB64 = base64UrlEncoded(signature.rawRepresentation)
71-
72-
let jwt = signingInput + "." + signatureB64
73-
74-
return jwt
75-
76-
} catch {
77-
// silently handled error
78-
return nil
7985
}
80-
86+
return nil // Return nil if unable to generate JWT after 3x retries
8187
}
8288

Sources/MagicSDK/Utilities/SEKP.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ func retrieveKeyFromKeyChain () throws -> SecureEnclave.P256.Signing.PrivateKey
6969
}
7070
}
7171

72+
func deleteKeyFromKeyChain() throws {
73+
let query = [kSecClass: kSecClassGenericPassword,
74+
kSecAttrAccount: account,
75+
kSecUseDataProtectionKeychain: true] as [String: Any]
76+
77+
let status = SecItemDelete(query as CFDictionary)
78+
guard status == errSecSuccess || status == errSecItemNotFound else {
79+
throw SEKPError.KeyStoreError("Unable to delete item: \(status.description)")
80+
}
81+
}
82+
7283

7384
protocol GenericPasswordConvertible: CustomStringConvertible {
7485
/// Creates a key from a raw representation.

0 commit comments

Comments
 (0)