react-native-dpop 0.3.0 → 0.4.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 +97 -47
- package/ReactNativeDPoP.podspec +1 -1
- package/android/src/main/java/com/reactnativedpop/DPoPKeyStore.kt +20 -0
- package/android/src/main/java/com/reactnativedpop/DPoPModule.kt +35 -8
- package/android/src/main/java/com/reactnativedpop/DPoPPackage.kt +7 -7
- package/ios/DPoPKeyStore.swift +10 -1
- package/ios/DPoPModule.swift +43 -5
- package/ios/DPoPModuleBridge.mm +1 -1
- package/lib/module/NativeReactNativeDPoP.js +3 -1
- package/lib/module/NativeReactNativeDPoP.js.map +1 -1
- package/lib/module/index.js +3 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/examples/shared/DPoPExampleContent.d.ts +2 -0
- package/lib/typescript/examples/shared/DPoPExampleContent.d.ts.map +1 -0
- package/lib/typescript/examples/v0.75/App.d.ts +2 -0
- package/lib/typescript/examples/v0.75/App.d.ts.map +1 -0
- package/lib/typescript/examples/v0.83/App.d.ts +2 -0
- package/lib/typescript/examples/v0.83/App.d.ts.map +1 -0
- package/lib/typescript/src/NativeReactNativeDPoP.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +5 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +8 -4
- package/src/NativeReactNativeDPoP.ts +2 -1
- package/src/index.tsx +7 -2
package/README.md
CHANGED
|
@@ -4,18 +4,19 @@ React Native library for DPoP proof generation and key management.
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- Generate DPoP proofs (`dpop+jwt`) signed with ES256
|
|
8
|
-
- Manage key pairs in the
|
|
9
|
-
- Export public key
|
|
10
|
-
- Calculate JWK
|
|
11
|
-
- Verify
|
|
12
|
-
- Retrieve non-sensitive key metadata
|
|
13
|
-
-
|
|
7
|
+
- Generate DPoP proofs (`dpop+jwt`) signed with ES256
|
|
8
|
+
- Manage key pairs in the platform keystore
|
|
9
|
+
- Export the public key as `JWK`, `DER`, or `RAW`
|
|
10
|
+
- Calculate JWK thumbprints (`SHA-256`, base64url)
|
|
11
|
+
- Verify whether a proof is bound to a given key alias
|
|
12
|
+
- Retrieve non-sensitive key metadata, including secure hardware details
|
|
13
|
+
- Use Secure Enclave on iOS when available, with Keychain fallback
|
|
14
|
+
- Prefer StrongBox on Android when available, with hardware-backed fallback
|
|
14
15
|
|
|
15
|
-
## Platform
|
|
16
|
+
## Platform support
|
|
16
17
|
|
|
17
|
-
- Android
|
|
18
|
-
- iOS
|
|
18
|
+
- Android
|
|
19
|
+
- iOS
|
|
19
20
|
|
|
20
21
|
## Installation
|
|
21
22
|
|
|
@@ -23,13 +24,13 @@ React Native library for DPoP proof generation and key management.
|
|
|
23
24
|
npm install react-native-dpop
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
For iOS
|
|
27
|
+
For iOS:
|
|
27
28
|
|
|
28
29
|
```sh
|
|
29
30
|
cd ios && pod install
|
|
30
31
|
```
|
|
31
32
|
|
|
32
|
-
## Quick
|
|
33
|
+
## Quick start
|
|
33
34
|
|
|
34
35
|
```ts
|
|
35
36
|
import { DPoP } from 'react-native-dpop';
|
|
@@ -38,27 +39,18 @@ const dpop = await DPoP.generateProof({
|
|
|
38
39
|
htu: 'https://api.example.com/token',
|
|
39
40
|
htm: 'POST',
|
|
40
41
|
accessToken: 'ACCESS_TOKEN',
|
|
41
|
-
nonce: '
|
|
42
|
+
nonce: 'SERVER_NONCE',
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
const proof = dpop.proof;
|
|
45
46
|
const thumbprint = await dpop.calculateThumbprint();
|
|
46
47
|
const publicJwk = await dpop.getPublicKey('JWK');
|
|
47
|
-
const
|
|
48
|
+
const keyInfo = await DPoP.getKeyInfo();
|
|
48
49
|
```
|
|
49
50
|
|
|
50
51
|
## API
|
|
51
52
|
|
|
52
|
-
###
|
|
53
|
-
|
|
54
|
-
- `GenerateProofInput`
|
|
55
|
-
- `DPoPProofContext`
|
|
56
|
-
- `DPoPKeyInfo`
|
|
57
|
-
- `SecureHardwareFallbackReason = 'UNAVAILABLE' | 'PROVIDER_ERROR' | 'POLICY_REJECTED' | 'UNKNOWN'`
|
|
58
|
-
- `PublicJwk`
|
|
59
|
-
- `PublicKeyFormat = 'JWK' | 'DER' | 'RAW'`
|
|
60
|
-
|
|
61
|
-
### `DPoP` static methods
|
|
53
|
+
### Static methods
|
|
62
54
|
|
|
63
55
|
- `DPoP.generateProof(input): Promise<DPoP>`
|
|
64
56
|
- `DPoP.assertHardwareBacked(alias?): Promise<void>`
|
|
@@ -67,22 +59,97 @@ const isBound = await dpop.isBoundToAlias();
|
|
|
67
59
|
- `DPoP.hasKeyPair(alias?): Promise<boolean>`
|
|
68
60
|
- `DPoP.rotateKeyPair(alias?): Promise<void>`
|
|
69
61
|
|
|
70
|
-
###
|
|
62
|
+
### Instance members
|
|
71
63
|
|
|
72
64
|
- `proof: string`
|
|
73
65
|
- `proofContext: DPoPProofContext`
|
|
74
66
|
- `alias?: string`
|
|
75
|
-
|
|
76
|
-
### `DPoP` instance methods
|
|
77
|
-
|
|
78
67
|
- `calculateThumbprint(): Promise<string>`
|
|
79
68
|
- `getPublicKey(format): Promise<PublicJwk | string>`
|
|
80
69
|
- `signWithDpopPrivateKey(payload): Promise<string>`
|
|
81
70
|
- `isBoundToAlias(alias?): Promise<boolean>`
|
|
82
71
|
|
|
83
|
-
|
|
72
|
+
### Main types
|
|
84
73
|
|
|
85
|
-
|
|
74
|
+
- `GenerateProofInput`
|
|
75
|
+
- `DPoPProofContext`
|
|
76
|
+
- `DPoPKeyInfo`
|
|
77
|
+
- `PublicJwk`
|
|
78
|
+
- `PublicKeyFormat = 'JWK' | 'DER' | 'RAW'`
|
|
79
|
+
- `SecureHardwareFallbackReason = 'UNAVAILABLE' | 'PROVIDER_ERROR' | 'POLICY_REJECTED' | 'UNKNOWN'`
|
|
80
|
+
- `AndroidSecurityLevelName = 'SOFTWARE' | 'TRUSTED_ENVIRONMENT' | 'STRONGBOX'`
|
|
81
|
+
- `IOSSecurityLevelName = 'SOFTWARE' | 'SECURE_ENCLAVE'`
|
|
82
|
+
|
|
83
|
+
## `getKeyInfo()`
|
|
84
|
+
|
|
85
|
+
`getKeyInfo()` returns shared fields plus platform-specific hardware metadata.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
type DPoPKeyInfo = {
|
|
89
|
+
alias: string;
|
|
90
|
+
hasKeyPair: boolean;
|
|
91
|
+
algorithm?: string;
|
|
92
|
+
curve?: string;
|
|
93
|
+
insideSecureHardware?: boolean;
|
|
94
|
+
hardware?: {
|
|
95
|
+
android?: {
|
|
96
|
+
strongBoxAvailable: boolean;
|
|
97
|
+
strongBoxBacked: boolean;
|
|
98
|
+
securityLevel?: number;
|
|
99
|
+
securityLevelName?: 'SOFTWARE' | 'TRUSTED_ENVIRONMENT' | 'STRONGBOX';
|
|
100
|
+
strongBoxFallbackReason?: 'UNAVAILABLE' | 'PROVIDER_ERROR' | 'POLICY_REJECTED' | 'UNKNOWN' | null;
|
|
101
|
+
};
|
|
102
|
+
ios?: {
|
|
103
|
+
secureEnclaveAvailable: boolean;
|
|
104
|
+
secureEnclaveBacked: boolean;
|
|
105
|
+
securityLevel?: number | null;
|
|
106
|
+
securityLevelName?: 'SOFTWARE' | 'SECURE_ENCLAVE';
|
|
107
|
+
secureEnclaveFallbackReason?: 'UNAVAILABLE' | 'PROVIDER_ERROR' | 'POLICY_REJECTED' | 'UNKNOWN' | null;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Security level semantics
|
|
114
|
+
|
|
115
|
+
- `securityLevel = 1`
|
|
116
|
+
Software-backed key material
|
|
117
|
+
- `securityLevel = 2`
|
|
118
|
+
Hardware-backed key material
|
|
119
|
+
On Android this usually means TEE
|
|
120
|
+
On iOS this means Secure Enclave
|
|
121
|
+
- `securityLevel = 3`
|
|
122
|
+
Android StrongBox-backed key
|
|
123
|
+
- `securityLevel = null`
|
|
124
|
+
No key material available, or the native platform did not report a numeric level
|
|
125
|
+
|
|
126
|
+
### Fallback semantics
|
|
127
|
+
|
|
128
|
+
- On Android, the library tries StrongBox first when available
|
|
129
|
+
- On iOS, the library tries Secure Enclave first when available
|
|
130
|
+
- Fallback reasons are sanitized enums rather than raw native errors
|
|
131
|
+
- On iOS Simulator, `secureEnclaveFallbackReason` is expected to be `UNAVAILABLE`
|
|
132
|
+
|
|
133
|
+
## Notes
|
|
134
|
+
|
|
135
|
+
- Default alias: `react-native-dpop`
|
|
136
|
+
- `htm` is normalized to uppercase
|
|
137
|
+
- `ath` is derived from `accessToken` when provided
|
|
138
|
+
- `jti` and `iat` are auto-generated when omitted
|
|
139
|
+
- For React Native 0.75 on Android, the library ensures `iat` is sent as a number to avoid an older bridge nullability issue with `Double`
|
|
140
|
+
|
|
141
|
+
## Example apps
|
|
142
|
+
|
|
143
|
+
This repository includes two example apps:
|
|
144
|
+
|
|
145
|
+
- `examples/v0.75`
|
|
146
|
+
- `examples/v0.83`
|
|
147
|
+
|
|
148
|
+
The root `example` script points to `examples/v0.83`.
|
|
149
|
+
|
|
150
|
+
## Errors
|
|
151
|
+
|
|
152
|
+
Native rejections use codes such as:
|
|
86
153
|
|
|
87
154
|
- `ERR_DPOP_GENERATE_PROOF`
|
|
88
155
|
- `ERR_DPOP_CALCULATE_THUMBPRINT`
|
|
@@ -95,23 +162,6 @@ Native errors are rejected with codes such as:
|
|
|
95
162
|
- `ERR_DPOP_ASSERT_HARDWARE_BACKED`
|
|
96
163
|
- `ERR_DPOP_IS_BOUND_TO_ALIAS`
|
|
97
164
|
|
|
98
|
-
## Notes
|
|
99
|
-
|
|
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.
|
|
111
|
-
- `htm` is normalized to uppercase in proof generation.
|
|
112
|
-
- `ath` is derived from `accessToken` (`SHA-256`, base64url) when provided.
|
|
113
|
-
- `jti` and `iat` are auto-generated when omitted.
|
|
114
|
-
|
|
115
165
|
## Contributing
|
|
116
166
|
|
|
117
167
|
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
package/ReactNativeDPoP.podspec
CHANGED
|
@@ -11,7 +11,7 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.license = package["license"]
|
|
12
12
|
s.authors = package["author"]
|
|
13
13
|
|
|
14
|
-
s.platforms = { :ios =>
|
|
14
|
+
s.platforms = { :ios => "14.0" }
|
|
15
15
|
s.source = { :git => "https://github.com/Cirilord/react-native-dpop.git", :tag => "#{s.version}" }
|
|
16
16
|
|
|
17
17
|
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
@@ -28,6 +28,7 @@ internal data class KeyStoreKeyInfo(
|
|
|
28
28
|
val curve: String,
|
|
29
29
|
val insideSecureHardware: Boolean,
|
|
30
30
|
val securityLevel: Int?,
|
|
31
|
+
val securityLevelName: String,
|
|
31
32
|
val strongBoxAvailable: Boolean,
|
|
32
33
|
val strongBoxBacked: Boolean
|
|
33
34
|
)
|
|
@@ -119,6 +120,7 @@ internal class DPoPKeyStore(private val context: Context) {
|
|
|
119
120
|
curve = "P-256",
|
|
120
121
|
insideSecureHardware = keyInfo.isInsideSecureHardware,
|
|
121
122
|
securityLevel = readSecurityLevel(keyInfo),
|
|
123
|
+
securityLevelName = readSecurityLevelName(keyInfo),
|
|
122
124
|
strongBoxAvailable = isStrongBoxEnabled(),
|
|
123
125
|
strongBoxBacked = readStrongBoxBacked(keyInfo)
|
|
124
126
|
)
|
|
@@ -172,6 +174,24 @@ internal class DPoPKeyStore(private val context: Context) {
|
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
176
|
|
|
177
|
+
private fun readSecurityLevelName(keyInfo: KeyInfo): String {
|
|
178
|
+
val strongBoxBacked = readStrongBoxBacked(keyInfo)
|
|
179
|
+
val securityLevel = readSecurityLevel(keyInfo)
|
|
180
|
+
|
|
181
|
+
return when (securityLevel) {
|
|
182
|
+
1 -> "SOFTWARE"
|
|
183
|
+
2 -> "TRUSTED_ENVIRONMENT"
|
|
184
|
+
3 -> "STRONGBOX"
|
|
185
|
+
else -> {
|
|
186
|
+
when {
|
|
187
|
+
strongBoxBacked -> "STRONGBOX"
|
|
188
|
+
keyInfo.isInsideSecureHardware -> "TRUSTED_ENVIRONMENT"
|
|
189
|
+
else -> "SOFTWARE"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
175
195
|
private fun readStrongBoxBacked(keyInfo: KeyInfo): Boolean {
|
|
176
196
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
|
177
197
|
return false
|
|
@@ -12,10 +12,32 @@ class DPoPModule(reactContext: ReactApplicationContext) :
|
|
|
12
12
|
NativeReactNativeDPoPSpec(reactContext) {
|
|
13
13
|
private val keyStore = DPoPKeyStore(reactContext)
|
|
14
14
|
|
|
15
|
+
companion object {
|
|
16
|
+
private const val DEFAULT_ALIAS = "react-native-dpop"
|
|
17
|
+
const val NAME = NativeReactNativeDPoPSpec.NAME
|
|
18
|
+
private const val UNKNOWN_STRONGBOX_FALLBACK_REASON = "UNKNOWN"
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
private fun resolveAlias(alias: String?): String {
|
|
16
22
|
return alias ?: DEFAULT_ALIAS
|
|
17
23
|
}
|
|
18
24
|
|
|
25
|
+
private fun resolveStrongBoxFallbackReason(
|
|
26
|
+
strongBoxAvailable: Boolean,
|
|
27
|
+
strongBoxBacked: Boolean,
|
|
28
|
+
fallbackReason: String?
|
|
29
|
+
): String? {
|
|
30
|
+
if (fallbackReason != null) {
|
|
31
|
+
return fallbackReason
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return if (strongBoxAvailable && !strongBoxBacked) {
|
|
35
|
+
UNKNOWN_STRONGBOX_FALLBACK_REASON
|
|
36
|
+
} else {
|
|
37
|
+
null
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
19
41
|
override fun assertHardwareBacked(alias: String?, promise: Promise) {
|
|
20
42
|
try {
|
|
21
43
|
val effectiveAlias = resolveAlias(alias)
|
|
@@ -70,9 +92,14 @@ class DPoPModule(reactContext: ReactApplicationContext) :
|
|
|
70
92
|
try {
|
|
71
93
|
val effectiveAlias = resolveAlias(alias)
|
|
72
94
|
if (!keyStore.hasKeyPair(effectiveAlias)) {
|
|
73
|
-
val
|
|
95
|
+
val strongBoxAvailable = keyStore.isStrongBoxAvailable()
|
|
96
|
+
val fallbackReason = resolveStrongBoxFallbackReason(
|
|
97
|
+
strongBoxAvailable = strongBoxAvailable,
|
|
98
|
+
strongBoxBacked = false,
|
|
99
|
+
fallbackReason = keyStore.getStrongBoxFallbackReason(effectiveAlias)
|
|
100
|
+
)
|
|
74
101
|
val hardwareAndroid = Arguments.createMap().apply {
|
|
75
|
-
putBoolean("strongBoxAvailable",
|
|
102
|
+
putBoolean("strongBoxAvailable", strongBoxAvailable)
|
|
76
103
|
putBoolean("strongBoxBacked", false)
|
|
77
104
|
if (fallbackReason != null) {
|
|
78
105
|
putString("strongBoxFallbackReason", fallbackReason)
|
|
@@ -93,13 +120,18 @@ class DPoPModule(reactContext: ReactApplicationContext) :
|
|
|
93
120
|
}
|
|
94
121
|
|
|
95
122
|
val keyInfo = keyStore.getKeyInfo(effectiveAlias)
|
|
96
|
-
val fallbackReason =
|
|
123
|
+
val fallbackReason = resolveStrongBoxFallbackReason(
|
|
124
|
+
strongBoxAvailable = keyInfo.strongBoxAvailable,
|
|
125
|
+
strongBoxBacked = keyInfo.strongBoxBacked,
|
|
126
|
+
fallbackReason = keyStore.getStrongBoxFallbackReason(effectiveAlias)
|
|
127
|
+
)
|
|
97
128
|
val hardwareAndroid = Arguments.createMap().apply {
|
|
98
129
|
putBoolean("strongBoxAvailable", keyInfo.strongBoxAvailable)
|
|
99
130
|
putBoolean("strongBoxBacked", keyInfo.strongBoxBacked)
|
|
100
131
|
if (keyInfo.securityLevel != null) {
|
|
101
132
|
putInt("securityLevel", keyInfo.securityLevel)
|
|
102
133
|
}
|
|
134
|
+
putString("securityLevelName", keyInfo.securityLevelName)
|
|
103
135
|
if (fallbackReason != null) {
|
|
104
136
|
putString("strongBoxFallbackReason", fallbackReason)
|
|
105
137
|
} else {
|
|
@@ -320,9 +352,4 @@ class DPoPModule(reactContext: ReactApplicationContext) :
|
|
|
320
352
|
promise.reject("ERR_DPOP_GENERATE_PROOF", e.message, e)
|
|
321
353
|
}
|
|
322
354
|
}
|
|
323
|
-
|
|
324
|
-
companion object {
|
|
325
|
-
private const val DEFAULT_ALIAS = "react-native-dpop"
|
|
326
|
-
const val NAME = NativeReactNativeDPoPSpec.NAME
|
|
327
|
-
}
|
|
328
355
|
}
|
|
@@ -5,7 +5,6 @@ import com.facebook.react.bridge.NativeModule
|
|
|
5
5
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
6
|
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
7
|
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
-
import java.util.HashMap
|
|
9
8
|
|
|
10
9
|
class DPoPPackage : BaseReactPackage() {
|
|
11
10
|
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
@@ -19,12 +18,13 @@ class DPoPPackage : BaseReactPackage() {
|
|
|
19
18
|
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
|
|
20
19
|
mapOf(
|
|
21
20
|
DPoPModule.NAME to ReactModuleInfo(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
DPoPModule.NAME,
|
|
22
|
+
DPoPModule::class.java.name,
|
|
23
|
+
false,
|
|
24
|
+
false,
|
|
25
|
+
false,
|
|
26
|
+
false,
|
|
27
|
+
true
|
|
28
28
|
)
|
|
29
29
|
)
|
|
30
30
|
}
|
package/ios/DPoPKeyStore.swift
CHANGED
|
@@ -19,6 +19,7 @@ final class DPoPKeyStore {
|
|
|
19
19
|
private let keychain = KeychainKeyStore()
|
|
20
20
|
private let fallbackReasonDefaults = UserDefaults.standard
|
|
21
21
|
private let fallbackReasonPrefix = "react_native_dpop_secure_enclave_fallback_reason_"
|
|
22
|
+
private let unavailableFallbackReason = "UNAVAILABLE"
|
|
22
23
|
private lazy var secureEnclaveAvailable = secureEnclave.isAvailable()
|
|
23
24
|
|
|
24
25
|
func generateKeyPair(alias: String) throws {
|
|
@@ -100,7 +101,15 @@ final class DPoPKeyStore {
|
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
func getSecureEnclaveFallbackReason(alias: String) -> String? {
|
|
103
|
-
|
|
104
|
+
if !secureEnclaveAvailable && keychain.hasKeyPair(alias: alias) {
|
|
105
|
+
return unavailableFallbackReason
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if let storedReason = fallbackReasonDefaults.string(forKey: fallbackReasonKey(alias: alias)) {
|
|
109
|
+
return storedReason
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return nil
|
|
104
113
|
}
|
|
105
114
|
|
|
106
115
|
private func storeSecureEnclaveFallbackReason(alias: String, reason: String) {
|
package/ios/DPoPModule.swift
CHANGED
|
@@ -7,6 +7,8 @@ final class DPoPModule {
|
|
|
7
7
|
|
|
8
8
|
private let keyStore = DPoPKeyStore()
|
|
9
9
|
private let defaultAlias = "react-native-dpop"
|
|
10
|
+
private let unknownSecureEnclaveFallbackReason = "UNKNOWN"
|
|
11
|
+
private let unavailableSecureEnclaveFallbackReason = "UNAVAILABLE"
|
|
10
12
|
|
|
11
13
|
private init() {}
|
|
12
14
|
|
|
@@ -45,9 +47,15 @@ final class DPoPModule {
|
|
|
45
47
|
func getKeyInfo(alias: String?) -> [String: Any] {
|
|
46
48
|
let effectiveAlias = resolveAlias(alias)
|
|
47
49
|
let secureEnclaveAvailable = keyStore.isSecureEnclaveAvailable()
|
|
48
|
-
let fallbackReason = keyStore.getSecureEnclaveFallbackReason(alias: effectiveAlias)
|
|
49
|
-
let secureEnclaveFallbackReason: Any = fallbackReason ?? NSNull()
|
|
50
50
|
let keyInfo = keyStore.getKeyInfo(alias: effectiveAlias)
|
|
51
|
+
let secureEnclaveBacked = secureEnclaveAvailable && keyInfo.insideSecureHardware
|
|
52
|
+
let fallbackReason = resolveSecureEnclaveFallbackReason(
|
|
53
|
+
secureEnclaveAvailable: secureEnclaveAvailable,
|
|
54
|
+
secureEnclaveBacked: secureEnclaveBacked,
|
|
55
|
+
hasKeyPair: keyInfo.hasKeyPair,
|
|
56
|
+
fallbackReason: keyStore.getSecureEnclaveFallbackReason(alias: effectiveAlias)
|
|
57
|
+
)
|
|
58
|
+
let secureEnclaveFallbackReason: Any = fallbackReason ?? NSNull()
|
|
51
59
|
|
|
52
60
|
if !keyInfo.hasKeyPair {
|
|
53
61
|
return [
|
|
@@ -58,14 +66,15 @@ final class DPoPModule {
|
|
|
58
66
|
"secureEnclaveAvailable": secureEnclaveAvailable,
|
|
59
67
|
"secureEnclaveBacked": false,
|
|
60
68
|
"securityLevel": NSNull(),
|
|
69
|
+
"securityLevelName": "SOFTWARE",
|
|
61
70
|
"secureEnclaveFallbackReason": secureEnclaveFallbackReason
|
|
62
71
|
]
|
|
63
72
|
]
|
|
64
73
|
]
|
|
65
74
|
}
|
|
66
75
|
|
|
67
|
-
let secureEnclaveBacked = secureEnclaveAvailable && keyInfo.insideSecureHardware
|
|
68
76
|
let securityLevel = secureEnclaveBacked ? 2 : 1
|
|
77
|
+
let securityLevelName = secureEnclaveBacked ? "SECURE_ENCLAVE" : "SOFTWARE"
|
|
69
78
|
|
|
70
79
|
return [
|
|
71
80
|
"alias": keyInfo.alias,
|
|
@@ -78,12 +87,34 @@ final class DPoPModule {
|
|
|
78
87
|
"secureEnclaveAvailable": secureEnclaveAvailable,
|
|
79
88
|
"secureEnclaveBacked": secureEnclaveBacked,
|
|
80
89
|
"securityLevel": securityLevel,
|
|
90
|
+
"securityLevelName": securityLevelName,
|
|
81
91
|
"secureEnclaveFallbackReason": secureEnclaveFallbackReason
|
|
82
92
|
]
|
|
83
93
|
]
|
|
84
94
|
]
|
|
85
95
|
}
|
|
86
96
|
|
|
97
|
+
private func resolveSecureEnclaveFallbackReason(
|
|
98
|
+
secureEnclaveAvailable: Bool,
|
|
99
|
+
secureEnclaveBacked: Bool,
|
|
100
|
+
hasKeyPair: Bool,
|
|
101
|
+
fallbackReason: String?
|
|
102
|
+
) -> String? {
|
|
103
|
+
if let fallbackReason {
|
|
104
|
+
return fallbackReason
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if hasKeyPair && !secureEnclaveAvailable {
|
|
108
|
+
return unavailableSecureEnclaveFallbackReason
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if hasKeyPair && !secureEnclaveBacked {
|
|
112
|
+
return unknownSecureEnclaveFallbackReason
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return nil
|
|
116
|
+
}
|
|
117
|
+
|
|
87
118
|
func getPublicKeyDer(alias: String?) throws -> String {
|
|
88
119
|
let effectiveAlias = resolveAlias(alias)
|
|
89
120
|
if !keyStore.hasKeyPair(alias: effectiveAlias) {
|
|
@@ -347,12 +378,19 @@ final class DPoPModule {
|
|
|
347
378
|
additional: [String: Any]?,
|
|
348
379
|
kid: String?,
|
|
349
380
|
jti: String?,
|
|
350
|
-
iat:
|
|
381
|
+
iat: Any?,
|
|
351
382
|
alias: String?,
|
|
352
383
|
resolve: @escaping RCTPromiseResolveBlock,
|
|
353
384
|
reject: @escaping RCTPromiseRejectBlock
|
|
354
385
|
) {
|
|
355
386
|
do {
|
|
387
|
+
let normalizedIat: NSNumber?
|
|
388
|
+
if iat is NSNull {
|
|
389
|
+
normalizedIat = nil
|
|
390
|
+
} else {
|
|
391
|
+
normalizedIat = iat as? NSNumber
|
|
392
|
+
}
|
|
393
|
+
|
|
356
394
|
resolve(
|
|
357
395
|
try DPoPModule.shared.generateProof(
|
|
358
396
|
htu: htu,
|
|
@@ -362,7 +400,7 @@ final class DPoPModule {
|
|
|
362
400
|
additional: additional,
|
|
363
401
|
kid: kid,
|
|
364
402
|
jti: jti,
|
|
365
|
-
iat:
|
|
403
|
+
iat: normalizedIat,
|
|
366
404
|
alias: alias
|
|
367
405
|
)
|
|
368
406
|
)
|
package/ios/DPoPModuleBridge.mm
CHANGED
|
@@ -70,7 +70,7 @@ RCT_EXTERN_METHOD(generateProof:(NSString *)htu
|
|
|
70
70
|
additional:(NSDictionary * _Nullable)additional
|
|
71
71
|
kid:(NSString * _Nullable)kid
|
|
72
72
|
jti:(NSString * _Nullable)jti
|
|
73
|
-
iat:(
|
|
73
|
+
iat:(id _Nullable)iat
|
|
74
74
|
alias:(NSString * _Nullable)alias
|
|
75
75
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
76
76
|
reject:(RCTPromiseRejectBlock)reject)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { NativeModules, TurboModuleRegistry } from 'react-native';
|
|
4
|
-
const nativeDpopModule =
|
|
4
|
+
const nativeDpopModule =
|
|
5
|
+
// eslint-disable-next-line dot-notation -- required by noPropertyAccessFromIndexSignature from @tsconfig/strictest
|
|
6
|
+
TurboModuleRegistry.get('ReactNativeDPoP') ?? NativeModules['ReactNativeDPoP'];
|
|
5
7
|
export default nativeDpopModule;
|
|
6
8
|
//# sourceMappingURL=NativeReactNativeDPoP.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NativeModules","TurboModuleRegistry","nativeDpopModule","get"
|
|
1
|
+
{"version":3,"names":["NativeModules","TurboModuleRegistry","nativeDpopModule","get"],"sourceRoot":"../../src","sources":["NativeReactNativeDPoP.ts"],"mappings":";;AACA,SAASA,aAAa,EAAEC,mBAAmB,QAAQ,cAAc;AA4BjE,MAAMC,gBAAgB;AACpB;AACAD,mBAAmB,CAACE,GAAG,CAAO,iBAAiB,CAAC,IAAKH,aAAa,CAAC,iBAAiB,CAAsB;AAE5G,eAAeE,gBAAgB","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -26,7 +26,9 @@ export class DPoP {
|
|
|
26
26
|
return NativeReactNativeDPoP.isBoundToAlias(this.proof, alias ?? this.alias ?? null);
|
|
27
27
|
}
|
|
28
28
|
static async generateProof(input) {
|
|
29
|
-
const result = await NativeReactNativeDPoP.generateProof(input.htu, input.htm, input.nonce ?? null, input.accessToken ?? null, input.additional ?? null, input.kid ?? null, input.jti ?? null,
|
|
29
|
+
const result = await NativeReactNativeDPoP.generateProof(input.htu, input.htm, input.nonce ?? null, input.accessToken ?? null, input.additional ?? null, input.kid ?? null, input.jti ?? null,
|
|
30
|
+
// RN 0.75 Android bridge can crash when a nullable Double arrives as null.
|
|
31
|
+
input.iat ?? Math.floor(Date.now() / 1000), input.alias ?? null);
|
|
30
32
|
return new DPoP(result.proof, result.proofContext, input.alias);
|
|
31
33
|
}
|
|
32
34
|
static async assertHardwareBacked(alias) {
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NativeReactNativeDPoP","DPoP","constructor","proof","proofContext","alias","calculateThumbprint","getPublicKey","format","getPublicKeyDer","getPublicKeyRaw","getPublicKeyJwk","signWithDpopPrivateKey","payload","isBoundToAlias","generateProof","input","result","htu","htm","nonce","accessToken","additional","kid","jti","iat","assertHardwareBacked","deleteKeyPair","getKeyInfo","hasKeyPair","rotateKeyPair"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,qBAAqB,MAAM,4BAAyB;
|
|
1
|
+
{"version":3,"names":["NativeReactNativeDPoP","DPoP","constructor","proof","proofContext","alias","calculateThumbprint","getPublicKey","format","getPublicKeyDer","getPublicKeyRaw","getPublicKeyJwk","signWithDpopPrivateKey","payload","isBoundToAlias","generateProof","input","result","htu","htm","nonce","accessToken","additional","kid","jti","iat","Math","floor","Date","now","assertHardwareBacked","deleteKeyPair","getKeyInfo","hasKeyPair","rotateKeyPair"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,qBAAqB,MAAM,4BAAyB;AAqE3D,OAAO,MAAMC,IAAI,CAAC;EAKRC,WAAWA,CAACC,KAAa,EAAEC,YAA8B,EAAEC,KAAc,EAAE;IACjF,IAAI,CAACF,KAAK,GAAGA,KAAK;IAClB,IAAI,CAACC,YAAY,GAAGA,YAAY;IAChC,IAAI,CAACC,KAAK,GAAGA,KAAK;EACpB;EAEA,MAAaC,mBAAmBA,CAAA,EAAoB;IAClD,OAAON,qBAAqB,CAACM,mBAAmB,CAAC,IAAI,CAACD,KAAK,IAAI,IAAI,CAAC;EACtE;EAEA,MAAaE,YAAYA,CAACC,MAAuB,EAA+B;IAC9E,IAAIA,MAAM,KAAK,KAAK,EAAE;MACpB,OAAOR,qBAAqB,CAACS,eAAe,CAAC,IAAI,CAACJ,KAAK,IAAI,IAAI,CAAC;IAClE;IACA,IAAIG,MAAM,KAAK,KAAK,EAAE;MACpB,OAAOR,qBAAqB,CAACU,eAAe,CAAC,IAAI,CAACL,KAAK,IAAI,IAAI,CAAC;IAClE;IAEA,OAAOL,qBAAqB,CAACW,eAAe,CAAC,IAAI,CAACN,KAAK,IAAI,IAAI,CAAC;EAClE;EAEA,MAAaO,sBAAsBA,CAACC,OAAe,EAAmB;IACpE,OAAOb,qBAAqB,CAACY,sBAAsB,CAACC,OAAO,EAAE,IAAI,CAACR,KAAK,IAAI,IAAI,CAAC;EAClF;EAEA,MAAaS,cAAcA,CAACT,KAAc,EAAoB;IAC5D,OAAOL,qBAAqB,CAACc,cAAc,CAAC,IAAI,CAACX,KAAK,EAAEE,KAAK,IAAI,IAAI,CAACA,KAAK,IAAI,IAAI,CAAC;EACtF;EAEA,aAAoBU,aAAaA,CAACC,KAAyB,EAAiB;IAC1E,MAAMC,MAAM,GAAI,MAAMjB,qBAAqB,CAACe,aAAa,CACvDC,KAAK,CAACE,GAAG,EACTF,KAAK,CAACG,GAAG,EACTH,KAAK,CAACI,KAAK,IAAI,IAAI,EACnBJ,KAAK,CAACK,WAAW,IAAI,IAAI,EACzBL,KAAK,CAACM,UAAU,IAAI,IAAI,EACxBN,KAAK,CAACO,GAAG,IAAI,IAAI,EACjBP,KAAK,CAACQ,GAAG,IAAI,IAAI;IACjB;IACAR,KAAK,CAACS,GAAG,IAAIC,IAAI,CAACC,KAAK,CAACC,IAAI,CAACC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAC1Cb,KAAK,CAACX,KAAK,IAAI,IACjB,CAAyB;IAEzB,OAAO,IAAIJ,IAAI,CAACgB,MAAM,CAACd,KAAK,EAAEc,MAAM,CAACb,YAAY,EAAEY,KAAK,CAACX,KAAK,CAAC;EACjE;EAEA,aAAoByB,oBAAoBA,CAACzB,KAAc,EAAiB;IACtE,MAAML,qBAAqB,CAAC8B,oBAAoB,CAACzB,KAAK,IAAI,IAAI,CAAC;EACjE;EAEA,aAAoB0B,aAAaA,CAAC1B,KAAc,EAAiB;IAC/D,MAAML,qBAAqB,CAAC+B,aAAa,CAAC1B,KAAK,IAAI,IAAI,CAAC;EAC1D;EAEA,aAAoB2B,UAAUA,CAAC3B,KAAc,EAAwB;IACnE,OAAOL,qBAAqB,CAACgC,UAAU,CAAC3B,KAAK,IAAI,IAAI,CAAC;EACxD;EAEA,aAAoB4B,UAAUA,CAAC5B,KAAc,EAAoB;IAC/D,OAAOL,qBAAqB,CAACiC,UAAU,CAAC5B,KAAK,IAAI,IAAI,CAAC;EACxD;EAEA,aAAoB6B,aAAaA,CAAC7B,KAAc,EAAiB;IAC/D,MAAML,qBAAqB,CAACkC,aAAa,CAAC7B,KAAK,IAAI,IAAI,CAAC;EAC1D;AACF","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DPoPExampleContent.d.ts","sourceRoot":"","sources":["../../../../examples/shared/DPoPExampleContent.tsx"],"names":[],"mappings":"AAIA,MAAM,CAAC,OAAO,UAAU,kBAAkB,4CA0BzC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../../../examples/v0.75/App.tsx"],"names":[],"mappings":"AAIA,MAAM,CAAC,OAAO,UAAU,GAAG,4CAM1B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../../../../examples/v0.83/App.tsx"],"names":[],"mappings":"AAKA,MAAM,CAAC,OAAO,UAAU,GAAG,4CAM1B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeReactNativeDPoP.d.ts","sourceRoot":"","sources":["../../../src/NativeReactNativeDPoP.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAE9E,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACxD,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7D,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/E,aAAa,CACX,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,UAAU,EAAE,YAAY,GAAG,IAAI,EAC/B,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,KAAK,EAAE,MAAM,GAAG,IAAI,GACnB,OAAO,CAAC,YAAY,CAAC,CAAC;CAC1B;
|
|
1
|
+
{"version":3,"file":"NativeReactNativeDPoP.d.ts","sourceRoot":"","sources":["../../../src/NativeReactNativeDPoP.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAE9E,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACxD,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7D,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvD,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnD,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/E,aAAa,CACX,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,UAAU,EAAE,YAAY,GAAG,IAAI,EAC/B,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,KAAK,EAAE,MAAM,GAAG,IAAI,GACnB,OAAO,CAAC,YAAY,CAAC,CAAC;CAC1B;wBAMkC,IAAI;AAAvC,wBAAwC"}
|
|
@@ -7,6 +7,8 @@ export type PublicJwk = {
|
|
|
7
7
|
};
|
|
8
8
|
export type PublicKeyFormat = 'JWK' | 'DER' | 'RAW';
|
|
9
9
|
export type SecureHardwareFallbackReason = 'UNAVAILABLE' | 'PROVIDER_ERROR' | 'POLICY_REJECTED' | 'UNKNOWN';
|
|
10
|
+
export type AndroidSecurityLevelName = 'SOFTWARE' | 'TRUSTED_ENVIRONMENT' | 'STRONGBOX';
|
|
11
|
+
export type IOSSecurityLevelName = 'SOFTWARE' | 'SECURE_ENCLAVE';
|
|
10
12
|
export type DPoPKeyInfo = {
|
|
11
13
|
alias: string;
|
|
12
14
|
hasKeyPair: boolean;
|
|
@@ -18,12 +20,14 @@ export type DPoPKeyInfo = {
|
|
|
18
20
|
strongBoxAvailable: boolean;
|
|
19
21
|
strongBoxBacked: boolean;
|
|
20
22
|
securityLevel?: number;
|
|
23
|
+
securityLevelName?: AndroidSecurityLevelName;
|
|
21
24
|
strongBoxFallbackReason?: SecureHardwareFallbackReason | null;
|
|
22
25
|
};
|
|
23
26
|
ios?: {
|
|
24
27
|
secureEnclaveAvailable: boolean;
|
|
25
28
|
secureEnclaveBacked: boolean;
|
|
26
29
|
securityLevel?: number | null;
|
|
30
|
+
securityLevelName?: IOSSecurityLevelName;
|
|
27
31
|
secureEnclaveFallbackReason?: SecureHardwareFallbackReason | null;
|
|
28
32
|
};
|
|
29
33
|
};
|
|
@@ -51,7 +55,7 @@ export type DPoPProofContext = {
|
|
|
51
55
|
};
|
|
52
56
|
export declare class DPoP {
|
|
53
57
|
readonly proof: string;
|
|
54
|
-
readonly alias
|
|
58
|
+
readonly alias: string | undefined;
|
|
55
59
|
readonly proofContext: DPoPProofContext;
|
|
56
60
|
private constructor();
|
|
57
61
|
calculateThumbprint(): Promise<string>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAEA,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEhD,MAAM,MAAM,SAAS,GAAG;IACtB,GAAG,EAAE,IAAI,CAAC;IACV,GAAG,EAAE,OAAO,CAAC;IACb,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAEpD,MAAM,MAAM,4BAA4B,GAAG,aAAa,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-dpop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "React Native library for DPoP proof generation and key management.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"android",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"author": "Pedro Cirilo <phscirilo123@gmail.com> (https://github.com/Cirilord)",
|
|
20
20
|
"main": "./lib/module/index.js",
|
|
21
|
+
"react-native": "./src/index.tsx",
|
|
21
22
|
"types": "./lib/typescript/src/index.d.ts",
|
|
22
23
|
"exports": {
|
|
23
24
|
".": {
|
|
@@ -47,8 +48,8 @@
|
|
|
47
48
|
"!**/.*"
|
|
48
49
|
],
|
|
49
50
|
"scripts": {
|
|
50
|
-
"clean": "del-cli android/build
|
|
51
|
-
"example": "yarn workspace react-native-dpop-example",
|
|
51
|
+
"clean": "del-cli android/build examples/v0.83/android/build examples/v0.83/android/app/build examples/v0.83/ios/build lib",
|
|
52
|
+
"example": "yarn workspace react-native-dpop-example-v0.83",
|
|
52
53
|
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
53
54
|
"prepare": "bob build",
|
|
54
55
|
"release": "release-it",
|
|
@@ -71,12 +72,14 @@
|
|
|
71
72
|
"@react-native/babel-preset": "0.83.0",
|
|
72
73
|
"@react-native/eslint-config": "0.83.0",
|
|
73
74
|
"@release-it/conventional-changelog": "^10.0.1",
|
|
75
|
+
"@tsconfig/strictest": "^2.0.8",
|
|
74
76
|
"@types/jest": "^29.5.14",
|
|
75
77
|
"@types/react": "^19.2.0",
|
|
76
78
|
"commitlint": "^19.8.1",
|
|
77
79
|
"del-cli": "^6.0.0",
|
|
78
80
|
"eslint": "^9.35.0",
|
|
79
81
|
"eslint-config-prettier": "^10.1.8",
|
|
82
|
+
"eslint-plugin-import": "^2.32.0",
|
|
80
83
|
"eslint-plugin-prettier": "^5.5.4",
|
|
81
84
|
"jest": "^29.7.0",
|
|
82
85
|
"lefthook": "^2.0.3",
|
|
@@ -89,7 +92,8 @@
|
|
|
89
92
|
"typescript": "^5.9.2"
|
|
90
93
|
},
|
|
91
94
|
"workspaces": [
|
|
92
|
-
"
|
|
95
|
+
"examples/v0.75",
|
|
96
|
+
"examples/v0.83"
|
|
93
97
|
],
|
|
94
98
|
"packageManager": "yarn@4.11.0",
|
|
95
99
|
"react-native-builder-bob": {
|
|
@@ -28,6 +28,7 @@ export interface Spec extends TurboModule {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const nativeDpopModule =
|
|
31
|
-
|
|
31
|
+
// eslint-disable-next-line dot-notation -- required by noPropertyAccessFromIndexSignature from @tsconfig/strictest
|
|
32
|
+
TurboModuleRegistry.get<Spec>('ReactNativeDPoP') ?? (NativeModules['ReactNativeDPoP'] as Spec | undefined);
|
|
32
33
|
|
|
33
34
|
export default nativeDpopModule as Spec;
|
package/src/index.tsx
CHANGED
|
@@ -12,6 +12,8 @@ export type PublicJwk = {
|
|
|
12
12
|
export type PublicKeyFormat = 'JWK' | 'DER' | 'RAW';
|
|
13
13
|
|
|
14
14
|
export type SecureHardwareFallbackReason = 'UNAVAILABLE' | 'PROVIDER_ERROR' | 'POLICY_REJECTED' | 'UNKNOWN';
|
|
15
|
+
export type AndroidSecurityLevelName = 'SOFTWARE' | 'TRUSTED_ENVIRONMENT' | 'STRONGBOX';
|
|
16
|
+
export type IOSSecurityLevelName = 'SOFTWARE' | 'SECURE_ENCLAVE';
|
|
15
17
|
|
|
16
18
|
export type DPoPKeyInfo = {
|
|
17
19
|
alias: string;
|
|
@@ -24,12 +26,14 @@ export type DPoPKeyInfo = {
|
|
|
24
26
|
strongBoxAvailable: boolean;
|
|
25
27
|
strongBoxBacked: boolean;
|
|
26
28
|
securityLevel?: number;
|
|
29
|
+
securityLevelName?: AndroidSecurityLevelName;
|
|
27
30
|
strongBoxFallbackReason?: SecureHardwareFallbackReason | null;
|
|
28
31
|
};
|
|
29
32
|
ios?: {
|
|
30
33
|
secureEnclaveAvailable: boolean;
|
|
31
34
|
secureEnclaveBacked: boolean;
|
|
32
35
|
securityLevel?: number | null;
|
|
36
|
+
securityLevelName?: IOSSecurityLevelName;
|
|
33
37
|
secureEnclaveFallbackReason?: SecureHardwareFallbackReason | null;
|
|
34
38
|
};
|
|
35
39
|
};
|
|
@@ -65,7 +69,7 @@ type GenerateProofResult = {
|
|
|
65
69
|
|
|
66
70
|
export class DPoP {
|
|
67
71
|
public readonly proof: string;
|
|
68
|
-
public readonly alias
|
|
72
|
+
public readonly alias: string | undefined;
|
|
69
73
|
public readonly proofContext: DPoPProofContext;
|
|
70
74
|
|
|
71
75
|
private constructor(proof: string, proofContext: DPoPProofContext, alias?: string) {
|
|
@@ -106,7 +110,8 @@ export class DPoP {
|
|
|
106
110
|
input.additional ?? null,
|
|
107
111
|
input.kid ?? null,
|
|
108
112
|
input.jti ?? null,
|
|
109
|
-
|
|
113
|
+
// RN 0.75 Android bridge can crash when a nullable Double arrives as null.
|
|
114
|
+
input.iat ?? Math.floor(Date.now() / 1000),
|
|
110
115
|
input.alias ?? null
|
|
111
116
|
)) as GenerateProofResult;
|
|
112
117
|
|