@pagopa/io-react-native-wallet 2.4.1 → 2.5.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/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js +148 -123
- package/lib/commonjs/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
- package/lib/commonjs/credential/issuance/mrtd-pop/02-init-challenge.js +12 -1
- package/lib/commonjs/credential/issuance/mrtd-pop/02-init-challenge.js.map +1 -1
- package/lib/commonjs/sd-jwt/index.js.map +1 -1
- package/lib/commonjs/utils/error-codes.js +5 -1
- package/lib/commonjs/utils/error-codes.js.map +1 -1
- package/lib/commonjs/utils/parser.js +20 -0
- package/lib/commonjs/utils/parser.js.map +1 -0
- package/lib/module/credential/issuance/07-verify-and-parse-credential.js +144 -119
- package/lib/module/credential/issuance/07-verify-and-parse-credential.js.map +1 -1
- package/lib/module/credential/issuance/mrtd-pop/02-init-challenge.js +13 -2
- package/lib/module/credential/issuance/mrtd-pop/02-init-challenge.js.map +1 -1
- package/lib/module/sd-jwt/__test__/index.test.js +1 -1
- package/lib/module/sd-jwt/__test__/index.test.js.map +1 -1
- package/lib/module/sd-jwt/index.js +1 -3
- package/lib/module/sd-jwt/index.js.map +1 -1
- package/lib/module/utils/error-codes.js +5 -1
- package/lib/module/utils/error-codes.js.map +1 -1
- package/lib/module/utils/parser.js +12 -0
- package/lib/module/utils/parser.js.map +1 -0
- package/lib/typescript/credential/issuance/07-verify-and-parse-credential.d.ts.map +1 -1
- package/lib/typescript/credential/issuance/mrtd-pop/02-init-challenge.d.ts.map +1 -1
- package/lib/typescript/sd-jwt/index.d.ts +1 -1
- package/lib/typescript/sd-jwt/index.d.ts.map +1 -1
- package/lib/typescript/utils/error-codes.d.ts +4 -0
- package/lib/typescript/utils/error-codes.d.ts.map +1 -1
- package/lib/typescript/utils/parser.d.ts +9 -0
- package/lib/typescript/utils/parser.d.ts.map +1 -0
- package/package.json +8 -3
- package/src/credential/issuance/07-verify-and-parse-credential.ts +138 -94
- package/src/credential/issuance/mrtd-pop/02-init-challenge.ts +25 -3
- package/src/sd-jwt/__test__/index.test.ts +1 -1
- package/src/sd-jwt/index.ts +7 -5
- package/src/utils/error-codes.ts +4 -0
- package/src/utils/parser.ts +18 -0
- package/lib/commonjs/utils/nestedProperty.js +0 -153
- package/lib/commonjs/utils/nestedProperty.js.map +0 -1
- package/lib/module/utils/nestedProperty.js +0 -147
- package/lib/module/utils/nestedProperty.js.map +0 -1
- package/lib/typescript/utils/nestedProperty.d.ts +0 -24
- package/lib/typescript/utils/nestedProperty.d.ts.map +0 -1
- package/src/utils/nestedProperty.ts +0 -223
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["isPathEqual","pathA","pathB","length","every","v","i","isPrefixOf","prefix","fullPath"],"sourceRoot":"../../../src","sources":["utils/parser.ts"],"mappings":"AAAA;AACA;AACA;AACA,OAAO,MAAMA,WAAW,GAAGA,CACzBC,KAAiC,EACjCC,KAAiC,KAEjCD,KAAK,CAACE,MAAM,KAAKD,KAAK,CAACC,MAAM,IAAIF,KAAK,CAACG,KAAK,CAAC,CAACC,CAAC,EAAEC,CAAC,KAAKD,CAAC,KAAKH,KAAK,CAACI,CAAC,CAAC,CAAC;AACxE;AACA;AACA;AACA,OAAO,MAAMC,UAAU,GAAGA,CACxBC,MAAkC,EAClCC,QAAoC,KACxB;EACZ,IAAID,MAAM,CAACL,MAAM,IAAIM,QAAQ,CAACN,MAAM,EAAE,OAAO,KAAK;EAClD,OAAOK,MAAM,CAACJ,KAAK,CAAC,CAACC,CAAC,EAAEC,CAAC,KAAKD,CAAC,KAAKI,QAAQ,CAACH,CAAC,CAAC,CAAC;AAClD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"07-verify-and-parse-credential.d.ts","sourceRoot":"","sources":["../../../../src/credential/issuance/07-verify-and-parse-credential.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"07-verify-and-parse-credential.d.ts","sourceRoot":"","sources":["../../../../src/credential/issuance/07-verify-and-parse-credential.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,KAAK,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAItE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAW/D,KAAK,UAAU,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC,YAAY,CAAC,CAAC;AAQzD,MAAM,MAAM,wBAAwB,GAAG,CACrC,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC,YAAY,CAAC,EAC/C,yBAAyB,EAAE,MAAM,EACjC,OAAO,EAAE;IACP,uBAAuB,EAAE,aAAa,CAAC;IACvC;;OAEG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;OAEG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC,EACD,YAAY,CAAC,EAAE,MAAM,KAClB,OAAO,CAAC;IACX,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,UAAU,EAAE,IAAI,CAAC;IACjB,QAAQ,EAAE,IAAI,GAAG,SAAS,CAAC;CAC5B,CAAC,CAAC;AAGH,KAAK,gBAAgB,GAAG;IACtB,oBAAoB;IACpB,CAAC,KAAK,EAAE,MAAM,GAAG;QACf,IAAI,EACA,yBAAyB,CAAC,MAAM,CAC9B,MAAM,EACN,MAAM,CACP,GACD,4BAA4B,CAAC,MAAM,GACnC,SAAS,CAAC;QACd,KAAK,EAAE,OAAO,CAAC;KAChB,CAAC;CACH,CAAC;AA2cF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,EAAE,wBAuCtC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"02-init-challenge.d.ts","sourceRoot":"","sources":["../../../../../src/credential/issuance/mrtd-pop/02-init-challenge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,GAAG,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"02-init-challenge.d.ts","sourceRoot":"","sources":["../../../../../src/credential/issuance/mrtd-pop/02-init-challenge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,GAAG,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAKjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAQ1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3C,MAAM,MAAM,aAAa,GAAG,CAC1B,UAAU,EAAE,GAAG,CAAC,mBAAmB,CAAC,CAAC,YAAY,CAAC,EAClD,OAAO,EAAE,MAAM,EACf,iBAAiB,EAAE,MAAM,EACzB,kBAAkB,EAAE,MAAM,EAC1B,OAAO,EAAE;IACP,gBAAgB,EAAE,aAAa,CAAC;IAChC,yBAAyB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;CACjC,KACE,OAAO,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC;AAE1C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,EAAE,aAgD3B,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { Disclosure,
|
|
2
|
+
import { Disclosure, type DisclosureWithEncoded, SdJwt4VC } from "./types";
|
|
3
3
|
import type { JWK } from "../utils/jwk";
|
|
4
4
|
import * as Errors from "./errors";
|
|
5
5
|
import { type Presentation } from "../credential/presentation/types";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sd-jwt/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sd-jwt/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,OAAO,EAAE,UAAU,EAAE,KAAK,qBAAqB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE3E,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AAEnC,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAErE,cAAc,SAAS,CAAC;AAQxB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WACV,MAAM;;iBAIA,qBAAqB,EAAE;CA0BrC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,QAAQ,UACZ,MAAM,UACL,MAAM,EAAE;WACE,MAAM;WAAS;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE;EA2CnE,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WACV,MAAM,aACF,GAAG,GAAG,GAAG,EAAE;;iBAEqB,UAAU,EAAE;EAqBxD,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,UAClB,MAAM,aACF,MAAM;cAGP,MAAM;EAyBjB,CAAC;AAEF,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC"}
|
|
@@ -16,6 +16,10 @@ export declare const IssuerResponseErrorCodes: {
|
|
|
16
16
|
* Error code thrown when an error occurs while obtaining a status attestation for a credential.
|
|
17
17
|
*/
|
|
18
18
|
readonly StatusAttestationRequestFailed: "ERR_STATUS_ATTESTATION_REQUEST_FAILED";
|
|
19
|
+
/**
|
|
20
|
+
* Error code thrown when an error occurs while initializing the MRTD challenge.
|
|
21
|
+
*/
|
|
22
|
+
readonly MrtdChallengeInitRequestFailed: "ERR_MRTD_CHALLENGE_INIT_REQUEST_FAILED";
|
|
19
23
|
};
|
|
20
24
|
export declare const WalletProviderResponseErrorCodes: {
|
|
21
25
|
readonly WalletProviderGenericError: "ERR_IO_WALLET_PROVIDER_GENERIC_ERROR";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../../../src/utils/error-codes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB;;IAEnC;;OAEG;;IAEH;;OAEG;;IAEH;;OAEG;;IAEH;;OAEG;;CAEK,CAAC;AAEX,eAAO,MAAM,gCAAgC;;IAE3C;;OAEG;;IAEH;;OAEG;;IAGH;;OAEG;;IAEH;;OAEG;;IAEH;;OAEG;;CAEK,CAAC;AAEX,eAAO,MAAM,8BAA8B;;IAEzC;;OAEG;;CAEK,CAAC;AAEX,MAAM,MAAM,uBAAuB,GACjC,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,OAAO,wBAAwB,CAAC,CAAC;AAE3E,MAAM,MAAM,+BAA+B,GACzC,CAAC,OAAO,gCAAgC,CAAC,CAAC,MAAM,OAAO,gCAAgC,CAAC,CAAC;AAE3F,MAAM,MAAM,6BAA6B,GACvC,CAAC,OAAO,8BAA8B,CAAC,CAAC,MAAM,OAAO,8BAA8B,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"error-codes.d.ts","sourceRoot":"","sources":["../../../src/utils/error-codes.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB;;IAEnC;;OAEG;;IAEH;;OAEG;;IAEH;;OAEG;;IAEH;;OAEG;;IAEH;;OAEG;;CAEK,CAAC;AAEX,eAAO,MAAM,gCAAgC;;IAE3C;;OAEG;;IAEH;;OAEG;;IAGH;;OAEG;;IAEH;;OAEG;;IAEH;;OAEG;;CAEK,CAAC;AAEX,eAAO,MAAM,8BAA8B;;IAEzC;;OAEG;;CAEK,CAAC;AAEX,MAAM,MAAM,uBAAuB,GACjC,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,OAAO,wBAAwB,CAAC,CAAC;AAE3E,MAAM,MAAM,+BAA+B,GACzC,CAAC,OAAO,gCAAgC,CAAC,CAAC,MAAM,OAAO,gCAAgC,CAAC,CAAC;AAE3F,MAAM,MAAM,6BAA6B,GACvC,CAAC,OAAO,8BAA8B,CAAC,CAAC,MAAM,OAAO,8BAA8B,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper to determine if two paths are equal (Supports string | number | null)
|
|
3
|
+
*/
|
|
4
|
+
export declare const isPathEqual: (pathA: (string | number | null)[], pathB: (string | number | null)[]) => boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Helper to check if prefix is the start of fullPath
|
|
7
|
+
*/
|
|
8
|
+
export declare const isPrefixOf: (prefix: (string | number | null)[], fullPath: (string | number | null)[]) => boolean;
|
|
9
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../src/utils/parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,WAAW,UACf,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,SAC1B,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,KAChC,OACqE,CAAC;AACzE;;GAEG;AACH,eAAO,MAAM,UAAU,WACb,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,YACxB,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,KACnC,OAGF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagopa/io-react-native-wallet",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Provide data structures, helpers and API for IO Wallet",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -74,7 +74,8 @@
|
|
|
74
74
|
"react": "19.0.0",
|
|
75
75
|
"react-native": "0.78.3",
|
|
76
76
|
"react-native-builder-bob": "^0.20.0",
|
|
77
|
-
"
|
|
77
|
+
"react-native-quick-crypto": "^0.7.17",
|
|
78
|
+
"release-it": "^19.0.6",
|
|
78
79
|
"typed-openapi": "^0.4.1",
|
|
79
80
|
"typescript": "5.0.4"
|
|
80
81
|
},
|
|
@@ -83,7 +84,8 @@
|
|
|
83
84
|
"@pagopa/io-react-native-iso18013": "*",
|
|
84
85
|
"@pagopa/io-react-native-jwt": "*",
|
|
85
86
|
"react": "*",
|
|
86
|
-
"react-native": "*"
|
|
87
|
+
"react-native": "*",
|
|
88
|
+
"react-native-quick-crypto": "*"
|
|
87
89
|
},
|
|
88
90
|
"engines": {
|
|
89
91
|
"node": ">= 16.0.0"
|
|
@@ -134,6 +136,9 @@
|
|
|
134
136
|
]
|
|
135
137
|
},
|
|
136
138
|
"dependencies": {
|
|
139
|
+
"@sd-jwt/core": "^0.18.0",
|
|
140
|
+
"@sd-jwt/crypto-nodejs": "^0.18.0",
|
|
141
|
+
"@sd-jwt/types": "^0.18.0",
|
|
137
142
|
"dcql": "^0.2.21",
|
|
138
143
|
"js-base64": "^3.7.7",
|
|
139
144
|
"js-sha256": "^0.9.0",
|
|
@@ -3,17 +3,17 @@ import { type Out } from "../../utils/misc";
|
|
|
3
3
|
import type { EvaluateIssuerTrust } from "./02-evaluate-issuer-trust";
|
|
4
4
|
import { IoWalletError } from "../../utils/errors";
|
|
5
5
|
import { SdJwt4VC, verify as verifySdJwt } from "../../sd-jwt";
|
|
6
|
-
import { getValueFromDisclosures } from "../../sd-jwt/converters";
|
|
7
6
|
import { isSameThumbprint, type JWK } from "../../utils/jwk";
|
|
8
7
|
import type { ObtainCredential } from "./06-obtain-credential";
|
|
9
|
-
import { verify as verifyMdoc } from "../../mdoc";
|
|
8
|
+
import { getParsedCredentialClaimKey, verify as verifyMdoc } from "../../mdoc";
|
|
10
9
|
import { MDOC_DEFAULT_NAMESPACE } from "../../mdoc/const";
|
|
11
|
-
import { getParsedCredentialClaimKey } from "../../mdoc/utils";
|
|
12
10
|
import { Logger, LogLevel } from "../../utils/logging";
|
|
13
11
|
import { extractElementValueAsDate } from "../../mdoc/converter";
|
|
14
12
|
import type { CBOR } from "@pagopa/io-react-native-iso18013";
|
|
15
13
|
import type { PublicKey } from "@pagopa/io-react-native-crypto";
|
|
16
|
-
import {
|
|
14
|
+
import { type SDJwt, SDJwtInstance } from "@sd-jwt/core";
|
|
15
|
+
import { digest } from "@sd-jwt/crypto-nodejs";
|
|
16
|
+
import { isPathEqual, isPrefixOf } from "../../utils/parser";
|
|
17
17
|
|
|
18
18
|
type IssuerConf = Out<EvaluateIssuerTrust>["issuerConf"];
|
|
19
19
|
type CredentialConf =
|
|
@@ -60,112 +60,139 @@ type ParsedCredential = {
|
|
|
60
60
|
};
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Parse a Sd-Jwt credential according to the issuer configuration
|
|
65
|
+
* @param credentialConfig - the list of supported credentials, as defined in the issuer configuration with their claims metadata
|
|
66
|
+
* @param parsedCredentialRaw - the raw parsed credential
|
|
67
|
+
* @param ignoreMissingAttributes - skip error when attributes declared in the issuer configuration are not found within disclosures
|
|
68
|
+
* @param includeUndefinedAttributes - include attributes not explicitly declared in the issuer configuration
|
|
69
|
+
* @returns The parsed credential with attributes in plain value
|
|
70
|
+
*/
|
|
68
71
|
const parseCredentialSdJwt = (
|
|
69
|
-
// The credential configuration to use to parse the provided credential
|
|
70
72
|
credentialConfig: CredentialConf,
|
|
71
|
-
|
|
73
|
+
parsedCredentialRaw: Record<string, unknown>,
|
|
72
74
|
ignoreMissingAttributes: boolean = false,
|
|
73
75
|
includeUndefinedAttributes: boolean = false
|
|
74
76
|
): ParsedCredential => {
|
|
75
|
-
|
|
76
|
-
const message = `Received credential is of an unknown type. Expected one of [${credentialConfig.format}], received '${sdJwt.header.typ}'`;
|
|
77
|
-
Logger.log(LogLevel.ERROR, message);
|
|
78
|
-
throw new IoWalletError(message);
|
|
79
|
-
}
|
|
77
|
+
const claimsMetadata = credentialConfig.claims || [];
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
Logger.log(LogLevel.ERROR, "Missing claims in the credential subject");
|
|
83
|
-
throw new IoWalletError("Missing claims in the credential subject"); // TODO [SIW-1268]: should not be optional
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const attrDefinitions = credentialConfig.claims;
|
|
87
|
-
|
|
88
|
-
// Validate that all attributes from the config exist in either disclosures OR payload
|
|
79
|
+
// Check that all mandatory attributes defined in the issuer configuration are present in the credential
|
|
89
80
|
if (!ignoreMissingAttributes) {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
81
|
+
const missingPaths: string[] = [];
|
|
82
|
+
const rootKeysToVerify = new Set(
|
|
83
|
+
claimsMetadata
|
|
84
|
+
.map((c) => c.path[0])
|
|
85
|
+
.filter((p): p is string => typeof p === "string")
|
|
95
86
|
);
|
|
96
87
|
|
|
97
|
-
const
|
|
98
|
-
(
|
|
99
|
-
|
|
88
|
+
for (const rootKey of rootKeysToVerify) {
|
|
89
|
+
if (!(rootKey in parsedCredentialRaw)) {
|
|
90
|
+
missingPaths.push(rootKey);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
100
93
|
|
|
101
|
-
if (
|
|
94
|
+
if (missingPaths.length > 0) {
|
|
95
|
+
const missing = missingPaths.join(", ");
|
|
96
|
+
const received = Object.keys(parsedCredentialRaw).join(", ");
|
|
102
97
|
throw new IoWalletError(
|
|
103
|
-
`Some attributes are missing in the credential. Missing: [${
|
|
98
|
+
`Some attributes are missing in the credential. Missing: [${missing}], received: [${received}]`
|
|
104
99
|
);
|
|
105
100
|
}
|
|
106
101
|
}
|
|
107
102
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
// Loop through each group
|
|
126
|
-
for (const topLevelKey in groupedDefinitions) {
|
|
127
|
-
const definitionsForThisKey = groupedDefinitions[topLevelKey];
|
|
103
|
+
/**
|
|
104
|
+
* Helper to find display metadata for any given path
|
|
105
|
+
*/
|
|
106
|
+
const getDisplayNames = (
|
|
107
|
+
path: (string | number | null)[]
|
|
108
|
+
): Record<string, string> | undefined => {
|
|
109
|
+
const match = claimsMetadata.find((c) => isPathEqual(c.path, path));
|
|
110
|
+
if (!match) return undefined;
|
|
111
|
+
|
|
112
|
+
const nameMap: Record<string, string> = {};
|
|
113
|
+
for (const entry of match.display) {
|
|
114
|
+
nameMap[entry.locale] = entry.name;
|
|
115
|
+
}
|
|
116
|
+
return nameMap;
|
|
117
|
+
};
|
|
128
118
|
|
|
129
|
-
|
|
130
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Recursive function to build the localized structure
|
|
121
|
+
*/
|
|
122
|
+
const processLevel = (
|
|
123
|
+
currentData: unknown,
|
|
124
|
+
currentPath: (string | number | null)[]
|
|
125
|
+
): unknown => {
|
|
126
|
+
// Handle Arrays
|
|
127
|
+
if (Array.isArray(currentData)) {
|
|
128
|
+
return currentData.map((item) =>
|
|
129
|
+
processLevel(item, [...currentPath, null])
|
|
130
|
+
);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
// Handle Objects
|
|
134
|
+
if (typeof currentData !== "object" || currentData === null) {
|
|
135
|
+
return currentData;
|
|
136
|
+
}
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
const dataObj = currentData as Record<string, unknown>;
|
|
139
|
+
const result: ParsedCredential = {};
|
|
140
|
+
const processedKeys = new Set<string | number>();
|
|
141
|
+
|
|
142
|
+
// Identify unique keys in config at this level
|
|
143
|
+
const configKeysAtThisLevel: (string | number)[] = [];
|
|
144
|
+
for (const claim of claimsMetadata) {
|
|
145
|
+
// Check if the claim path starts with the current path
|
|
146
|
+
if (isPrefixOf(currentPath, claim.path)) {
|
|
147
|
+
const nextPart = claim.path[currentPath.length];
|
|
148
|
+
if (
|
|
149
|
+
(typeof nextPart === "string" || typeof nextPart === "number") &&
|
|
150
|
+
!configKeysAtThisLevel.includes(nextPart)
|
|
151
|
+
) {
|
|
152
|
+
configKeysAtThisLevel.push(nextPart);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
139
155
|
}
|
|
140
156
|
|
|
141
|
-
|
|
157
|
+
// Process keys
|
|
158
|
+
for (const key of configKeysAtThisLevel) {
|
|
159
|
+
const stringKey = key.toString();
|
|
160
|
+
const dataValue = dataObj[stringKey];
|
|
161
|
+
if (dataValue === undefined) continue;
|
|
142
162
|
|
|
143
|
-
|
|
144
|
-
(acc, { path, display }) =>
|
|
145
|
-
createNestedProperty(acc, path, disclosureValue, display),
|
|
146
|
-
{}
|
|
147
|
-
);
|
|
163
|
+
const newPath = [...currentPath, key];
|
|
148
164
|
|
|
149
|
-
|
|
150
|
-
Object.assign(definedValues, tempObjectForGroup);
|
|
151
|
-
}
|
|
165
|
+
let localizedNames = getDisplayNames(newPath);
|
|
152
166
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
disclosures
|
|
158
|
-
.filter((_) => !Object.keys(definedValues).includes(_[1]))
|
|
159
|
-
.map(([, key, value]) => [key, { value, name: key }])
|
|
160
|
-
);
|
|
167
|
+
// Fallback for arrays
|
|
168
|
+
if (!localizedNames && Array.isArray(dataValue)) {
|
|
169
|
+
localizedNames = getDisplayNames([...newPath, null]);
|
|
170
|
+
}
|
|
161
171
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
172
|
+
result[stringKey] = {
|
|
173
|
+
name: localizedNames || stringKey,
|
|
174
|
+
value: processLevel(dataValue, newPath),
|
|
175
|
+
};
|
|
167
176
|
|
|
168
|
-
|
|
177
|
+
processedKeys.add(key);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Handle Undefined Attributes
|
|
181
|
+
if (includeUndefinedAttributes) {
|
|
182
|
+
for (const [key, value] of Object.entries(dataObj)) {
|
|
183
|
+
if (!processedKeys.has(key)) {
|
|
184
|
+
result[key] = {
|
|
185
|
+
name: key,
|
|
186
|
+
value: value,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return result;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return processLevel(parsedCredentialRaw, []) as ParsedCredential;
|
|
169
196
|
};
|
|
170
197
|
|
|
171
198
|
const parseCredentialMDoc = (
|
|
@@ -305,7 +332,8 @@ async function verifyCredentialSdJwt(
|
|
|
305
332
|
rawCredential: string,
|
|
306
333
|
issuerKeys: JWK[],
|
|
307
334
|
holderBindingContext: CryptoContext
|
|
308
|
-
): Promise<
|
|
335
|
+
): Promise<SDJwt> {
|
|
336
|
+
// TODO: change verification using sd-jwt library with 1.3.x update
|
|
309
337
|
const [decodedCredential, holderBindingKey] =
|
|
310
338
|
// parallel for optimization
|
|
311
339
|
await Promise.all([
|
|
@@ -320,8 +348,13 @@ async function verifyCredentialSdJwt(
|
|
|
320
348
|
throw new IoWalletError(message);
|
|
321
349
|
}
|
|
322
350
|
|
|
323
|
-
|
|
351
|
+
const sdJwtInstance = new SDJwtInstance({
|
|
352
|
+
hasher: digest,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
return await sdJwtInstance.decode(rawCredential);
|
|
324
356
|
}
|
|
357
|
+
|
|
325
358
|
/**
|
|
326
359
|
* Given a credential, verify it's in the supported format
|
|
327
360
|
* and the credential is correctly signed
|
|
@@ -396,26 +429,37 @@ const verifyAndParseCredentialSdJwt: VerifyAndParseCredential = async (
|
|
|
396
429
|
throw new IoWalletError("Credential type not supported by the issuer");
|
|
397
430
|
}
|
|
398
431
|
|
|
432
|
+
const parsedCredentialRaw = (await decoded.getClaims(digest)) as Record<
|
|
433
|
+
string,
|
|
434
|
+
unknown
|
|
435
|
+
>;
|
|
436
|
+
|
|
399
437
|
const parsedCredential = parseCredentialSdJwt(
|
|
400
438
|
credentialConfig,
|
|
401
|
-
|
|
439
|
+
parsedCredentialRaw,
|
|
402
440
|
ignoreMissingAttributes,
|
|
403
441
|
includeUndefinedAttributes
|
|
404
442
|
);
|
|
405
|
-
|
|
443
|
+
|
|
444
|
+
const issuedAt =
|
|
445
|
+
typeof parsedCredentialRaw.iat === "number"
|
|
446
|
+
? new Date(parsedCredentialRaw.iat * 1000)
|
|
447
|
+
: undefined;
|
|
448
|
+
|
|
449
|
+
if (typeof parsedCredentialRaw.exp !== "number") {
|
|
450
|
+
throw new IoWalletError("Invalid or missing expiration claim (exp)");
|
|
451
|
+
}
|
|
452
|
+
const expiration = new Date(parsedCredentialRaw.exp * 1000);
|
|
406
453
|
|
|
407
454
|
Logger.log(
|
|
408
455
|
LogLevel.DEBUG,
|
|
409
|
-
`Parsed credential: ${JSON.stringify(parsedCredential)}\nIssued at: ${
|
|
456
|
+
`Parsed credential: ${JSON.stringify(parsedCredential)}\nIssued at: ${issuedAt}`
|
|
410
457
|
);
|
|
411
458
|
|
|
412
459
|
return {
|
|
413
460
|
parsedCredential,
|
|
414
|
-
expiration
|
|
415
|
-
issuedAt
|
|
416
|
-
typeof maybeIssuedAt === "number"
|
|
417
|
-
? new Date(maybeIssuedAt * 1000)
|
|
418
|
-
: undefined,
|
|
461
|
+
expiration,
|
|
462
|
+
issuedAt,
|
|
419
463
|
};
|
|
420
464
|
};
|
|
421
465
|
|
|
@@ -2,9 +2,15 @@ import { hasStatusOrThrow, type Out } from "../../../utils/misc";
|
|
|
2
2
|
import type { CryptoContext } from "@pagopa/io-react-native-jwt";
|
|
3
3
|
import { v4 as uuidv4 } from "uuid";
|
|
4
4
|
import { createPopToken } from "../../../utils/pop";
|
|
5
|
+
import { Logger, LogLevel } from "../../../utils/logging";
|
|
5
6
|
import * as WalletInstanceAttestation from "../../../wallet-instance-attestation";
|
|
6
7
|
import type { EvaluateIssuerTrust } from "../../issuance";
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
IssuerResponseError,
|
|
10
|
+
IssuerResponseErrorCodes,
|
|
11
|
+
ResponseErrorBuilder,
|
|
12
|
+
UnexpectedStatusCodeError,
|
|
13
|
+
} from "../../../utils/errors";
|
|
8
14
|
import { decode as decodeJwt } from "@pagopa/io-react-native-jwt";
|
|
9
15
|
import { MrtdPoPChallenge } from "./types";
|
|
10
16
|
|
|
@@ -72,11 +78,27 @@ export const initChallenge: InitChallenge = async (
|
|
|
72
78
|
},
|
|
73
79
|
body: JSON.stringify(requestBody),
|
|
74
80
|
})
|
|
75
|
-
.then(hasStatusOrThrow(202
|
|
76
|
-
.then((res) => res.text())
|
|
81
|
+
.then(hasStatusOrThrow(202))
|
|
82
|
+
.then((res) => res.text())
|
|
83
|
+
.catch(handleInitChallengeError);
|
|
77
84
|
|
|
78
85
|
const mrtdPoPChallengeDecoded = decodeJwt(mrtdPoPChallengeJwt);
|
|
79
86
|
const { payload } = MrtdPoPChallenge.parse(mrtdPoPChallengeDecoded);
|
|
80
87
|
|
|
81
88
|
return payload;
|
|
82
89
|
};
|
|
90
|
+
|
|
91
|
+
const handleInitChallengeError = (e: unknown) => {
|
|
92
|
+
Logger.log(LogLevel.ERROR, `Failed to get MRTD challenge: ${e}`);
|
|
93
|
+
|
|
94
|
+
if (!(e instanceof UnexpectedStatusCodeError)) {
|
|
95
|
+
throw e;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw new ResponseErrorBuilder(IssuerResponseError)
|
|
99
|
+
.handle("*", {
|
|
100
|
+
code: IssuerResponseErrorCodes.MrtdChallengeInitRequestFailed,
|
|
101
|
+
message: "Unable to initialize MRTD challenge",
|
|
102
|
+
})
|
|
103
|
+
.buildFrom(e);
|
|
104
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { decode, disclose } from "../index";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { decodeBase64, encodeBase64 } from "@pagopa/io-react-native-jwt";
|
|
5
5
|
import { SdJwt4VC } from "../types";
|
|
6
6
|
import { pid } from "../__mocks__/sd-jwt";
|
|
7
7
|
|
package/src/sd-jwt/index.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import {
|
|
4
|
+
decode as decodeJwt,
|
|
5
|
+
sha256ToBase64,
|
|
6
|
+
SignJWT,
|
|
7
|
+
verify as verifyJwt,
|
|
8
|
+
} from "@pagopa/io-react-native-jwt";
|
|
9
|
+
import { Disclosure, type DisclosureWithEncoded, SdJwt4VC } from "./types";
|
|
7
10
|
import { verifyDisclosure } from "./verifier";
|
|
8
11
|
import type { JWK } from "../utils/jwk";
|
|
9
12
|
import * as Errors from "./errors";
|
|
@@ -123,7 +126,6 @@ export const disclose = async (
|
|
|
123
126
|
|
|
124
127
|
// compose the final disclosed token
|
|
125
128
|
const disclosedToken = [rawSdJwt, ...filteredDisclosures].join("~");
|
|
126
|
-
|
|
127
129
|
return { token: disclosedToken, paths };
|
|
128
130
|
};
|
|
129
131
|
|
package/src/utils/error-codes.ts
CHANGED
|
@@ -16,6 +16,10 @@ export const IssuerResponseErrorCodes = {
|
|
|
16
16
|
* Error code thrown when an error occurs while obtaining a status attestation for a credential.
|
|
17
17
|
*/
|
|
18
18
|
StatusAttestationRequestFailed: "ERR_STATUS_ATTESTATION_REQUEST_FAILED",
|
|
19
|
+
/**
|
|
20
|
+
* Error code thrown when an error occurs while initializing the MRTD challenge.
|
|
21
|
+
*/
|
|
22
|
+
MrtdChallengeInitRequestFailed: "ERR_MRTD_CHALLENGE_INIT_REQUEST_FAILED",
|
|
19
23
|
} as const;
|
|
20
24
|
|
|
21
25
|
export const WalletProviderResponseErrorCodes = {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper to determine if two paths are equal (Supports string | number | null)
|
|
3
|
+
*/
|
|
4
|
+
export const isPathEqual = (
|
|
5
|
+
pathA: (string | number | null)[],
|
|
6
|
+
pathB: (string | number | null)[]
|
|
7
|
+
): boolean =>
|
|
8
|
+
pathA.length === pathB.length && pathA.every((v, i) => v === pathB[i]);
|
|
9
|
+
/**
|
|
10
|
+
* Helper to check if prefix is the start of fullPath
|
|
11
|
+
*/
|
|
12
|
+
export const isPrefixOf = (
|
|
13
|
+
prefix: (string | number | null)[],
|
|
14
|
+
fullPath: (string | number | null)[]
|
|
15
|
+
): boolean => {
|
|
16
|
+
if (prefix.length >= fullPath.length) return false;
|
|
17
|
+
return prefix.every((v, i) => v === fullPath[i]);
|
|
18
|
+
};
|