@dynamic-labs/react-native-extension 4.67.3-device-registration.0 → 4.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/index.cjs +1 -66
  2. package/index.js +1 -66
  3. package/package.json +7 -8
  4. package/android/AndroidManifest.xml +0 -1
  5. package/android/KeyStoreKeyManager.kt +0 -148
  6. package/android/KeychainModule.kt +0 -71
  7. package/android/build.gradle +0 -32
  8. package/android/dynamic/keychain/KeyStoreKeyManager.kt +0 -148
  9. package/android/dynamic/keychain/KeychainModule.kt +0 -71
  10. package/android/java/xyz/dynamic/keychain/KeyStoreKeyManager.kt +0 -148
  11. package/android/java/xyz/dynamic/keychain/KeychainModule.kt +0 -71
  12. package/android/keychain/KeyStoreKeyManager.kt +0 -148
  13. package/android/keychain/KeychainModule.kt +0 -71
  14. package/android/main/AndroidManifest.xml +0 -1
  15. package/android/main/java/xyz/dynamic/keychain/KeyStoreKeyManager.kt +0 -148
  16. package/android/main/java/xyz/dynamic/keychain/KeychainModule.kt +0 -71
  17. package/android/src/main/AndroidManifest.xml +0 -1
  18. package/android/src/main/java/xyz/dynamic/keychain/KeyStoreKeyManager.kt +0 -148
  19. package/android/src/main/java/xyz/dynamic/keychain/KeychainModule.kt +0 -71
  20. package/android/xyz/dynamic/keychain/KeyStoreKeyManager.kt +0 -148
  21. package/android/xyz/dynamic/keychain/KeychainModule.kt +0 -71
  22. package/expo-module.config.json +0 -9
  23. package/ios/Keychain.podspec +0 -15
  24. package/ios/KeychainModule.swift +0 -39
  25. package/ios/SecureEnclaveKeyManager.swift +0 -170
  26. package/src/ReactNativeExtension/setupKeychainHandler/index.d.ts +0 -1
  27. package/src/ReactNativeExtension/setupKeychainHandler/setupKeychainHandler.d.ts +0 -2
  28. package/src/nativeModules/Keychain.d.ts +0 -16
  29. package/src/nativeModules/index.d.ts +0 -1
