@pagopa/io-react-native-wallet 2.0.0-next.6 → 2.0.0-next.7
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/lib/commonjs/credential/issuance/02-evaluate-issuer-trust.js +6 -1
- package/lib/commonjs/credential/issuance/02-evaluate-issuer-trust.js.map +1 -1
- package/lib/commonjs/credential/issuance/06-obtain-credential.js.map +1 -1
- package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js +186 -9
- package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
- package/lib/commonjs/credential/issuance/README.md +7 -2
- package/lib/commonjs/mdoc/const.js +9 -0
- package/lib/commonjs/mdoc/const.js.map +1 -0
- package/lib/commonjs/mdoc/converter.js +26 -0
- package/lib/commonjs/mdoc/converter.js.map +1 -0
- package/lib/commonjs/mdoc/index.js +74 -0
- package/lib/commonjs/mdoc/index.js.map +1 -0
- package/lib/commonjs/mdoc/utils.js +14 -0
- package/lib/commonjs/mdoc/utils.js.map +1 -0
- package/lib/commonjs/trust/types.js.map +1 -1
- package/lib/commonjs/utils/crypto.js +35 -1
- package/lib/commonjs/utils/crypto.js.map +1 -1
- package/lib/module/credential/issuance/02-evaluate-issuer-trust.js +6 -1
- package/lib/module/credential/issuance/02-evaluate-issuer-trust.js.map +1 -1
- package/lib/module/credential/issuance/06-obtain-credential.js.map +1 -1
- package/lib/module/credential/issuance/07-verify-and-parse-credential.js +187 -10
- package/lib/module/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
- package/lib/module/credential/issuance/README.md +7 -2
- package/lib/module/mdoc/const.js +2 -0
- package/lib/module/mdoc/const.js.map +1 -0
- package/lib/module/mdoc/converter.js +20 -0
- package/lib/module/mdoc/converter.js.map +1 -0
- package/lib/module/mdoc/index.js +67 -0
- package/lib/module/mdoc/index.js.map +1 -0
- package/lib/module/mdoc/utils.js +7 -0
- package/lib/module/mdoc/utils.js.map +1 -0
- package/lib/module/trust/types.js.map +1 -1
- package/lib/module/utils/crypto.js +32 -0
- package/lib/module/utils/crypto.js.map +1 -1
- package/lib/typescript/credential/issuance/02-evaluate-issuer-trust.d.ts.map +1 -1
- package/lib/typescript/credential/issuance/06-obtain-credential.d.ts +2 -1
- package/lib/typescript/credential/issuance/06-obtain-credential.d.ts.map +1 -1
- package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts +8 -9
- package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts.map +1 -1
- package/lib/typescript/mdoc/const.d.ts +2 -0
- package/lib/typescript/mdoc/const.d.ts.map +1 -0
- package/lib/typescript/mdoc/converter.d.ts +8 -0
- package/lib/typescript/mdoc/converter.d.ts.map +1 -0
- package/lib/typescript/mdoc/index.d.ts +5 -0
- package/lib/typescript/mdoc/index.d.ts.map +1 -0
- package/lib/typescript/mdoc/utils.d.ts +7 -0
- package/lib/typescript/mdoc/utils.d.ts.map +1 -0
- package/lib/typescript/trust/types.d.ts +135 -0
- package/lib/typescript/trust/types.d.ts.map +1 -1
- package/lib/typescript/utils/crypto.d.ts +16 -0
- package/lib/typescript/utils/crypto.d.ts.map +1 -1
- package/package.json +8 -4
- package/src/credential/issuance/02-evaluate-issuer-trust.ts +2 -1
- package/src/credential/issuance/06-obtain-credential.ts +2 -1
- package/src/credential/issuance/07-verify-and-parse-credential.ts +257 -22
- package/src/credential/issuance/README.md +7 -2
- package/src/mdoc/const.ts +1 -0
- package/src/mdoc/converter.ts +26 -0
- package/src/mdoc/index.ts +93 -0
- package/src/mdoc/utils.ts +7 -0
- package/src/trust/types.ts +4 -0
- package/src/utils/crypto.ts +39 -1
- package/lib/commonjs/credential/issuance/const.js +0 -14
- package/lib/commonjs/credential/issuance/const.js.map +0 -1
- package/lib/module/credential/issuance/const.js +0 -4
- package/lib/module/credential/issuance/const.js.map +0 -1
- package/lib/typescript/credential/issuance/const.d.ts +0 -5
- package/lib/typescript/credential/issuance/const.d.ts.map +0 -1
- package/src/credential/issuance/const.ts +0 -11
@@ -6,12 +6,22 @@ import { SdJwt4VC, verify as verifySdJwt } from "../../sd-jwt";
|
|
6
6
|
import { getValueFromDisclosures } from "../../sd-jwt/converters";
|
7
7
|
import { isSameThumbprint, type JWK } from "../../utils/jwk";
|
8
8
|
import type { ObtainCredential } from "./06-obtain-credential";
|
9
|
-
import {
|
9
|
+
import { verify as verifyMdoc } from "../../mdoc";
|
10
|
+
import { MDOC_DEFAULT_NAMESPACE } from "../../mdoc/const";
|
11
|
+
import { getParsedCredentialClaimKey } from "../../mdoc/utils";
|
12
|
+
import { LogLevel, Logger } from "../../utils/logging";
|
13
|
+
import { extractElementValueAsDate } from "../../mdoc/converter";
|
14
|
+
import type { CBOR } from "@pagopa/io-react-native-iso18013";
|
15
|
+
import type { PublicKey } from "@pagopa/io-react-native-crypto";
|
10
16
|
|
11
17
|
type IssuerConf = Out<EvaluateIssuerTrust>["issuerConf"];
|
12
18
|
type CredentialConf =
|
13
19
|
IssuerConf["openid_credential_issuer"]["credential_configurations_supported"][string];
|
14
20
|
|
21
|
+
type DecodedMDocCredential = Out<typeof verifyMdoc> & {
|
22
|
+
issuerSigned: CBOR.IssuerSigned;
|
23
|
+
};
|
24
|
+
|
15
25
|
export type VerifyAndParseCredential = (
|
16
26
|
issuerConf: IssuerConf,
|
17
27
|
credential: Out<ObtainCredential>["credential"],
|
@@ -26,7 +36,8 @@ export type VerifyAndParseCredential = (
|
|
26
36
|
* Include attributes that are not explicitly mapped in the issuer configuration.
|
27
37
|
*/
|
28
38
|
includeUndefinedAttributes?: boolean;
|
29
|
-
}
|
39
|
+
},
|
40
|
+
x509CertRoot?: string
|
30
41
|
) => Promise<{
|
31
42
|
parsedCredential: ParsedCredential;
|
32
43
|
expiration: Date;
|
@@ -34,11 +45,9 @@ export type VerifyAndParseCredential = (
|
|
34
45
|
}>;
|
35
46
|
|
36
47
|
// The credential as a collection of attributes in plain value
|
37
|
-
type ParsedCredential =
|
48
|
+
type ParsedCredential = {
|
38
49
|
/** Attribute key */
|
39
|
-
string
|
40
|
-
{
|
41
|
-
/** Human-readable name of the attribute */
|
50
|
+
[claim: string]: {
|
42
51
|
name:
|
43
52
|
| /* if i18n is provided */ Record<
|
44
53
|
string /* locale */,
|
@@ -46,10 +55,9 @@ type ParsedCredential = Record<
|
|
46
55
|
>
|
47
56
|
| /* if no i18n is provided */ string
|
48
57
|
| undefined; // Add undefined as a possible value for the name property
|
49
|
-
/** The actual value of the attribute */
|
50
58
|
value: unknown;
|
51
|
-
}
|
52
|
-
|
59
|
+
};
|
60
|
+
};
|
53
61
|
|
54
62
|
// handy alias
|
55
63
|
type DecodedSdJwtCredential = Out<typeof verifySdJwt> & {
|
@@ -139,7 +147,123 @@ const parseCredentialSdJwt = (
|
|
139
147
|
|
140
148
|
return definedValues;
|
141
149
|
};
|
150
|
+
const parseCredentialMDoc = (
|
151
|
+
// the list of supported credentials, as defined in the issuer configuration
|
152
|
+
credentialConfig: CredentialConf,
|
153
|
+
// credential_type: string,
|
154
|
+
{ issuerSigned }: DecodedMDocCredential,
|
155
|
+
ignoreMissingAttributes: boolean = false,
|
156
|
+
includeUndefinedAttributes: boolean = false
|
157
|
+
): ParsedCredential => {
|
158
|
+
if (!credentialConfig) {
|
159
|
+
throw new IoWalletError("Credential type not supported by the issuer");
|
160
|
+
}
|
161
|
+
|
162
|
+
if (!credentialConfig.claims) {
|
163
|
+
throw new IoWalletError("Missing claims in the credential subject");
|
164
|
+
}
|
165
|
+
|
166
|
+
const attrDefinitions = credentialConfig.claims.map<
|
167
|
+
[string, string, { name: string; locale: string }[]]
|
168
|
+
>(({ path: [namespace, attribute], display }) => [
|
169
|
+
namespace as string,
|
170
|
+
attribute as string,
|
171
|
+
display,
|
172
|
+
]);
|
173
|
+
|
174
|
+
if (!issuerSigned.nameSpaces) {
|
175
|
+
throw new IoWalletError("Missing claims in the credential");
|
176
|
+
}
|
177
|
+
|
178
|
+
const flatNamespaces = Object.entries(issuerSigned.nameSpaces).flatMap(
|
179
|
+
([namespace, values]) =>
|
180
|
+
values.map<[string, string, string]>((v) => [
|
181
|
+
namespace,
|
182
|
+
v.elementIdentifier,
|
183
|
+
v.elementValue,
|
184
|
+
])
|
185
|
+
);
|
186
|
+
|
187
|
+
// Check that all mandatory attributes defined in the issuer configuration are present in the disclosure set
|
188
|
+
// and filter the non present ones
|
189
|
+
const attrsNotInDisclosures = attrDefinitions.filter(
|
190
|
+
([attrDefNamespace, attrKey]) =>
|
191
|
+
flatNamespaces.some(
|
192
|
+
([namespace, claim]) =>
|
193
|
+
attrDefNamespace === namespace && attrKey === claim
|
194
|
+
)
|
195
|
+
);
|
196
|
+
if (attrsNotInDisclosures.length > 0) {
|
197
|
+
const missing = attrsNotInDisclosures
|
198
|
+
.map(([, attrKey]) => attrKey)
|
199
|
+
.join(", ");
|
200
|
+
const received = Object.keys(Object.values(flatNamespaces));
|
201
|
+
|
202
|
+
if (!ignoreMissingAttributes) {
|
203
|
+
throw new IoWalletError(
|
204
|
+
`Some attributes are missing in the credential. Missing: [${missing}], received: [${received}]`
|
205
|
+
);
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
// Attributes defined in the issuer configuration and present in the disclosure set
|
210
|
+
const definedValues = attrDefinitions
|
211
|
+
// Retrieve the value from the corresponding disclosure
|
212
|
+
.map(
|
213
|
+
([attrDefNamespace, attrKey, display]) =>
|
214
|
+
[
|
215
|
+
attrDefNamespace,
|
216
|
+
attrKey,
|
217
|
+
{
|
218
|
+
display,
|
219
|
+
value: flatNamespaces.find(
|
220
|
+
([namespace, name]) =>
|
221
|
+
attrDefNamespace === namespace && name === attrKey
|
222
|
+
)?.[2],
|
223
|
+
},
|
224
|
+
] as const
|
225
|
+
)
|
226
|
+
//filter the not found elements
|
227
|
+
.filter(([_, __, definition]) => definition.value !== undefined)
|
228
|
+
// Add a human-readable attribute name, with i18n, in the form { locale: name }
|
229
|
+
// Example: { "it-IT": "Nome", "en-EN": "Name", "es-ES": "Nombre" }
|
230
|
+
.reduce<ParsedCredential>(
|
231
|
+
(acc, [attrDefNamespace, attrKey, { display, value }]) => ({
|
232
|
+
...acc,
|
233
|
+
[getParsedCredentialClaimKey(attrDefNamespace, attrKey)]: {
|
234
|
+
value,
|
235
|
+
name: display.reduce(
|
236
|
+
(names, { locale, name }) => ({
|
237
|
+
...names,
|
238
|
+
[locale]: name,
|
239
|
+
}),
|
240
|
+
{}
|
241
|
+
),
|
242
|
+
},
|
243
|
+
}),
|
244
|
+
{}
|
245
|
+
);
|
246
|
+
|
247
|
+
if (includeUndefinedAttributes) {
|
248
|
+
const undefinedValues: ParsedCredential = Object.fromEntries(
|
249
|
+
Object.values(flatNamespaces)
|
250
|
+
.filter(
|
251
|
+
([namespace, key]) =>
|
252
|
+
!definedValues[getParsedCredentialClaimKey(namespace, key)]
|
253
|
+
)
|
254
|
+
.map(([namespace, key, value]) => [
|
255
|
+
getParsedCredentialClaimKey(namespace, key),
|
256
|
+
{ value, name: key },
|
257
|
+
])
|
258
|
+
);
|
259
|
+
return {
|
260
|
+
...definedValues,
|
261
|
+
...undefinedValues,
|
262
|
+
};
|
263
|
+
}
|
142
264
|
|
265
|
+
return definedValues;
|
266
|
+
};
|
143
267
|
/**
|
144
268
|
* Given a credential, verify it's in the supported format
|
145
269
|
* and the credential is correctly signed
|
@@ -176,6 +300,48 @@ async function verifyCredentialSdJwt(
|
|
176
300
|
|
177
301
|
return decodedCredential;
|
178
302
|
}
|
303
|
+
/**
|
304
|
+
* Given a credential, verify it's in the supported format
|
305
|
+
* and the credential is correctly signed
|
306
|
+
* and it's bound to the given key
|
307
|
+
*
|
308
|
+
* @param rawCredential The received credential
|
309
|
+
* @param issuerKeys The set of public keys of the issuer,
|
310
|
+
* which will be used to verify the signature
|
311
|
+
* @param holderBindingContext The access to the holder's key
|
312
|
+
*
|
313
|
+
* @throws If the signature verification fails
|
314
|
+
* @throws If the credential is not in the SdJwt4VC format
|
315
|
+
* @throws If the holder binding is not properly configured
|
316
|
+
*
|
317
|
+
*/
|
318
|
+
async function verifyCredentialMDoc(
|
319
|
+
rawCredential: string,
|
320
|
+
x509CertRoot: string,
|
321
|
+
holderBindingContext: CryptoContext
|
322
|
+
): Promise<DecodedMDocCredential> {
|
323
|
+
const [decodedCredential, holderBindingKey] =
|
324
|
+
// parallel for optimization
|
325
|
+
await Promise.all([
|
326
|
+
verifyMdoc(rawCredential, x509CertRoot),
|
327
|
+
holderBindingContext.getPublicKey(),
|
328
|
+
]);
|
329
|
+
|
330
|
+
if (!decodedCredential) {
|
331
|
+
throw new IoWalletError("No MDOC credentials found!");
|
332
|
+
}
|
333
|
+
|
334
|
+
const key =
|
335
|
+
decodedCredential.issuerSigned.issuerAuth.payload.deviceKeyInfo.deviceKey;
|
336
|
+
|
337
|
+
if (!(await isSameThumbprint(key, holderBindingKey as PublicKey))) {
|
338
|
+
throw new IoWalletError(
|
339
|
+
`Failed to verify holder binding, holder binding key and mDoc deviceKey don't match`
|
340
|
+
);
|
341
|
+
}
|
342
|
+
|
343
|
+
return decodedCredential;
|
344
|
+
}
|
179
345
|
|
180
346
|
const verifyAndParseCredentialSdJwt: VerifyAndParseCredential = async (
|
181
347
|
issuerConf,
|
@@ -231,6 +397,60 @@ const verifyAndParseCredentialSdJwt: VerifyAndParseCredential = async (
|
|
231
397
|
};
|
232
398
|
};
|
233
399
|
|
400
|
+
const verifyAndParseCredentialMDoc: VerifyAndParseCredential = async (
|
401
|
+
issuerConf,
|
402
|
+
credential,
|
403
|
+
credentialConfigurationId,
|
404
|
+
{ credentialCryptoContext, ignoreMissingAttributes },
|
405
|
+
x509CertRoot
|
406
|
+
) => {
|
407
|
+
if (!x509CertRoot) {
|
408
|
+
throw new IoWalletError("Missing x509CertRoot");
|
409
|
+
}
|
410
|
+
|
411
|
+
const decoded = await verifyCredentialMDoc(
|
412
|
+
credential,
|
413
|
+
x509CertRoot,
|
414
|
+
credentialCryptoContext
|
415
|
+
);
|
416
|
+
|
417
|
+
const credentialConfig =
|
418
|
+
issuerConf.openid_credential_issuer.credential_configurations_supported[
|
419
|
+
credentialConfigurationId
|
420
|
+
]!;
|
421
|
+
const parsedCredential = parseCredentialMDoc(
|
422
|
+
credentialConfig,
|
423
|
+
decoded,
|
424
|
+
ignoreMissingAttributes,
|
425
|
+
ignoreMissingAttributes
|
426
|
+
);
|
427
|
+
|
428
|
+
const expirationDate = extractElementValueAsDate(
|
429
|
+
parsedCredential?.[
|
430
|
+
getParsedCredentialClaimKey(MDOC_DEFAULT_NAMESPACE, "expiry_date")
|
431
|
+
]?.value as string
|
432
|
+
);
|
433
|
+
if (!expirationDate) {
|
434
|
+
throw new IoWalletError(`expirationDate must be present!!`);
|
435
|
+
}
|
436
|
+
expirationDate.setDate(expirationDate.getDate() + 1);
|
437
|
+
|
438
|
+
const maybeIssuedAt = extractElementValueAsDate(
|
439
|
+
parsedCredential?.[
|
440
|
+
getParsedCredentialClaimKey(MDOC_DEFAULT_NAMESPACE, "issue_date")
|
441
|
+
]?.value as string
|
442
|
+
);
|
443
|
+
maybeIssuedAt?.setDate(maybeIssuedAt.getDate() + 1);
|
444
|
+
|
445
|
+
return {
|
446
|
+
parsedCredential,
|
447
|
+
credential,
|
448
|
+
credentialConfigurationId,
|
449
|
+
expiration: expirationDate,
|
450
|
+
issuedAt: maybeIssuedAt ?? undefined,
|
451
|
+
};
|
452
|
+
};
|
453
|
+
|
234
454
|
/**
|
235
455
|
* Verify and parse an encoded credential.
|
236
456
|
* @param issuerConf The Issuer configuration returned by {@link evaluateIssuerTrust}
|
@@ -248,24 +468,39 @@ export const verifyAndParseCredential: VerifyAndParseCredential = async (
|
|
248
468
|
issuerConf,
|
249
469
|
credential,
|
250
470
|
credentialConfigurationId,
|
251
|
-
context
|
471
|
+
context,
|
472
|
+
x509CertRoot
|
252
473
|
) => {
|
253
474
|
const format =
|
254
475
|
issuerConf.openid_credential_issuer.credential_configurations_supported[
|
255
476
|
credentialConfigurationId
|
256
477
|
]?.format;
|
257
478
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
479
|
+
switch (format) {
|
480
|
+
case "dc+sd-jwt": {
|
481
|
+
Logger.log(LogLevel.DEBUG, "Parsing credential in dc+sd-jwt format");
|
482
|
+
return verifyAndParseCredentialSdJwt(
|
483
|
+
issuerConf,
|
484
|
+
credential,
|
485
|
+
credentialConfigurationId,
|
486
|
+
context
|
487
|
+
);
|
488
|
+
}
|
489
|
+
case "mso_mdoc": {
|
490
|
+
Logger.log(LogLevel.DEBUG, "Parsing credential in mso_mdoc format");
|
491
|
+
return verifyAndParseCredentialMDoc(
|
492
|
+
issuerConf,
|
493
|
+
credential,
|
494
|
+
credentialConfigurationId,
|
495
|
+
context,
|
496
|
+
x509CertRoot
|
497
|
+
);
|
498
|
+
}
|
267
499
|
|
268
|
-
|
269
|
-
|
270
|
-
|
500
|
+
default: {
|
501
|
+
const message = `Unsupported credential format: ${format}`;
|
502
|
+
Logger.log(LogLevel.ERROR, message);
|
503
|
+
throw new IoWalletError(message);
|
504
|
+
}
|
505
|
+
}
|
271
506
|
};
|
@@ -171,7 +171,7 @@ const { credential_configuration_id, credential_identifiers } =
|
|
171
171
|
accessToken.authorization_details[0]!;
|
172
172
|
|
173
173
|
// Obtain the credential
|
174
|
-
const { credential } = await Credential.Issuance.obtainCredential(
|
174
|
+
const { credential, format } = await Credential.Issuance.obtainCredential(
|
175
175
|
issuerConf,
|
176
176
|
accessToken,
|
177
177
|
clientId,
|
@@ -186,6 +186,10 @@ const { credential } = await Credential.Issuance.obtainCredential(
|
|
186
186
|
}
|
187
187
|
);
|
188
188
|
|
189
|
+
// The certificate below is required to perform the `x5chain` validation of credentials in `mdoc` format.
|
190
|
+
// In a real-world scenario, it must be obtained from the appropriate endpoint exposed by the Trust Anchor
|
191
|
+
const mockX509CertRoot = format === "mso_mdoc" ? "base64encodedX509CertRoot" : undefined
|
192
|
+
|
189
193
|
/*
|
190
194
|
* Parse and verify the credential. The ignoreMissingAttributes flag must be set to false or omitted in production.
|
191
195
|
* WARNING: includeUndefinedAttributes should not be set to true in production in order to get only claims explicitly declared by the issuer.
|
@@ -199,7 +203,8 @@ const { parsedCredential } =
|
|
199
203
|
credentialCryptoContext,
|
200
204
|
ignoreMissingAttributes: true,
|
201
205
|
includeUndefinedAttributes: false
|
202
|
-
}
|
206
|
+
},
|
207
|
+
mockX509CertRoot
|
203
208
|
);
|
204
209
|
|
205
210
|
const credentialType =
|
@@ -0,0 +1 @@
|
|
1
|
+
export const MDOC_DEFAULT_NAMESPACE = "org.iso.18013.5.1";
|
@@ -0,0 +1,26 @@
|
|
1
|
+
/**
|
2
|
+
* Extracts the date value of a given elementIdentifier from an MDOC object.
|
3
|
+
* Searches through the issuerSigned namespaces and attempts to parse the value as a Date.
|
4
|
+
* The expected date format is "YYYY-MM-DD".
|
5
|
+
* Returns the Date object if found, otherwise returns null.
|
6
|
+
*/
|
7
|
+
export function extractElementValueAsDate(elementValue: string): Date | null {
|
8
|
+
if (typeof elementValue === "string") {
|
9
|
+
const dateParts = elementValue.split("-");
|
10
|
+
if (dateParts.length === 3) {
|
11
|
+
const [year, month, day] = dateParts.map(Number);
|
12
|
+
if (
|
13
|
+
day !== undefined &&
|
14
|
+
month !== undefined &&
|
15
|
+
year !== undefined &&
|
16
|
+
!isNaN(day) &&
|
17
|
+
!isNaN(month) &&
|
18
|
+
!isNaN(year)
|
19
|
+
) {
|
20
|
+
return new Date(year, month - 1, day); // Month is zero-based in JS Date
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
return null; // Return null if no matching element is found or it's not a valid date
|
26
|
+
}
|
@@ -0,0 +1,93 @@
|
|
1
|
+
import { CBOR, COSE } from "@pagopa/io-react-native-iso18013";
|
2
|
+
import { b64utob64 } from "jsrsasign";
|
3
|
+
import {
|
4
|
+
verifyCertificateChain,
|
5
|
+
type CertificateValidationResult,
|
6
|
+
type PublicKey,
|
7
|
+
type X509CertificateOptions,
|
8
|
+
} from "@pagopa/io-react-native-crypto";
|
9
|
+
import { MissingX509CertsError, X509ValidationError } from "../trust/errors";
|
10
|
+
import { IoWalletError } from "../utils/errors";
|
11
|
+
import { convertBase64DerToPem, getSigninJwkFromCert } from "../utils/crypto";
|
12
|
+
|
13
|
+
export const verify = async (
|
14
|
+
token: string,
|
15
|
+
x509CertRoot: string
|
16
|
+
): Promise<{ issuerSigned: CBOR.IssuerSigned }> => {
|
17
|
+
// get decoded data
|
18
|
+
const issuerSigned = await CBOR.decodeIssuerSigned(token);
|
19
|
+
|
20
|
+
if (!issuerSigned) {
|
21
|
+
throw new IoWalletError("Invalid mDoc");
|
22
|
+
}
|
23
|
+
|
24
|
+
if (
|
25
|
+
!issuerSigned.issuerAuth.unprotectedHeader?.x5chain &&
|
26
|
+
(!Array.isArray(issuerSigned.issuerAuth.unprotectedHeader.x5chain) ||
|
27
|
+
issuerSigned.issuerAuth.unprotectedHeader.x5chain.length === 0)
|
28
|
+
) {
|
29
|
+
throw new MissingX509CertsError("Missing x509 certificates");
|
30
|
+
}
|
31
|
+
const x5chain =
|
32
|
+
issuerSigned.issuerAuth.unprotectedHeader.x5chain.map(b64utob64);
|
33
|
+
// Verify the x5chain
|
34
|
+
await verifyX5chain(x5chain, x509CertRoot);
|
35
|
+
|
36
|
+
const coseSign1 = issuerSigned.issuerAuth.rawValue;
|
37
|
+
|
38
|
+
if (!coseSign1) {
|
39
|
+
throw new IoWalletError("Missing coseSign1");
|
40
|
+
}
|
41
|
+
// Once the x5chain is verified, the signatures verification can be performed
|
42
|
+
await verifyMdocSignature(coseSign1, x5chain[0]!);
|
43
|
+
|
44
|
+
return { issuerSigned };
|
45
|
+
};
|
46
|
+
|
47
|
+
/**
|
48
|
+
* This function checks whether the x509 certificate chain is valid against a specified Certificate Authority (CA)
|
49
|
+
*
|
50
|
+
* @param x5chain The mdoc's x509 certificate chain
|
51
|
+
* @param x509CertRoot The Trust Anchor CA
|
52
|
+
* @param options Options for certificate validation
|
53
|
+
*/
|
54
|
+
const verifyX5chain = async (
|
55
|
+
x5chain: string[],
|
56
|
+
x509CertRoot: string,
|
57
|
+
options: X509CertificateOptions = {
|
58
|
+
connectTimeout: 10000,
|
59
|
+
readTimeout: 10000,
|
60
|
+
requireCrl: true,
|
61
|
+
}
|
62
|
+
) => {
|
63
|
+
const x509ValidationResult: CertificateValidationResult =
|
64
|
+
await verifyCertificateChain(x5chain, x509CertRoot, options);
|
65
|
+
|
66
|
+
if (!x509ValidationResult.isValid) {
|
67
|
+
throw new X509ValidationError(
|
68
|
+
`X.509 certificate chain validation failed. Status: ${x509ValidationResult.validationStatus}. Error: ${x509ValidationResult.errorMessage}`,
|
69
|
+
{
|
70
|
+
x509ValidationStatus: x509ValidationResult.validationStatus,
|
71
|
+
x509ErrorMessage: x509ValidationResult.errorMessage,
|
72
|
+
}
|
73
|
+
);
|
74
|
+
}
|
75
|
+
};
|
76
|
+
/**
|
77
|
+
* This function verifies that the signature is valid for the given certificate.
|
78
|
+
* If not, it throws an error
|
79
|
+
*
|
80
|
+
* @param coseSign1 The COSE-Sign1 object encoded in base64 or base64url
|
81
|
+
* @param cert The `x5chain`'s leaf certificate
|
82
|
+
*/
|
83
|
+
const verifyMdocSignature = async (coseSign1: string, cert: string) => {
|
84
|
+
const pemcert = convertBase64DerToPem(cert);
|
85
|
+
const jwk = getSigninJwkFromCert(pemcert);
|
86
|
+
|
87
|
+
jwk.x = b64utob64(jwk.x!);
|
88
|
+
jwk.y = b64utob64(jwk.y!);
|
89
|
+
|
90
|
+
const signatureCorrect = await COSE.verify(coseSign1, jwk as PublicKey);
|
91
|
+
|
92
|
+
if (!signatureCorrect) throw new Error("Invalid mDoc signature");
|
93
|
+
};
|
@@ -0,0 +1,7 @@
|
|
1
|
+
/**
|
2
|
+
* @param namespace The mdoc credential `namespace`
|
3
|
+
* @param key The claim attribute key
|
4
|
+
* @returns A string consisting of the concatenation of the namespace and the claim key, separated by a colon
|
5
|
+
*/
|
6
|
+
export const getParsedCredentialClaimKey = (namespace: string, key: string) =>
|
7
|
+
`${namespace}:${key}`;
|
package/src/trust/types.ts
CHANGED
@@ -71,6 +71,10 @@ const SupportedCredentialMetadata = z.intersection(
|
|
71
71
|
})
|
72
72
|
);
|
73
73
|
|
74
|
+
export type SupportedCredentialFormat = z.infer<
|
75
|
+
typeof SupportedCredentialMetadata
|
76
|
+
>["format"];
|
77
|
+
|
74
78
|
export type EntityStatement = z.infer<typeof EntityStatement>;
|
75
79
|
export const EntityStatement = z.object({
|
76
80
|
header: z.object({
|
package/src/utils/crypto.ts
CHANGED
@@ -5,7 +5,10 @@ import {
|
|
5
5
|
sign,
|
6
6
|
} from "@pagopa/io-react-native-crypto";
|
7
7
|
import { v4 as uuidv4 } from "uuid";
|
8
|
-
import { type CryptoContext
|
8
|
+
import { thumbprint, type CryptoContext } from "@pagopa/io-react-native-jwt";
|
9
|
+
import { JWK } from "./jwk";
|
10
|
+
import { KEYUTIL, KJUR, RSAKey, X509 } from "jsrsasign";
|
11
|
+
import { IoWalletError } from "./errors";
|
9
12
|
|
10
13
|
/**
|
11
14
|
* Create a CryptoContext bound to a key pair.
|
@@ -55,3 +58,38 @@ export const withEphemeralKey = async <R>(
|
|
55
58
|
const ephemeralContext = createCryptoContextFor(keytag);
|
56
59
|
return fn(ephemeralContext).finally(() => deleteKey(keytag));
|
57
60
|
};
|
61
|
+
/**
|
62
|
+
* Converts a base64-encoded DER certificate to PEM format.
|
63
|
+
*
|
64
|
+
* @param certificate - The base64-encoded DER certificate.
|
65
|
+
* @returns The PEM-formatted certificate.
|
66
|
+
*/
|
67
|
+
export const convertBase64DerToPem = (certificate: string): string =>
|
68
|
+
`-----BEGIN CERTIFICATE-----\n${certificate}\n-----END CERTIFICATE-----`;
|
69
|
+
|
70
|
+
/**
|
71
|
+
* Retrieves the signing JWK from a PEM-formatted certificate.
|
72
|
+
*
|
73
|
+
* @param pemCert - The PEM-formatted certificate.
|
74
|
+
* @returns The signing JWK.
|
75
|
+
* @throws Will throw an error if the public key is unsupported.
|
76
|
+
*/
|
77
|
+
export const getSigninJwkFromCert = (pemCert: string): JWK => {
|
78
|
+
const x509 = new X509();
|
79
|
+
x509.readCertPEM(pemCert);
|
80
|
+
const publicKey = x509.getPublicKey();
|
81
|
+
|
82
|
+
console.log("INSTANCE OF RSA", publicKey instanceof RSAKey);
|
83
|
+
console.log("INSTANCE OF ECDSA", publicKey instanceof KJUR.crypto.ECDSA);
|
84
|
+
|
85
|
+
if (publicKey instanceof RSAKey || publicKey instanceof KJUR.crypto.ECDSA) {
|
86
|
+
return {
|
87
|
+
...JWK.parse(KEYUTIL.getJWKFromKey(publicKey)),
|
88
|
+
use: "sig",
|
89
|
+
};
|
90
|
+
}
|
91
|
+
|
92
|
+
throw new IoWalletError(
|
93
|
+
"Unable to find the signing key inside the PEM certificate"
|
94
|
+
);
|
95
|
+
};
|
@@ -1,14 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
4
|
-
value: true
|
5
|
-
});
|
6
|
-
exports.SupportedCredentialFormat = exports.ASSERTION_TYPE = void 0;
|
7
|
-
var z = _interopRequireWildcard(require("zod"));
|
8
|
-
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
9
|
-
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
10
|
-
const ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-client-attestation";
|
11
|
-
exports.ASSERTION_TYPE = ASSERTION_TYPE;
|
12
|
-
const SupportedCredentialFormat = z.union([z.literal("dc+sd-jwt"), z.literal("vc+mdoc-cbor")]);
|
13
|
-
exports.SupportedCredentialFormat = SupportedCredentialFormat;
|
14
|
-
//# sourceMappingURL=const.js.map
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"names":["z","_interopRequireWildcard","require","_getRequireWildcardCache","nodeInterop","WeakMap","cacheBabelInterop","cacheNodeInterop","obj","__esModule","default","cache","has","get","newObj","hasPropertyDescriptor","Object","defineProperty","getOwnPropertyDescriptor","key","prototype","hasOwnProperty","call","desc","set","ASSERTION_TYPE","exports","SupportedCredentialFormat","union","literal"],"sourceRoot":"../../../../src","sources":["credential/issuance/const.ts"],"mappings":";;;;;;AAAA,IAAAA,CAAA,GAAAC,uBAAA,CAAAC,OAAA;AAAyB,SAAAC,yBAAAC,WAAA,eAAAC,OAAA,kCAAAC,iBAAA,OAAAD,OAAA,QAAAE,gBAAA,OAAAF,OAAA,YAAAF,wBAAA,YAAAA,CAAAC,WAAA,WAAAA,WAAA,GAAAG,gBAAA,GAAAD,iBAAA,KAAAF,WAAA;AAAA,SAAAH,wBAAAO,GAAA,EAAAJ,WAAA,SAAAA,WAAA,IAAAI,GAAA,IAAAA,GAAA,CAAAC,UAAA,WAAAD,GAAA,QAAAA,GAAA,oBAAAA,GAAA,wBAAAA,GAAA,4BAAAE,OAAA,EAAAF,GAAA,UAAAG,KAAA,GAAAR,wBAAA,CAAAC,WAAA,OAAAO,KAAA,IAAAA,KAAA,CAAAC,GAAA,CAAAJ,GAAA,YAAAG,KAAA,CAAAE,GAAA,CAAAL,GAAA,SAAAM,MAAA,WAAAC,qBAAA,GAAAC,MAAA,CAAAC,cAAA,IAAAD,MAAA,CAAAE,wBAAA,WAAAC,GAAA,IAAAX,GAAA,QAAAW,GAAA,kBAAAH,MAAA,CAAAI,SAAA,CAAAC,cAAA,CAAAC,IAAA,CAAAd,GAAA,EAAAW,GAAA,SAAAI,IAAA,GAAAR,qBAAA,GAAAC,MAAA,CAAAE,wBAAA,CAAAV,GAAA,EAAAW,GAAA,cAAAI,IAAA,KAAAA,IAAA,CAAAV,GAAA,IAAAU,IAAA,CAAAC,GAAA,KAAAR,MAAA,CAAAC,cAAA,CAAAH,MAAA,EAAAK,GAAA,EAAAI,IAAA,YAAAT,MAAA,CAAAK,GAAA,IAAAX,GAAA,CAAAW,GAAA,SAAAL,MAAA,CAAAJ,OAAA,GAAAF,GAAA,MAAAG,KAAA,IAAAA,KAAA,CAAAa,GAAA,CAAAhB,GAAA,EAAAM,MAAA,YAAAA,MAAA;AAClB,MAAMW,cAAc,GACzB,oEAAoE;AAACC,OAAA,CAAAD,cAAA,GAAAA,cAAA;AAKhE,MAAME,yBAAyB,GAAG3B,CAAC,CAAC4B,KAAK,CAAC,CAC/C5B,CAAC,CAAC6B,OAAO,CAAC,WAAW,CAAC,EACtB7B,CAAC,CAAC6B,OAAO,CAAC,cAAc,CAAC,CAC1B,CAAC;AAACH,OAAA,CAAAC,yBAAA,GAAAA,yBAAA"}
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"names":["z","ASSERTION_TYPE","SupportedCredentialFormat","union","literal"],"sourceRoot":"../../../../src","sources":["credential/issuance/const.ts"],"mappings":"AAAA,OAAO,KAAKA,CAAC,MAAM,KAAK;AACxB,OAAO,MAAMC,cAAc,GACzB,oEAAoE;AAKtE,OAAO,MAAMC,yBAAyB,GAAGF,CAAC,CAACG,KAAK,CAAC,CAC/CH,CAAC,CAACI,OAAO,CAAC,WAAW,CAAC,EACtBJ,CAAC,CAACI,OAAO,CAAC,cAAc,CAAC,CAC1B,CAAC"}
|
@@ -1,5 +0,0 @@
|
|
1
|
-
import * as z from "zod";
|
2
|
-
export declare const ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-client-attestation";
|
3
|
-
export type SupportedCredentialFormat = z.infer<typeof SupportedCredentialFormat>;
|
4
|
-
export declare const SupportedCredentialFormat: z.ZodUnion<[z.ZodLiteral<"dc+sd-jwt">, z.ZodLiteral<"vc+mdoc-cbor">]>;
|
5
|
-
//# sourceMappingURL=const.d.ts.map
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"const.d.ts","sourceRoot":"","sources":["../../../../src/credential/issuance/const.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,eAAO,MAAM,cAAc,uEAC2C,CAAC;AAEvE,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAC7C,OAAO,yBAAyB,CACjC,CAAC;AACF,eAAO,MAAM,yBAAyB,uEAGpC,CAAC"}
|
@@ -1,11 +0,0 @@
|
|
1
|
-
import * as z from "zod";
|
2
|
-
export const ASSERTION_TYPE =
|
3
|
-
"urn:ietf:params:oauth:client-assertion-type:jwt-client-attestation";
|
4
|
-
|
5
|
-
export type SupportedCredentialFormat = z.infer<
|
6
|
-
typeof SupportedCredentialFormat
|
7
|
-
>;
|
8
|
-
export const SupportedCredentialFormat = z.union([
|
9
|
-
z.literal("dc+sd-jwt"),
|
10
|
-
z.literal("vc+mdoc-cbor"),
|
11
|
-
]);
|