react-native-dpop 0.1.0 → 0.2.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 +10 -3
- package/{Dpop.podspec → ReactNativeDPoP.podspec} +7 -3
- package/android/build.gradle +3 -3
- package/android/src/main/java/com/{dpop → reactnativedpop}/DPoPKeyStore.kt +1 -1
- package/android/src/main/java/com/{dpop/DpopModule.kt → reactnativedpop/DPoPModule.kt} +4 -4
- 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 +99 -0
- package/ios/DPoPModule.h +5 -0
- package/ios/DPoPModule.swift +353 -0
- package/ios/DPoPModuleBridge.mm +93 -0
- package/ios/DPoPUtils.swift +203 -0
- package/ios/KeychainKeyStore.swift +82 -0
- package/ios/SecureEnclaveKeyStore.swift +110 -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.map +1 -1
- package/package.json +3 -3
- package/src/{NativeDpop.ts → NativeReactNativeDPoP.ts} +5 -2
- package/src/index.tsx +13 -36
- 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
|
|
@@ -78,6 +84,7 @@ const publicJwk = await dpop.getPublicKey('JWK');
|
|
|
78
84
|
Native errors are rejected with codes such as:
|
|
79
85
|
|
|
80
86
|
- `ERR_DPOP_GENERATE_PROOF`
|
|
87
|
+
- `ERR_DPOP_CALCULATE_THUMBPRINT`
|
|
81
88
|
- `ERR_DPOP_PUBLIC_KEY`
|
|
82
89
|
- `ERR_DPOP_SIGN_WITH_PRIVATE_KEY`
|
|
83
90
|
- `ERR_DPOP_HAS_KEY_PAIR`
|
|
@@ -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 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 {
|
|
@@ -297,6 +297,6 @@ class DpopModule(reactContext: ReactApplicationContext) :
|
|
|
297
297
|
|
|
298
298
|
companion object {
|
|
299
299
|
private const val DEFAULT_ALIAS = "react-native-dpop"
|
|
300
|
-
const val NAME =
|
|
300
|
+
const val NAME = NativeReactNativeDPoPSpec.NAME
|
|
301
301
|
}
|
|
302
302
|
}
|
|
@@ -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,99 @@
|
|
|
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
|
+
let strongBoxAvailable: Bool
|
|
16
|
+
let strongBoxBacked: Bool
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
final class DPoPKeyStore {
|
|
20
|
+
private let secureEnclave = SecureEnclaveKeyStore()
|
|
21
|
+
private let keychain = KeychainKeyStore()
|
|
22
|
+
|
|
23
|
+
func generateKeyPair(alias: String) throws {
|
|
24
|
+
try deleteKeyPair(alias: alias)
|
|
25
|
+
|
|
26
|
+
do {
|
|
27
|
+
try secureEnclave.generateKeyPair(alias: alias)
|
|
28
|
+
} catch {
|
|
29
|
+
try keychain.generateKeyPair(alias: alias)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func deleteKeyPair(alias: String) throws {
|
|
34
|
+
try secureEnclave.deleteKeyPair(alias: alias)
|
|
35
|
+
try keychain.deleteKeyPair(alias: alias)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
func hasKeyPair(alias: String) -> Bool {
|
|
39
|
+
secureEnclave.hasKeyPair(alias: alias) || keychain.hasKeyPair(alias: alias)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func getKeyPair(alias: String) throws -> DPoPKeyPairReference {
|
|
43
|
+
if secureEnclave.hasKeyPair(alias: alias) {
|
|
44
|
+
return DPoPKeyPairReference(
|
|
45
|
+
privateKey: try secureEnclave.getPrivateKey(alias: alias),
|
|
46
|
+
publicKey: try secureEnclave.getPublicKey(alias: alias)
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if keychain.hasKeyPair(alias: alias) {
|
|
51
|
+
return DPoPKeyPairReference(
|
|
52
|
+
privateKey: try keychain.getPrivateKey(alias: alias),
|
|
53
|
+
publicKey: try keychain.getPublicKey(alias: alias)
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
throw DPoPError.keyNotFound(alias: alias)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func getKeyInfo(alias: String) -> DPoPKeyInfo {
|
|
61
|
+
if secureEnclave.hasKeyPair(alias: alias) {
|
|
62
|
+
return DPoPKeyInfo(
|
|
63
|
+
alias: alias,
|
|
64
|
+
algorithm: "EC",
|
|
65
|
+
curve: "P-256",
|
|
66
|
+
hasKeyPair: true,
|
|
67
|
+
insideSecureHardware: true,
|
|
68
|
+
strongBoxAvailable: false,
|
|
69
|
+
strongBoxBacked: false
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if keychain.hasKeyPair(alias: alias) {
|
|
74
|
+
return DPoPKeyInfo(
|
|
75
|
+
alias: alias,
|
|
76
|
+
algorithm: "EC",
|
|
77
|
+
curve: "P-256",
|
|
78
|
+
hasKeyPair: true,
|
|
79
|
+
insideSecureHardware: false,
|
|
80
|
+
strongBoxAvailable: false,
|
|
81
|
+
strongBoxBacked: false
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return DPoPKeyInfo(
|
|
86
|
+
alias: alias,
|
|
87
|
+
algorithm: "EC",
|
|
88
|
+
curve: "P-256",
|
|
89
|
+
hasKeyPair: false,
|
|
90
|
+
insideSecureHardware: false,
|
|
91
|
+
strongBoxAvailable: false,
|
|
92
|
+
strongBoxBacked: false
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func isHardwareBacked(alias: String) -> Bool {
|
|
97
|
+
secureEnclave.isHardwareBacked(alias: alias)
|
|
98
|
+
}
|
|
99
|
+
}
|
package/ios/DPoPModule.h
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
import React
|
|
4
|
+
|
|
5
|
+
final class DPoPModule {
|
|
6
|
+
static let shared = DPoPModule()
|
|
7
|
+
|
|
8
|
+
private let keyStore = DPoPKeyStore()
|
|
9
|
+
private let defaultAlias = "react-native-dpop"
|
|
10
|
+
|
|
11
|
+
private init() {}
|
|
12
|
+
|
|
13
|
+
func resolveAlias(_ alias: String?) -> String {
|
|
14
|
+
guard let alias, !alias.isEmpty else {
|
|
15
|
+
return defaultAlias
|
|
16
|
+
}
|
|
17
|
+
return alias
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func assertHardwareBacked(alias: String?) throws {
|
|
21
|
+
let effectiveAlias = resolveAlias(alias)
|
|
22
|
+
guard keyStore.hasKeyPair(alias: effectiveAlias) else {
|
|
23
|
+
throw DPoPError.keyNotFound(alias: effectiveAlias)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
guard keyStore.isHardwareBacked(alias: effectiveAlias) else {
|
|
27
|
+
throw DPoPError.notHardwareBacked(alias: effectiveAlias)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
func calculateThumbprint(alias: String?) throws -> String {
|
|
32
|
+
let effectiveAlias = resolveAlias(alias)
|
|
33
|
+
if !keyStore.hasKeyPair(alias: effectiveAlias) {
|
|
34
|
+
try keyStore.generateKeyPair(alias: effectiveAlias)
|
|
35
|
+
}
|
|
36
|
+
let keyPair = try keyStore.getKeyPair(alias: effectiveAlias)
|
|
37
|
+
let coordinates = try DPoPUtils.getPublicCoordinates(fromRawPublicKey: try DPoPUtils.toRawPublicKey(keyPair.publicKey))
|
|
38
|
+
return DPoPUtils.calculateThumbprint(kty: "EC", crv: "P-256", x: coordinates.x, y: coordinates.y)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func deleteKeyPair(alias: String?) throws {
|
|
42
|
+
try keyStore.deleteKeyPair(alias: resolveAlias(alias))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func getKeyInfo(alias: String?) -> [String: Any] {
|
|
46
|
+
let effectiveAlias = resolveAlias(alias)
|
|
47
|
+
let keyInfo = keyStore.getKeyInfo(alias: effectiveAlias)
|
|
48
|
+
|
|
49
|
+
if !keyInfo.hasKeyPair {
|
|
50
|
+
return [
|
|
51
|
+
"alias": effectiveAlias,
|
|
52
|
+
"hasKeyPair": false
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [
|
|
57
|
+
"alias": keyInfo.alias,
|
|
58
|
+
"algorithm": keyInfo.algorithm,
|
|
59
|
+
"curve": keyInfo.curve,
|
|
60
|
+
"hasKeyPair": true,
|
|
61
|
+
"insideSecureHardware": keyInfo.insideSecureHardware,
|
|
62
|
+
"strongBoxAvailable": keyInfo.strongBoxAvailable,
|
|
63
|
+
"strongBoxBacked": keyInfo.strongBoxBacked
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func getPublicKeyDer(alias: String?) throws -> String {
|
|
68
|
+
let effectiveAlias = resolveAlias(alias)
|
|
69
|
+
if !keyStore.hasKeyPair(alias: effectiveAlias) {
|
|
70
|
+
try keyStore.generateKeyPair(alias: effectiveAlias)
|
|
71
|
+
}
|
|
72
|
+
let keyPair = try keyStore.getKeyPair(alias: effectiveAlias)
|
|
73
|
+
return DPoPUtils.base64UrlEncode(try DPoPUtils.toDerPublicKey(keyPair.publicKey))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func getPublicKeyJwk(alias: String?) throws -> [String: Any] {
|
|
77
|
+
let effectiveAlias = resolveAlias(alias)
|
|
78
|
+
if !keyStore.hasKeyPair(alias: effectiveAlias) {
|
|
79
|
+
try keyStore.generateKeyPair(alias: effectiveAlias)
|
|
80
|
+
}
|
|
81
|
+
let keyPair = try keyStore.getKeyPair(alias: effectiveAlias)
|
|
82
|
+
let coordinates = try DPoPUtils.getPublicCoordinates(fromRawPublicKey: try DPoPUtils.toRawPublicKey(keyPair.publicKey))
|
|
83
|
+
return [
|
|
84
|
+
"kty": "EC",
|
|
85
|
+
"crv": "P-256",
|
|
86
|
+
"x": coordinates.x,
|
|
87
|
+
"y": coordinates.y
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func getPublicKeyRaw(alias: String?) throws -> String {
|
|
92
|
+
let effectiveAlias = resolveAlias(alias)
|
|
93
|
+
if !keyStore.hasKeyPair(alias: effectiveAlias) {
|
|
94
|
+
try keyStore.generateKeyPair(alias: effectiveAlias)
|
|
95
|
+
}
|
|
96
|
+
let keyPair = try keyStore.getKeyPair(alias: effectiveAlias)
|
|
97
|
+
return DPoPUtils.base64UrlEncode(try DPoPUtils.toRawPublicKey(keyPair.publicKey))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
func hasKeyPair(alias: String?) -> Bool {
|
|
101
|
+
keyStore.hasKeyPair(alias: resolveAlias(alias))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func isBoundToAlias(proof: String, alias: String?) throws -> Bool {
|
|
105
|
+
let effectiveAlias = resolveAlias(alias)
|
|
106
|
+
if !keyStore.hasKeyPair(alias: effectiveAlias) {
|
|
107
|
+
try keyStore.generateKeyPair(alias: effectiveAlias)
|
|
108
|
+
}
|
|
109
|
+
let keyPair = try keyStore.getKeyPair(alias: effectiveAlias)
|
|
110
|
+
return try DPoPUtils.isProofBoundToPublicKey(proof, publicKey: keyPair.publicKey)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
func rotateKeyPair(alias: String?) throws {
|
|
114
|
+
try keyStore.generateKeyPair(alias: resolveAlias(alias))
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
func signWithDpopPrivateKey(payload: String, alias: String?) throws -> String {
|
|
118
|
+
let effectiveAlias = resolveAlias(alias)
|
|
119
|
+
if !keyStore.hasKeyPair(alias: effectiveAlias) {
|
|
120
|
+
try keyStore.generateKeyPair(alias: effectiveAlias)
|
|
121
|
+
}
|
|
122
|
+
let keyPair = try keyStore.getKeyPair(alias: effectiveAlias)
|
|
123
|
+
var error: Unmanaged<CFError>?
|
|
124
|
+
guard let derSignature = SecKeyCreateSignature(
|
|
125
|
+
keyPair.privateKey,
|
|
126
|
+
.ecdsaSignatureMessageX962SHA256,
|
|
127
|
+
Data(payload.utf8) as CFData,
|
|
128
|
+
&error
|
|
129
|
+
) as Data? else {
|
|
130
|
+
throw DPoPError.securityError(error?.takeRetainedValue())
|
|
131
|
+
}
|
|
132
|
+
let joseSignature = try DPoPUtils.derToJose(derSignature, partLength: 32)
|
|
133
|
+
return DPoPUtils.base64UrlEncode(joseSignature)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
func generateProof(
|
|
137
|
+
htu: String,
|
|
138
|
+
htm: String,
|
|
139
|
+
nonce: String?,
|
|
140
|
+
accessToken: String?,
|
|
141
|
+
additional: [String: Any]?,
|
|
142
|
+
kid: String?,
|
|
143
|
+
jti: String?,
|
|
144
|
+
iat: NSNumber?,
|
|
145
|
+
alias: String?
|
|
146
|
+
) throws -> [String: Any] {
|
|
147
|
+
let effectiveAlias = resolveAlias(alias)
|
|
148
|
+
if !keyStore.hasKeyPair(alias: effectiveAlias) {
|
|
149
|
+
try keyStore.generateKeyPair(alias: effectiveAlias)
|
|
150
|
+
}
|
|
151
|
+
let keyPair = try keyStore.getKeyPair(alias: effectiveAlias)
|
|
152
|
+
let coordinates = try DPoPUtils.getPublicCoordinates(fromRawPublicKey: try DPoPUtils.toRawPublicKey(keyPair.publicKey))
|
|
153
|
+
|
|
154
|
+
var jwk: [String: Any] = [
|
|
155
|
+
"kty": "EC",
|
|
156
|
+
"crv": "P-256",
|
|
157
|
+
"x": coordinates.x,
|
|
158
|
+
"y": coordinates.y
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
var header: [String: Any] = [
|
|
162
|
+
"typ": "dpop+jwt",
|
|
163
|
+
"alg": "ES256",
|
|
164
|
+
"jwk": jwk
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
if let kid, !kid.isEmpty {
|
|
168
|
+
header["kid"] = kid
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let issuedAt = iat?.int64Value ?? Int64(Date().timeIntervalSince1970)
|
|
172
|
+
let finalJti = (jti?.isEmpty == false) ? jti! : UUID().uuidString
|
|
173
|
+
|
|
174
|
+
var payload: [String: Any] = [
|
|
175
|
+
"jti": finalJti,
|
|
176
|
+
"htm": htm.uppercased(),
|
|
177
|
+
"htu": htu,
|
|
178
|
+
"iat": issuedAt
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
if let nonce, !nonce.isEmpty {
|
|
182
|
+
payload["nonce"] = nonce
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if let accessToken, !accessToken.isEmpty {
|
|
186
|
+
payload["ath"] = DPoPUtils.hashAccessToken(accessToken)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if let additional {
|
|
190
|
+
for (key, value) in additional {
|
|
191
|
+
payload[key] = value
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let headerSegment = DPoPUtils.base64UrlEncode(try DPoPUtils.jsonData(header))
|
|
196
|
+
let payloadSegment = DPoPUtils.base64UrlEncode(try DPoPUtils.jsonData(payload))
|
|
197
|
+
let signingInput = "\(headerSegment).\(payloadSegment)"
|
|
198
|
+
|
|
199
|
+
var error: Unmanaged<CFError>?
|
|
200
|
+
guard let derSignature = SecKeyCreateSignature(
|
|
201
|
+
keyPair.privateKey,
|
|
202
|
+
.ecdsaSignatureMessageX962SHA256,
|
|
203
|
+
Data(signingInput.utf8) as CFData,
|
|
204
|
+
&error
|
|
205
|
+
) as Data? else {
|
|
206
|
+
throw DPoPError.securityError(error?.takeRetainedValue())
|
|
207
|
+
}
|
|
208
|
+
let joseSignature = try DPoPUtils.derToJose(derSignature, partLength: 32)
|
|
209
|
+
let jwt = "\(signingInput).\(DPoPUtils.base64UrlEncode(joseSignature))"
|
|
210
|
+
|
|
211
|
+
let proofContext: [String: Any] = [
|
|
212
|
+
"htu": payload["htu"] as? String ?? htu,
|
|
213
|
+
"htm": payload["htm"] as? String ?? htm.uppercased(),
|
|
214
|
+
"nonce": payload["nonce"] ?? NSNull(),
|
|
215
|
+
"ath": payload["ath"] ?? NSNull(),
|
|
216
|
+
"kid": header["kid"] ?? NSNull(),
|
|
217
|
+
"jti": payload["jti"] as? String ?? finalJti,
|
|
218
|
+
"iat": Double(issuedAt),
|
|
219
|
+
"additional": additional ?? NSNull()
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
return [
|
|
223
|
+
"proof": jwt,
|
|
224
|
+
"proofContext": proofContext
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
@objc extension ReactNativeDPoP {
|
|
231
|
+
static func moduleName() -> String! {
|
|
232
|
+
"ReactNativeDPoP"
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
static func requiresMainQueueSetup() -> Bool {
|
|
236
|
+
false
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
func assertHardwareBacked(_ alias: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
240
|
+
do {
|
|
241
|
+
try DPoPModule.shared.assertHardwareBacked(alias: alias)
|
|
242
|
+
resolve(nil)
|
|
243
|
+
} catch {
|
|
244
|
+
reject("ERR_DPOP_ASSERT_HARDWARE_BACKED", error.localizedDescription, error)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
func calculateThumbprint(_ alias: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
249
|
+
do {
|
|
250
|
+
resolve(try DPoPModule.shared.calculateThumbprint(alias: alias))
|
|
251
|
+
} catch {
|
|
252
|
+
reject("ERR_DPOP_CALCULATE_THUMBPRINT", error.localizedDescription, error)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
func deleteKeyPair(_ alias: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
257
|
+
do {
|
|
258
|
+
try DPoPModule.shared.deleteKeyPair(alias: alias)
|
|
259
|
+
resolve(nil)
|
|
260
|
+
} catch {
|
|
261
|
+
reject("ERR_DPOP_DELETE_KEY_PAIR", error.localizedDescription, error)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
func getKeyInfo(_ alias: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
266
|
+
resolve(DPoPModule.shared.getKeyInfo(alias: alias))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
func getPublicKeyDer(_ alias: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
270
|
+
do {
|
|
271
|
+
resolve(try DPoPModule.shared.getPublicKeyDer(alias: alias))
|
|
272
|
+
} catch {
|
|
273
|
+
reject("ERR_DPOP_PUBLIC_KEY", error.localizedDescription, error)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
func getPublicKeyJwk(_ alias: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
278
|
+
do {
|
|
279
|
+
resolve(try DPoPModule.shared.getPublicKeyJwk(alias: alias))
|
|
280
|
+
} catch {
|
|
281
|
+
reject("ERR_DPOP_PUBLIC_KEY", error.localizedDescription, error)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
func getPublicKeyRaw(_ alias: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
286
|
+
do {
|
|
287
|
+
resolve(try DPoPModule.shared.getPublicKeyRaw(alias: alias))
|
|
288
|
+
} catch {
|
|
289
|
+
reject("ERR_DPOP_PUBLIC_KEY", error.localizedDescription, error)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
func hasKeyPair(_ alias: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
294
|
+
resolve(DPoPModule.shared.hasKeyPair(alias: alias))
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
func isBoundToAlias(_ proof: String, alias: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
298
|
+
do {
|
|
299
|
+
resolve(try DPoPModule.shared.isBoundToAlias(proof: proof, alias: alias))
|
|
300
|
+
} catch {
|
|
301
|
+
reject("ERR_DPOP_IS_BOUND_TO_ALIAS", error.localizedDescription, error)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
func rotateKeyPair(_ alias: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
306
|
+
do {
|
|
307
|
+
try DPoPModule.shared.rotateKeyPair(alias: alias)
|
|
308
|
+
resolve(nil)
|
|
309
|
+
} catch {
|
|
310
|
+
reject("ERR_DPOP_ROTATE_KEY_PAIR", error.localizedDescription, error)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
func signWithDpopPrivateKey(_ payload: String, alias: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
315
|
+
do {
|
|
316
|
+
resolve(try DPoPModule.shared.signWithDpopPrivateKey(payload: payload, alias: alias))
|
|
317
|
+
} catch {
|
|
318
|
+
reject("ERR_DPOP_SIGN_WITH_PRIVATE_KEY", error.localizedDescription, error)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
func generateProof(
|
|
323
|
+
_ htu: String,
|
|
324
|
+
htm: String,
|
|
325
|
+
nonce: String?,
|
|
326
|
+
accessToken: String?,
|
|
327
|
+
additional: [String: Any]?,
|
|
328
|
+
kid: String?,
|
|
329
|
+
jti: String?,
|
|
330
|
+
iat: NSNumber?,
|
|
331
|
+
alias: String?,
|
|
332
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
333
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
334
|
+
) {
|
|
335
|
+
do {
|
|
336
|
+
resolve(
|
|
337
|
+
try DPoPModule.shared.generateProof(
|
|
338
|
+
htu: htu,
|
|
339
|
+
htm: htm,
|
|
340
|
+
nonce: nonce,
|
|
341
|
+
accessToken: accessToken,
|
|
342
|
+
additional: additional,
|
|
343
|
+
kid: kid,
|
|
344
|
+
jti: jti,
|
|
345
|
+
iat: iat,
|
|
346
|
+
alias: alias
|
|
347
|
+
)
|
|
348
|
+
)
|
|
349
|
+
} catch {
|
|
350
|
+
reject("ERR_DPOP_GENERATE_PROOF", error.localizedDescription, error)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|