react-native-dpop 0.1.0 → 0.3.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.
- package/README.md +21 -3
- package/{Dpop.podspec → ReactNativeDPoP.podspec} +7 -3
- package/android/build.gradle +3 -3
- package/android/src/main/java/com/{dpop → reactnativedpop}/DPoPKeyStore.kt +30 -1
- package/android/src/main/java/com/{dpop/DpopModule.kt → reactnativedpop/DPoPModule.kt} +35 -9
- package/android/src/main/java/com/{dpop/DpopPackage.kt → reactnativedpop/DPoPPackage.kt} +7 -7
- package/android/src/main/java/com/{dpop → reactnativedpop}/DPoPUtils.kt +1 -1
- package/ios/DPoPKeyStore.swift +134 -0
- package/ios/DPoPModule.h +5 -0
- package/ios/DPoPModule.swift +373 -0
- package/ios/DPoPModuleBridge.mm +93 -0
- package/ios/DPoPUtils.swift +203 -0
- package/ios/KeychainKeyStore.swift +82 -0
- package/ios/SecureEnclaveKeyStore.swift +125 -0
- package/lib/module/NativeReactNativeDPoP.js +6 -0
- package/lib/module/NativeReactNativeDPoP.js.map +1 -0
- package/lib/module/index.js +13 -33
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/{NativeDpop.d.ts → NativeReactNativeDPoP.d.ts} +1 -1
- package/lib/typescript/src/NativeReactNativeDPoP.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +15 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/{NativeDpop.ts → NativeReactNativeDPoP.ts} +5 -2
- package/src/index.tsx +29 -39
- package/ios/Dpop.h +0 -5
- package/ios/Dpop.mm +0 -21
- package/lib/module/NativeDpop.js +0 -5
- package/lib/module/NativeDpop.js.map +0 -1
- package/lib/typescript/src/NativeDpop.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -10,13 +10,12 @@ React Native library for DPoP proof generation and key management.
|
|
|
10
10
|
- Calculate JWK thumbprint (`SHA-256`, base64url).
|
|
11
11
|
- Verify if a proof is bound to a given key alias.
|
|
12
12
|
- Retrieve non-sensitive key metadata (hardware-backed, StrongBox info, etc.).
|
|
13
|
+
- iOS key storage uses Secure Enclave when available, with Keychain fallback.
|
|
13
14
|
|
|
14
15
|
## Platform Support
|
|
15
16
|
|
|
16
17
|
- Android: supported.
|
|
17
|
-
- iOS:
|
|
18
|
-
|
|
19
|
-
Current implementation throws on non-Android platforms.
|
|
18
|
+
- iOS: supported.
|
|
20
19
|
|
|
21
20
|
## Installation
|
|
22
21
|
|
|
@@ -24,6 +23,12 @@ Current implementation throws on non-Android platforms.
|
|
|
24
23
|
npm install react-native-dpop
|
|
25
24
|
```
|
|
26
25
|
|
|
26
|
+
For iOS, install pods in your app project:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
cd ios && pod install
|
|
30
|
+
```
|
|
31
|
+
|
|
27
32
|
## Quick Start
|
|
28
33
|
|
|
29
34
|
```ts
|
|
@@ -39,6 +44,7 @@ const dpop = await DPoP.generateProof({
|
|
|
39
44
|
const proof = dpop.proof;
|
|
40
45
|
const thumbprint = await dpop.calculateThumbprint();
|
|
41
46
|
const publicJwk = await dpop.getPublicKey('JWK');
|
|
47
|
+
const isBound = await dpop.isBoundToAlias();
|
|
42
48
|
```
|
|
43
49
|
|
|
44
50
|
## API
|
|
@@ -48,6 +54,7 @@ const publicJwk = await dpop.getPublicKey('JWK');
|
|
|
48
54
|
- `GenerateProofInput`
|
|
49
55
|
- `DPoPProofContext`
|
|
50
56
|
- `DPoPKeyInfo`
|
|
57
|
+
- `SecureHardwareFallbackReason = 'UNAVAILABLE' | 'PROVIDER_ERROR' | 'POLICY_REJECTED' | 'UNKNOWN'`
|
|
51
58
|
- `PublicJwk`
|
|
52
59
|
- `PublicKeyFormat = 'JWK' | 'DER' | 'RAW'`
|
|
53
60
|
|
|
@@ -78,6 +85,7 @@ const publicJwk = await dpop.getPublicKey('JWK');
|
|
|
78
85
|
Native errors are rejected with codes such as:
|
|
79
86
|
|
|
80
87
|
- `ERR_DPOP_GENERATE_PROOF`
|
|
88
|
+
- `ERR_DPOP_CALCULATE_THUMBPRINT`
|
|
81
89
|
- `ERR_DPOP_PUBLIC_KEY`
|
|
82
90
|
- `ERR_DPOP_SIGN_WITH_PRIVATE_KEY`
|
|
83
91
|
- `ERR_DPOP_HAS_KEY_PAIR`
|
|
@@ -90,6 +98,16 @@ Native errors are rejected with codes such as:
|
|
|
90
98
|
## Notes
|
|
91
99
|
|
|
92
100
|
- If no alias is provided, the default alias is `react-native-dpop`.
|
|
101
|
+
- `getKeyInfo` returns cross-platform fields and platform-specific details in `hardware`:
|
|
102
|
+
- Android: `hardware.android.strongBoxAvailable`, `hardware.android.strongBoxBacked`, `hardware.android.securityLevel`, `hardware.android.strongBoxFallbackReason`
|
|
103
|
+
- iOS: `hardware.ios.secureEnclaveAvailable`, `hardware.ios.secureEnclaveBacked`, `hardware.ios.securityLevel`, `hardware.ios.secureEnclaveFallbackReason`
|
|
104
|
+
- Fallback reasons are sanitized enums (no raw native error): `UNAVAILABLE`, `PROVIDER_ERROR`, `POLICY_REJECTED`, `UNKNOWN`.
|
|
105
|
+
- `securityLevel` semantics:
|
|
106
|
+
- `null`: no key material available (or not reported)
|
|
107
|
+
- `1`: not backed by secure enclave/strong dedicated hardware
|
|
108
|
+
- `2`: hardware-backed (iOS Secure Enclave, Android typically TEE)
|
|
109
|
+
- `3`: Android-only StrongBox (when reported by the device)
|
|
110
|
+
- On iOS, `securityLevel` is normalized by this library (`2` for Secure Enclave-backed keys, `1` for Keychain fallback), not a native Apple numeric level API.
|
|
93
111
|
- `htm` is normalized to uppercase in proof generation.
|
|
94
112
|
- `ath` is derived from `accessToken` (`SHA-256`, base64url) when provided.
|
|
95
113
|
- `jti` and `iat` are auto-generated when omitted.
|
|
@@ -3,7 +3,8 @@ require "json"
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
4
|
|
|
5
5
|
Pod::Spec.new do |s|
|
|
6
|
-
s.name = "
|
|
6
|
+
s.name = "ReactNativeDPoP"
|
|
7
|
+
s.module_name = "ReactNativeDPoP"
|
|
7
8
|
s.version = package["version"]
|
|
8
9
|
s.summary = package["description"]
|
|
9
10
|
s.homepage = package["homepage"]
|
|
@@ -14,7 +15,10 @@ Pod::Spec.new do |s|
|
|
|
14
15
|
s.source = { :git => "https://github.com/Cirilord/react-native-dpop.git", :tag => "#{s.version}" }
|
|
15
16
|
|
|
16
17
|
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
|
-
s.private_header_files = "ios/**/*.h"
|
|
18
18
|
|
|
19
|
-
install_modules_dependencies
|
|
19
|
+
if respond_to?(:install_modules_dependencies, true)
|
|
20
|
+
install_modules_dependencies(s)
|
|
21
|
+
else
|
|
22
|
+
s.dependency "React-Core"
|
|
23
|
+
end
|
|
20
24
|
end
|
package/android/build.gradle
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
buildscript {
|
|
2
|
-
ext.
|
|
2
|
+
ext.ReactNativeDPoP = [
|
|
3
3
|
kotlinVersion: "2.0.21",
|
|
4
4
|
minSdkVersion: 24,
|
|
5
5
|
compileSdkVersion: 36,
|
|
@@ -11,7 +11,7 @@ buildscript {
|
|
|
11
11
|
return rootProject.ext.get(prop)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
return
|
|
14
|
+
return ReactNativeDPoP[prop]
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
repositories {
|
|
@@ -33,7 +33,7 @@ apply plugin: "kotlin-android"
|
|
|
33
33
|
apply plugin: "com.facebook.react"
|
|
34
34
|
|
|
35
35
|
android {
|
|
36
|
-
namespace "com.
|
|
36
|
+
namespace "com.reactnativedpop"
|
|
37
37
|
|
|
38
38
|
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
39
39
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.reactnativedpop
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.content.pm.PackageManager
|
|
@@ -36,6 +36,14 @@ internal class DPoPKeyStore(private val context: Context) {
|
|
|
36
36
|
companion object {
|
|
37
37
|
private const val KEYSTORE_PROVIDER = "AndroidKeyStore"
|
|
38
38
|
private const val EC_CURVE = "secp256r1"
|
|
39
|
+
private const val META_PREFS = "react_native_dpop_keystore_meta"
|
|
40
|
+
private const val META_STRONGBOX_FALLBACK_PREFIX = "strongbox_fallback_reason_"
|
|
41
|
+
private const val REASON_UNAVAILABLE = "UNAVAILABLE"
|
|
42
|
+
private const val REASON_PROVIDER_ERROR = "PROVIDER_ERROR"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private val metadataPrefs by lazy {
|
|
46
|
+
context.getSharedPreferences(META_PREFS, Context.MODE_PRIVATE)
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
private val keyStore: KeyStore by lazy {
|
|
@@ -46,6 +54,7 @@ internal class DPoPKeyStore(private val context: Context) {
|
|
|
46
54
|
if (keyStore.containsAlias(alias)) {
|
|
47
55
|
keyStore.deleteEntry(alias)
|
|
48
56
|
}
|
|
57
|
+
clearStrongBoxFallbackReason(alias)
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
fun generateKeyPair(alias: String): Boolean {
|
|
@@ -56,16 +65,20 @@ internal class DPoPKeyStore(private val context: Context) {
|
|
|
56
65
|
if (keyStore.containsAlias(alias)) {
|
|
57
66
|
keyStore.deleteEntry(alias)
|
|
58
67
|
}
|
|
68
|
+
clearStrongBoxFallbackReason(alias)
|
|
59
69
|
|
|
60
70
|
val generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, KEYSTORE_PROVIDER)
|
|
61
71
|
if (isStrongBoxEnabled()) {
|
|
62
72
|
try {
|
|
63
73
|
generator.initialize(buildSpec(alias, useStrongBox = true))
|
|
64
74
|
generator.generateKeyPair()
|
|
75
|
+
clearStrongBoxFallbackReason(alias)
|
|
65
76
|
return true
|
|
66
77
|
} catch (_: StrongBoxUnavailableException) {
|
|
78
|
+
storeStrongBoxFallbackReason(alias, REASON_UNAVAILABLE)
|
|
67
79
|
// Fallback to hardware-backed keystore when StrongBox is unavailable.
|
|
68
80
|
} catch (_: ProviderException) {
|
|
81
|
+
storeStrongBoxFallbackReason(alias, REASON_PROVIDER_ERROR)
|
|
69
82
|
// Some devices expose StrongBox but fail during generation.
|
|
70
83
|
}
|
|
71
84
|
}
|
|
@@ -89,6 +102,12 @@ internal class DPoPKeyStore(private val context: Context) {
|
|
|
89
102
|
return privateKey != null && publicKey != null
|
|
90
103
|
}
|
|
91
104
|
|
|
105
|
+
fun isStrongBoxAvailable(): Boolean = isStrongBoxEnabled()
|
|
106
|
+
|
|
107
|
+
fun getStrongBoxFallbackReason(alias: String): String? {
|
|
108
|
+
return metadataPrefs.getString(strongBoxFallbackKey(alias), null)
|
|
109
|
+
}
|
|
110
|
+
|
|
92
111
|
fun getKeyInfo(alias: String): KeyStoreKeyInfo {
|
|
93
112
|
val keyPair = getKeyPair(alias)
|
|
94
113
|
val keyFactory = KeyFactory.getInstance(keyPair.privateKey.algorithm, KEYSTORE_PROVIDER)
|
|
@@ -165,4 +184,14 @@ internal class DPoPKeyStore(private val context: Context) {
|
|
|
165
184
|
false
|
|
166
185
|
}
|
|
167
186
|
}
|
|
187
|
+
|
|
188
|
+
private fun storeStrongBoxFallbackReason(alias: String, reason: String) {
|
|
189
|
+
metadataPrefs.edit().putString(strongBoxFallbackKey(alias), reason).apply()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private fun clearStrongBoxFallbackReason(alias: String) {
|
|
193
|
+
metadataPrefs.edit().remove(strongBoxFallbackKey(alias)).apply()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private fun strongBoxFallbackKey(alias: String): String = "$META_STRONGBOX_FALLBACK_PREFIX$alias"
|
|
168
197
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.reactnativedpop
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.Arguments
|
|
4
4
|
import com.facebook.react.bridge.Promise
|
|
@@ -8,8 +8,8 @@ import java.security.Signature
|
|
|
8
8
|
import java.util.UUID
|
|
9
9
|
import org.json.JSONObject
|
|
10
10
|
|
|
11
|
-
class
|
|
12
|
-
|
|
11
|
+
class DPoPModule(reactContext: ReactApplicationContext) :
|
|
12
|
+
NativeReactNativeDPoPSpec(reactContext) {
|
|
13
13
|
private val keyStore = DPoPKeyStore(reactContext)
|
|
14
14
|
|
|
15
15
|
private fun resolveAlias(alias: String?): String {
|
|
@@ -70,26 +70,52 @@ class DpopModule(reactContext: ReactApplicationContext) :
|
|
|
70
70
|
try {
|
|
71
71
|
val effectiveAlias = resolveAlias(alias)
|
|
72
72
|
if (!keyStore.hasKeyPair(effectiveAlias)) {
|
|
73
|
+
val fallbackReason = keyStore.getStrongBoxFallbackReason(effectiveAlias)
|
|
74
|
+
val hardwareAndroid = Arguments.createMap().apply {
|
|
75
|
+
putBoolean("strongBoxAvailable", keyStore.isStrongBoxAvailable())
|
|
76
|
+
putBoolean("strongBoxBacked", false)
|
|
77
|
+
if (fallbackReason != null) {
|
|
78
|
+
putString("strongBoxFallbackReason", fallbackReason)
|
|
79
|
+
} else {
|
|
80
|
+
putNull("strongBoxFallbackReason")
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
val hardware = Arguments.createMap().apply {
|
|
84
|
+
putMap("android", hardwareAndroid)
|
|
85
|
+
}
|
|
73
86
|
val result = Arguments.createMap().apply {
|
|
74
87
|
putString("alias", effectiveAlias)
|
|
75
88
|
putBoolean("hasKeyPair", false)
|
|
89
|
+
putMap("hardware", hardware)
|
|
76
90
|
}
|
|
77
91
|
promise.resolve(result)
|
|
78
92
|
return
|
|
79
93
|
}
|
|
80
94
|
|
|
81
95
|
val keyInfo = keyStore.getKeyInfo(effectiveAlias)
|
|
96
|
+
val fallbackReason = keyStore.getStrongBoxFallbackReason(effectiveAlias)
|
|
97
|
+
val hardwareAndroid = Arguments.createMap().apply {
|
|
98
|
+
putBoolean("strongBoxAvailable", keyInfo.strongBoxAvailable)
|
|
99
|
+
putBoolean("strongBoxBacked", keyInfo.strongBoxBacked)
|
|
100
|
+
if (keyInfo.securityLevel != null) {
|
|
101
|
+
putInt("securityLevel", keyInfo.securityLevel)
|
|
102
|
+
}
|
|
103
|
+
if (fallbackReason != null) {
|
|
104
|
+
putString("strongBoxFallbackReason", fallbackReason)
|
|
105
|
+
} else {
|
|
106
|
+
putNull("strongBoxFallbackReason")
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
val hardware = Arguments.createMap().apply {
|
|
110
|
+
putMap("android", hardwareAndroid)
|
|
111
|
+
}
|
|
82
112
|
val result = Arguments.createMap().apply {
|
|
83
113
|
putString("alias", keyInfo.alias)
|
|
84
114
|
putString("algorithm", keyInfo.algorithm)
|
|
85
115
|
putString("curve", keyInfo.curve)
|
|
86
116
|
putBoolean("hasKeyPair", true)
|
|
87
117
|
putBoolean("insideSecureHardware", keyInfo.insideSecureHardware)
|
|
88
|
-
|
|
89
|
-
putBoolean("strongBoxBacked", keyInfo.strongBoxBacked)
|
|
90
|
-
if (keyInfo.securityLevel != null) {
|
|
91
|
-
putInt("securityLevel", keyInfo.securityLevel)
|
|
92
|
-
}
|
|
118
|
+
putMap("hardware", hardware)
|
|
93
119
|
}
|
|
94
120
|
promise.resolve(result)
|
|
95
121
|
} catch (e: Exception) {
|
|
@@ -297,6 +323,6 @@ class DpopModule(reactContext: ReactApplicationContext) :
|
|
|
297
323
|
|
|
298
324
|
companion object {
|
|
299
325
|
private const val DEFAULT_ALIAS = "react-native-dpop"
|
|
300
|
-
const val NAME =
|
|
326
|
+
const val NAME = NativeReactNativeDPoPSpec.NAME
|
|
301
327
|
}
|
|
302
328
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package com.
|
|
1
|
+
package com.reactnativedpop
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.BaseReactPackage
|
|
4
4
|
import com.facebook.react.bridge.NativeModule
|
|
@@ -7,10 +7,10 @@ import com.facebook.react.module.model.ReactModuleInfo
|
|
|
7
7
|
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
8
|
import java.util.HashMap
|
|
9
9
|
|
|
10
|
-
class
|
|
10
|
+
class DPoPPackage : BaseReactPackage() {
|
|
11
11
|
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
12
|
-
return if (name ==
|
|
13
|
-
|
|
12
|
+
return if (name == DPoPModule.NAME) {
|
|
13
|
+
DPoPModule(reactContext)
|
|
14
14
|
} else {
|
|
15
15
|
null
|
|
16
16
|
}
|
|
@@ -18,9 +18,9 @@ class DpopPackage : BaseReactPackage() {
|
|
|
18
18
|
|
|
19
19
|
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
|
|
20
20
|
mapOf(
|
|
21
|
-
|
|
22
|
-
name =
|
|
23
|
-
className =
|
|
21
|
+
DPoPModule.NAME to ReactModuleInfo(
|
|
22
|
+
name = DPoPModule.NAME,
|
|
23
|
+
className = DPoPModule.NAME,
|
|
24
24
|
canOverrideExistingModule = false,
|
|
25
25
|
needsEagerInit = false,
|
|
26
26
|
isCxxModule = false,
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
|
|
4
|
+
struct DPoPKeyPairReference {
|
|
5
|
+
let privateKey: SecKey
|
|
6
|
+
let publicKey: SecKey
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
struct DPoPKeyInfo {
|
|
10
|
+
let alias: String
|
|
11
|
+
let algorithm: String
|
|
12
|
+
let curve: String
|
|
13
|
+
let hasKeyPair: Bool
|
|
14
|
+
let insideSecureHardware: Bool
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
final class DPoPKeyStore {
|
|
18
|
+
private let secureEnclave = SecureEnclaveKeyStore()
|
|
19
|
+
private let keychain = KeychainKeyStore()
|
|
20
|
+
private let fallbackReasonDefaults = UserDefaults.standard
|
|
21
|
+
private let fallbackReasonPrefix = "react_native_dpop_secure_enclave_fallback_reason_"
|
|
22
|
+
private lazy var secureEnclaveAvailable = secureEnclave.isAvailable()
|
|
23
|
+
|
|
24
|
+
func generateKeyPair(alias: String) throws {
|
|
25
|
+
try deleteKeyPair(alias: alias)
|
|
26
|
+
|
|
27
|
+
do {
|
|
28
|
+
try secureEnclave.generateKeyPair(alias: alias)
|
|
29
|
+
clearSecureEnclaveFallbackReason(alias: alias)
|
|
30
|
+
} catch {
|
|
31
|
+
storeSecureEnclaveFallbackReason(alias: alias, reason: mapSecureEnclaveFallbackReason(error))
|
|
32
|
+
try keychain.generateKeyPair(alias: alias)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func deleteKeyPair(alias: String) throws {
|
|
37
|
+
try secureEnclave.deleteKeyPair(alias: alias)
|
|
38
|
+
try keychain.deleteKeyPair(alias: alias)
|
|
39
|
+
clearSecureEnclaveFallbackReason(alias: alias)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func hasKeyPair(alias: String) -> Bool {
|
|
43
|
+
secureEnclave.hasKeyPair(alias: alias) || keychain.hasKeyPair(alias: alias)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func getKeyPair(alias: String) throws -> DPoPKeyPairReference {
|
|
47
|
+
if secureEnclave.hasKeyPair(alias: alias) {
|
|
48
|
+
return DPoPKeyPairReference(
|
|
49
|
+
privateKey: try secureEnclave.getPrivateKey(alias: alias),
|
|
50
|
+
publicKey: try secureEnclave.getPublicKey(alias: alias)
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if keychain.hasKeyPair(alias: alias) {
|
|
55
|
+
return DPoPKeyPairReference(
|
|
56
|
+
privateKey: try keychain.getPrivateKey(alias: alias),
|
|
57
|
+
publicKey: try keychain.getPublicKey(alias: alias)
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
throw DPoPError.keyNotFound(alias: alias)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func getKeyInfo(alias: String) -> DPoPKeyInfo {
|
|
65
|
+
if secureEnclave.hasKeyPair(alias: alias) {
|
|
66
|
+
return DPoPKeyInfo(
|
|
67
|
+
alias: alias,
|
|
68
|
+
algorithm: "EC",
|
|
69
|
+
curve: "P-256",
|
|
70
|
+
hasKeyPair: true,
|
|
71
|
+
insideSecureHardware: true
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if keychain.hasKeyPair(alias: alias) {
|
|
76
|
+
return DPoPKeyInfo(
|
|
77
|
+
alias: alias,
|
|
78
|
+
algorithm: "EC",
|
|
79
|
+
curve: "P-256",
|
|
80
|
+
hasKeyPair: true,
|
|
81
|
+
insideSecureHardware: false
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return DPoPKeyInfo(
|
|
86
|
+
alias: alias,
|
|
87
|
+
algorithm: "EC",
|
|
88
|
+
curve: "P-256",
|
|
89
|
+
hasKeyPair: false,
|
|
90
|
+
insideSecureHardware: false
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
func isHardwareBacked(alias: String) -> Bool {
|
|
95
|
+
secureEnclave.isHardwareBacked(alias: alias)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
func isSecureEnclaveAvailable() -> Bool {
|
|
99
|
+
secureEnclaveAvailable
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func getSecureEnclaveFallbackReason(alias: String) -> String? {
|
|
103
|
+
fallbackReasonDefaults.string(forKey: fallbackReasonKey(alias: alias))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private func storeSecureEnclaveFallbackReason(alias: String, reason: String) {
|
|
107
|
+
fallbackReasonDefaults.set(reason, forKey: fallbackReasonKey(alias: alias))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private func clearSecureEnclaveFallbackReason(alias: String) {
|
|
111
|
+
fallbackReasonDefaults.removeObject(forKey: fallbackReasonKey(alias: alias))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private func fallbackReasonKey(alias: String) -> String {
|
|
115
|
+
"\(fallbackReasonPrefix)\(alias)"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private func mapSecureEnclaveFallbackReason(_ error: Error) -> String {
|
|
119
|
+
let nsError = error as NSError
|
|
120
|
+
|
|
121
|
+
if nsError.domain == NSOSStatusErrorDomain {
|
|
122
|
+
switch nsError.code {
|
|
123
|
+
case Int(errSecNotAvailable), Int(errSecUnimplemented):
|
|
124
|
+
return "UNAVAILABLE"
|
|
125
|
+
case Int(errSecAuthFailed), Int(errSecInteractionNotAllowed), Int(errSecUserCanceled):
|
|
126
|
+
return "POLICY_REJECTED"
|
|
127
|
+
default:
|
|
128
|
+
return "PROVIDER_ERROR"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return "UNKNOWN"
|
|
133
|
+
}
|
|
134
|
+
}
|