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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
|
|
4
|
+
final class KeychainKeyStore {
|
|
5
|
+
private let service = "com.dpop.keychain"
|
|
6
|
+
|
|
7
|
+
func generateKeyPair(alias: String) throws {
|
|
8
|
+
try deleteKeyPair(alias: alias)
|
|
9
|
+
|
|
10
|
+
let tag = keyTag(alias: alias)
|
|
11
|
+
var error: Unmanaged<CFError>?
|
|
12
|
+
|
|
13
|
+
let attributes: [String: Any] = [
|
|
14
|
+
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
|
|
15
|
+
kSecAttrKeySizeInBits as String: 256,
|
|
16
|
+
kSecPrivateKeyAttrs as String: [
|
|
17
|
+
kSecAttrIsPermanent as String: true,
|
|
18
|
+
kSecAttrApplicationTag as String: tag,
|
|
19
|
+
kSecAttrLabel as String: service,
|
|
20
|
+
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
|
21
|
+
]
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
guard SecKeyCreateRandomKey(attributes as CFDictionary, &error) != nil else {
|
|
25
|
+
throw DPoPError.securityError(error?.takeRetainedValue())
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func deleteKeyPair(alias: String) throws {
|
|
30
|
+
let tag = keyTag(alias: alias)
|
|
31
|
+
|
|
32
|
+
let privateQuery: [String: Any] = [
|
|
33
|
+
kSecClass as String: kSecClassKey,
|
|
34
|
+
kSecAttrApplicationTag as String: tag,
|
|
35
|
+
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
let status = SecItemDelete(privateQuery as CFDictionary)
|
|
39
|
+
guard status == errSecSuccess || status == errSecItemNotFound else {
|
|
40
|
+
throw NSError(domain: NSOSStatusErrorDomain, code: Int(status))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func getPrivateKey(alias: String) throws -> SecKey {
|
|
45
|
+
let tag = keyTag(alias: alias)
|
|
46
|
+
let query: [String: Any] = [
|
|
47
|
+
kSecClass as String: kSecClassKey,
|
|
48
|
+
kSecAttrApplicationTag as String: tag,
|
|
49
|
+
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
|
|
50
|
+
kSecReturnRef as String: true
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
var result: CFTypeRef?
|
|
54
|
+
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
55
|
+
guard status == errSecSuccess, let key = result as! SecKey? else {
|
|
56
|
+
throw DPoPError.keyNotFound(alias: alias)
|
|
57
|
+
}
|
|
58
|
+
return key
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func getPublicKey(alias: String) throws -> SecKey {
|
|
62
|
+
let privateKey = try getPrivateKey(alias: alias)
|
|
63
|
+
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
|
|
64
|
+
throw DPoPError.keyNotFound(alias: alias)
|
|
65
|
+
}
|
|
66
|
+
return publicKey
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func hasKeyPair(alias: String) -> Bool {
|
|
70
|
+
do {
|
|
71
|
+
_ = try getPrivateKey(alias: alias)
|
|
72
|
+
_ = try getPublicKey(alias: alias)
|
|
73
|
+
return true
|
|
74
|
+
} catch {
|
|
75
|
+
return false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private func keyTag(alias: String) -> Data {
|
|
80
|
+
Data("\(service).\(alias)".utf8)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
|
|
4
|
+
final class SecureEnclaveKeyStore {
|
|
5
|
+
private let service = "com.dpop.secureenclave"
|
|
6
|
+
|
|
7
|
+
func isAvailable() -> Bool {
|
|
8
|
+
#if targetEnvironment(simulator)
|
|
9
|
+
return false
|
|
10
|
+
#else
|
|
11
|
+
let probeAlias = "__secure_enclave_probe_\(UUID().uuidString)"
|
|
12
|
+
do {
|
|
13
|
+
try generateKeyPair(alias: probeAlias)
|
|
14
|
+
try deleteKeyPair(alias: probeAlias)
|
|
15
|
+
return true
|
|
16
|
+
} catch {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
#endif
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func generateKeyPair(alias: String) throws {
|
|
23
|
+
try deleteKeyPair(alias: alias)
|
|
24
|
+
|
|
25
|
+
let tag = keyTag(alias: alias)
|
|
26
|
+
var error: Unmanaged<CFError>?
|
|
27
|
+
|
|
28
|
+
guard let access = SecAccessControlCreateWithFlags(
|
|
29
|
+
nil,
|
|
30
|
+
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
31
|
+
.privateKeyUsage,
|
|
32
|
+
&error
|
|
33
|
+
) else {
|
|
34
|
+
throw DPoPError.securityError(error?.takeRetainedValue())
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let attributes: [String: Any] = [
|
|
38
|
+
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
|
|
39
|
+
kSecAttrKeySizeInBits as String: 256,
|
|
40
|
+
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
|
|
41
|
+
kSecPrivateKeyAttrs as String: [
|
|
42
|
+
kSecAttrIsPermanent as String: true,
|
|
43
|
+
kSecAttrApplicationTag as String: tag,
|
|
44
|
+
kSecAttrLabel as String: service,
|
|
45
|
+
kSecAttrAccessControl as String: access
|
|
46
|
+
]
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
guard SecKeyCreateRandomKey(attributes as CFDictionary, &error) != nil else {
|
|
50
|
+
throw DPoPError.securityError(error?.takeRetainedValue())
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func deleteKeyPair(alias: String) throws {
|
|
55
|
+
let tag = keyTag(alias: alias)
|
|
56
|
+
|
|
57
|
+
let privateQuery: [String: Any] = [
|
|
58
|
+
kSecClass as String: kSecClassKey,
|
|
59
|
+
kSecAttrApplicationTag as String: tag,
|
|
60
|
+
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
|
|
61
|
+
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
let status = SecItemDelete(privateQuery as CFDictionary)
|
|
65
|
+
guard status == errSecSuccess || status == errSecItemNotFound else {
|
|
66
|
+
throw NSError(domain: NSOSStatusErrorDomain, code: Int(status))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func getPrivateKey(alias: String) throws -> SecKey {
|
|
71
|
+
let tag = keyTag(alias: alias)
|
|
72
|
+
let query: [String: Any] = [
|
|
73
|
+
kSecClass as String: kSecClassKey,
|
|
74
|
+
kSecAttrApplicationTag as String: tag,
|
|
75
|
+
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
|
|
76
|
+
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
|
|
77
|
+
kSecReturnRef as String: true
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
var result: CFTypeRef?
|
|
81
|
+
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
82
|
+
guard status == errSecSuccess, let key = result as! SecKey? else {
|
|
83
|
+
throw DPoPError.keyNotFound(alias: alias)
|
|
84
|
+
}
|
|
85
|
+
return key
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func getPublicKey(alias: String) throws -> SecKey {
|
|
89
|
+
let privateKey = try getPrivateKey(alias: alias)
|
|
90
|
+
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
|
|
91
|
+
throw DPoPError.keyNotFound(alias: alias)
|
|
92
|
+
}
|
|
93
|
+
return publicKey
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func hasKeyPair(alias: String) -> Bool {
|
|
97
|
+
do {
|
|
98
|
+
_ = try getPrivateKey(alias: alias)
|
|
99
|
+
_ = try getPublicKey(alias: alias)
|
|
100
|
+
return true
|
|
101
|
+
} catch {
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
func isHardwareBacked(alias: String) -> Bool {
|
|
107
|
+
guard let attrs = try? attributes(alias: alias) else {
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (attrs[kSecAttrTokenID as String] as? String) == (kSecAttrTokenIDSecureEnclave as String)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private func attributes(alias: String) throws -> [String: Any] {
|
|
115
|
+
let privateKey = try getPrivateKey(alias: alias)
|
|
116
|
+
guard let attrs = SecKeyCopyAttributes(privateKey) as? [String: Any] else {
|
|
117
|
+
return [:]
|
|
118
|
+
}
|
|
119
|
+
return attrs
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private func keyTag(alias: String) -> Data {
|
|
123
|
+
Data("\(service).\(alias)".utf8)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { NativeModules, TurboModuleRegistry } from 'react-native';
|
|
4
|
+
const nativeDpopModule = TurboModuleRegistry.get('ReactNativeDPoP') ?? NativeModules.ReactNativeDPoP;
|
|
5
|
+
export default nativeDpopModule;
|
|
6
|
+
//# sourceMappingURL=NativeReactNativeDPoP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["NativeModules","TurboModuleRegistry","nativeDpopModule","get","ReactNativeDPoP"],"sourceRoot":"../../src","sources":["NativeReactNativeDPoP.ts"],"mappings":";;AACA,SAASA,aAAa,EAAEC,mBAAmB,QAAQ,cAAc;AA4BjE,MAAMC,gBAAgB,GACpBD,mBAAmB,CAACE,GAAG,CAAO,iBAAiB,CAAC,IAAKH,aAAa,CAACI,eAAoC;AAEzG,eAAeF,gBAAgB","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import Dpop from "./NativeDpop.js";
|
|
5
|
-
const LINKING_ERROR = 'Dpop module nao encontrado. Verifique se o app Android foi recompilado apos adicionar o modulo nativo.';
|
|
6
|
-
function requireAndroid() {
|
|
7
|
-
if (Platform.OS !== 'android') {
|
|
8
|
-
throw new Error('react-native-dpop (MVP atual) suporta somente Android.');
|
|
9
|
-
}
|
|
10
|
-
if (!Dpop) {
|
|
11
|
-
throw new Error(LINKING_ERROR);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
3
|
+
import NativeReactNativeDPoP from "./NativeReactNativeDPoP.js";
|
|
14
4
|
export class DPoP {
|
|
15
5
|
constructor(proof, proofContext, alias) {
|
|
16
6
|
this.proof = proof;
|
|
@@ -18,51 +8,41 @@ export class DPoP {
|
|
|
18
8
|
this.alias = alias;
|
|
19
9
|
}
|
|
20
10
|
async calculateThumbprint() {
|
|
21
|
-
|
|
22
|
-
return Dpop.calculateThumbprint(this.alias ?? null);
|
|
11
|
+
return NativeReactNativeDPoP.calculateThumbprint(this.alias ?? null);
|
|
23
12
|
}
|
|
24
13
|
async getPublicKey(format) {
|
|
25
|
-
requireAndroid();
|
|
26
14
|
if (format === 'DER') {
|
|
27
|
-
return
|
|
15
|
+
return NativeReactNativeDPoP.getPublicKeyDer(this.alias ?? null);
|
|
28
16
|
}
|
|
29
17
|
if (format === 'RAW') {
|
|
30
|
-
return
|
|
18
|
+
return NativeReactNativeDPoP.getPublicKeyRaw(this.alias ?? null);
|
|
31
19
|
}
|
|
32
|
-
return
|
|
20
|
+
return NativeReactNativeDPoP.getPublicKeyJwk(this.alias ?? null);
|
|
33
21
|
}
|
|
34
22
|
async signWithDpopPrivateKey(payload) {
|
|
35
|
-
|
|
36
|
-
return Dpop.signWithDpopPrivateKey(payload, this.alias ?? null);
|
|
23
|
+
return NativeReactNativeDPoP.signWithDpopPrivateKey(payload, this.alias ?? null);
|
|
37
24
|
}
|
|
38
25
|
async isBoundToAlias(alias) {
|
|
39
|
-
|
|
40
|
-
return Dpop.isBoundToAlias(this.proof, alias ?? this.alias ?? null);
|
|
26
|
+
return NativeReactNativeDPoP.isBoundToAlias(this.proof, alias ?? this.alias ?? null);
|
|
41
27
|
}
|
|
42
28
|
static async generateProof(input) {
|
|
43
|
-
|
|
44
|
-
const result = await Dpop.generateProof(input.htu, input.htm, input.nonce ?? null, input.accessToken ?? null, input.additional ?? null, input.kid ?? null, input.jti ?? null, input.iat ?? null, input.alias ?? 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, input.iat ?? null, input.alias ?? null);
|
|
45
30
|
return new DPoP(result.proof, result.proofContext, input.alias);
|
|
46
31
|
}
|
|
47
32
|
static async assertHardwareBacked(alias) {
|
|
48
|
-
|
|
49
|
-
await Dpop.assertHardwareBacked(alias ?? null);
|
|
33
|
+
await NativeReactNativeDPoP.assertHardwareBacked(alias ?? null);
|
|
50
34
|
}
|
|
51
35
|
static async deleteKeyPair(alias) {
|
|
52
|
-
|
|
53
|
-
await Dpop.deleteKeyPair(alias ?? null);
|
|
36
|
+
await NativeReactNativeDPoP.deleteKeyPair(alias ?? null);
|
|
54
37
|
}
|
|
55
38
|
static async getKeyInfo(alias) {
|
|
56
|
-
|
|
57
|
-
return Dpop.getKeyInfo(alias ?? null);
|
|
39
|
+
return NativeReactNativeDPoP.getKeyInfo(alias ?? null);
|
|
58
40
|
}
|
|
59
41
|
static async hasKeyPair(alias) {
|
|
60
|
-
|
|
61
|
-
return Dpop.hasKeyPair(alias ?? null);
|
|
42
|
+
return NativeReactNativeDPoP.hasKeyPair(alias ?? null);
|
|
62
43
|
}
|
|
63
44
|
static async rotateKeyPair(alias) {
|
|
64
|
-
|
|
65
|
-
await Dpop.rotateKeyPair(alias ?? null);
|
|
45
|
+
await NativeReactNativeDPoP.rotateKeyPair(alias ?? null);
|
|
66
46
|
}
|
|
67
47
|
}
|
|
68
48
|
//# sourceMappingURL=index.js.map
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["
|
|
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;AAiE3D,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,EACjBR,KAAK,CAACS,GAAG,IAAI,IAAI,EACjBT,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,aAAoBqB,oBAAoBA,CAACrB,KAAc,EAAiB;IACtE,MAAML,qBAAqB,CAAC0B,oBAAoB,CAACrB,KAAK,IAAI,IAAI,CAAC;EACjE;EAEA,aAAoBsB,aAAaA,CAACtB,KAAc,EAAiB;IAC/D,MAAML,qBAAqB,CAAC2B,aAAa,CAACtB,KAAK,IAAI,IAAI,CAAC;EAC1D;EAEA,aAAoBuB,UAAUA,CAACvB,KAAc,EAAwB;IACnE,OAAOL,qBAAqB,CAAC4B,UAAU,CAACvB,KAAK,IAAI,IAAI,CAAC;EACxD;EAEA,aAAoBwB,UAAUA,CAACxB,KAAc,EAAoB;IAC/D,OAAOL,qBAAqB,CAAC6B,UAAU,CAACxB,KAAK,IAAI,IAAI,CAAC;EACxD;EAEA,aAAoByB,aAAaA,CAACzB,KAAc,EAAiB;IAC/D,MAAML,qBAAqB,CAAC8B,aAAa,CAACzB,KAAK,IAAI,IAAI,CAAC;EAC1D;AACF","ignoreList":[]}
|
|
@@ -0,0 +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;wBAKkC,IAAI;AAAvC,wBAAwC"}
|
|
@@ -6,15 +6,27 @@ export type PublicJwk = {
|
|
|
6
6
|
y: string;
|
|
7
7
|
};
|
|
8
8
|
export type PublicKeyFormat = 'JWK' | 'DER' | 'RAW';
|
|
9
|
+
export type SecureHardwareFallbackReason = 'UNAVAILABLE' | 'PROVIDER_ERROR' | 'POLICY_REJECTED' | 'UNKNOWN';
|
|
9
10
|
export type DPoPKeyInfo = {
|
|
10
11
|
alias: string;
|
|
11
12
|
hasKeyPair: boolean;
|
|
12
13
|
algorithm?: string;
|
|
13
14
|
curve?: string;
|
|
14
15
|
insideSecureHardware?: boolean;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
hardware?: {
|
|
17
|
+
android?: {
|
|
18
|
+
strongBoxAvailable: boolean;
|
|
19
|
+
strongBoxBacked: boolean;
|
|
20
|
+
securityLevel?: number;
|
|
21
|
+
strongBoxFallbackReason?: SecureHardwareFallbackReason | null;
|
|
22
|
+
};
|
|
23
|
+
ios?: {
|
|
24
|
+
secureEnclaveAvailable: boolean;
|
|
25
|
+
secureEnclaveBacked: boolean;
|
|
26
|
+
securityLevel?: number | null;
|
|
27
|
+
secureEnclaveFallbackReason?: SecureHardwareFallbackReason | null;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
18
30
|
};
|
|
19
31
|
export type GenerateProofInput = {
|
|
20
32
|
htu: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"
|
|
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.3.0",
|
|
4
4
|
"description": "React Native library for DPoP proof generation and key management.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"android",
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
"example": "yarn workspace react-native-dpop-example",
|
|
52
52
|
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
53
53
|
"prepare": "bob build",
|
|
54
|
-
"release": "release-it
|
|
54
|
+
"release": "release-it",
|
|
55
|
+
"release:version": "release-it --only-version",
|
|
55
56
|
"test": "jest",
|
|
56
57
|
"typecheck": "tsc"
|
|
57
58
|
},
|
|
@@ -110,11 +111,11 @@
|
|
|
110
111
|
]
|
|
111
112
|
},
|
|
112
113
|
"codegenConfig": {
|
|
113
|
-
"name": "
|
|
114
|
+
"name": "ReactNativeDPoPSpec",
|
|
114
115
|
"type": "modules",
|
|
115
116
|
"jsSrcsDir": "src",
|
|
116
117
|
"android": {
|
|
117
|
-
"javaPackageName": "com.
|
|
118
|
+
"javaPackageName": "com.reactnativedpop"
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TurboModule } from 'react-native';
|
|
2
|
-
import { TurboModuleRegistry } from 'react-native';
|
|
2
|
+
import { NativeModules, TurboModuleRegistry } from 'react-native';
|
|
3
3
|
import type { UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes';
|
|
4
4
|
|
|
5
5
|
export interface Spec extends TurboModule {
|
|
@@ -27,4 +27,7 @@ export interface Spec extends TurboModule {
|
|
|
27
27
|
): Promise<UnsafeObject>;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
const nativeDpopModule =
|
|
31
|
+
TurboModuleRegistry.get<Spec>('ReactNativeDPoP') ?? (NativeModules.ReactNativeDPoP as Spec | undefined);
|
|
32
|
+
|
|
33
|
+
export default nativeDpopModule as Spec;
|
package/src/index.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import Dpop from './NativeDpop';
|
|
1
|
+
import NativeReactNativeDPoP from './NativeReactNativeDPoP';
|
|
3
2
|
|
|
4
3
|
type AdditionalClaims = Record<string, unknown>;
|
|
5
4
|
|
|
@@ -12,15 +11,28 @@ export type PublicJwk = {
|
|
|
12
11
|
|
|
13
12
|
export type PublicKeyFormat = 'JWK' | 'DER' | 'RAW';
|
|
14
13
|
|
|
14
|
+
export type SecureHardwareFallbackReason = 'UNAVAILABLE' | 'PROVIDER_ERROR' | 'POLICY_REJECTED' | 'UNKNOWN';
|
|
15
|
+
|
|
15
16
|
export type DPoPKeyInfo = {
|
|
16
17
|
alias: string;
|
|
17
18
|
hasKeyPair: boolean;
|
|
18
19
|
algorithm?: string;
|
|
19
20
|
curve?: string;
|
|
20
21
|
insideSecureHardware?: boolean;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
hardware?: {
|
|
23
|
+
android?: {
|
|
24
|
+
strongBoxAvailable: boolean;
|
|
25
|
+
strongBoxBacked: boolean;
|
|
26
|
+
securityLevel?: number;
|
|
27
|
+
strongBoxFallbackReason?: SecureHardwareFallbackReason | null;
|
|
28
|
+
};
|
|
29
|
+
ios?: {
|
|
30
|
+
secureEnclaveAvailable: boolean;
|
|
31
|
+
secureEnclaveBacked: boolean;
|
|
32
|
+
securityLevel?: number | null;
|
|
33
|
+
secureEnclaveFallbackReason?: SecureHardwareFallbackReason | null;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
24
36
|
};
|
|
25
37
|
|
|
26
38
|
export type GenerateProofInput = {
|
|
@@ -51,18 +63,6 @@ type GenerateProofResult = {
|
|
|
51
63
|
proofContext: DPoPProofContext;
|
|
52
64
|
};
|
|
53
65
|
|
|
54
|
-
const LINKING_ERROR =
|
|
55
|
-
'Dpop module nao encontrado. Verifique se o app Android foi recompilado apos adicionar o modulo nativo.';
|
|
56
|
-
|
|
57
|
-
function requireAndroid() {
|
|
58
|
-
if (Platform.OS !== 'android') {
|
|
59
|
-
throw new Error('react-native-dpop (MVP atual) suporta somente Android.');
|
|
60
|
-
}
|
|
61
|
-
if (!Dpop) {
|
|
62
|
-
throw new Error(LINKING_ERROR);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
66
|
export class DPoP {
|
|
67
67
|
public readonly proof: string;
|
|
68
68
|
public readonly alias?: string;
|
|
@@ -75,35 +75,30 @@ export class DPoP {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
public async calculateThumbprint(): Promise<string> {
|
|
78
|
-
|
|
79
|
-
return Dpop.calculateThumbprint(this.alias ?? null);
|
|
78
|
+
return NativeReactNativeDPoP.calculateThumbprint(this.alias ?? null);
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
public async getPublicKey(format: PublicKeyFormat): Promise<PublicJwk | string> {
|
|
83
|
-
requireAndroid();
|
|
84
82
|
if (format === 'DER') {
|
|
85
|
-
return
|
|
83
|
+
return NativeReactNativeDPoP.getPublicKeyDer(this.alias ?? null);
|
|
86
84
|
}
|
|
87
85
|
if (format === 'RAW') {
|
|
88
|
-
return
|
|
86
|
+
return NativeReactNativeDPoP.getPublicKeyRaw(this.alias ?? null);
|
|
89
87
|
}
|
|
90
88
|
|
|
91
|
-
return
|
|
89
|
+
return NativeReactNativeDPoP.getPublicKeyJwk(this.alias ?? null) as Promise<PublicJwk>;
|
|
92
90
|
}
|
|
93
91
|
|
|
94
92
|
public async signWithDpopPrivateKey(payload: string): Promise<string> {
|
|
95
|
-
|
|
96
|
-
return Dpop.signWithDpopPrivateKey(payload, this.alias ?? null);
|
|
93
|
+
return NativeReactNativeDPoP.signWithDpopPrivateKey(payload, this.alias ?? null);
|
|
97
94
|
}
|
|
98
95
|
|
|
99
96
|
public async isBoundToAlias(alias?: string): Promise<boolean> {
|
|
100
|
-
|
|
101
|
-
return Dpop.isBoundToAlias(this.proof, alias ?? this.alias ?? null);
|
|
97
|
+
return NativeReactNativeDPoP.isBoundToAlias(this.proof, alias ?? this.alias ?? null);
|
|
102
98
|
}
|
|
103
99
|
|
|
104
100
|
public static async generateProof(input: GenerateProofInput): Promise<DPoP> {
|
|
105
|
-
|
|
106
|
-
const result = (await Dpop.generateProof(
|
|
101
|
+
const result = (await NativeReactNativeDPoP.generateProof(
|
|
107
102
|
input.htu,
|
|
108
103
|
input.htm,
|
|
109
104
|
input.nonce ?? null,
|
|
@@ -119,27 +114,22 @@ export class DPoP {
|
|
|
119
114
|
}
|
|
120
115
|
|
|
121
116
|
public static async assertHardwareBacked(alias?: string): Promise<void> {
|
|
122
|
-
|
|
123
|
-
await Dpop.assertHardwareBacked(alias ?? null);
|
|
117
|
+
await NativeReactNativeDPoP.assertHardwareBacked(alias ?? null);
|
|
124
118
|
}
|
|
125
119
|
|
|
126
120
|
public static async deleteKeyPair(alias?: string): Promise<void> {
|
|
127
|
-
|
|
128
|
-
await Dpop.deleteKeyPair(alias ?? null);
|
|
121
|
+
await NativeReactNativeDPoP.deleteKeyPair(alias ?? null);
|
|
129
122
|
}
|
|
130
123
|
|
|
131
124
|
public static async getKeyInfo(alias?: string): Promise<DPoPKeyInfo> {
|
|
132
|
-
|
|
133
|
-
return Dpop.getKeyInfo(alias ?? null) as Promise<DPoPKeyInfo>;
|
|
125
|
+
return NativeReactNativeDPoP.getKeyInfo(alias ?? null) as Promise<DPoPKeyInfo>;
|
|
134
126
|
}
|
|
135
127
|
|
|
136
128
|
public static async hasKeyPair(alias?: string): Promise<boolean> {
|
|
137
|
-
|
|
138
|
-
return Dpop.hasKeyPair(alias ?? null);
|
|
129
|
+
return NativeReactNativeDPoP.hasKeyPair(alias ?? null);
|
|
139
130
|
}
|
|
140
131
|
|
|
141
132
|
public static async rotateKeyPair(alias?: string): Promise<void> {
|
|
142
|
-
|
|
143
|
-
await Dpop.rotateKeyPair(alias ?? null);
|
|
133
|
+
await NativeReactNativeDPoP.rotateKeyPair(alias ?? null);
|
|
144
134
|
}
|
|
145
135
|
}
|
package/ios/Dpop.h
DELETED
package/ios/Dpop.mm
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
#import "Dpop.h"
|
|
2
|
-
|
|
3
|
-
@implementation Dpop
|
|
4
|
-
- (NSNumber *)multiply:(double)a b:(double)b {
|
|
5
|
-
NSNumber *result = @(a * b);
|
|
6
|
-
|
|
7
|
-
return result;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
11
|
-
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
12
|
-
{
|
|
13
|
-
return std::make_shared<facebook::react::NativeDpopSpecJSI>(params);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
+ (NSString *)moduleName
|
|
17
|
-
{
|
|
18
|
-
return @"Dpop";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
@end
|
package/lib/module/NativeDpop.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeDpop.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AA4BlD,eAAeA,mBAAmB,CAACC,YAAY,CAAO,MAAM,CAAC","ignoreList":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NativeDpop.d.ts","sourceRoot":"","sources":["../../../src/NativeDpop.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;;AAED,wBAA8D"}
|