@sphereon/oid4vci-client 0.8.2-next.12 → 0.8.2-next.28
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/dist/AccessTokenClient.d.ts.map +1 -1
- package/dist/AccessTokenClient.js +8 -8
- package/dist/AccessTokenClient.js.map +1 -1
- package/dist/CredentialRequestClient.d.ts.map +1 -1
- package/dist/CredentialRequestClient.js +4 -5
- package/dist/CredentialRequestClient.js.map +1 -1
- package/dist/CredentialRequestClientBuilder.d.ts +6 -0
- package/dist/CredentialRequestClientBuilder.d.ts.map +1 -1
- package/dist/CredentialRequestClientBuilder.js +9 -0
- package/dist/CredentialRequestClientBuilder.js.map +1 -1
- package/dist/OpenID4VCIClient.d.ts +15 -5
- package/dist/OpenID4VCIClient.d.ts.map +1 -1
- package/dist/OpenID4VCIClient.js +99 -38
- package/dist/OpenID4VCIClient.js.map +1 -1
- package/dist/ProofOfPossessionBuilder.d.ts +3 -1
- package/dist/ProofOfPossessionBuilder.d.ts.map +1 -1
- package/dist/ProofOfPossessionBuilder.js +5 -0
- package/dist/ProofOfPossessionBuilder.js.map +1 -1
- package/dist/functions/ProofUtil.d.ts +2 -1
- package/dist/functions/ProofUtil.d.ts.map +1 -1
- package/dist/functions/ProofUtil.js +6 -4
- package/dist/functions/ProofUtil.js.map +1 -1
- package/lib/AccessTokenClient.ts +10 -8
- package/lib/CredentialRequestClient.ts +4 -5
- package/lib/CredentialRequestClientBuilder.ts +19 -0
- package/lib/OpenID4VCIClient.ts +130 -38
- package/lib/ProofOfPossessionBuilder.ts +8 -0
- package/lib/__tests__/CredentialRequestClient.spec.ts +9 -2
- package/lib/__tests__/EBSIE2E.spec.test.ts +138 -0
- package/lib/__tests__/MetadataClient.spec.ts +4 -1
- package/lib/__tests__/OpenID4VCIClient.spec.ts +5 -0
- package/lib/__tests__/SdJwt.spec.ts +161 -0
- package/lib/__tests__/data/VciDataFixtures.ts +1 -1
- package/lib/functions/ProofUtil.ts +18 -4
- package/package.json +6 -4
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { AccessTokenRequest, CredentialRequestV1_0_11, CredentialSupportedSdJwtVc } from '@sphereon/oid4vci-common';
|
|
2
|
+
import nock from 'nock';
|
|
3
|
+
|
|
4
|
+
import { OpenID4VCIClient } from '..';
|
|
5
|
+
import { createAccessTokenResponse, IssuerMetadataBuilderV1_11, VcIssuerBuilder } from '../../../issuer';
|
|
6
|
+
|
|
7
|
+
export const UNIT_TEST_TIMEOUT = 30000;
|
|
8
|
+
|
|
9
|
+
const alg = 'ES256';
|
|
10
|
+
const jwk = { kty: 'EC', crv: 'P-256', x: 'zQOowIC1gWJtdddB5GAt4lau6Lt8Ihy771iAfam-1pc', y: 'cjD_7o3gdQ1vgiQy3_sMGs7WrwCMU9FQYimA3HxnMlw' };
|
|
11
|
+
|
|
12
|
+
const issuerMetadata = new IssuerMetadataBuilderV1_11()
|
|
13
|
+
.withCredentialIssuer('https://example.com')
|
|
14
|
+
.withCredentialEndpoint('https://credenital-endpoint.example.com')
|
|
15
|
+
.withTokenEndpoint('https://token-endpoint.example.com')
|
|
16
|
+
.addSupportedCredential({
|
|
17
|
+
format: 'vc+sd-jwt',
|
|
18
|
+
vct: 'SdJwtCredential',
|
|
19
|
+
id: 'SdJwtCredentialId',
|
|
20
|
+
})
|
|
21
|
+
.build();
|
|
22
|
+
|
|
23
|
+
const vcIssuer = new VcIssuerBuilder()
|
|
24
|
+
.withIssuerMetadata(issuerMetadata)
|
|
25
|
+
.withInMemoryCNonceState()
|
|
26
|
+
.withInMemoryCredentialOfferState()
|
|
27
|
+
.withInMemoryCredentialOfferURIState()
|
|
28
|
+
// TODO: see if we can construct an sd-jwt vc based on the input
|
|
29
|
+
.withCredentialSignerCallback(async () => {
|
|
30
|
+
return 'sd-jwt';
|
|
31
|
+
})
|
|
32
|
+
.withJWTVerifyCallback(() =>
|
|
33
|
+
Promise.resolve({
|
|
34
|
+
alg,
|
|
35
|
+
jwk,
|
|
36
|
+
jwt: {
|
|
37
|
+
header: {
|
|
38
|
+
typ: 'openid4vci-proof+jwt',
|
|
39
|
+
alg,
|
|
40
|
+
jwk,
|
|
41
|
+
},
|
|
42
|
+
payload: {
|
|
43
|
+
aud: issuerMetadata.credential_issuer,
|
|
44
|
+
iat: +new Date(),
|
|
45
|
+
nonce: 'a-c-nonce',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
)
|
|
50
|
+
.build();
|
|
51
|
+
|
|
52
|
+
describe('sd-jwt vc', () => {
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
nock.cleanAll();
|
|
55
|
+
});
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
nock.cleanAll();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it(
|
|
61
|
+
'succeed with a full flow',
|
|
62
|
+
async () => {
|
|
63
|
+
const offerUri = await vcIssuer.createCredentialOfferURI({
|
|
64
|
+
grants: {
|
|
65
|
+
'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
|
|
66
|
+
'pre-authorized_code': '123',
|
|
67
|
+
user_pin_required: false,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
credentials: ['SdJwtCredentialId'],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
nock(vcIssuer.issuerMetadata.credential_issuer).get('/.well-known/openid-credential-issuer').reply(200, JSON.stringify(issuerMetadata));
|
|
74
|
+
nock(vcIssuer.issuerMetadata.credential_issuer).get('/.well-known/openid-configuration').reply(404);
|
|
75
|
+
nock(vcIssuer.issuerMetadata.credential_issuer).get('/.well-known/oauth-authorization-server').reply(404);
|
|
76
|
+
|
|
77
|
+
expect(offerUri.uri).toEqual(
|
|
78
|
+
'openid-credential-offer://?credential_offer=%7B%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22123%22%2C%22user_pin_required%22%3Afalse%7D%7D%2C%22credentials%22%3A%5B%22SdJwtCredentialId%22%5D%2C%22credential_issuer%22%3A%22https%3A%2F%2Fexample.com%22%7D',
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const client = await OpenID4VCIClient.fromURI({
|
|
82
|
+
uri: offerUri.uri,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(client.credentialOffer?.credential_offer).toEqual({
|
|
86
|
+
credential_issuer: 'https://example.com',
|
|
87
|
+
credentials: ['SdJwtCredentialId'],
|
|
88
|
+
grants: {
|
|
89
|
+
'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
|
|
90
|
+
'pre-authorized_code': '123',
|
|
91
|
+
user_pin_required: false,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const supported = client.getCredentialsSupported(true, 'vc+sd-jwt');
|
|
97
|
+
expect(supported).toEqual([
|
|
98
|
+
{
|
|
99
|
+
vct: 'SdJwtCredential',
|
|
100
|
+
format: 'vc+sd-jwt',
|
|
101
|
+
id: 'SdJwtCredentialId',
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const offered = supported[0] as CredentialSupportedSdJwtVc;
|
|
106
|
+
|
|
107
|
+
nock(issuerMetadata.token_endpoint as string)
|
|
108
|
+
.post('/')
|
|
109
|
+
.reply(200, async (_, body: string) => {
|
|
110
|
+
const parsedBody = Object.fromEntries(body.split('&').map((x) => x.split('=')));
|
|
111
|
+
return createAccessTokenResponse(parsedBody as AccessTokenRequest, {
|
|
112
|
+
credentialOfferSessions: vcIssuer.credentialOfferSessions,
|
|
113
|
+
accessTokenIssuer: 'https://issuer.example.com',
|
|
114
|
+
cNonces: vcIssuer.cNonces,
|
|
115
|
+
cNonce: 'a-c-nonce',
|
|
116
|
+
accessTokenSignerCallback: async () => 'ey.val.ue',
|
|
117
|
+
tokenExpiresIn: 500,
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await client.acquireAccessToken({});
|
|
122
|
+
|
|
123
|
+
nock(issuerMetadata.credential_endpoint as string)
|
|
124
|
+
.post('/')
|
|
125
|
+
.reply(200, async (_, body) =>
|
|
126
|
+
vcIssuer.issueCredential({
|
|
127
|
+
credentialRequest: body as CredentialRequestV1_0_11,
|
|
128
|
+
credential: {
|
|
129
|
+
vct: 'Hello',
|
|
130
|
+
iss: 'did:example:123',
|
|
131
|
+
iat: 123,
|
|
132
|
+
// Defines what can be disclosed (optional)
|
|
133
|
+
__disclosureFrame: {
|
|
134
|
+
name: true,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
newCNonce: 'new-c-nonce',
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const credentials = await client.acquireCredentials({
|
|
142
|
+
credentialTypes: [offered.vct],
|
|
143
|
+
format: 'vc+sd-jwt',
|
|
144
|
+
alg,
|
|
145
|
+
jwk,
|
|
146
|
+
proofCallbacks: {
|
|
147
|
+
// When using sd-jwt for real, this jwt should include a jwk
|
|
148
|
+
signCallback: async () => 'ey.ja.ja',
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(credentials).toEqual({
|
|
153
|
+
c_nonce: 'new-c-nonce',
|
|
154
|
+
c_nonce_expires_in: 300000,
|
|
155
|
+
credential: 'sd-jwt',
|
|
156
|
+
format: 'vc+sd-jwt',
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
UNIT_TEST_TIMEOUT,
|
|
160
|
+
);
|
|
161
|
+
});
|
|
@@ -358,7 +358,7 @@ const mockData: VciMockDataStructure = {
|
|
|
358
358
|
url: 'https://jff.walt.id/issuer-api/default/oidc/credential',
|
|
359
359
|
request: {
|
|
360
360
|
types: ['OpenBadgeCredential'],
|
|
361
|
-
format: '
|
|
361
|
+
format: 'jwt_vc',
|
|
362
362
|
proof: {
|
|
363
363
|
proof_type: 'jwt',
|
|
364
364
|
jwt: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6andrOmV5SmhiR2NpT2lKRlV6STFOa3NpTENKMWMyVWlPaUp6YVdjaUxDSnJkSGtpT2lKRlF5SXNJbU55ZGlJNkluTmxZM0F5TlRack1TSXNJbmdpT2lKclpuVmpTa0V0VEhKck9VWjBPRmx5TFVkMlQzSmpia3N3YjNkc2RqUlhNblUwU3pJeFNHZHZTVlIzSWl3aWVTSTZJalozY0ZCUE1rOUNRVXBTU0ZFMVRXdEtXVlJaV0dsQlJFUXdOMU5OTlV0amVXcDNYMkUzVUUxWmVGa2lmUSMwIn0.eyJhdWQiOiJodHRwczovL2pmZi53YWx0LmlkL2lzc3Vlci1hcGkvZGVmYXVsdC9vaWRjLyIsImlhdCI6MTY4MTkxMTk0Mi4yMzgsImV4cCI6MTY4MTkxMjYwMi4yMzgsIm5vbmNlIjoiZjA2YTMxMDUtYTJlZC00NGZjLTk1NGItNGEyNTk3MDM0OTNiIiwiaXNzIjoic3BoZXJlb246c3NpLXdhbGxldCIsImp0aSI6IjA1OWM3ODA5LTlmOGYtNGE3ZS1hZDI4YTNhMTNhMGIzNmViIn0.RfiWyybxpe3nkx3b0yIsqDHQtvB1WwhDW4t0X-kijy2dsSfv2cYhSEmAzs1shg7OV4EW8fSzt_Te79xiVl6jCw',
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
BAD_PARAMS,
|
|
3
|
+
BaseJWK,
|
|
4
|
+
JWK,
|
|
5
|
+
JWS_NOT_VALID,
|
|
6
|
+
Jwt,
|
|
7
|
+
JWTHeader,
|
|
8
|
+
JWTPayload,
|
|
9
|
+
ProofOfPossession,
|
|
10
|
+
ProofOfPossessionCallbacks,
|
|
11
|
+
Typ,
|
|
12
|
+
} from '@sphereon/oid4vci-common';
|
|
2
13
|
import Debug from 'debug';
|
|
3
14
|
|
|
4
15
|
const debug = Debug('sphereon:openid4vci:token');
|
|
@@ -61,6 +72,7 @@ const partiallyValidateJWS = (jws: string): void => {
|
|
|
61
72
|
export interface JwtProps {
|
|
62
73
|
typ?: Typ;
|
|
63
74
|
kid?: string;
|
|
75
|
+
jwk?: JWK;
|
|
64
76
|
issuer?: string;
|
|
65
77
|
clientId?: string;
|
|
66
78
|
alg?: string;
|
|
@@ -76,7 +88,8 @@ const createJWT = (jwtProps?: JwtProps, existingJwt?: Jwt): Jwt => {
|
|
|
76
88
|
const nonce = getJwtProperty<string>('nonce', false, jwtProps?.nonce, existingJwt?.payload?.nonce); // Officially this is required, but some implementations don't have it
|
|
77
89
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
78
90
|
const alg = getJwtProperty<string>('alg', false, jwtProps?.alg, existingJwt?.header?.alg, 'ES256')!;
|
|
79
|
-
const kid = getJwtProperty<string>('kid',
|
|
91
|
+
const kid = getJwtProperty<string>('kid', false, jwtProps?.kid, existingJwt?.header?.kid);
|
|
92
|
+
const jwk = getJwtProperty<BaseJWK>('jwk', false, jwtProps?.jwk, existingJwt?.header?.jwk);
|
|
80
93
|
const jwt: Partial<Jwt> = existingJwt ? existingJwt : {};
|
|
81
94
|
const now = +new Date();
|
|
82
95
|
const jwtPayload: Partial<JWTPayload> = {
|
|
@@ -92,6 +105,7 @@ const createJWT = (jwtProps?: JwtProps, existingJwt?: Jwt): Jwt => {
|
|
|
92
105
|
typ,
|
|
93
106
|
alg,
|
|
94
107
|
kid,
|
|
108
|
+
jwk,
|
|
95
109
|
};
|
|
96
110
|
return {
|
|
97
111
|
payload: { ...jwt.payload, ...jwtPayload },
|
|
@@ -99,8 +113,8 @@ const createJWT = (jwtProps?: JwtProps, existingJwt?: Jwt): Jwt => {
|
|
|
99
113
|
};
|
|
100
114
|
};
|
|
101
115
|
|
|
102
|
-
const getJwtProperty = <T>(propertyName: string, required: boolean, option?: string, jwtProperty?: T, defaultValue?: T): T | undefined => {
|
|
103
|
-
if (option && jwtProperty && option !== jwtProperty) {
|
|
116
|
+
const getJwtProperty = <T>(propertyName: string, required: boolean, option?: string | JWK, jwtProperty?: T, defaultValue?: T): T | undefined => {
|
|
117
|
+
if (typeof option === 'string' && option && jwtProperty && option !== jwtProperty) {
|
|
104
118
|
throw Error(`Cannot have a property '${propertyName}' with value '${option}' and different JWT value '${jwtProperty}' at the same time`);
|
|
105
119
|
}
|
|
106
120
|
let result = (jwtProperty ? jwtProperty : option) as T | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sphereon/oid4vci-client",
|
|
3
|
-
"version": "0.8.2-next.
|
|
3
|
+
"version": "0.8.2-next.28+cc67892",
|
|
4
4
|
"description": "OpenID for Verifiable Credential Issuance (OpenID4VCI) client",
|
|
5
5
|
"source": "lib/index.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
"build": "tsc"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@sphereon/oid4vci-common": "0.8.2-next.
|
|
19
|
-
"@sphereon/ssi-types": "0.17.
|
|
18
|
+
"@sphereon/oid4vci-common": "0.8.2-next.28+cc67892",
|
|
19
|
+
"@sphereon/ssi-types": "0.17.6-unstable.69",
|
|
20
20
|
"cross-fetch": "^3.1.8",
|
|
21
21
|
"debug": "^4.3.4"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
+
"@sphereon/ssi-sdk-ext.key-utils": "^0.15.1-next.7",
|
|
24
25
|
"@transmute/did-key.js": "^0.3.0-unstable.10",
|
|
26
|
+
"@trust/keyto": "^2.0.0-alpha1",
|
|
25
27
|
"@types/jest": "^29.5.3",
|
|
26
28
|
"@types/node": "^18.17.4",
|
|
27
29
|
"@types/uuid": "^9.0.6",
|
|
@@ -67,5 +69,5 @@
|
|
|
67
69
|
"OIDC4VCI",
|
|
68
70
|
"OID4VCI"
|
|
69
71
|
],
|
|
70
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "cc678929ad038691a1174370c674541b9e91f680"
|
|
71
73
|
}
|