@pagopa/io-react-native-wallet 0.7.4 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +49 -31
- package/lib/commonjs/credential/index.js +13 -0
- package/lib/commonjs/credential/index.js.map +1 -0
- package/lib/commonjs/credential/issuance/01-start-flow.js +2 -0
- package/lib/commonjs/credential/issuance/01-start-flow.js.map +1 -0
- package/lib/commonjs/credential/issuance/02-evaluate-issuer-trust.js +26 -0
- package/lib/commonjs/credential/issuance/02-evaluate-issuer-trust.js.map +1 -0
- package/lib/commonjs/credential/issuance/03-start-user-authorization.js +119 -0
- package/lib/commonjs/credential/issuance/03-start-user-authorization.js.map +1 -0
- package/lib/commonjs/credential/issuance/04-complete-user-authorization.js +6 -0
- package/lib/commonjs/credential/issuance/04-complete-user-authorization.js.map +1 -0
- package/lib/commonjs/credential/issuance/05-authorize-access.js +63 -0
- package/lib/commonjs/credential/issuance/05-authorize-access.js.map +1 -0
- package/lib/commonjs/credential/issuance/06-obtain-credential.js +128 -0
- package/lib/commonjs/credential/issuance/06-obtain-credential.js.map +1 -0
- package/lib/commonjs/credential/issuance/07-confirm-credential.js +6 -0
- package/lib/commonjs/credential/issuance/07-confirm-credential.js.map +1 -0
- package/lib/commonjs/credential/issuance/const.js +9 -0
- package/lib/commonjs/credential/issuance/const.js.map +1 -0
- package/lib/commonjs/credential/issuance/index.js +34 -0
- package/lib/commonjs/credential/issuance/index.js.map +1 -0
- package/lib/commonjs/credential/presentation/01-start-flow.js +55 -0
- package/lib/commonjs/credential/presentation/01-start-flow.js.map +1 -0
- package/lib/commonjs/credential/presentation/02-evaluate-rp-trust.js +32 -0
- package/lib/commonjs/credential/presentation/02-evaluate-rp-trust.js.map +1 -0
- package/lib/commonjs/credential/presentation/03-get-request-object.js +68 -0
- package/lib/commonjs/credential/presentation/03-get-request-object.js.map +1 -0
- package/lib/commonjs/credential/presentation/04-send-authorization-response.js +139 -0
- package/lib/commonjs/credential/presentation/04-send-authorization-response.js.map +1 -0
- package/lib/commonjs/credential/presentation/index.js +34 -0
- package/lib/commonjs/credential/presentation/index.js.map +1 -0
- package/lib/commonjs/{rp → credential/presentation}/types.js +17 -34
- package/lib/commonjs/credential/presentation/types.js.map +1 -0
- package/lib/commonjs/index.js +10 -61
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/pid/index.js +1 -3
- package/lib/commonjs/pid/index.js.map +1 -1
- package/lib/commonjs/sd-jwt/index.js +1 -1
- package/lib/commonjs/sd-jwt/index.js.map +1 -1
- package/lib/commonjs/sd-jwt/types.js +1 -1
- package/lib/commonjs/sd-jwt/types.js.map +1 -1
- package/lib/commonjs/trust/chain.js +32 -4
- package/lib/commonjs/trust/chain.js.map +1 -1
- package/lib/commonjs/trust/index.js +105 -20
- package/lib/commonjs/trust/index.js.map +1 -1
- package/lib/commonjs/trust/types.js +54 -35
- package/lib/commonjs/trust/types.js.map +1 -1
- package/lib/commonjs/utils/crypto.js +5 -18
- package/lib/commonjs/utils/crypto.js.map +1 -1
- package/lib/commonjs/utils/errors.js +35 -4
- package/lib/commonjs/utils/errors.js.map +1 -1
- package/lib/commonjs/utils/misc.js +23 -0
- package/lib/commonjs/utils/misc.js.map +1 -0
- package/lib/commonjs/utils/par.js +86 -0
- package/lib/commonjs/utils/par.js.map +1 -0
- package/lib/module/credential/index.js +4 -0
- package/lib/module/credential/index.js.map +1 -0
- package/lib/module/credential/issuance/01-start-flow.js +2 -0
- package/lib/module/credential/issuance/01-start-flow.js.map +1 -0
- package/lib/module/credential/issuance/02-evaluate-issuer-trust.js +19 -0
- package/lib/module/credential/issuance/02-evaluate-issuer-trust.js.map +1 -0
- package/lib/module/credential/issuance/03-start-user-authorization.js +109 -0
- package/lib/module/credential/issuance/03-start-user-authorization.js.map +1 -0
- package/lib/module/credential/issuance/04-complete-user-authorization.js +2 -0
- package/lib/module/credential/issuance/04-complete-user-authorization.js.map +1 -0
- package/lib/module/credential/issuance/05-authorize-access.js +55 -0
- package/lib/module/credential/issuance/05-authorize-access.js.map +1 -0
- package/lib/module/credential/issuance/06-obtain-credential.js +117 -0
- package/lib/module/credential/issuance/06-obtain-credential.js.map +1 -0
- package/lib/module/credential/issuance/07-confirm-credential.js +2 -0
- package/lib/module/credential/issuance/07-confirm-credential.js.map +1 -0
- package/lib/module/credential/issuance/const.js +2 -0
- package/lib/module/credential/issuance/const.js.map +1 -0
- package/lib/module/credential/issuance/index.js +6 -0
- package/lib/module/credential/issuance/index.js.map +1 -0
- package/lib/module/credential/presentation/01-start-flow.js +46 -0
- package/lib/module/credential/presentation/01-start-flow.js.map +1 -0
- package/lib/module/credential/presentation/02-evaluate-rp-trust.js +25 -0
- package/lib/module/credential/presentation/02-evaluate-rp-trust.js.map +1 -0
- package/lib/module/credential/presentation/03-get-request-object.js +60 -0
- package/lib/module/credential/presentation/03-get-request-object.js.map +1 -0
- package/lib/module/credential/presentation/04-send-authorization-response.js +128 -0
- package/lib/module/credential/presentation/04-send-authorization-response.js.map +1 -0
- package/lib/module/credential/presentation/index.js +6 -0
- package/lib/module/credential/presentation/index.js.map +1 -0
- package/lib/module/credential/presentation/types.js +21 -0
- package/lib/module/credential/presentation/types.js.map +1 -0
- package/lib/module/index.js +4 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/pid/index.js +1 -2
- package/lib/module/pid/index.js.map +1 -1
- package/lib/module/sd-jwt/index.js +1 -1
- package/lib/module/sd-jwt/index.js.map +1 -1
- package/lib/module/sd-jwt/types.js +1 -1
- package/lib/module/sd-jwt/types.js.map +1 -1
- package/lib/module/trust/chain.js +30 -3
- package/lib/module/trust/chain.js.map +1 -1
- package/lib/module/trust/index.js +99 -16
- package/lib/module/trust/index.js.map +1 -1
- package/lib/module/trust/types.js +50 -31
- package/lib/module/trust/types.js.map +1 -1
- package/lib/module/utils/crypto.js +2 -15
- package/lib/module/utils/crypto.js.map +1 -1
- package/lib/module/utils/errors.js +35 -4
- package/lib/module/utils/errors.js.map +1 -1
- package/lib/module/utils/misc.js +17 -0
- package/lib/module/utils/misc.js.map +1 -0
- package/lib/module/utils/par.js +74 -0
- package/lib/module/utils/par.js.map +1 -0
- package/lib/typescript/credential/index.d.ts +4 -0
- package/lib/typescript/credential/index.d.ts.map +1 -0
- package/lib/typescript/credential/issuance/01-start-flow.d.ts +11 -0
- package/lib/typescript/credential/issuance/01-start-flow.d.ts.map +1 -0
- package/lib/typescript/credential/issuance/02-evaluate-issuer-trust.d.ts +18 -0
- package/lib/typescript/credential/issuance/02-evaluate-issuer-trust.d.ts.map +1 -0
- package/lib/typescript/credential/issuance/03-start-user-authorization.d.ts +31 -0
- package/lib/typescript/credential/issuance/03-start-user-authorization.d.ts.map +1 -0
- package/lib/typescript/credential/issuance/04-complete-user-authorization.d.ts +16 -0
- package/lib/typescript/credential/issuance/04-complete-user-authorization.d.ts.map +1 -0
- package/lib/typescript/credential/issuance/05-authorize-access.d.ts +26 -0
- package/lib/typescript/credential/issuance/05-authorize-access.d.ts.map +1 -0
- package/lib/typescript/credential/issuance/06-obtain-credential.d.ts +32 -0
- package/lib/typescript/credential/issuance/06-obtain-credential.d.ts.map +1 -0
- package/lib/typescript/credential/issuance/07-confirm-credential.d.ts +11 -0
- package/lib/typescript/credential/issuance/07-confirm-credential.d.ts.map +1 -0
- package/lib/typescript/credential/issuance/const.d.ts +2 -0
- package/lib/typescript/credential/issuance/const.d.ts.map +1 -0
- package/lib/typescript/credential/issuance/index.d.ts +10 -0
- package/lib/typescript/credential/issuance/index.d.ts.map +1 -0
- package/lib/typescript/credential/presentation/01-start-flow.d.ts +20 -0
- package/lib/typescript/credential/presentation/01-start-flow.d.ts.map +1 -0
- package/lib/typescript/credential/presentation/02-evaluate-rp-trust.d.ts +18 -0
- package/lib/typescript/credential/presentation/02-evaluate-rp-trust.d.ts.map +1 -0
- package/lib/typescript/credential/presentation/03-get-request-object.d.ts +25 -0
- package/lib/typescript/credential/presentation/03-get-request-object.d.ts.map +1 -0
- package/lib/typescript/credential/presentation/04-send-authorization-response.d.ts +34 -0
- package/lib/typescript/credential/presentation/04-send-authorization-response.d.ts.map +1 -0
- package/lib/typescript/credential/presentation/index.d.ts +7 -0
- package/lib/typescript/credential/presentation/index.d.ts.map +1 -0
- package/lib/typescript/credential/presentation/types.d.ts +49 -0
- package/lib/typescript/credential/presentation/types.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +4 -5
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/pid/index.d.ts +1 -2
- package/lib/typescript/pid/index.d.ts.map +1 -1
- package/lib/typescript/sd-jwt/index.d.ts +2 -2
- package/lib/typescript/sd-jwt/index.d.ts.map +1 -1
- package/lib/typescript/sd-jwt/types.d.ts +5 -5
- package/lib/typescript/trust/chain.d.ts +12 -3
- package/lib/typescript/trust/chain.d.ts.map +1 -1
- package/lib/typescript/trust/index.d.ts +198 -24
- package/lib/typescript/trust/index.d.ts.map +1 -1
- package/lib/typescript/trust/types.d.ts +1299 -623
- package/lib/typescript/trust/types.d.ts.map +1 -1
- package/lib/typescript/utils/crypto.d.ts +1 -1
- package/lib/typescript/utils/crypto.d.ts.map +1 -1
- package/lib/typescript/utils/dpop.d.ts +2 -2
- package/lib/typescript/utils/errors.d.ts.map +1 -1
- package/lib/typescript/utils/misc.d.ts +8 -0
- package/lib/typescript/utils/misc.d.ts.map +1 -0
- package/lib/typescript/utils/par.d.ts +68 -0
- package/lib/typescript/utils/par.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/credential/index.ts +4 -0
- package/src/credential/issuance/01-start-flow.ts +10 -0
- package/src/credential/issuance/02-evaluate-issuer-trust.ts +31 -0
- package/src/credential/issuance/03-start-user-authorization.ts +138 -0
- package/src/credential/issuance/04-complete-user-authorization.ts +17 -0
- package/src/credential/issuance/05-authorize-access.ts +92 -0
- package/src/credential/issuance/06-obtain-credential.ts +179 -0
- package/src/credential/issuance/07-confirm-credential.ts +14 -0
- package/src/credential/issuance/const.ts +2 -0
- package/src/credential/issuance/index.ts +32 -0
- package/src/credential/presentation/01-start-flow.ts +51 -0
- package/src/credential/presentation/02-evaluate-rp-trust.ts +33 -0
- package/src/credential/presentation/03-get-request-object.ts +85 -0
- package/src/credential/presentation/04-send-authorization-response.ts +168 -0
- package/src/credential/presentation/index.ts +26 -0
- package/src/credential/presentation/types.ts +27 -0
- package/src/index.ts +7 -28
- package/src/pid/index.ts +1 -2
- package/src/sd-jwt/index.ts +2 -2
- package/src/sd-jwt/types.ts +1 -1
- package/src/trust/chain.ts +45 -3
- package/src/trust/index.ts +136 -19
- package/src/trust/types.ts +57 -35
- package/src/utils/crypto.ts +2 -20
- package/src/utils/errors.ts +40 -8
- package/src/utils/misc.ts +23 -0
- package/src/utils/par.ts +103 -0
- package/lib/commonjs/pid/issuing.js +0 -276
- package/lib/commonjs/pid/issuing.js.map +0 -1
- package/lib/commonjs/rp/__test__/index.test.js +0 -172
- package/lib/commonjs/rp/__test__/index.test.js.map +0 -1
- package/lib/commonjs/rp/index.js +0 -239
- package/lib/commonjs/rp/index.js.map +0 -1
- package/lib/commonjs/rp/types.js.map +0 -1
- package/lib/module/pid/issuing.js +0 -266
- package/lib/module/pid/issuing.js.map +0 -1
- package/lib/module/rp/__test__/index.test.js +0 -168
- package/lib/module/rp/__test__/index.test.js.map +0 -1
- package/lib/module/rp/index.js +0 -228
- package/lib/module/rp/index.js.map +0 -1
- package/lib/module/rp/types.js +0 -36
- package/lib/module/rp/types.js.map +0 -1
- package/lib/typescript/pid/issuing.d.ts +0 -57
- package/lib/typescript/pid/issuing.d.ts.map +0 -1
- package/lib/typescript/rp/__test__/index.test.d.ts +0 -2
- package/lib/typescript/rp/__test__/index.test.d.ts.map +0 -1
- package/lib/typescript/rp/index.d.ts +0 -43
- package/lib/typescript/rp/index.d.ts.map +0 -1
- package/lib/typescript/rp/types.d.ts +0 -122
- package/lib/typescript/rp/types.d.ts.map +0 -1
- package/src/pid/issuing.ts +0 -405
- package/src/rp/__test__/index.test.ts +0 -250
- package/src/rp/index.ts +0 -287
- package/src/rp/types.ts +0 -42
@@ -0,0 +1,179 @@
|
|
1
|
+
import * as z from "zod";
|
2
|
+
import uuid from "react-native-uuid";
|
3
|
+
import { SignJWT, type CryptoContext } from "@pagopa/io-react-native-jwt";
|
4
|
+
import { verify as verifySdJwt } from "../../sd-jwt";
|
5
|
+
import { createDPopToken } from "../../utils/dpop";
|
6
|
+
|
7
|
+
import type { StartFlow } from "./01-start-flow";
|
8
|
+
import { hasStatus, type Out } from "../../utils/misc";
|
9
|
+
import type { EvaluateIssuerTrust } from "./02-evaluate-issuer-trust";
|
10
|
+
import type { AuthorizeAccess } from "./05-authorize-access";
|
11
|
+
import { SdJwt4VC } from "../../sd-jwt/types";
|
12
|
+
import { IoWalletError } from "../../utils/errors";
|
13
|
+
import type { JWK } from "../../utils/jwk";
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Return the signed jwt for nonce proof of possession
|
17
|
+
*/
|
18
|
+
export const createNonceProof = async (
|
19
|
+
nonce: string,
|
20
|
+
issuer: string,
|
21
|
+
audience: string,
|
22
|
+
ctx: CryptoContext
|
23
|
+
): Promise<string> => {
|
24
|
+
return new SignJWT(ctx)
|
25
|
+
.setPayload({
|
26
|
+
nonce,
|
27
|
+
jwk: await ctx.getPublicKey(),
|
28
|
+
})
|
29
|
+
.setProtectedHeader({
|
30
|
+
type: "openid4vci-proof+jwt",
|
31
|
+
})
|
32
|
+
.setAudience(audience)
|
33
|
+
.setIssuer(issuer)
|
34
|
+
.setIssuedAt()
|
35
|
+
.setExpirationTime("1h")
|
36
|
+
.sign();
|
37
|
+
};
|
38
|
+
|
39
|
+
/**
|
40
|
+
* Given a credential, verify it's in the supported format
|
41
|
+
* and the credential is correctly signed
|
42
|
+
* and it's bound to the given key
|
43
|
+
*
|
44
|
+
* @param rawCredential The received credential
|
45
|
+
* @param issuerKeys The set of public keys of the issuer,
|
46
|
+
* which will be used to verify the signature
|
47
|
+
* @param holderBindingContext The access to the holder's key
|
48
|
+
*
|
49
|
+
* @throws If the signature verification fails
|
50
|
+
* @throws If the credential is not in the SdJwt4VC format
|
51
|
+
* @throws If the holder binding is not properly configured
|
52
|
+
*
|
53
|
+
*/
|
54
|
+
async function verifyCredential(
|
55
|
+
rawCredential: string,
|
56
|
+
issuerKeys: JWK[],
|
57
|
+
holderBindingContext: CryptoContext
|
58
|
+
): Promise<void> {
|
59
|
+
const [{ sdJwt }, holderBindingKey] =
|
60
|
+
// parallel for optimization
|
61
|
+
await Promise.all([
|
62
|
+
verifySdJwt(rawCredential, issuerKeys, SdJwt4VC),
|
63
|
+
holderBindingContext.getPublicKey(),
|
64
|
+
]);
|
65
|
+
|
66
|
+
if (
|
67
|
+
!sdJwt.payload.cnf.jwk.kid ||
|
68
|
+
sdJwt.payload.cnf.jwk.kid !== holderBindingKey.kid
|
69
|
+
) {
|
70
|
+
throw new IoWalletError(
|
71
|
+
`Failed to verify holder binding, expected kid: ${holderBindingKey.kid}, got: ${sdJwt.payload.cnf.jwk.kid}`
|
72
|
+
);
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
const CredentialEndpointResponse = z.object({
|
77
|
+
credential: z.string(),
|
78
|
+
format: z.literal("vc+sd-jwt"),
|
79
|
+
});
|
80
|
+
|
81
|
+
export type ObtainCredential = (
|
82
|
+
issuerConf: Out<EvaluateIssuerTrust>["issuerConf"],
|
83
|
+
accessToken: Out<AuthorizeAccess>["accessToken"],
|
84
|
+
nonce: Out<AuthorizeAccess>["nonce"],
|
85
|
+
clientId: Out<AuthorizeAccess>["clientId"],
|
86
|
+
credentialType: Out<StartFlow>["credentialType"],
|
87
|
+
context: {
|
88
|
+
credentialCryptoContext: CryptoContext;
|
89
|
+
walletProviderBaseUrl: string;
|
90
|
+
appFetch?: GlobalFetch["fetch"];
|
91
|
+
}
|
92
|
+
) => Promise<{ credential: string; format: string }>;
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Fetch a credential from the issuer
|
96
|
+
*
|
97
|
+
* @param issuerConf The Issuer configuration
|
98
|
+
* @param accessToken The access token to grant access to the credential, obtained with the access authorization step
|
99
|
+
* @param nonce The nonce value to prevent reply attacks, obtained with the access authorization step
|
100
|
+
* @param clientId Identifies the current client across all the requests of the issuing flow
|
101
|
+
* @param credentialType The type of the credential to be requested
|
102
|
+
* @param context.credentialCryptoContext The context to access the key the Credential will be bound to
|
103
|
+
* @param context.walletProviderBaseUrl The base url of the Wallet Provider
|
104
|
+
* @param context.appFetch (optional) fetch api implementation. Default: built-in fetch
|
105
|
+
* @returns The signed credential token
|
106
|
+
*/
|
107
|
+
export const obtainCredential: ObtainCredential = async (
|
108
|
+
issuerConf,
|
109
|
+
accessToken,
|
110
|
+
nonce,
|
111
|
+
clientId,
|
112
|
+
credentialType,
|
113
|
+
context
|
114
|
+
) => {
|
115
|
+
const {
|
116
|
+
credentialCryptoContext,
|
117
|
+
walletProviderBaseUrl,
|
118
|
+
appFetch = fetch,
|
119
|
+
} = context;
|
120
|
+
|
121
|
+
const credentialUrl = issuerConf.openid_credential_issuer.credential_endpoint;
|
122
|
+
|
123
|
+
/** DPoP token for demonstating the possession
|
124
|
+
of the key that will bind the holder User with the Credential
|
125
|
+
@see https://datatracker.ietf.org/doc/html/rfc9449 */
|
126
|
+
const signedDPopForPid = await createDPopToken(
|
127
|
+
{
|
128
|
+
htm: "POST",
|
129
|
+
htu: credentialUrl,
|
130
|
+
jti: `${uuid.v4()}`,
|
131
|
+
},
|
132
|
+
credentialCryptoContext
|
133
|
+
);
|
134
|
+
|
135
|
+
/** JWT proof token to bind the request nonce
|
136
|
+
to the key that will bind the holder User with the Credential
|
137
|
+
@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-proof-types */
|
138
|
+
const signedNonceProof = await createNonceProof(
|
139
|
+
nonce,
|
140
|
+
clientId,
|
141
|
+
walletProviderBaseUrl,
|
142
|
+
credentialCryptoContext
|
143
|
+
);
|
144
|
+
|
145
|
+
/** The credential request body */
|
146
|
+
const formBody = new URLSearchParams({
|
147
|
+
credential_definition: JSON.stringify({
|
148
|
+
type: [credentialType],
|
149
|
+
}),
|
150
|
+
format: "vc+sd-jwt",
|
151
|
+
proof: JSON.stringify({
|
152
|
+
jwt: signedNonceProof,
|
153
|
+
proof_type: "jwt",
|
154
|
+
}),
|
155
|
+
});
|
156
|
+
|
157
|
+
const { credential, format } = await appFetch(credentialUrl, {
|
158
|
+
method: "POST",
|
159
|
+
headers: {
|
160
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
161
|
+
DPoP: signedDPopForPid,
|
162
|
+
Authorization: accessToken,
|
163
|
+
},
|
164
|
+
body: formBody.toString(),
|
165
|
+
})
|
166
|
+
.then(hasStatus(200))
|
167
|
+
.then((res) => res.json())
|
168
|
+
.then(CredentialEndpointResponse.parse);
|
169
|
+
|
170
|
+
/** validate the received credential signature
|
171
|
+
is correct and refers to the public keys of the issuer */
|
172
|
+
await verifyCredential(
|
173
|
+
credential,
|
174
|
+
issuerConf.openid_credential_issuer.jwks.keys,
|
175
|
+
credentialCryptoContext
|
176
|
+
);
|
177
|
+
|
178
|
+
return { credential, format };
|
179
|
+
};
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import type { ObtainCredential } from "./06-obtain-credential";
|
2
|
+
import type { Out } from "../../utils/misc";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* The end of the issuing flow.
|
6
|
+
* The User accepted the Credential and it can be stored in the device according to the app implementation preferences.
|
7
|
+
* To be implemented.
|
8
|
+
*
|
9
|
+
* @returns The type of the Credential to be issued and the url of the Issuer
|
10
|
+
*/
|
11
|
+
export type ConfirmCredential = (
|
12
|
+
credential: Out<ObtainCredential>["credential"],
|
13
|
+
format: Out<ObtainCredential>["format"]
|
14
|
+
) => Promise<void>;
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import { type StartFlow } from "./01-start-flow";
|
2
|
+
import {
|
3
|
+
evaluateIssuerTrust,
|
4
|
+
type EvaluateIssuerTrust,
|
5
|
+
} from "./02-evaluate-issuer-trust";
|
6
|
+
import {
|
7
|
+
startUserAuthorization,
|
8
|
+
type StartUserAuthorization,
|
9
|
+
} from "./03-start-user-authorization";
|
10
|
+
import { type CompleteUserAuthorization } from "./04-complete-user-authorization";
|
11
|
+
import { authorizeAccess, type AuthorizeAccess } from "./05-authorize-access";
|
12
|
+
import {
|
13
|
+
obtainCredential,
|
14
|
+
type ObtainCredential,
|
15
|
+
} from "./06-obtain-credential";
|
16
|
+
import type { ConfirmCredential } from "./07-confirm-credential";
|
17
|
+
|
18
|
+
export {
|
19
|
+
evaluateIssuerTrust,
|
20
|
+
startUserAuthorization,
|
21
|
+
authorizeAccess,
|
22
|
+
obtainCredential,
|
23
|
+
};
|
24
|
+
export type {
|
25
|
+
StartFlow,
|
26
|
+
EvaluateIssuerTrust,
|
27
|
+
StartUserAuthorization,
|
28
|
+
CompleteUserAuthorization,
|
29
|
+
AuthorizeAccess,
|
30
|
+
ObtainCredential,
|
31
|
+
ConfirmCredential,
|
32
|
+
};
|
@@ -0,0 +1,51 @@
|
|
1
|
+
import * as z from "zod";
|
2
|
+
import { decodeBase64 } from "@pagopa/io-react-native-jwt";
|
3
|
+
import { AuthRequestDecodeError } from "../../utils/errors";
|
4
|
+
|
5
|
+
const QRCodePayload = z.object({
|
6
|
+
protocol: z.string(),
|
7
|
+
resource: z.string(), // TODO: refine to known paths using literals
|
8
|
+
clientId: z.string(),
|
9
|
+
requestURI: z.string(),
|
10
|
+
});
|
11
|
+
|
12
|
+
/**
|
13
|
+
* The beginning of the presentation flow.
|
14
|
+
* To be implemented accordind to the user touchpoint
|
15
|
+
*
|
16
|
+
* @param Optional parameters, depending on the starting touchoint
|
17
|
+
* @returns The url for the Relying Party to connect with
|
18
|
+
*/
|
19
|
+
export type StartFlow<T extends Array<unknown> = []> = (...args: T) => Promise<{
|
20
|
+
requestURI: string;
|
21
|
+
clientId: string;
|
22
|
+
}>;
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Start a presentation flow by decoding an incoming QR-code
|
26
|
+
*
|
27
|
+
* @param qrcode The encoded QR-code content
|
28
|
+
* @returns The url for the Relying Party to connect with
|
29
|
+
* @throws If the provided qr code fails to be decoded
|
30
|
+
*/
|
31
|
+
export const startFlowFromQR: StartFlow<[string]> = async (qrcode) => {
|
32
|
+
const decoded = decodeBase64(qrcode);
|
33
|
+
const decodedUrl = new URL(decoded);
|
34
|
+
const protocol = decodedUrl.protocol;
|
35
|
+
const resource = decodedUrl.hostname;
|
36
|
+
const requestURI = decodedUrl.searchParams.get("request_uri");
|
37
|
+
const clientId = decodedUrl.searchParams.get("client_id");
|
38
|
+
|
39
|
+
const result = QRCodePayload.safeParse({
|
40
|
+
protocol,
|
41
|
+
resource,
|
42
|
+
requestURI,
|
43
|
+
clientId,
|
44
|
+
});
|
45
|
+
|
46
|
+
if (result.success) {
|
47
|
+
return result.data;
|
48
|
+
} else {
|
49
|
+
throw new AuthRequestDecodeError(result.error.message, `${decodedUrl}`);
|
50
|
+
}
|
51
|
+
};
|
@@ -0,0 +1,33 @@
|
|
1
|
+
import { getRelyingPartyEntityConfiguration } from "../../trust";
|
2
|
+
import { RelyingPartyEntityConfiguration } from "../../trust/types";
|
3
|
+
import type { StartFlow } from "../issuance/01-start-flow";
|
4
|
+
import type { Out } from "../../utils/misc";
|
5
|
+
|
6
|
+
export type EvaluateRelyingPartyTrust = (
|
7
|
+
rpUrl: Out<StartFlow>["issuerUrl"],
|
8
|
+
context?: {
|
9
|
+
appFetch?: GlobalFetch["fetch"];
|
10
|
+
}
|
11
|
+
) => Promise<{
|
12
|
+
rpConf: RelyingPartyEntityConfiguration["payload"]["metadata"];
|
13
|
+
}>;
|
14
|
+
|
15
|
+
/**
|
16
|
+
* The Relying Party trust evaluation phase.
|
17
|
+
* Fetch the Relying Party's configuration and verify trust.
|
18
|
+
*
|
19
|
+
* @param rpUrl The base url of the Issuer
|
20
|
+
* @param context.appFetch (optional) fetch api implementation. Default: built-in fetch
|
21
|
+
* @returns The Relying Party's configuration
|
22
|
+
*/
|
23
|
+
export const evaluateRelyingPartyTrust: EvaluateRelyingPartyTrust = async (
|
24
|
+
rpUrl,
|
25
|
+
{ appFetch = fetch } = {}
|
26
|
+
) => {
|
27
|
+
const {
|
28
|
+
payload: { metadata: rpConf },
|
29
|
+
} = await getRelyingPartyEntityConfiguration(rpUrl, {
|
30
|
+
appFetch,
|
31
|
+
});
|
32
|
+
return { rpConf };
|
33
|
+
};
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import uuid from "react-native-uuid";
|
2
|
+
import {
|
3
|
+
decode as decodeJwt,
|
4
|
+
sha256ToBase64,
|
5
|
+
verify,
|
6
|
+
type CryptoContext,
|
7
|
+
} from "@pagopa/io-react-native-jwt";
|
8
|
+
|
9
|
+
import { createDPopToken } from "../../utils/dpop";
|
10
|
+
import { NoSuitableKeysFoundInEntityConfiguration } from "../../utils/errors";
|
11
|
+
import type { EvaluateRelyingPartyTrust } from "./02-evaluate-rp-trust";
|
12
|
+
import { hasStatus, type Out } from "../../utils/misc";
|
13
|
+
import type { StartFlow } from "./01-start-flow";
|
14
|
+
import { RequestObject } from "./types";
|
15
|
+
|
16
|
+
export type GetRequestObject = (
|
17
|
+
requestUri: Out<StartFlow>["requestURI"],
|
18
|
+
rpConf: Out<EvaluateRelyingPartyTrust>["rpConf"],
|
19
|
+
context: {
|
20
|
+
wiaCryptoContext: CryptoContext;
|
21
|
+
appFetch?: GlobalFetch["fetch"];
|
22
|
+
walletInstanceAttestation: string;
|
23
|
+
}
|
24
|
+
) => Promise<{ requestObject: RequestObject }>;
|
25
|
+
|
26
|
+
/**
|
27
|
+
* Obtain the Request Object for RP authentication
|
28
|
+
* @see https://italia.github.io/eudi-wallet-it-docs/versione-corrente/en/relying-party-solution.html
|
29
|
+
*
|
30
|
+
* @param requestUri The url for the Relying Party to connect with
|
31
|
+
* @param rpConf The Relying Party's configuration
|
32
|
+
* @param context.wiaCryptoContext The context to access the key associated with the Wallet Instance Attestation
|
33
|
+
* @param context.walletInstanceAttestation The Wallet Instance Attestation token
|
34
|
+
* @param context.appFetch (optional) fetch api implementation. Default: built-in fetch
|
35
|
+
* @returns The Request Object that describes the presentation
|
36
|
+
*/
|
37
|
+
export const getRequestObject: GetRequestObject = async (
|
38
|
+
requestUri,
|
39
|
+
rpConf,
|
40
|
+
{ wiaCryptoContext, appFetch = fetch, walletInstanceAttestation }
|
41
|
+
) => {
|
42
|
+
const signedWalletInstanceDPoP = await createDPopToken(
|
43
|
+
{
|
44
|
+
jti: `${uuid.v4()}`,
|
45
|
+
htm: "GET",
|
46
|
+
htu: requestUri,
|
47
|
+
ath: await sha256ToBase64(walletInstanceAttestation),
|
48
|
+
},
|
49
|
+
wiaCryptoContext
|
50
|
+
);
|
51
|
+
|
52
|
+
const responseEncodedJwt = await appFetch(requestUri, {
|
53
|
+
method: "GET",
|
54
|
+
headers: {
|
55
|
+
Authorization: `DPoP ${walletInstanceAttestation}`,
|
56
|
+
DPoP: signedWalletInstanceDPoP,
|
57
|
+
},
|
58
|
+
})
|
59
|
+
.then(hasStatus(200))
|
60
|
+
.then((res) => res.json())
|
61
|
+
.then((responseJson) => responseJson.response);
|
62
|
+
|
63
|
+
const responseJwt = decodeJwt(responseEncodedJwt);
|
64
|
+
|
65
|
+
// verify token signature according to RP's entity configuration
|
66
|
+
// to ensure the request object is authentic
|
67
|
+
{
|
68
|
+
const pubKey = rpConf.wallet_relying_party.jwks.keys.find(
|
69
|
+
({ kid }) => kid === responseJwt.protectedHeader.kid
|
70
|
+
);
|
71
|
+
if (!pubKey) {
|
72
|
+
throw new NoSuitableKeysFoundInEntityConfiguration(
|
73
|
+
"Request Object signature verification"
|
74
|
+
);
|
75
|
+
}
|
76
|
+
await verify(responseEncodedJwt, pubKey);
|
77
|
+
}
|
78
|
+
|
79
|
+
// Ensure that the request object conforms to the expected specification.
|
80
|
+
const requestObject = RequestObject.parse(responseJwt.payload);
|
81
|
+
|
82
|
+
return {
|
83
|
+
requestObject,
|
84
|
+
};
|
85
|
+
};
|
@@ -0,0 +1,168 @@
|
|
1
|
+
import { EncryptJwe, SignJWT } from "@pagopa/io-react-native-jwt";
|
2
|
+
import uuid from "react-native-uuid";
|
3
|
+
import * as WalletInstanceAttestation from "../../wallet-instance-attestation";
|
4
|
+
import type { JWK } from "@pagopa/io-react-native-jwt/lib/typescript/types";
|
5
|
+
import { NoSuitableKeysFoundInEntityConfiguration } from "../../utils/errors";
|
6
|
+
import { hasStatus, type Out } from "../../utils/misc";
|
7
|
+
import type { GetRequestObject } from "./03-get-request-object";
|
8
|
+
import { disclose } from "../../sd-jwt";
|
9
|
+
import type { EvaluateRelyingPartyTrust } from "./02-evaluate-rp-trust";
|
10
|
+
import { type Presentation } from "./types";
|
11
|
+
import * as z from "zod";
|
12
|
+
|
13
|
+
export type AuthorizationResponse = z.infer<typeof AuthorizationResponse>;
|
14
|
+
export const AuthorizationResponse = z.object({
|
15
|
+
status: z.string(),
|
16
|
+
response_code: z
|
17
|
+
.string() /**
|
18
|
+
FIXME: [SIW-627] we expect this value from every RP implementation
|
19
|
+
Actually some RP does not return the value
|
20
|
+
We make it optional to not break the flow.
|
21
|
+
*/
|
22
|
+
.optional(),
|
23
|
+
});
|
24
|
+
|
25
|
+
/**
|
26
|
+
* Choose an RSA public key from those offered by the RP for encryption.
|
27
|
+
*
|
28
|
+
* @param entity The RP entity configuration
|
29
|
+
* @returns A suitable public key with its compatible encryption algorithm
|
30
|
+
* @throws {NoSuitableKeysFoundInEntityConfiguration} If entity do not contain any public key suitable for encrypting
|
31
|
+
*/
|
32
|
+
const chooseRSAPublicKeyToEncrypt = (
|
33
|
+
entity: Out<EvaluateRelyingPartyTrust>["rpConf"]
|
34
|
+
): JWK => {
|
35
|
+
const [usingRsa256] = entity.wallet_relying_party.jwks.keys.filter(
|
36
|
+
(jwk) => jwk.use === "enc" && jwk.kty === "RSA"
|
37
|
+
);
|
38
|
+
|
39
|
+
if (usingRsa256) {
|
40
|
+
return usingRsa256;
|
41
|
+
}
|
42
|
+
|
43
|
+
// No suitable key has been found
|
44
|
+
throw new NoSuitableKeysFoundInEntityConfiguration(
|
45
|
+
"Encrypt with RP public key"
|
46
|
+
);
|
47
|
+
};
|
48
|
+
|
49
|
+
/**
|
50
|
+
* Generate a Verified Presentation token for a received request object within the context of an authorization request flow.
|
51
|
+
* The presentation is created by revealing data from the provided credentials based on the requested claims.
|
52
|
+
* Each Verified Credential is accompanied by the claims that the user consents to disclose from it.
|
53
|
+
*
|
54
|
+
* @todo: Allow for handling more than one Verified Credential.
|
55
|
+
*/
|
56
|
+
const prepareVpToken = async (
|
57
|
+
requestObject: Out<GetRequestObject>["requestObject"],
|
58
|
+
walletInstanceAttestation: string,
|
59
|
+
[vc, claims, cryptoCtx]: Presentation // TODO: [SIW-353] support multiple presentations,
|
60
|
+
): Promise<{
|
61
|
+
vp_token: string;
|
62
|
+
presentation_submission: Record<string, unknown>;
|
63
|
+
}> => {
|
64
|
+
// this throws if vc cannot satisfy all the requested claims
|
65
|
+
const { token: vp, paths } = await disclose(vc, claims);
|
66
|
+
|
67
|
+
// obtain issuer from Wallet Instance
|
68
|
+
const {
|
69
|
+
payload: { iss },
|
70
|
+
} = WalletInstanceAttestation.decode(walletInstanceAttestation);
|
71
|
+
|
72
|
+
const pidKid = await cryptoCtx.getPublicKey().then((_) => _.kid);
|
73
|
+
|
74
|
+
// TODO: [SIW-359] check all requeste claims of the requestedObj are satisfied
|
75
|
+
const vp_token = await new SignJWT(cryptoCtx)
|
76
|
+
.setProtectedHeader({
|
77
|
+
typ: "JWT",
|
78
|
+
kid: pidKid,
|
79
|
+
})
|
80
|
+
.setPayload({
|
81
|
+
vp: vp,
|
82
|
+
jti: `${uuid.v4()}`,
|
83
|
+
iss,
|
84
|
+
nonce: requestObject.nonce,
|
85
|
+
})
|
86
|
+
.setAudience(requestObject.response_uri)
|
87
|
+
.setIssuedAt()
|
88
|
+
.setExpirationTime("1h")
|
89
|
+
.sign();
|
90
|
+
|
91
|
+
const vc_scope = requestObject.scope;
|
92
|
+
const presentation_submission = {
|
93
|
+
definition_id: `${uuid.v4()}`,
|
94
|
+
id: `${uuid.v4()}`,
|
95
|
+
descriptor_map: paths.map((p) => ({
|
96
|
+
id: vc_scope,
|
97
|
+
path: `$.vp_token.${p.path}`,
|
98
|
+
format: "vc+sd-jwt",
|
99
|
+
})),
|
100
|
+
};
|
101
|
+
|
102
|
+
return { vp_token, presentation_submission };
|
103
|
+
};
|
104
|
+
|
105
|
+
export type SendAuthorizationResponse = (
|
106
|
+
requestObject: Out<GetRequestObject>["requestObject"],
|
107
|
+
rpConf: Out<EvaluateRelyingPartyTrust>["rpConf"],
|
108
|
+
presentation: Presentation, // TODO: [SIW-353] support multiple presentations
|
109
|
+
context: {
|
110
|
+
walletInstanceAttestation: string;
|
111
|
+
appFetch?: GlobalFetch["fetch"];
|
112
|
+
}
|
113
|
+
) => Promise<AuthorizationResponse>;
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Complete the presentation flow by sending the authorization response to the Relying Party
|
117
|
+
*
|
118
|
+
* @param requestObject The Request Object that describes the presentation
|
119
|
+
* @param rpConf The Relying Party's configuration
|
120
|
+
* @param presentation The presentation tuple consisting in the signed credential,
|
121
|
+
* the list of claims to be disclosed, and the context to access the key that proves the holder binding
|
122
|
+
* @param context.walletInstanceAttestation The Wallet Instance Attestation token
|
123
|
+
* @param context.appFetch (optional) fetch api implementation. Default: built-in fetch
|
124
|
+
* @returns The result of the presentation flow
|
125
|
+
*/
|
126
|
+
export const sendAuthorizationResponse: SendAuthorizationResponse = async (
|
127
|
+
requestObject,
|
128
|
+
rpConf,
|
129
|
+
presentation,
|
130
|
+
{ appFetch = fetch, walletInstanceAttestation }
|
131
|
+
): Promise<AuthorizationResponse> => {
|
132
|
+
// the request is an unsigned jws without iss, aud, exp
|
133
|
+
// https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-signed-and-encrypted-respon
|
134
|
+
const rsaPublicJwk = chooseRSAPublicKeyToEncrypt(rpConf);
|
135
|
+
|
136
|
+
const { vp_token, presentation_submission } = await prepareVpToken(
|
137
|
+
requestObject,
|
138
|
+
walletInstanceAttestation,
|
139
|
+
presentation
|
140
|
+
);
|
141
|
+
|
142
|
+
const authzResponsePayload = JSON.stringify({
|
143
|
+
state: requestObject.state,
|
144
|
+
presentation_submission,
|
145
|
+
nonce: requestObject.nonce,
|
146
|
+
vp_token,
|
147
|
+
});
|
148
|
+
|
149
|
+
const encrypted = await new EncryptJwe(authzResponsePayload, {
|
150
|
+
alg: "RSA-OAEP-256",
|
151
|
+
enc: "A256CBC-HS512",
|
152
|
+
kid: rsaPublicJwk.kid,
|
153
|
+
}).encrypt(rsaPublicJwk);
|
154
|
+
|
155
|
+
const formBody = new URLSearchParams({ response: encrypted });
|
156
|
+
const body = formBody.toString();
|
157
|
+
|
158
|
+
return appFetch(requestObject.response_uri, {
|
159
|
+
method: "POST",
|
160
|
+
headers: {
|
161
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
162
|
+
},
|
163
|
+
body,
|
164
|
+
})
|
165
|
+
.then(hasStatus(200))
|
166
|
+
.then((res) => res.json())
|
167
|
+
.then(AuthorizationResponse.parse);
|
168
|
+
};
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import { startFlowFromQR, type StartFlow } from "./01-start-flow";
|
2
|
+
import {
|
3
|
+
evaluateRelyingPartyTrust,
|
4
|
+
type EvaluateRelyingPartyTrust,
|
5
|
+
} from "./02-evaluate-rp-trust";
|
6
|
+
import {
|
7
|
+
getRequestObject,
|
8
|
+
type GetRequestObject,
|
9
|
+
} from "./03-get-request-object";
|
10
|
+
import {
|
11
|
+
sendAuthorizationResponse,
|
12
|
+
type SendAuthorizationResponse,
|
13
|
+
} from "./04-send-authorization-response";
|
14
|
+
|
15
|
+
export {
|
16
|
+
startFlowFromQR,
|
17
|
+
evaluateRelyingPartyTrust,
|
18
|
+
getRequestObject,
|
19
|
+
sendAuthorizationResponse,
|
20
|
+
};
|
21
|
+
export type {
|
22
|
+
StartFlow,
|
23
|
+
EvaluateRelyingPartyTrust,
|
24
|
+
GetRequestObject,
|
25
|
+
SendAuthorizationResponse,
|
26
|
+
};
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import type { CryptoContext } from "@pagopa/io-react-native-jwt";
|
2
|
+
import { UnixTime } from "../../sd-jwt/types";
|
3
|
+
import * as z from "zod";
|
4
|
+
|
5
|
+
/**
|
6
|
+
* A pair that associate a tokenized Verified Credential with the claims presented or requested to present.
|
7
|
+
*/
|
8
|
+
export type Presentation = [
|
9
|
+
/* verified credential token */ string,
|
10
|
+
/* claims */ string[],
|
11
|
+
/* the context for the key associated to the credential */ CryptoContext
|
12
|
+
];
|
13
|
+
|
14
|
+
export type RequestObject = z.infer<typeof RequestObject>;
|
15
|
+
export const RequestObject = z.object({
|
16
|
+
iss: z.string(),
|
17
|
+
iat: UnixTime,
|
18
|
+
exp: UnixTime,
|
19
|
+
state: z.string(),
|
20
|
+
nonce: z.string(),
|
21
|
+
response_uri: z.string(),
|
22
|
+
response_type: z.literal("vp_token"),
|
23
|
+
response_mode: z.literal("direct_post.jwt"),
|
24
|
+
client_id: z.string(),
|
25
|
+
client_id_scheme: z.literal("entity_id"),
|
26
|
+
scope: z.string(),
|
27
|
+
});
|
package/src/index.ts
CHANGED
@@ -2,42 +2,21 @@
|
|
2
2
|
// https://github.com/facebook/react-native/issues/24428
|
3
3
|
import "react-native-url-polyfill/auto";
|
4
4
|
|
5
|
+
import * as Credential from "./credential";
|
5
6
|
import * as PID from "./pid";
|
6
|
-
import * as RP from "./rp";
|
7
7
|
import * as Errors from "./utils/errors";
|
8
8
|
import * as WalletInstanceAttestation from "./wallet-instance-attestation";
|
9
|
-
import * as
|
10
|
-
import {
|
11
|
-
verifyTrustChain,
|
12
|
-
getEntityConfiguration,
|
13
|
-
getCredentialIssuerEntityConfiguration,
|
14
|
-
getRelyingPartyEntityConfiguration,
|
15
|
-
getTrustAnchorEntityConfiguration,
|
16
|
-
getWalletProviderEntityConfiguration,
|
17
|
-
} from "./trust";
|
18
|
-
import {
|
19
|
-
RelyingPartyEntityConfiguration,
|
20
|
-
WalletProviderEntityConfiguration,
|
21
|
-
TrustAnchorEntityConfiguration,
|
22
|
-
CredentialIssuerEntityConfiguration,
|
23
|
-
} from "./trust/types";
|
9
|
+
import * as Trust from "./trust";
|
10
|
+
import { AuthorizationDetail, AuthorizationDetails } from "./utils/par";
|
24
11
|
import { createCryptoContextFor } from "./utils/crypto";
|
25
12
|
|
26
13
|
export {
|
27
14
|
PID,
|
28
|
-
|
15
|
+
Credential,
|
29
16
|
WalletInstanceAttestation,
|
30
17
|
Errors,
|
31
|
-
|
32
|
-
verifyTrustChain,
|
33
|
-
getEntityConfiguration,
|
34
|
-
getCredentialIssuerEntityConfiguration,
|
35
|
-
getRelyingPartyEntityConfiguration,
|
36
|
-
getTrustAnchorEntityConfiguration,
|
37
|
-
getWalletProviderEntityConfiguration,
|
18
|
+
Trust,
|
38
19
|
createCryptoContextFor,
|
39
|
-
|
40
|
-
|
41
|
-
TrustAnchorEntityConfiguration,
|
42
|
-
CredentialIssuerEntityConfiguration,
|
20
|
+
AuthorizationDetail,
|
21
|
+
AuthorizationDetails,
|
43
22
|
};
|
package/src/pid/index.ts
CHANGED