@@ -1,148 +0,0 @@
1
- package xyz.dynamic.keychain
2
-
3
- import android.content.Context
4
- import android.content.pm.PackageManager
5
- import android.os.Build
6
- import android.security.keystore.KeyGenParameterSpec
7
- import android.security.keystore.KeyProperties
8
- import android.security.keystore.StrongBoxUnavailableException
9
- import java.security.KeyPairGenerator
10
- import java.security.KeyStore
11
- import java.security.Signature
12
- import java.security.spec.ECGenParameterSpec
13
-
14
- /// Platform-agnostic Android KeyStore key manager.
15
- /// Provides P-256 key generation, signing, and management backed by hardware TEE/StrongBox.
16
- /// All public keys are returned in uncompressed SEC1 format (65 bytes: 04 || x || y).
17
- /// All binary data uses base64url encoding (RFC 4648 §5, no padding).
18
- class KeyStoreKeyManager {
19
-
20
- fun isAvailable(context: Context?): Boolean {
21
- return try {
22
- val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
23
- keyStore.load(null)
24
- val hasStrongBox = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && context != null) {
25
- context.packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
26
- } else {
27
- false
28
- }
29
- // Even without StrongBox, Android KeyStore provides TEE-backed keys on most devices
30
- hasStrongBox || Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
31
- } catch (e: Exception) {
32
- false
33
- }
34
- }
35
-
36
- fun hasKey(alias: String): Boolean {
37
- val keyStore = loadKeyStore()
38
- return keyStore.containsAlias(alias)
39
- }
40
-
41
- fun generateKeyPair(alias: String): String {
42
- val specBuilder = KeyGenParameterSpec.Builder(
43
- alias,
44
- KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
45
- )
46
- .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
47
- .setDigests(KeyProperties.DIGEST_SHA256)
48
-
49
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
50
- specBuilder.setIsStrongBoxBacked(true)
51
- }
52
-
53
- val keyPair = try {
54
- val kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, ANDROID_KEYSTORE)
55
- kpg.initialize(specBuilder.build())
56
- kpg.generateKeyPair()
57
- } catch (e: Exception) {
58
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && e is StrongBoxUnavailableException) {
59
- // Retry without StrongBox
60
- specBuilder.setIsStrongBoxBacked(false)
61
- val kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, ANDROID_KEYSTORE)
62
- kpg.initialize(specBuilder.build())
63
- kpg.generateKeyPair()
64
- } else {
65
- throw e
66
- }
67
- }
68
-
69
- val publicKeyBytes = extractUncompressedPublicKey(keyPair.public.encoded)
70
- return base64urlEncode(publicKeyBytes)
71
- }
72
-
73
- fun getPublicKey(alias: String): String? {
74
- val keyStore = loadKeyStore()
75
- val entry = keyStore.getEntry(alias, null)
76
-
77
- if (entry == null || entry !is KeyStore.PrivateKeyEntry) {
78
- return null
79
- }
80
-
81
- val publicKeyBytes = extractUncompressedPublicKey(entry.certificate.publicKey.encoded)
82
- return base64urlEncode(publicKeyBytes)
83
- }
84
-
85
- fun sign(alias: String, payload: ByteArray): String {
86
- val keyStore = loadKeyStore()
87
- val entry = keyStore.getEntry(alias, null)
88
-
89
- if (entry == null || entry !is KeyStore.PrivateKeyEntry) {
90
- throw IllegalArgumentException("Key not found: $alias")
91
- }
92
-
93
- val signature = Signature.getInstance("SHA256withECDSA")
94
- signature.initSign(entry.privateKey)
95
- signature.update(payload)
96
- val signatureBytes = signature.sign()
97
-
98
- return base64urlEncode(signatureBytes)
99
- }
100
-
101
- fun deleteKey(alias: String) {
102
- val keyStore = loadKeyStore()
103
- keyStore.deleteEntry(alias)
104
- }
105
-
106
- // region Private helpers
107
-
108
- private fun loadKeyStore(): KeyStore {
109
- val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
110
- keyStore.load(null)
111
- return keyStore
112
- }
113
-
114
- /**
115
- * Extract uncompressed SEC1 public key (65 bytes: 04 || x || y)
116
- * from X.509 SubjectPublicKeyInfo DER encoding.
117
- *
118
- * For a P-256 key, the SubjectPublicKeyInfo contains the uncompressed
119
- * point at the end of the DER structure. The point is always 65 bytes.
120
- */
121
- private fun extractUncompressedPublicKey(x509Encoded: ByteArray): ByteArray {
122
- val uncompressedPointLength = 65
123
- return x509Encoded.copyOfRange(
124
- x509Encoded.size - uncompressedPointLength,
125
- x509Encoded.size
126
- )
127
- }
128
-
129
- private fun base64urlEncode(data: ByteArray): String {
130
- return android.util.Base64.encodeToString(
131
- data,
132
- android.util.Base64.URL_SAFE or android.util.Base64.NO_WRAP or android.util.Base64.NO_PADDING
133
- )
134
- }
135
-
136
- companion object {
137
- private const val ANDROID_KEYSTORE = "AndroidKeyStore"
138
-
139
- fun base64urlDecode(input: String): ByteArray {
140
- return android.util.Base64.decode(
141
- input,
142
- android.util.Base64.URL_SAFE or android.util.Base64.NO_WRAP or android.util.Base64.NO_PADDING
143
- )
144
- }
145
- }
146
-
147
- // endregion
148
- }
@@ -1,71 +0,0 @@
1
- package xyz.dynamic.keychain
2
-
3
- import expo.modules.kotlin.modules.Module
4
- import expo.modules.kotlin.modules.ModuleDefinition
5
- import expo.modules.kotlin.Promise
6
-
7
- class KeychainModule : Module() {
8
- private val keyManager = KeyStoreKeyManager()
9
-
10
- override fun definition() = ModuleDefinition {
11
- Name("Keychain")
12
-
13
- AsyncFunction("isAvailable") { promise: Promise ->
14
- try {
15
- val context = appContext.reactContext
16
- promise.resolve(keyManager.isAvailable(context))
17
- } catch (e: Exception) {
18
- promise.resolve(false)
19
- }
20
- }
21
-
22
- AsyncFunction("hasKey") { key: String, promise: Promise ->
23
- try {
24
- promise.resolve(keyManager.hasKey(key))
25
- } catch (e: Exception) {
26
- promise.reject("ERR_KEYCHAIN", "Failed to check key: ${e.message}", e)
27
- }
28
- }
29
-
30
- AsyncFunction("generateKeyPair") { key: String, promise: Promise ->
31
- try {
32
- val publicKey = keyManager.generateKeyPair(key)
33
- promise.resolve(mapOf("publicKey" to publicKey))
34
- } catch (e: Exception) {
35
- promise.reject("ERR_KEYCHAIN", "Failed to generate key pair: ${e.message}", e)
36
- }
37
- }
38
-
39
- AsyncFunction("getPublicKey") { key: String, promise: Promise ->
40
- try {
41
- val publicKey = keyManager.getPublicKey(key)
42
- if (publicKey == null) {
43
- promise.resolve(null)
44
- } else {
45
- promise.resolve(mapOf("publicKey" to publicKey))
46
- }
47
- } catch (e: Exception) {
48
- promise.reject("ERR_KEYCHAIN", "Failed to get public key: ${e.message}", e)
49
- }
50
- }
51
-
52
- AsyncFunction("sign") { key: String, payload: String, promise: Promise ->
53
- try {
54
- val payloadData = KeyStoreKeyManager.base64urlDecode(payload)
55
- val signature = keyManager.sign(key, payloadData)
56
- promise.resolve(mapOf("signature" to signature))
57
- } catch (e: Exception) {
58
- promise.reject("ERR_KEYCHAIN", "Failed to sign: ${e.message}", e)
59
- }
60
- }
61
-
62
- AsyncFunction("deleteKey") { key: String, promise: Promise ->
63
- try {
64
- keyManager.deleteKey(key)
65
- promise.resolve(null)
66
- } catch (e: Exception) {
67
- promise.reject("ERR_KEYCHAIN", "Failed to delete key: ${e.message}", e)
68
- }
69
- }
70
- }
71
- }
@@ -1,148 +0,0 @@
1
- package xyz.dynamic.keychain
2
-
3
- import android.content.Context
4
- import android.content.pm.PackageManager
5
- import android.os.Build
6
- import android.security.keystore.KeyGenParameterSpec
7
- import android.security.keystore.KeyProperties
8
- import android.security.keystore.StrongBoxUnavailableException
9
- import java.security.KeyPairGenerator
10
- import java.security.KeyStore
11
- import java.security.Signature
12
- import java.security.spec.ECGenParameterSpec
13
-
14
- /// Platform-agnostic Android KeyStore key manager.
15
- /// Provides P-256 key generation, signing, and management backed by hardware TEE/StrongBox.
16
- /// All public keys are returned in uncompressed SEC1 format (65 bytes: 04 || x || y).
17
- /// All binary data uses base64url encoding (RFC 4648 §5, no padding).
18
- class KeyStoreKeyManager {
19
-
20
- fun isAvailable(context: Context?): Boolean {
21
- return try {
22
- val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
23
- keyStore.load(null)
24
- val hasStrongBox = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && context != null) {
25
- context.packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
26
- } else {
27
- false
28
- }
29
- // Even without StrongBox, Android KeyStore provides TEE-backed keys on most devices
30
- hasStrongBox || Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
31
- } catch (e: Exception) {
32
- false
33
- }
34
- }
35
-
36
- fun hasKey(alias: String): Boolean {
37
- val keyStore = loadKeyStore()
38
- return keyStore.containsAlias(alias)
39
- }
40
-
41
- fun generateKeyPair(alias: String): String {
42
- val specBuilder = KeyGenParameterSpec.Builder(
43
- alias,
44
- KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
45
- )
46
- .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
47
- .setDigests(KeyProperties.DIGEST_SHA256)
48
-
49
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
50
- specBuilder.setIsStrongBoxBacked(true)
51
- }
52
-
53
- val keyPair = try {
54
- val kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, ANDROID_KEYSTORE)
55
- kpg.initialize(specBuilder.build())
56
- kpg.generateKeyPair()
57
- } catch (e: Exception) {
58
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && e is StrongBoxUnavailableException) {
59
- // Retry without StrongBox
60
- specBuilder.setIsStrongBoxBacked(false)
61
- val kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, ANDROID_KEYSTORE)
62
- kpg.initialize(specBuilder.build())
63
- kpg.generateKeyPair()
64
- } else {
65
- throw e
66
- }
67
- }
68
-
69
- val publicKeyBytes = extractUncompressedPublicKey(keyPair.public.encoded)
70
- return base64urlEncode(publicKeyBytes)
71
- }
72
-
73
- fun getPublicKey(alias: String): String? {
74
- val keyStore = loadKeyStore()
75
- val entry = keyStore.getEntry(alias, null)
76
-
77
- if (entry == null || entry !is KeyStore.PrivateKeyEntry) {
78
- return null
79
- }
80
-
81
- val publicKeyBytes = extractUncompressedPublicKey(entry.certificate.publicKey.encoded)
82
- return base64urlEncode(publicKeyBytes)
83
- }
84
-
85
- fun sign(alias: String, payload: ByteArray): String {
86
- val keyStore = loadKeyStore()
87
- val entry = keyStore.getEntry(alias, null)
88
-
89
- if (entry == null || entry !is KeyStore.PrivateKeyEntry) {
90
- throw IllegalArgumentException("Key not found: $alias")
91
- }
92
-
93
- val signature = Signature.getInstance("SHA256withECDSA")
94
- signature.initSign(entry.privateKey)
95
- signature.update(payload)
96
- val signatureBytes = signature.sign()
97
-
98
- return base64urlEncode(signatureBytes)
99
- }
100
-
101
- fun deleteKey(alias: String) {
102
- val keyStore = loadKeyStore()
103
- keyStore.deleteEntry(alias)
104
- }
105
-
106
- // region Private helpers
107
-
108
- private fun loadKeyStore(): KeyStore {
109
- val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
110
- keyStore.load(null)
111
- return keyStore
112
- }
113
-
114
- /**
115
- * Extract uncompressed SEC1 public key (65 bytes: 04 || x || y)
116
- * from X.509 SubjectPublicKeyInfo DER encoding.
117
- *
118
- * For a P-256 key, the SubjectPublicKeyInfo contains the uncompressed
119
- * point at the end of the DER structure. The point is always 65 bytes.
120
- */
121
- private fun extractUncompressedPublicKey(x509Encoded: ByteArray): ByteArray {
122
- val uncompressedPointLength = 65
123
- return x509Encoded.copyOfRange(
124
- x509Encoded.size - uncompressedPointLength,
125
- x509Encoded.size
126
- )
127
- }
128
-
129
- private fun base64urlEncode(data: ByteArray): String {
130
- return android.util.Base64.encodeToString(
131
- data,
132
- android.util.Base64.URL_SAFE or android.util.Base64.NO_WRAP or android.util.Base64.NO_PADDING
133
- )
134
- }
135
-
136
- companion object {
137
- private const val ANDROID_KEYSTORE = "AndroidKeyStore"
138
-
139
- fun base64urlDecode(input: String): ByteArray {
140
- return android.util.Base64.decode(
141
- input,
142
- android.util.Base64.URL_SAFE or android.util.Base64.NO_WRAP or android.util.Base64.NO_PADDING
143
- )
144
- }
145
- }
146
-
147
- // endregion
148
- }
@@ -1,71 +0,0 @@
1
- package xyz.dynamic.keychain
2
-
3
- import expo.modules.kotlin.modules.Module
4
- import expo.modules.kotlin.modules.ModuleDefinition
5
- import expo.modules.kotlin.Promise
6
-
7
- class KeychainModule : Module() {
8
- private val keyManager = KeyStoreKeyManager()
9
-
10
- override fun definition() = ModuleDefinition {
11
- Name("Keychain")
12
-
13
- AsyncFunction("isAvailable") { promise: Promise ->
14
- try {
15
- val context = appContext.reactContext
16
- promise.resolve(keyManager.isAvailable(context))
17
- } catch (e: Exception) {
18
- promise.resolve(false)
19
- }
20
- }
21
-
22
- AsyncFunction("hasKey") { key: String, promise: Promise ->
23
- try {
24
- promise.resolve(keyManager.hasKey(key))
25
- } catch (e: Exception) {
26
- promise.reject("ERR_KEYCHAIN", "Failed to check key: ${e.message}", e)
27
- }
28
- }
29
-
30
- AsyncFunction("generateKeyPair") { key: String, promise: Promise ->
31
- try {
32
- val publicKey = keyManager.generateKeyPair(key)
33
- promise.resolve(mapOf("publicKey" to publicKey))
34
- } catch (e: Exception) {
35
- promise.reject("ERR_KEYCHAIN", "Failed to generate key pair: ${e.message}", e)
36
- }
37
- }
38
-
39
- AsyncFunction("getPublicKey") { key: String, promise: Promise ->
40
- try {
41
- val publicKey = keyManager.getPublicKey(key)
42
- if (publicKey == null) {
43
- promise.resolve(null)
44
- } else {
45
- promise.resolve(mapOf("publicKey" to publicKey))
46
- }
47
- } catch (e: Exception) {
48
- promise.reject("ERR_KEYCHAIN", "Failed to get public key: ${e.message}", e)
49
- }
50
- }
51
-
52
- AsyncFunction("sign") { key: String, payload: String, promise: Promise ->
53
- try {
54
- val payloadData = KeyStoreKeyManager.base64urlDecode(payload)
55
- val signature = keyManager.sign(key, payloadData)
56
- promise.resolve(mapOf("signature" to signature))
57
- } catch (e: Exception) {
58
- promise.reject("ERR_KEYCHAIN", "Failed to sign: ${e.message}", e)
59
- }
60
- }
61
-
62
- AsyncFunction("deleteKey") { key: String, promise: Promise ->
63
- try {
64
- keyManager.deleteKey(key)
65
- promise.resolve(null)
66
- } catch (e: Exception) {
67
- promise.reject("ERR_KEYCHAIN", "Failed to delete key: ${e.message}", e)
68
- }
69
- }
70
- }
71
- }
@@ -1,9 +0,0 @@
1
- {
2
- "platforms": ["ios", "android"],
3
- "ios": {
4
- "modules": ["KeychainModule"]
5
- },
6
- "android": {
7
- "modules": ["xyz.dynamic.keychain.KeychainModule"]
8
- }
9
- }
@@ -1,15 +0,0 @@
1
- Pod::Spec.new do |s|
2
- s.name = 'Keychain'
3
- s.version = '1.0.0'
4
- s.summary = 'TEE-backed key operations for Dynamic SDK'
5
- s.description = 'Provides Secure Enclave key generation, signing, and management via Expo Modules'
6
- s.homepage = 'https://www.dynamic.xyz'
7
- s.license = { type: 'MIT' }
8
- s.author = 'Dynamic Labs'
9
- s.source = { git: '' }
10
- s.platform = :ios, '15.1'
11
- s.swift_version = '5.4'
12
- s.source_files = '*.swift'
13
-
14
- s.dependency 'ExpoModulesCore'
15
- end
@@ -1,39 +0,0 @@
1
- import ExpoModulesCore
2
-
3
- public class KeychainModule: Module {
4
- private let keyManager = SecureEnclaveKeyManager()
5
-
6
- public func definition() -> ModuleDefinition {
7
- Name("Keychain")
8
-
9
- AsyncFunction("isAvailable") { () -> Bool in
10
- self.keyManager.isAvailable()
11
- }
12
-
13
- AsyncFunction("hasKey") { (key: String) -> Bool in
14
- self.keyManager.hasKey(tag: key)
15
- }
16
-
17
- AsyncFunction("generateKeyPair") { (key: String) -> [String: String] in
18
- let publicKey = try self.keyManager.generateKeyPair(tag: key)
19
- return ["publicKey": publicKey]
20
- }
21
-
22
- AsyncFunction("getPublicKey") { (key: String) -> [String: String]? in
23
- guard let publicKey = try self.keyManager.getPublicKey(tag: key) else {
24
- return nil
25
- }
26
- return ["publicKey": publicKey]
27
- }
28
-
29
- AsyncFunction("sign") { (key: String, payload: String) -> [String: String] in
30
- let payloadData = base64urlDecode(payload)
31
- let signature = try self.keyManager.sign(tag: key, payload: payloadData)
32
- return ["signature": signature]
33
- }
34
-
35
- AsyncFunction("deleteKey") { (key: String) in
36
- self.keyManager.deleteKey(tag: key)
37
- }
38
- }
39
- }
@@ -1,170 +0,0 @@
1
- import Foundation
2
- import Security
3
- import CryptoKit
4
-
5
- public enum SecureEnclaveKeyManagerError: Error, LocalizedError {
6
- case generateFailed(String)
7
- case exportFailed(String)
8
- case keyNotFound(String)
9
- case signFailed(String)
10
-
11
- public var errorDescription: String? {
12
- switch self {
13
- case .generateFailed(let detail):
14
- return "Failed to generate key pair: \(detail)"
15
- case .exportFailed(let detail):
16
- return "Failed to export public key: \(detail)"
17
- case .keyNotFound(let tag):
18
- return "Key not found for tag: \(tag)"
19
- case .signFailed(let detail):
20
- return "Failed to sign: \(detail)"
21
- }
22
- }
23
- }
24
-
25
- /// Platform-agnostic Secure Enclave key manager.
26
- /// Provides P-256 key generation, signing, and management backed by the iOS Secure Enclave.
27
- /// All public keys are returned in uncompressed SEC1 format (65 bytes: 04 || x || y).
28
- /// All binary data uses base64url encoding (RFC 4648 §5, no padding).
29
- public class SecureEnclaveKeyManager {
30
-
31
- public init() {}
32
-
33
- public func isAvailable() -> Bool {
34
- if #available(iOS 13.0, *) {
35
- return SecureEnclave.isAvailable
36
- }
37
- return false
38
- }
39
-
40
- public func hasKey(tag: String) -> Bool {
41
- let query = baseQuery(for: tag, returning: true)
42
- var item: CFTypeRef?
43
- let status = SecItemCopyMatching(query as CFDictionary, &item)
44
- return status == errSecSuccess
45
- }
46
-
47
- public func generateKeyPair(tag: String) throws -> String {
48
- let tagData = tag.data(using: .utf8)!
49
-
50
- let access = SecAccessControlCreateWithFlags(
51
- kCFAllocatorDefault,
52
- kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
53
- .privateKeyUsage,
54
- nil
55
- )!
56
-
57
- let attributes: [String: Any] = [
58
- kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
59
- kSecAttrKeySizeInBits as String: 256,
60
- kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
61
- kSecPrivateKeyAttrs as String: [
62
- kSecAttrIsPermanent as String: true,
63
- kSecAttrApplicationTag as String: tagData,
64
- kSecAttrAccessControl as String: access,
65
- ] as [String: Any],
66
- ]
67
-
68
- var error: Unmanaged<CFError>?
69
- guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
70
- throw SecureEnclaveKeyManagerError.generateFailed(
71
- "\(error!.takeRetainedValue())"
72
- )
73
- }
74
-
75
- let publicKey = SecKeyCopyPublicKey(privateKey)!
76
- let publicKeyData = try exportPublicKey(publicKey)
77
- return base64urlEncode(publicKeyData)
78
- }
79
-
80
- public func getPublicKey(tag: String) throws -> String? {
81
- let query = baseQuery(for: tag, returning: true)
82
-
83
- var item: CFTypeRef?
84
- let status = SecItemCopyMatching(query as CFDictionary, &item)
85
-
86
- guard status == errSecSuccess else {
87
- return nil
88
- }
89
-
90
- let privateKey = item as! SecKey
91
- let publicKey = SecKeyCopyPublicKey(privateKey)!
92
- let publicKeyData = try exportPublicKey(publicKey)
93
- return base64urlEncode(publicKeyData)
94
- }
95
-
96
- public func sign(tag: String, payload: Data) throws -> String {
97
- let query = baseQuery(for: tag, returning: true)
98
-
99
- var item: CFTypeRef?
100
- let status = SecItemCopyMatching(query as CFDictionary, &item)
101
-
102
- guard status == errSecSuccess else {
103
- throw SecureEnclaveKeyManagerError.keyNotFound(tag)
104
- }
105
-
106
- let privateKey = item as! SecKey
107
-
108
- var error: Unmanaged<CFError>?
109
- guard let signature = SecKeyCreateSignature(
110
- privateKey,
111
- .ecdsaSignatureMessageX962SHA256,
112
- payload as CFData,
113
- &error
114
- ) else {
115
- throw SecureEnclaveKeyManagerError.signFailed(
116
- "\(error!.takeRetainedValue())"
117
- )
118
- }
119
-
120
- return base64urlEncode(signature as Data)
121
- }
122
-
123
- public func deleteKey(tag: String) {
124
- let query = baseQuery(for: tag, returning: false)
125
- SecItemDelete(query as CFDictionary)
126
- }
127
-
128
- // MARK: - Private helpers
129
-
130
- private func baseQuery(for tag: String, returning returnRef: Bool) -> [String: Any] {
131
- var query: [String: Any] = [
132
- kSecClass as String: kSecClassKey,
133
- kSecAttrApplicationTag as String: tag.data(using: .utf8)!,
134
- kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
135
- ]
136
- if returnRef {
137
- query[kSecReturnRef as String] = true
138
- }
139
- return query
140
- }
141
-
142
- private func exportPublicKey(_ publicKey: SecKey) throws -> Data {
143
- var error: Unmanaged<CFError>?
144
- guard let data = SecKeyCopyExternalRepresentation(publicKey, &error) else {
145
- throw SecureEnclaveKeyManagerError.exportFailed(
146
- "\(error!.takeRetainedValue())"
147
- )
148
- }
149
- // SecKeyCopyExternalRepresentation returns uncompressed SEC1 format (04 || x || y)
150
- return data as Data
151
- }
152
-
153
- private func base64urlEncode(_ data: Data) -> String {
154
- data.base64EncodedString()
155
- .replacingOccurrences(of: "+", with: "-")
156
- .replacingOccurrences(of: "/", with: "_")
157
- .replacingOccurrences(of: "=", with: "")
158
- }
159
- }
160
-
161
- public func base64urlDecode(_ string: String) -> Data {
162
- var base64 = string
163
- .replacingOccurrences(of: "-", with: "+")
164
- .replacingOccurrences(of: "_", with: "/")
165
- let remainder = base64.count % 4
166
- if remainder > 0 {
167
- base64 += String(repeating: "=", count: 4 - remainder)
168
- }
169
- return Data(base64Encoded: base64)!
170
- }
@@ -1 +0,0 @@
1
- export { setupKeychainHandler } from './setupKeychainHandler';
@@ -1,2 +0,0 @@
1
- import { Core } from '@dynamic-labs/client';
2
- export declare const setupKeychainHandler: (core: Core) => void;
@@ -1,16 +0,0 @@
1
- type KeychainNativeModule = {
2
- isAvailable: () => Promise<boolean>;
3
- hasKey: (key: string) => Promise<boolean>;
4
- generateKeyPair: (key: string) => Promise<{
5
- publicKey: string;
6
- }>;
7
- getPublicKey: (key: string) => Promise<{
8
- publicKey: string;
9
- } | null>;
10
- sign: (key: string, payload: string) => Promise<{
11
- signature: string;
12
- }>;
13
- deleteKey: (key: string) => Promise<void>;
14
- };
15
- export declare const getKeychain: () => KeychainNativeModule;
16
- export {};