@pagopa/io-react-native-iso18013 0.1.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/IoReactNativeIso18013.podspec +29 -0
- package/LICENSE +20 -0
- package/README.md +30 -0
- package/android/build.gradle +83 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/ioreactnativeiso18013/Base64Utils.kt +56 -0
- package/android/src/main/java/com/ioreactnativeiso18013/IoReactNativeCborModule.kt +209 -0
- package/android/src/main/java/com/ioreactnativeiso18013/IoReactNativeIso18013Module.kt +439 -0
- package/android/src/main/java/com/ioreactnativeiso18013/IoReactNativeIso18013Package.kt +17 -0
- package/ios/Base64Utils.swift +47 -0
- package/ios/IoReactNativeCbor.mm +32 -0
- package/ios/IoReactNativeCbor.swift +188 -0
- package/ios/IoReactNativeIso18013-Bridging-Header.h +2 -0
- package/ios/IoReactNativeIso18013.mm +43 -0
- package/ios/IoReactNativeIso18013.swift +491 -0
- package/lib/module/cbor/cbor/README.md +95 -0
- package/lib/module/cbor/cbor/decoder.js +51 -0
- package/lib/module/cbor/cbor/decoder.js.map +1 -0
- package/lib/module/cbor/cbor/failure.js +2 -0
- package/lib/module/cbor/cbor/failure.js.map +1 -0
- package/lib/module/cbor/cbor/index.js +5 -0
- package/lib/module/cbor/cbor/index.js.map +1 -0
- package/lib/module/cbor/cbor/schema.js +117 -0
- package/lib/module/cbor/cbor/schema.js.map +1 -0
- package/lib/module/cbor/cbor/schema.utils.js +16 -0
- package/lib/module/cbor/cbor/schema.utils.js.map +1 -0
- package/lib/module/cbor/cose/README.md +53 -0
- package/lib/module/cbor/cose/failure.js +2 -0
- package/lib/module/cbor/cose/failure.js.map +1 -0
- package/lib/module/cbor/cose/index.js +4 -0
- package/lib/module/cbor/cose/index.js.map +1 -0
- package/lib/module/cbor/cose/sign.js +23 -0
- package/lib/module/cbor/cose/sign.js.map +1 -0
- package/lib/module/cbor/index.js +13 -0
- package/lib/module/cbor/index.js.map +1 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/iso18013/index.js +14 -0
- package/lib/module/iso18013/index.js.map +1 -0
- package/lib/module/iso18013/iso18013-5/README.md +234 -0
- package/lib/module/iso18013/iso18013-5/index.js +5 -0
- package/lib/module/iso18013/iso18013-5/index.js.map +1 -0
- package/lib/module/iso18013/iso18013-5/proximity.js +112 -0
- package/lib/module/iso18013/iso18013-5/proximity.js.map +1 -0
- package/lib/module/iso18013/iso18013-5/schema.js +79 -0
- package/lib/module/iso18013/iso18013-5/schema.js.map +1 -0
- package/lib/module/iso18013/iso18013-7/README.md +59 -0
- package/lib/module/iso18013/iso18013-7/index.js +4 -0
- package/lib/module/iso18013/iso18013-7/index.js.map +1 -0
- package/lib/module/iso18013/iso18013-7/remote.js +31 -0
- package/lib/module/iso18013/iso18013-7/remote.js.map +1 -0
- package/lib/module/iso18013/types.js +2 -0
- package/lib/module/iso18013/types.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/cbor/cbor/decoder.d.ts +35 -0
- package/lib/typescript/src/cbor/cbor/decoder.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cbor/failure.d.ts +15 -0
- package/lib/typescript/src/cbor/cbor/failure.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cbor/index.d.ts +4 -0
- package/lib/typescript/src/cbor/cbor/index.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cbor/schema.d.ts +2420 -0
- package/lib/typescript/src/cbor/cbor/schema.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cbor/schema.utils.d.ts +8 -0
- package/lib/typescript/src/cbor/cbor/schema.utils.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cose/failure.d.ts +15 -0
- package/lib/typescript/src/cbor/cose/failure.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cose/index.d.ts +3 -0
- package/lib/typescript/src/cbor/cose/index.d.ts.map +1 -0
- package/lib/typescript/src/cbor/cose/sign.d.ts +19 -0
- package/lib/typescript/src/cbor/cose/sign.d.ts.map +1 -0
- package/lib/typescript/src/cbor/index.d.ts +2 -0
- package/lib/typescript/src/cbor/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/index.d.ts +3 -0
- package/lib/typescript/src/iso18013/index.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/iso18013-5/index.d.ts +4 -0
- package/lib/typescript/src/iso18013/iso18013-5/index.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/iso18013-5/proximity.d.ts +88 -0
- package/lib/typescript/src/iso18013/iso18013-5/proximity.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/iso18013-5/schema.d.ts +85 -0
- package/lib/typescript/src/iso18013/iso18013-5/schema.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/iso18013-7/index.d.ts +3 -0
- package/lib/typescript/src/iso18013/iso18013-7/index.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/iso18013-7/remote.d.ts +29 -0
- package/lib/typescript/src/iso18013/iso18013-7/remote.d.ts.map +1 -0
- package/lib/typescript/src/iso18013/types.d.ts +16 -0
- package/lib/typescript/src/iso18013/types.d.ts.map +1 -0
- package/package.json +163 -0
- package/src/cbor/cbor/README.md +95 -0
- package/src/cbor/cbor/decoder.ts +56 -0
- package/src/cbor/cbor/failure.ts +18 -0
- package/src/cbor/cbor/index.ts +9 -0
- package/src/cbor/cbor/schema.ts +150 -0
- package/src/cbor/cbor/schema.utils.ts +13 -0
- package/src/cbor/cose/README.md +53 -0
- package/src/cbor/cose/failure.ts +20 -0
- package/src/cbor/cose/index.ts +2 -0
- package/src/cbor/cose/sign.ts +25 -0
- package/src/cbor/index.ts +18 -0
- package/src/index.tsx +5 -0
- package/src/iso18013/index.ts +25 -0
- package/src/iso18013/iso18013-5/README.md +234 -0
- package/src/iso18013/iso18013-5/index.ts +23 -0
- package/src/iso18013/iso18013-5/proximity.ts +134 -0
- package/src/iso18013/iso18013-5/schema.ts +87 -0
- package/src/iso18013/iso18013-7/README.md +59 -0
- package/src/iso18013/iso18013-7/index.ts +7 -0
- package/src/iso18013/iso18013-7/remote.ts +54 -0
- package/src/iso18013/types.ts +16 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { IoReactNativeCbor } from '..';
|
|
2
|
+
import {
|
|
3
|
+
Documents,
|
|
4
|
+
DocumentsFromString,
|
|
5
|
+
IssuerSigned,
|
|
6
|
+
IssuerSignedFromString,
|
|
7
|
+
} from './schema';
|
|
8
|
+
import { coerceToJSON } from './schema.utils';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Decode base64 encoded CBOR data to JSON object.
|
|
12
|
+
*
|
|
13
|
+
* If it is not possibile to decode the provided data, the promise will be rejected with
|
|
14
|
+
* an instance of {@link CborFailure}.
|
|
15
|
+
*
|
|
16
|
+
* **NOTE**: this method does not handle nested CBOR data, which will need additional
|
|
17
|
+
* parsing.
|
|
18
|
+
*
|
|
19
|
+
* @param data - The base64 or base64url encoded CBOR data
|
|
20
|
+
* @returns The decoded data as JSON object
|
|
21
|
+
*/
|
|
22
|
+
export const decode = async (data: string): Promise<any> => {
|
|
23
|
+
const jsonString = await IoReactNativeCbor.decode(data);
|
|
24
|
+
return await coerceToJSON.parseAsync(jsonString);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Decode base64 or base64url encoded CBOR data to mDOC object
|
|
29
|
+
*
|
|
30
|
+
* If it is not possibile to decode the provided data, the promise will be rejected with
|
|
31
|
+
* an instance of {@link CborFailure}.
|
|
32
|
+
*
|
|
33
|
+
* @param data - The base64 encoded MDOC data
|
|
34
|
+
* @returns The decoded data as mDOC object
|
|
35
|
+
*/
|
|
36
|
+
export const decodeDocuments = async (data: string): Promise<Documents> => {
|
|
37
|
+
const documentsString = await IoReactNativeCbor.decodeDocuments(data);
|
|
38
|
+
return await DocumentsFromString.parseAsync(documentsString);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extract and decode the {@link IssuerSigned} with the {@link IssuerAuth} decoded from base64 encoded CBOR
|
|
43
|
+
*
|
|
44
|
+
* If it is not possibile to decode the provided data, the promise will be rejected with
|
|
45
|
+
* an instance of {@link CborFailure}.
|
|
46
|
+
*
|
|
47
|
+
* @param issuerSigned - The base64 or base64url encoded MDOC data
|
|
48
|
+
* @returns The decoded {@link IssuerSigned} contained in the mDOC object
|
|
49
|
+
*/
|
|
50
|
+
export const decodeIssuerSigned = async (
|
|
51
|
+
issuerSigned: string
|
|
52
|
+
): Promise<IssuerSigned> => {
|
|
53
|
+
const decodedIssuerSignedString =
|
|
54
|
+
await IoReactNativeCbor.decodeIssuerSigned(issuerSigned);
|
|
55
|
+
return await IssuerSignedFromString.parseAsync(decodedIssuerSignedString);
|
|
56
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All error codes that the module could return.
|
|
3
|
+
*/
|
|
4
|
+
export type CborFailureCodes =
|
|
5
|
+
| 'UNABLE_TO_DECODE'
|
|
6
|
+
| 'INVALID_ENCODING'
|
|
7
|
+
| 'UNKNOWN_EXCEPTION';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Error type returned by a rejected promise.
|
|
11
|
+
*
|
|
12
|
+
* If additional error information are available,
|
|
13
|
+
* they are stored in the {@link CborFailure["userInfo"]} field.
|
|
14
|
+
*/
|
|
15
|
+
export type CborFailure = {
|
|
16
|
+
message: CborFailureCodes;
|
|
17
|
+
userInfo: Record<string, string>;
|
|
18
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { coerceToJSON } from './schema.utils';
|
|
3
|
+
|
|
4
|
+
export enum DocumentTypeEnum {
|
|
5
|
+
MDL = 'org.iso.18013.5.1.mDL',
|
|
6
|
+
EU_PID = 'eu.europa.ec.eudi.pid.1',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const DocumentType = z.nativeEnum(DocumentTypeEnum);
|
|
10
|
+
export type DocumentType = z.infer<typeof DocumentType>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Value contained in a document
|
|
14
|
+
*/
|
|
15
|
+
export const DocumentValue = z.object({
|
|
16
|
+
digestID: z.coerce.number().optional(),
|
|
17
|
+
random: z.string().optional(),
|
|
18
|
+
elementIdentifier: z.string(),
|
|
19
|
+
elementValue: z.any(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export type DocumentValue = z.infer<typeof DocumentValue>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Issuer Auth Unprotected Header Entry
|
|
26
|
+
*/
|
|
27
|
+
export const IssuerAuthUnprotectedHeader = z.object({
|
|
28
|
+
algorithm: z.coerce.string().optional(),
|
|
29
|
+
keyId: z.string().optional(),
|
|
30
|
+
x5chain: z.array(z.string()).optional(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export type IssuerAuthUnprotectedHeader = z.infer<
|
|
34
|
+
typeof IssuerAuthUnprotectedHeader
|
|
35
|
+
>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Issuer Auth Payload Validity Info
|
|
39
|
+
*/
|
|
40
|
+
export const IssuerAuthPayloadValidityInfo = z.object({
|
|
41
|
+
signed: z.coerce.date(),
|
|
42
|
+
validUntil: z.coerce.date(),
|
|
43
|
+
validFrom: z.coerce.date(),
|
|
44
|
+
expectedUpdate: z.coerce.date().optional(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export type IssuerAuthPayloadValidityInfo = z.infer<
|
|
48
|
+
typeof IssuerAuthPayloadValidityInfo
|
|
49
|
+
>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* EC KEY
|
|
53
|
+
*/
|
|
54
|
+
const ECKey = z.object({
|
|
55
|
+
kty: z.enum(['EC']),
|
|
56
|
+
crv: z.enum(['P-256', 'P-384', 'P-521']),
|
|
57
|
+
x: z.string(),
|
|
58
|
+
y: z.string(),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* RSA KEY
|
|
63
|
+
*/
|
|
64
|
+
const RSAKEY = z.object({
|
|
65
|
+
kty: z.enum(['RSA']),
|
|
66
|
+
alg: z.string(),
|
|
67
|
+
e: z.string(),
|
|
68
|
+
n: z.string(),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const PublicKey = z.union([RSAKEY, ECKey]);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Issuer Auth Payload Device Key Info
|
|
75
|
+
*/
|
|
76
|
+
export const IssuerAuthPayloadDeviceKeyInfo = z.object({
|
|
77
|
+
deviceKey: PublicKey,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export type IssuerAuthPayloadDeviceKeyInfo = z.infer<
|
|
81
|
+
typeof IssuerAuthPayloadDeviceKeyInfo
|
|
82
|
+
>;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Issuer Auth Payload Object
|
|
86
|
+
*/
|
|
87
|
+
export const IssuerAuthPayload = z.object({
|
|
88
|
+
docType: z.string().optional(),
|
|
89
|
+
version: z.string().optional(),
|
|
90
|
+
validityInfo: IssuerAuthPayloadValidityInfo,
|
|
91
|
+
digestAlgorithm: z.string().optional(),
|
|
92
|
+
deviceKeyInfo: IssuerAuthPayloadDeviceKeyInfo,
|
|
93
|
+
valueDigests: z.record(z.any()),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export type IssuerAuthPayload = z.infer<typeof IssuerAuthPayload>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Issuer Auth Object
|
|
100
|
+
*/
|
|
101
|
+
export const IssuerAuth = z.object({
|
|
102
|
+
rawValue: z.string().optional(),
|
|
103
|
+
protectedHeader: z.string().optional(),
|
|
104
|
+
unprotectedHeader: IssuerAuthUnprotectedHeader,
|
|
105
|
+
payload: IssuerAuthPayload,
|
|
106
|
+
signature: z.string().optional(),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
export type IssuerAuth = z.infer<typeof IssuerAuth>;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Issuer signed object
|
|
113
|
+
*/
|
|
114
|
+
export const IssuerSigned = z.object({
|
|
115
|
+
nameSpaces: z.record(z.string(), coerceToJSON.pipe(z.array(DocumentValue))),
|
|
116
|
+
issuerAuth: IssuerAuth,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
export type IssuerSigned = z.infer<typeof IssuerSigned>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* mDOC object
|
|
123
|
+
*/
|
|
124
|
+
export const MDOC = z.object({
|
|
125
|
+
docType: DocumentType,
|
|
126
|
+
issuerSigned: IssuerSigned,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
export type MDOC = z.infer<typeof MDOC>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* CBOR decoded data containing the status, version and the list of documents
|
|
133
|
+
*/
|
|
134
|
+
export const Documents = z.object({
|
|
135
|
+
status: z.number().optional(),
|
|
136
|
+
version: z.string().optional(),
|
|
137
|
+
documents: coerceToJSON.pipe(z.array(MDOC)),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Documents object from string
|
|
142
|
+
*/
|
|
143
|
+
export const DocumentsFromString = coerceToJSON.pipe(Documents);
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* IssuerSigned object from string
|
|
147
|
+
*/
|
|
148
|
+
export const IssuerSignedFromString = coerceToJSON.pipe(IssuerSigned);
|
|
149
|
+
|
|
150
|
+
export type Documents = z.infer<typeof Documents>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* If the value is a string, parse it as JSON.
|
|
5
|
+
* Otherwise, return the value as is.
|
|
6
|
+
* Useful for unescaping stringified JSON content
|
|
7
|
+
*/
|
|
8
|
+
export const coerceToJSON = z.any().transform((any) => {
|
|
9
|
+
if (typeof any === 'object') {
|
|
10
|
+
return any;
|
|
11
|
+
}
|
|
12
|
+
return JSON.parse(any);
|
|
13
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
## COSE
|
|
2
|
+
|
|
3
|
+
This module provides methods to sign and verify data with COSE.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { COSE } from '@pagopa/io-react-native-cbor';
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
### Methods
|
|
10
|
+
|
|
11
|
+
#### `sign`
|
|
12
|
+
|
|
13
|
+
Signs base64 encoded data using COSE (CBOR Object Signing and Encryption).
|
|
14
|
+
Returns a `Promise` which resolves to a `string` containing the COSE-Sign1 object in base64 encoding or rejects with an instance of `CoseFailure` in case of failures.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
try {
|
|
18
|
+
const coseSign1 = await COSE.sign('base64EncodedData', 'keyTag');
|
|
19
|
+
} catch (e) {
|
|
20
|
+
const { message, userInfo } = e as CoseFailure;
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
#### `verify`
|
|
25
|
+
|
|
26
|
+
Verifies a COSE-Sign1 object using the provided public key.
|
|
27
|
+
Returns a `Promise` which resolves to a `boolean` indicating if the signature is valid or rejects with an instance of `CoseFailure` in case of failures.
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// public key in JWK format
|
|
31
|
+
const publicKey = {
|
|
32
|
+
kty: 'EC',
|
|
33
|
+
crv: 'P-256',
|
|
34
|
+
x: '...',
|
|
35
|
+
y: '...',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const isValid = await COSE.verify('coseSign1Base64Data', publicKey);
|
|
40
|
+
} catch (e) {
|
|
41
|
+
const { message, userInfo } = e as CoseFailure;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Error Codes
|
|
46
|
+
|
|
47
|
+
| Type | Platform | Description |
|
|
48
|
+
| -------------------- | ----------- | -------------------------------------------------- |
|
|
49
|
+
| PUBLIC_KEY_NOT_FOUND | Android/iOS | The public key is missing for the specified keyTag |
|
|
50
|
+
| INVALID_ENCODING | Android/iOS | Provided payload has incorrect encoding |
|
|
51
|
+
| UNABLE_TO_SIGN | Android/iOS | It was not possible to sign the given string |
|
|
52
|
+
| THREADING_ERROR | iOS | Unexpected failure |
|
|
53
|
+
| UNKNOWN_EXCEPTION | Android/iOS | Unexpected failure |
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All error codes that the module could return.
|
|
3
|
+
*/
|
|
4
|
+
export type CoseFailureCodes =
|
|
5
|
+
| 'PUBLIC_KEY_NOT_FOUND'
|
|
6
|
+
| 'UNABLE_TO_SIGN'
|
|
7
|
+
| 'INVALID_ENCODING'
|
|
8
|
+
| 'THREADING_ERROR'
|
|
9
|
+
| 'UNKNOWN_EXCEPTION';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Error type returned by a rejected promise.
|
|
13
|
+
*
|
|
14
|
+
* If additional error information are available,
|
|
15
|
+
* they are stored in the {@link CoseError["userInfo"]} field.
|
|
16
|
+
*/
|
|
17
|
+
export type CoseFailure = {
|
|
18
|
+
message: CoseFailureCodes;
|
|
19
|
+
userInfo: Record<string, string>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PublicKey } from '@pagopa/io-react-native-crypto';
|
|
2
|
+
import { IoReactNativeCbor } from '..';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Sign base64 encoded data with COSE and return the COSE-Sign1 object in base64 encoding
|
|
6
|
+
*
|
|
7
|
+
* @param payload - The base64 or base64url encoded payload to sign
|
|
8
|
+
* @param keyTag - The alias of the key to use for signing.
|
|
9
|
+
* @throws {CoseFailure} If the key does not exist
|
|
10
|
+
* @returns The COSE-Sign1 object in base64 encoding
|
|
11
|
+
*/
|
|
12
|
+
export const sign = async (payload: string, keyTag: string): Promise<string> =>
|
|
13
|
+
await IoReactNativeCbor.sign(payload, keyTag);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Verifies a COSE-Sign1 object with the provided public key
|
|
17
|
+
*
|
|
18
|
+
* @param data - The COSE-Sign1 object in base64 or base64url encoding
|
|
19
|
+
* @param publicKey - The public key in JWK format
|
|
20
|
+
* @returns true if the signature is valid, false otherwise
|
|
21
|
+
*/
|
|
22
|
+
export const verify = async (
|
|
23
|
+
data: string,
|
|
24
|
+
publicKey: PublicKey
|
|
25
|
+
): Promise<boolean> => await IoReactNativeCbor.verify(data, publicKey);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { NativeModules, Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const LINKING_ERROR =
|
|
4
|
+
`The package '@pagopa/io-react-native-cbor' doesn't seem to be linked. Make sure: \n\n` +
|
|
5
|
+
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
|
|
6
|
+
'- You rebuilt the app after installing the package\n' +
|
|
7
|
+
'- You are not using Expo Go\n';
|
|
8
|
+
|
|
9
|
+
export const IoReactNativeCbor = NativeModules.IoReactNativeCbor
|
|
10
|
+
? NativeModules.IoReactNativeCbor
|
|
11
|
+
: new Proxy(
|
|
12
|
+
{},
|
|
13
|
+
{
|
|
14
|
+
get() {
|
|
15
|
+
throw new Error(LINKING_ERROR);
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
);
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { NativeModules, Platform } from 'react-native';
|
|
2
|
+
export {
|
|
3
|
+
type AcceptedFields,
|
|
4
|
+
type EventError,
|
|
5
|
+
type VerifierRequest,
|
|
6
|
+
parseEventError,
|
|
7
|
+
parseVerifierRequest,
|
|
8
|
+
} from './iso18013-5/schema';
|
|
9
|
+
|
|
10
|
+
const LINKING_ERROR =
|
|
11
|
+
`The package '@pagopa/io-react-native-proximity' doesn't seem to be linked. Make sure: \n\n` +
|
|
12
|
+
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
|
|
13
|
+
'- You rebuilt the app after installing the package\n' +
|
|
14
|
+
'- You are not using Expo Go\n';
|
|
15
|
+
|
|
16
|
+
export const IoReactNativeProximity = NativeModules.IoReactNativeIso18013
|
|
17
|
+
? NativeModules.IoReactNativeIso18013
|
|
18
|
+
: new Proxy(
|
|
19
|
+
{},
|
|
20
|
+
{
|
|
21
|
+
get() {
|
|
22
|
+
throw new Error(LINKING_ERROR);
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
);
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# ISO18013-5
|
|
2
|
+
|
|
3
|
+
This library provides a React Native module based on [iso18013-android](https://github.com/pagopa/iso18013-android) and [iso18013-ios](https://github.com/pagopa/iso18013-ios) which allows mDL proximity presentation according to the
|
|
4
|
+
ISO 18013-5 standard and remote presentation according to the ISO 18013-7 standard.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
### `events`
|
|
11
|
+
|
|
12
|
+
This library emits the following events:
|
|
13
|
+
| Event | Payload | Description |
|
|
14
|
+
|---------------------------|--------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
|
|
15
|
+
| onDeviceConnecting (iOS only) | `undefined` | Event dispatched when the verifier app is connecting |
|
|
16
|
+
| onDeviceConnected | `undefined` | Event dispatched when the verifier app is connected. |
|
|
17
|
+
| onDocumentRequestReceived | `{ data: string } \| undefined` | Event dispatched when the consumer app receives a new request, contained in the data payload. It can be parsed via the `parseVerifierRequest` provided [here](src/schema.ts). |
|
|
18
|
+
| onDeviceDisconnected | `undefined` | Event dispatched when the verifier app disconnects. |
|
|
19
|
+
| onError | `{ error: string } \| undefined` | Event dispatched when an error occurs which is contained in the error payload. It can be parsed via the `parseError` provided [here](src/schema.ts). |
|
|
20
|
+
|
|
21
|
+
Listeners can be added using the `addListener` method and removed using the `removeListener` method.
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { ISO18013_5 } from '@pagopa/io-react-native-iso18013';
|
|
25
|
+
|
|
26
|
+
ISO18013_5.addListener('event', () => console.log('event occurred'));
|
|
27
|
+
|
|
28
|
+
ISO18013_5.removeListener('event');
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
#### `onDeviceConnecting`
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { ISO18013_5 } from '@pagopa/io-react-native-iso18013';
|
|
35
|
+
|
|
36
|
+
ISO18013_5.addListener('onDeviceConnecting', () => {
|
|
37
|
+
console.log('Device is connecting');
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### `onDeviceConnected`
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { ISO18013_5 } from '@pagopa/io-react-native-iso18013';
|
|
45
|
+
|
|
46
|
+
ISO18013_5.addListener('onDeviceConnected', () => {
|
|
47
|
+
console.log('Device is connected');
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### `onDocumentRequestReceived`
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { ISO18013_5 } from '@pagopa/io-react-native-iso18013';
|
|
55
|
+
|
|
56
|
+
ISO18013_5.addListener(
|
|
57
|
+
'onDocumentRequestReceived',
|
|
58
|
+
(payload: ISO18013_5.EventsPayload['onDocumentRequestReceived']) => {
|
|
59
|
+
console.log('onDocumentRequestReceived', payload);
|
|
60
|
+
if (!payload || !payload.data) {
|
|
61
|
+
console.warn('Request does not contain a message.');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Parse and verify the received request with the exposed function
|
|
66
|
+
const parsedJson = JSON.parse(payload.data);
|
|
67
|
+
console.log('Parsed JSON:', parsedJson);
|
|
68
|
+
const parsedResponse = parseVerifierRequest(parsedJson);
|
|
69
|
+
console.log('Parsed response:', JSON.stringify(parsedResponse));
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### `onDeviceDisconnected`
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { ISO18013_5 } from '@pagopa/io-react-native-iso18013';
|
|
78
|
+
|
|
79
|
+
ISO18013_5.addListener('onDeviceDisconnected', () => {
|
|
80
|
+
console.log('Device is disconnected');
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### `onError`
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { ISO18013_5 } from '@pagopa/io-react-native-iso18013';
|
|
88
|
+
|
|
89
|
+
ISO18013_5.addListener(
|
|
90
|
+
'onError',
|
|
91
|
+
async (data: ISO18013_5.EventsPayload['onError']) => {
|
|
92
|
+
try {
|
|
93
|
+
if (!data || !data.error) {
|
|
94
|
+
throw new Error('No error data received');
|
|
95
|
+
}
|
|
96
|
+
const parsedError = parseError(data.error);
|
|
97
|
+
console.error(`onError: ${parsedError}`);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.error('Error parsing onError data:', e);
|
|
100
|
+
} finally {
|
|
101
|
+
// Close the flow on error
|
|
102
|
+
await closeFlow();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `start`
|
|
109
|
+
|
|
110
|
+
Starts the proximity flow and starts the bluetooth service. This method also accepts optional parameters to configure the initialization on Android, along with the possibility
|
|
111
|
+
to specify a certificates of array to verify the reader app.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { ISO18013_5 } from '@pagopa/io-react-native-iso18013';
|
|
115
|
+
|
|
116
|
+
await ISO18013_5.start();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `getQrCodeString`
|
|
120
|
+
|
|
121
|
+
Returns the QR code string which contains a base64url encoded CBOR object which encodes the bluetooth engagement data.
|
|
122
|
+
It can be used to display the QR code in the UI which will be scanned by the verifier app.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { ISO18013_5 } from '@pagopa/io-react-native-iso18013';
|
|
126
|
+
|
|
127
|
+
const qrCodeString = await ISO18013_5.getQrCodeString();
|
|
128
|
+
console.log(qrCodeString);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### `generateResponse`
|
|
132
|
+
|
|
133
|
+
Generates a response that will be sent to the verifier app containing the requested documents.
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { ISO18013_5 } from '@pagopa/io-react-native-iso18013';
|
|
137
|
+
|
|
138
|
+
const response = await ISO18013_5.generateResponse({
|
|
139
|
+
documents: [
|
|
140
|
+
{
|
|
141
|
+
type: 'mDL',
|
|
142
|
+
data: 'base64url-encoded-data',
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
console.log(response);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### `sendResponse`
|
|
150
|
+
|
|
151
|
+
Sends the response generate by `generateResponse` to the verifier app.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { ISO18013_5 } from '@pagopa/io-react-native-iso18013';
|
|
155
|
+
|
|
156
|
+
await ISO18013_5.sendResponse(response);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `sendErrorResponse`
|
|
160
|
+
|
|
161
|
+
Sends an error response to the verifier app. The supported error codes are defined in the Table 20 of the ISO 18013-5 standard and are coded in the `ErrorCode` enum.
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { ISO18013_5, ErrorCode } from '@pagopa/io-react-native-iso18013';
|
|
165
|
+
|
|
166
|
+
await ISO18013_5.sendErrorResponse({
|
|
167
|
+
errorCode: ErrorCode.SESSION_ENCRYPTION,
|
|
168
|
+
errorMessage: 'An error occurred while encrypting the session',
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### `close`
|
|
173
|
+
|
|
174
|
+
Closes the QR engagement by releasing the resources allocated during the `start` method.
|
|
175
|
+
Before starting a new flow, it is necessary to call this method to ensure that the previous flow is properly closed.
|
|
176
|
+
The listeners can be removed using the `removeListener` method.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { ISO18013_5 } from '@pagopa/io-react-native-iso18013';
|
|
180
|
+
|
|
181
|
+
await ISO18013_5.close();
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Proximity Flow Schema
|
|
185
|
+
|
|
186
|
+
This section describes a high level overview of the interactions between an app implementing the `io-react-native-proximity` library and a verifier app.
|
|
187
|
+
|
|
188
|
+
```mermaid
|
|
189
|
+
sequenceDiagram
|
|
190
|
+
participant app as Consumer App
|
|
191
|
+
participant proximity as io-react-native-iso18013
|
|
192
|
+
participant verifier as Verifier App
|
|
193
|
+
|
|
194
|
+
Note over proximity, verifier: If an error occurs during the flow, the onError callback is triggered
|
|
195
|
+
app->>+proximity: Calls start()
|
|
196
|
+
app->>+proximity: Calls getQrCode()
|
|
197
|
+
proximity-->>+app: QR code string
|
|
198
|
+
app->>+app: Renders the QR code string
|
|
199
|
+
verifier->>+app: Scans the QR code
|
|
200
|
+
proximity->>+app: Triggers the onDeviceConnecting callback
|
|
201
|
+
verifier->>+app: Connects to the verifier app
|
|
202
|
+
proximity->>+app: Triggers the onDeviceConnected callback
|
|
203
|
+
verifier->>+app: Requests the credential(s)
|
|
204
|
+
proximity->>+app: Triggers the onDocumentRequestReceived() callback
|
|
205
|
+
app->>+proximity: Parses the request by calling parseVerifierRequest()
|
|
206
|
+
proximity-->>+app: Returns a VerifierRequest
|
|
207
|
+
app->>+app: Shows the requested data and asks for user consent
|
|
208
|
+
alt The user accepts
|
|
209
|
+
app->>+proximity: Calls generateResponse()
|
|
210
|
+
proximity-->>+app: Returns the response
|
|
211
|
+
app->>+proximity: Calls sendResponse()
|
|
212
|
+
proximity->>+verifier: Sends the response
|
|
213
|
+
verifier->>+verifier: Shows the received credential(s) and the verification result
|
|
214
|
+
else The user rejects
|
|
215
|
+
app->>+proximity: Calls sendErrorResponse()
|
|
216
|
+
proximity->>+verifier: Sends the error response code
|
|
217
|
+
verifier->>+verifier: Shows the received error response code
|
|
218
|
+
end
|
|
219
|
+
verifier->>+app: Closes the connection
|
|
220
|
+
proximity->>+app: Calls the onDeviceDisconnected callback
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Error Codes
|
|
224
|
+
|
|
225
|
+
| Type | Platform | Description |
|
|
226
|
+
| ------------------------------- | ----------- | ---------------------------------------------------------------------------- |
|
|
227
|
+
| DRH_NOT_DEFINED | Android | The device retrieval helper hasn't been initialized, call the `start` method |
|
|
228
|
+
| QR_ENGAGEMENT_NOT_DEFINED_ERROR | Android | The QR engagement hasn't been initialized, call the `start` method |
|
|
229
|
+
| START_ERROR | Android/iOS | An error occurred while initializing the required resources |
|
|
230
|
+
| GET_QR_CODE_ERROR | Android/iOS | An error occurred while generating the engagement QR code |
|
|
231
|
+
| GENERATE_RESPONSE_ERROR | Android/iOS | An error occurred while generating the response for the verifier app |
|
|
232
|
+
| SEND_RESPONSE_ERROR | Android/iOS | An error occurred while sending the response to the verifier app |
|
|
233
|
+
| SEND_ERROR_RESPONSE_ERROR | Android/iOS | An error occurred while sending the error response to the verifier app |
|
|
234
|
+
| CLOSE_ERROR | Android | An error occured while closing the required resources |
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export {
|
|
2
|
+
type AcceptedFields,
|
|
3
|
+
type EventError,
|
|
4
|
+
type VerifierRequest,
|
|
5
|
+
parseEventError,
|
|
6
|
+
parseVerifierRequest,
|
|
7
|
+
} from './schema';
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
ErrorCode,
|
|
11
|
+
type Events,
|
|
12
|
+
type EventsPayload,
|
|
13
|
+
addListener,
|
|
14
|
+
close,
|
|
15
|
+
generateResponse,
|
|
16
|
+
getQrCodeString,
|
|
17
|
+
removeListener,
|
|
18
|
+
sendErrorResponse,
|
|
19
|
+
sendResponse,
|
|
20
|
+
start,
|
|
21
|
+
} from './proximity';
|
|
22
|
+
|
|
23
|
+
export { type RequestedDocument } from '../types';
|