@sphereon/oid4vci-client 0.12.0 → 0.12.1-unstable.2
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/__tests__/CredentialRequestClientBuilder.spec.ts +37 -0
- package/lib/__tests__/CredentialRequestClientV1_0_11.spec.ts +96 -0
- package/lib/__tests__/IT.spec.ts +135 -3
- package/lib/__tests__/ProofOfPossessionBuilder.spec.ts +85 -0
- package/lib/__tests__/SdJwt.spec.ts +103 -0
- package/package.json +3 -3
|
@@ -16,6 +16,7 @@ import { CredentialRequestClientBuilder, ProofOfPossessionBuilder } from '..';
|
|
|
16
16
|
import { IDENTIPROOF_ISSUER_URL, IDENTIPROOF_OID4VCI_METADATA, INITIATION_TEST_URI, WALT_ISSUER_URL, WALT_OID4VCI_METADATA } from './MetadataMocks';
|
|
17
17
|
|
|
18
18
|
const partialJWT = 'eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmN';
|
|
19
|
+
const partialJWT_withoutDid = 'eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJlYmZlYjFmNzEyZWJjNmYxYzI3N';
|
|
19
20
|
|
|
20
21
|
/*const jwtv1_0_08: Jwt = {
|
|
21
22
|
header: { alg: Alg.ES256, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', typ: 'jwt' },
|
|
@@ -27,8 +28,15 @@ const jwtv1_0_11: Jwt = {
|
|
|
27
28
|
payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL },
|
|
28
29
|
};
|
|
29
30
|
|
|
31
|
+
const jwtv1_0_13_withoutDid: Jwt = {
|
|
32
|
+
header: { alg: Alg.ES256, kid: 'ebfeb1f712ebc6f1c276e12ec21/keys/1', typ: 'openid4vci-proof+jwt' },
|
|
33
|
+
payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL },
|
|
34
|
+
};
|
|
35
|
+
|
|
30
36
|
const kid = 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1';
|
|
31
37
|
|
|
38
|
+
const kid_withoutDid = 'ebfeb1f712ebc6f1c276e12ec21/keys/1';
|
|
39
|
+
|
|
32
40
|
let keypair: KeyPair;
|
|
33
41
|
|
|
34
42
|
beforeAll(async () => {
|
|
@@ -115,6 +123,35 @@ describe('Credential Request Client Builder', () => {
|
|
|
115
123
|
}
|
|
116
124
|
});
|
|
117
125
|
|
|
126
|
+
it('should build credential request correctly without did', async () => {
|
|
127
|
+
const credReqClient = (await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI }))
|
|
128
|
+
.withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential')
|
|
129
|
+
.withFormat('jwt_vc')
|
|
130
|
+
.withCredentialType('OpenBadgeCredential')
|
|
131
|
+
.build();
|
|
132
|
+
const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
|
|
133
|
+
jwt: jwtv1_0_13_withoutDid,
|
|
134
|
+
callbacks: {
|
|
135
|
+
signCallback: proofOfPossessionCallbackFunction,
|
|
136
|
+
verifyCallback: proofOfPossessionVerifierCallbackFunction,
|
|
137
|
+
},
|
|
138
|
+
version: OpenId4VCIVersion.VER_1_0_13,
|
|
139
|
+
})
|
|
140
|
+
.withClientId('sphereon:wallet')
|
|
141
|
+
.withKid(kid_withoutDid)
|
|
142
|
+
.build();
|
|
143
|
+
await proofOfPossessionVerifierCallbackFunction({ ...proof, kid: kid_withoutDid });
|
|
144
|
+
const credentialRequest: CredentialRequestV1_0_13 = await credReqClient.createCredentialRequest({
|
|
145
|
+
proofInput: proof,
|
|
146
|
+
credentialTypes: 'OpenBadgeCredential',
|
|
147
|
+
version: OpenId4VCIVersion.VER_1_0_13,
|
|
148
|
+
});
|
|
149
|
+
expect(credentialRequest.proof?.jwt).toContain(partialJWT_withoutDid);
|
|
150
|
+
if ('types' in credentialRequest) {
|
|
151
|
+
expect(credentialRequest.types).toStrictEqual(['OpenBadgeCredential']);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
118
155
|
it('should build correctly from metadata', async () => {
|
|
119
156
|
const credReqClient = (
|
|
120
157
|
await CredentialRequestClientBuilder.fromURI({
|
|
@@ -28,14 +28,22 @@ import {
|
|
|
28
28
|
import { getMockData } from './data/VciDataFixtures';
|
|
29
29
|
|
|
30
30
|
const partialJWT = 'eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmN';
|
|
31
|
+
const partialJWT_withoutDid = 'eyJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJlYmZlYjFmNzEyZWJjNmYxYzI3N';
|
|
31
32
|
|
|
32
33
|
const jwt: Jwt = {
|
|
33
34
|
header: { alg: Alg.ES256, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', typ: 'jwt' },
|
|
34
35
|
payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL },
|
|
35
36
|
};
|
|
36
37
|
|
|
38
|
+
const jwt_withoutDid: Jwt = {
|
|
39
|
+
header: { alg: Alg.ES256, kid: 'ebfeb1f712ebc6f1c276e12ec21/keys/1', typ: 'jwt' },
|
|
40
|
+
payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL },
|
|
41
|
+
};
|
|
42
|
+
|
|
37
43
|
const kid = 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1';
|
|
38
44
|
|
|
45
|
+
const kid_withoutDid = 'ebfeb1f712ebc6f1c276e12ec21/keys/1';
|
|
46
|
+
|
|
39
47
|
let keypair: KeyPair;
|
|
40
48
|
|
|
41
49
|
async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> {
|
|
@@ -102,6 +110,36 @@ describe('Credential Request Client ', () => {
|
|
|
102
110
|
expect(result?.errorBody?.error).toBe('unsupported_format');
|
|
103
111
|
});
|
|
104
112
|
|
|
113
|
+
it('should get a failed credential response with an unsupported format and without did', async function () {
|
|
114
|
+
const basePath = 'https://sphereonjunit2022101301.com/';
|
|
115
|
+
nock(basePath).post(/.*/).reply(500, {
|
|
116
|
+
error: 'unsupported_format',
|
|
117
|
+
error_description: 'This is a mock error message',
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const credReqClient = CredentialRequestClientBuilderV1_0_11.fromCredentialOffer({ credentialOffer: INITIATION_TEST_V1_0_08 })
|
|
121
|
+
.withCredentialEndpoint(basePath + '/credential')
|
|
122
|
+
.withFormat('ldp_vc')
|
|
123
|
+
.withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential')
|
|
124
|
+
.build();
|
|
125
|
+
const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
|
|
126
|
+
jwt: jwt_withoutDid,
|
|
127
|
+
callbacks: {
|
|
128
|
+
signCallback: proofOfPossessionCallbackFunction,
|
|
129
|
+
},
|
|
130
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
131
|
+
})
|
|
132
|
+
// .withEndpointMetadata(metadata)
|
|
133
|
+
.withClientId('sphereon:wallet')
|
|
134
|
+
.withKid(kid_withoutDid)
|
|
135
|
+
.build();
|
|
136
|
+
expect(credReqClient.getCredentialEndpoint()).toEqual(basePath + '/credential');
|
|
137
|
+
const credentialRequest = await credReqClient.createCredentialRequest({ proofInput: proof, version: OpenId4VCIVersion.VER_1_0_08 });
|
|
138
|
+
expect(credentialRequest.proof?.jwt?.includes(partialJWT_withoutDid)).toBeTruthy();
|
|
139
|
+
const result = await credReqClient.acquireCredentialsUsingRequest(credentialRequest);
|
|
140
|
+
expect(result?.errorBody?.error).toBe('unsupported_format');
|
|
141
|
+
});
|
|
142
|
+
|
|
105
143
|
it('should get success credential response', async function () {
|
|
106
144
|
const mockedVC =
|
|
107
145
|
'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiaHR0cHM6Ly9leGFtcGxlLmVkdS9pc3N1ZXJzLzU2NTA0OSIsImlzc3VhbmNlRGF0ZSI6IjIwMTAtMDEtMDFUMDA6MDA6MDBaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEiLCJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSJ9.z5vgMTK1nfizNCg5N-niCOL3WUIAL7nXy-nGhDZYO_-PNGeE-0djCpWAMH8fD8eWSID5PfkPBYkx_dfLJnQ7NA';
|
|
@@ -138,6 +176,42 @@ describe('Credential Request Client ', () => {
|
|
|
138
176
|
expect(result?.successBody?.credential).toEqual(mockedVC);
|
|
139
177
|
});
|
|
140
178
|
|
|
179
|
+
it('should get success credential response without did', async function () {
|
|
180
|
+
const mockedVC =
|
|
181
|
+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiaHR0cHM6Ly9leGFtcGxlLmVkdS9pc3N1ZXJzLzU2NTA0OSIsImlzc3VhbmNlRGF0ZSI6IjIwMTAtMDEtMDFUMDA6MDA6MDBaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJlYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEiLCJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSIsImlhdCI6MTcxODM1NzcxOH0.7iiOTuIjQRyrIincYyDW6m0nBYmDoYfXcTYFrywsKEY';
|
|
182
|
+
nock('https://oidc4vci.demo.spruceid.com')
|
|
183
|
+
.post(/credential/)
|
|
184
|
+
.reply(200, {
|
|
185
|
+
format: 'jwt-vc',
|
|
186
|
+
credential: mockedVC,
|
|
187
|
+
});
|
|
188
|
+
const credReqClient = CredentialRequestClientBuilderV1_0_11.fromCredentialOfferRequest({ request: INITIATION_TEST })
|
|
189
|
+
.withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential')
|
|
190
|
+
.withFormat('jwt_vc')
|
|
191
|
+
.withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential')
|
|
192
|
+
.build();
|
|
193
|
+
const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
|
|
194
|
+
jwt: jwt_withoutDid,
|
|
195
|
+
callbacks: {
|
|
196
|
+
signCallback: proofOfPossessionCallbackFunction,
|
|
197
|
+
},
|
|
198
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
199
|
+
})
|
|
200
|
+
// .withEndpointMetadata(metadata)
|
|
201
|
+
.withKid(kid_withoutDid)
|
|
202
|
+
.withClientId('sphereon:wallet')
|
|
203
|
+
.build();
|
|
204
|
+
const credentialRequest = await credReqClient.createCredentialRequest({
|
|
205
|
+
proofInput: proof,
|
|
206
|
+
format: 'jwt',
|
|
207
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
208
|
+
});
|
|
209
|
+
expect(credentialRequest.proof?.jwt?.includes(partialJWT_withoutDid)).toBeTruthy();
|
|
210
|
+
expect(credentialRequest.format).toEqual('jwt_vc');
|
|
211
|
+
const result = await credReqClient.acquireCredentialsUsingRequest(credentialRequest);
|
|
212
|
+
expect(result?.successBody?.credential).toEqual(mockedVC);
|
|
213
|
+
});
|
|
214
|
+
|
|
141
215
|
it('should fail with invalid url', async () => {
|
|
142
216
|
const credReqClient = CredentialRequestClientBuilderV1_0_11.fromCredentialOfferRequest({ request: INITIATION_TEST })
|
|
143
217
|
.withCredentialEndpoint('httpsf://oidc4vci.demo.spruceid.com/credential')
|
|
@@ -159,6 +233,28 @@ describe('Credential Request Client ', () => {
|
|
|
159
233
|
Error(URL_NOT_VALID),
|
|
160
234
|
);
|
|
161
235
|
});
|
|
236
|
+
|
|
237
|
+
it('should fail with invalid url without did', async () => {
|
|
238
|
+
const credReqClient = CredentialRequestClientBuilderV1_0_11.fromCredentialOfferRequest({ request: INITIATION_TEST })
|
|
239
|
+
.withCredentialEndpoint('httpsf://oidc4vci.demo.spruceid.com/credential')
|
|
240
|
+
.withFormat('jwt_vc')
|
|
241
|
+
.withCredentialType('https://imsglobal.github.io/openbadges-specification/ob_v3p0.html#OpenBadgeCredential')
|
|
242
|
+
.build();
|
|
243
|
+
const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
|
|
244
|
+
jwt: jwt_withoutDid,
|
|
245
|
+
callbacks: {
|
|
246
|
+
signCallback: proofOfPossessionCallbackFunction,
|
|
247
|
+
},
|
|
248
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
249
|
+
})
|
|
250
|
+
// .withEndpointMetadata(metadata)
|
|
251
|
+
.withKid(kid_withoutDid)
|
|
252
|
+
.withClientId('sphereon:wallet')
|
|
253
|
+
.build();
|
|
254
|
+
await expect(credReqClient.acquireCredentialsUsingRequest({ format: 'jwt_vc_json', types: ['random'], proof })).rejects.toThrow(
|
|
255
|
+
Error(URL_NOT_VALID),
|
|
256
|
+
);
|
|
257
|
+
});
|
|
162
258
|
});
|
|
163
259
|
|
|
164
260
|
describe('Credential Request Client with Walt.id ', () => {
|
package/lib/__tests__/IT.spec.ts
CHANGED
|
@@ -29,11 +29,16 @@ import { IDENTIPROOF_AS_METADATA, IDENTIPROOF_AS_URL, IDENTIPROOF_ISSUER_URL, ID
|
|
|
29
29
|
export const UNIT_TEST_TIMEOUT = 30000;
|
|
30
30
|
|
|
31
31
|
const ISSUER_URL = 'https://issuer.research.identiproof.io';
|
|
32
|
-
const
|
|
32
|
+
const jwtDid = {
|
|
33
33
|
header: { alg: Alg.ES256, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', typ: 'openid4vci-proof+jwt' },
|
|
34
34
|
payload: { iss: 'test-clientId', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: ISSUER_URL },
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
const jwtWithoutDid = {
|
|
38
|
+
header: { alg: Alg.ES256, kid: 'ebfeb1f712ebc6f1c276e12ec21/keys/1', typ: 'openid4vci-proof+jwt' },
|
|
39
|
+
payload: { iss: 'test-clientId', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: ISSUER_URL },
|
|
40
|
+
};
|
|
41
|
+
|
|
37
42
|
describe('OID4VCI-Client should', () => {
|
|
38
43
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
39
44
|
async function proofOfPossessionCallbackFunction(_args: Jwt, _kid?: string): Promise<string> {
|
|
@@ -202,7 +207,7 @@ describe('OID4VCI-Client should', () => {
|
|
|
202
207
|
// Types of parameters 'args' and 'args' are incompatible.
|
|
203
208
|
// Property 'kid' is missing in type '{ header: unknown; payload: unknown; }' but required in type 'ProofOfPossessionCallbackArgs'.
|
|
204
209
|
const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
|
|
205
|
-
jwt,
|
|
210
|
+
jwt: jwtDid,
|
|
206
211
|
callbacks: {
|
|
207
212
|
signCallback: proofOfPossessionCallbackFunction,
|
|
208
213
|
},
|
|
@@ -221,6 +226,66 @@ describe('OID4VCI-Client should', () => {
|
|
|
221
226
|
UNIT_TEST_TIMEOUT,
|
|
222
227
|
);
|
|
223
228
|
|
|
229
|
+
it(
|
|
230
|
+
'succeed with a full flow with a not-did-kid without the client v1_0_11',
|
|
231
|
+
async () => {
|
|
232
|
+
/* Convert the URI into an object */
|
|
233
|
+
const credentialOffer: CredentialOfferRequestWithBaseUrl = await CredentialOfferClientV1_0_11.fromURI(INITIATE_QR_V1_0_08);
|
|
234
|
+
|
|
235
|
+
expect(credentialOffer.baseUrl).toEqual('openid-initiate-issuance://');
|
|
236
|
+
expect(credentialOffer.original_credential_offer).toEqual({
|
|
237
|
+
credential_type: ['OpenBadgeCredentialUrl'],
|
|
238
|
+
issuer: ISSUER_URL,
|
|
239
|
+
'pre-authorized_code':
|
|
240
|
+
'4jLs9xZHEfqcoow0kHE7d1a8hUk6Sy-5bVSV2MqBUGUgiFFQi-ImL62T-FmLIo8hKA1UdMPH0lM1xAgcFkJfxIw9L-lI3mVs0hRT8YVwsEM1ma6N3wzuCdwtMU4bcwKp',
|
|
241
|
+
user_pin_required: 'true',
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
nock(ISSUER_URL)
|
|
245
|
+
.post(/token.*/)
|
|
246
|
+
.reply(200, JSON.stringify(mockedAccessTokenResponse));
|
|
247
|
+
|
|
248
|
+
/* The actual access token calls */
|
|
249
|
+
const accessTokenClient: AccessTokenClientV1_0_11 = new AccessTokenClientV1_0_11();
|
|
250
|
+
const accessTokenResponse = await accessTokenClient.acquireAccessToken({ credentialOffer: credentialOffer, pin: '1234' });
|
|
251
|
+
expect(accessTokenResponse.successBody).toEqual(mockedAccessTokenResponse);
|
|
252
|
+
// Get the credential
|
|
253
|
+
nock(ISSUER_URL)
|
|
254
|
+
.post(/credential/)
|
|
255
|
+
.reply(200, {
|
|
256
|
+
format: 'jwt-vc',
|
|
257
|
+
credential: mockedVC,
|
|
258
|
+
});
|
|
259
|
+
const credReqClient = CredentialRequestClientBuilderV1_0_11.fromCredentialOffer({ credentialOffer: credentialOffer })
|
|
260
|
+
.withFormat('jwt_vc')
|
|
261
|
+
|
|
262
|
+
.withTokenFromResponse(accessTokenResponse.successBody!)
|
|
263
|
+
.build();
|
|
264
|
+
|
|
265
|
+
//TS2322: Type '(args: ProofOfPossessionCallbackArgs) => Promise<string>'
|
|
266
|
+
// is not assignable to type 'ProofOfPossessionCallback'.
|
|
267
|
+
// Types of parameters 'args' and 'args' are incompatible.
|
|
268
|
+
// Property 'kid' is missing in type '{ header: unknown; payload: unknown; }' but required in type 'ProofOfPossessionCallbackArgs'.
|
|
269
|
+
const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
|
|
270
|
+
jwt: jwtWithoutDid,
|
|
271
|
+
callbacks: {
|
|
272
|
+
signCallback: proofOfPossessionCallbackFunction,
|
|
273
|
+
},
|
|
274
|
+
version: OpenId4VCIVersion.VER_1_0_11,
|
|
275
|
+
})
|
|
276
|
+
.withEndpointMetadata({
|
|
277
|
+
issuer: 'https://issuer.research.identiproof.io',
|
|
278
|
+
credential_endpoint: 'https://issuer.research.identiproof.io/credential',
|
|
279
|
+
token_endpoint: 'https://issuer.research.identiproof.io/token',
|
|
280
|
+
})
|
|
281
|
+
.withKid('ebfeb1f712ebc6f1c276e12ec21/keys/1')
|
|
282
|
+
.build();
|
|
283
|
+
const credResponse = await credReqClient.acquireCredentialsUsingProof({ proofInput: proof });
|
|
284
|
+
expect(credResponse.successBody?.credential).toEqual(mockedVC);
|
|
285
|
+
},
|
|
286
|
+
UNIT_TEST_TIMEOUT,
|
|
287
|
+
);
|
|
288
|
+
|
|
224
289
|
it(
|
|
225
290
|
'succeed with a full flow without the client v1_0_13',
|
|
226
291
|
async () => {
|
|
@@ -266,7 +331,7 @@ describe('OID4VCI-Client should', () => {
|
|
|
266
331
|
// Types of parameters 'args' and 'args' are incompatible.
|
|
267
332
|
// Property 'kid' is missing in type '{ header: unknown; payload: unknown; }' but required in type 'ProofOfPossessionCallbackArgs'.
|
|
268
333
|
const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
|
|
269
|
-
jwt,
|
|
334
|
+
jwt: jwtDid,
|
|
270
335
|
callbacks: {
|
|
271
336
|
signCallback: proofOfPossessionCallbackFunction,
|
|
272
337
|
},
|
|
@@ -287,6 +352,73 @@ describe('OID4VCI-Client should', () => {
|
|
|
287
352
|
},
|
|
288
353
|
UNIT_TEST_TIMEOUT,
|
|
289
354
|
);
|
|
355
|
+
|
|
356
|
+
it(
|
|
357
|
+
'succeed with a full flow with a not-did-kid without the client v1_0_13',
|
|
358
|
+
async () => {
|
|
359
|
+
/* Convert the URI into an object */
|
|
360
|
+
const credentialOffer: CredentialOfferRequestWithBaseUrl = await CredentialOfferClient.fromURI(INITIATE_QR_V1_0_13);
|
|
361
|
+
const preAuthorizedCode = 'oaKazRN8I0IbtZ0C7JuMn5';
|
|
362
|
+
expect(credentialOffer.baseUrl).toEqual('openid-credential-offer://');
|
|
363
|
+
expect((credentialOffer.credential_offer as CredentialOfferPayloadV1_0_13).credential_configuration_ids).toEqual(['OpenBadgeCredentialUrl']);
|
|
364
|
+
expect(credentialOffer.original_credential_offer.grants).toEqual({
|
|
365
|
+
'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
|
|
366
|
+
'pre-authorized_code': preAuthorizedCode,
|
|
367
|
+
tx_code: {
|
|
368
|
+
input_mode: 'text',
|
|
369
|
+
description: 'Please enter the serial number of your physical drivers license',
|
|
370
|
+
length: preAuthorizedCode.length,
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
nock(ISSUER_URL)
|
|
376
|
+
.post(/token.*/)
|
|
377
|
+
.reply(200, JSON.stringify(mockedAccessTokenResponse));
|
|
378
|
+
|
|
379
|
+
/* The actual access token calls */
|
|
380
|
+
const accessTokenClient: AccessTokenClient = new AccessTokenClient();
|
|
381
|
+
const accessTokenResponse = await accessTokenClient.acquireAccessToken({ credentialOffer: credentialOffer, pin: '1234' });
|
|
382
|
+
expect(accessTokenResponse.successBody).toEqual(mockedAccessTokenResponse);
|
|
383
|
+
// Get the credential
|
|
384
|
+
nock(ISSUER_URL)
|
|
385
|
+
.post(/credential/)
|
|
386
|
+
.reply(200, {
|
|
387
|
+
format: 'jwt-vc',
|
|
388
|
+
credential: mockedVC,
|
|
389
|
+
});
|
|
390
|
+
const credReqClient = CredentialRequestClientBuilder.fromCredentialOffer({ credentialOffer: credentialOffer })
|
|
391
|
+
.withFormat('jwt_vc')
|
|
392
|
+
|
|
393
|
+
.withTokenFromResponse(accessTokenResponse.successBody!)
|
|
394
|
+
.build();
|
|
395
|
+
|
|
396
|
+
//TS2322: Type '(args: ProofOfPossessionCallbackArgs) => Promise<string>'
|
|
397
|
+
// is not assignable to type 'ProofOfPossessionCallback'.
|
|
398
|
+
// Types of parameters 'args' and 'args' are incompatible.
|
|
399
|
+
// Property 'kid' is missing in type '{ header: unknown; payload: unknown; }' but required in type 'ProofOfPossessionCallbackArgs'.
|
|
400
|
+
const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
|
|
401
|
+
jwt: jwtWithoutDid,
|
|
402
|
+
callbacks: {
|
|
403
|
+
signCallback: proofOfPossessionCallbackFunction,
|
|
404
|
+
},
|
|
405
|
+
version: OpenId4VCIVersion.VER_1_0_11,
|
|
406
|
+
})
|
|
407
|
+
.withEndpointMetadata({
|
|
408
|
+
issuer: 'https://issuer.research.identiproof.io',
|
|
409
|
+
credential_endpoint: 'https://issuer.research.identiproof.io/credential',
|
|
410
|
+
token_endpoint: 'https://issuer.research.identiproof.io/token',
|
|
411
|
+
})
|
|
412
|
+
.withKid('ebfeb1f712ebc6f1c276e12ec21/keys/1')
|
|
413
|
+
.build();
|
|
414
|
+
const credResponse = await credReqClient.acquireCredentialsUsingProof({
|
|
415
|
+
proofInput: proof,
|
|
416
|
+
credentialTypes: credentialOffer.original_credential_offer.credential_configuration_ids,
|
|
417
|
+
});
|
|
418
|
+
expect(credResponse.successBody?.credential).toEqual(mockedVC);
|
|
419
|
+
},
|
|
420
|
+
UNIT_TEST_TIMEOUT,
|
|
421
|
+
);
|
|
290
422
|
});
|
|
291
423
|
|
|
292
424
|
describe('OIDVCI-Client for v1_0_13 should', () => {
|
|
@@ -12,8 +12,15 @@ const jwt: Jwt = {
|
|
|
12
12
|
payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL, iat: Date.now() / 1000 },
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
+
const jwt_withoutDid: Jwt = {
|
|
16
|
+
header: { alg: Alg.ES256, kid: 'ebfeb1f712ebc6f1c276e12ec21/keys/1', typ: 'jwt' },
|
|
17
|
+
payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL, iat: Date.now() / 1000 },
|
|
18
|
+
};
|
|
19
|
+
|
|
15
20
|
const kid = 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1';
|
|
16
21
|
|
|
22
|
+
const kid_withoutDid = 'ebfeb1f712ebc6f1c276e12ec21/keys/1';
|
|
23
|
+
|
|
17
24
|
let keypair: KeyPair;
|
|
18
25
|
|
|
19
26
|
async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> {
|
|
@@ -52,6 +59,16 @@ describe('ProofOfPossession Builder ', () => {
|
|
|
52
59
|
).rejects.toThrow(Error(PROOF_CANT_BE_CONSTRUCTED));
|
|
53
60
|
});
|
|
54
61
|
|
|
62
|
+
it('should fail without supplied proof or callbacks and with kid without did', async function () {
|
|
63
|
+
await expect(
|
|
64
|
+
ProofOfPossessionBuilder.fromProof(undefined as never, OpenId4VCIVersion.VER_1_0_13)
|
|
65
|
+
.withIssuer(IDENTIPROOF_ISSUER_URL)
|
|
66
|
+
.withClientId('sphereon:wallet')
|
|
67
|
+
.withKid(kid_withoutDid)
|
|
68
|
+
.build(),
|
|
69
|
+
).rejects.toThrow(Error(PROOF_CANT_BE_CONSTRUCTED));
|
|
70
|
+
});
|
|
71
|
+
|
|
55
72
|
it('should fail wit undefined jwt supplied', async function () {
|
|
56
73
|
await expect(() =>
|
|
57
74
|
ProofOfPossessionBuilder.fromJwt({ jwt, callbacks: { signCallback: proofOfPossessionCallbackFunction }, version: OpenId4VCIVersion.VER_1_0_08 })
|
|
@@ -63,6 +80,21 @@ describe('ProofOfPossession Builder ', () => {
|
|
|
63
80
|
).toThrow(Error(NO_JWT_PROVIDED));
|
|
64
81
|
});
|
|
65
82
|
|
|
83
|
+
it('should fail with undefined jwt supplied and kid without did', async function () {
|
|
84
|
+
await expect(() =>
|
|
85
|
+
ProofOfPossessionBuilder.fromJwt({
|
|
86
|
+
jwt: jwt_withoutDid,
|
|
87
|
+
callbacks: { signCallback: proofOfPossessionCallbackFunction },
|
|
88
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
89
|
+
})
|
|
90
|
+
.withJwt(undefined as never)
|
|
91
|
+
.withIssuer(IDENTIPROOF_ISSUER_URL)
|
|
92
|
+
.withClientId('sphereon:wallet')
|
|
93
|
+
.withKid(kid_withoutDid)
|
|
94
|
+
.build(),
|
|
95
|
+
).toThrow(Error(NO_JWT_PROVIDED));
|
|
96
|
+
});
|
|
97
|
+
|
|
66
98
|
it('should build a proof with all required params present', async function () {
|
|
67
99
|
const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
|
|
68
100
|
jwt,
|
|
@@ -78,6 +110,21 @@ describe('ProofOfPossession Builder ', () => {
|
|
|
78
110
|
expect(proof).toBeDefined();
|
|
79
111
|
});
|
|
80
112
|
|
|
113
|
+
it('should build a proof with all required params present without did', async function () {
|
|
114
|
+
const proof: ProofOfPossession = await ProofOfPossessionBuilder.fromJwt({
|
|
115
|
+
jwt: jwt_withoutDid,
|
|
116
|
+
callbacks: {
|
|
117
|
+
signCallback: proofOfPossessionCallbackFunction,
|
|
118
|
+
},
|
|
119
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
120
|
+
})
|
|
121
|
+
.withIssuer(IDENTIPROOF_ISSUER_URL)
|
|
122
|
+
.withKid(kid_withoutDid)
|
|
123
|
+
.withClientId('sphereon:wallet')
|
|
124
|
+
.build();
|
|
125
|
+
expect(proof).toBeDefined();
|
|
126
|
+
});
|
|
127
|
+
|
|
81
128
|
it('should fail creating a proof of possession with simple verification', async () => {
|
|
82
129
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
83
130
|
async function proofOfPossessionCallbackFunction(_args: Jwt, _kid?: string): Promise<string> {
|
|
@@ -93,6 +140,25 @@ describe('ProofOfPossession Builder ', () => {
|
|
|
93
140
|
).rejects.toThrow(Error(JWS_NOT_VALID));
|
|
94
141
|
});
|
|
95
142
|
|
|
143
|
+
it('should fail creating a proof of possession with simple verification and without did', async () => {
|
|
144
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
145
|
+
async function proofOfPossessionCallbackFunction(_args: Jwt, _kid?: string): Promise<string> {
|
|
146
|
+
throw new Error(JWS_NOT_VALID);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await expect(
|
|
150
|
+
ProofOfPossessionBuilder.fromJwt({
|
|
151
|
+
jwt: jwt_withoutDid,
|
|
152
|
+
callbacks: { signCallback: proofOfPossessionCallbackFunction },
|
|
153
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
154
|
+
})
|
|
155
|
+
.withIssuer(IDENTIPROOF_ISSUER_URL)
|
|
156
|
+
.withClientId('sphereon:wallet')
|
|
157
|
+
.withKid(kid_withoutDid)
|
|
158
|
+
.build(),
|
|
159
|
+
).rejects.toThrow(Error(JWS_NOT_VALID));
|
|
160
|
+
});
|
|
161
|
+
|
|
96
162
|
it('should fail creating a proof of possession without verify callback', async () => {
|
|
97
163
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
98
164
|
async function proofOfPossessionCallbackFunction(_args: Jwt, _kid?: string): Promise<string> {
|
|
@@ -107,4 +173,23 @@ describe('ProofOfPossession Builder ', () => {
|
|
|
107
173
|
.build(),
|
|
108
174
|
).rejects.toThrow(Error(JWS_NOT_VALID));
|
|
109
175
|
});
|
|
176
|
+
|
|
177
|
+
it('should fail creating a proof of possession without verify callback and without did', async () => {
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
179
|
+
async function proofOfPossessionCallbackFunction(_args: Jwt, _kid?: string): Promise<string> {
|
|
180
|
+
throw new Error(JWS_NOT_VALID);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
await expect(
|
|
184
|
+
ProofOfPossessionBuilder.fromJwt({
|
|
185
|
+
jwt: jwt_withoutDid,
|
|
186
|
+
callbacks: { signCallback: proofOfPossessionCallbackFunction },
|
|
187
|
+
version: OpenId4VCIVersion.VER_1_0_08,
|
|
188
|
+
})
|
|
189
|
+
.withIssuer(IDENTIPROOF_ISSUER_URL)
|
|
190
|
+
.withClientId('sphereon:wallet')
|
|
191
|
+
.withKid(kid_withoutDid)
|
|
192
|
+
.build(),
|
|
193
|
+
).rejects.toThrow(Error(JWS_NOT_VALID));
|
|
194
|
+
});
|
|
110
195
|
});
|
|
@@ -166,4 +166,107 @@ describe('sd-jwt vc', () => {
|
|
|
166
166
|
},
|
|
167
167
|
UNIT_TEST_TIMEOUT,
|
|
168
168
|
);
|
|
169
|
+
|
|
170
|
+
it(
|
|
171
|
+
'succeed with a full flow without did',
|
|
172
|
+
async () => {
|
|
173
|
+
const offerUri = await vcIssuer.createCredentialOfferURI({
|
|
174
|
+
grants: {
|
|
175
|
+
'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
|
|
176
|
+
tx_code: {
|
|
177
|
+
input_mode: 'text',
|
|
178
|
+
length: 3,
|
|
179
|
+
},
|
|
180
|
+
'pre-authorized_code': '123',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
credential_configuration_ids: ['SdJwtCredential'],
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
nock(vcIssuer.issuerMetadata.credential_issuer).get('/.well-known/openid-credential-issuer').reply(200, JSON.stringify(issuerMetadata));
|
|
187
|
+
nock(vcIssuer.issuerMetadata.credential_issuer).get('/.well-known/openid-configuration').reply(404);
|
|
188
|
+
nock(vcIssuer.issuerMetadata.credential_issuer).get('/.well-known/oauth-authorization-server').reply(404);
|
|
189
|
+
|
|
190
|
+
expect(offerUri.uri).toEqual(
|
|
191
|
+
'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%22tx_code%22%3A%7B%22input_mode%22%3A%22text%22%2C%22length%22%3A3%7D%7D%7D%2C%22credential_configuration_ids%22%3A%5B%22SdJwtCredential%22%5D%2C%22credential_issuer%22%3A%22https%3A%2F%2Fexample.com%22%7D',
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const client = await OpenID4VCIClientV1_0_13.fromURI({
|
|
195
|
+
uri: offerUri.uri,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(client.credentialOffer?.credential_offer).toEqual({
|
|
199
|
+
credential_issuer: 'https://example.com',
|
|
200
|
+
credential_configuration_ids: ['SdJwtCredential'],
|
|
201
|
+
grants: {
|
|
202
|
+
'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
|
|
203
|
+
'pre-authorized_code': '123',
|
|
204
|
+
tx_code: {
|
|
205
|
+
input_mode: 'text',
|
|
206
|
+
length: 3,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const supported = client.getCredentialsSupported('vc+sd-jwt');
|
|
213
|
+
expect(supported).toEqual({ SdJwtCredentialId: { format: 'vc+sd-jwt', id: 'SdJwtCredentialId', vct: 'SdJwtCredentialId' } });
|
|
214
|
+
|
|
215
|
+
const offered = supported['SdJwtCredentialId'] as CredentialSupportedSdJwtVc;
|
|
216
|
+
|
|
217
|
+
nock(issuerMetadata.token_endpoint as string)
|
|
218
|
+
.post('/')
|
|
219
|
+
.reply(200, async (_, body: string) => {
|
|
220
|
+
const parsedBody = Object.fromEntries(body.split('&').map((x) => x.split('=')));
|
|
221
|
+
return createAccessTokenResponse(parsedBody as AccessTokenRequest, {
|
|
222
|
+
credentialOfferSessions: vcIssuer.credentialOfferSessions,
|
|
223
|
+
accessTokenIssuer: 'https://issuer.example.com',
|
|
224
|
+
cNonces: vcIssuer.cNonces,
|
|
225
|
+
cNonce: 'a-c-nonce',
|
|
226
|
+
accessTokenSignerCallback: async () => 'ey.val.ue',
|
|
227
|
+
tokenExpiresIn: 500,
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
await client.acquireAccessToken({ pin: '123' });
|
|
232
|
+
nock(issuerMetadata.credential_endpoint as string)
|
|
233
|
+
.post('/')
|
|
234
|
+
.reply(200, async (_, body) =>
|
|
235
|
+
vcIssuer.issueCredential({
|
|
236
|
+
credentialRequest: { ...(body as CredentialRequestV1_0_13), credential_identifier: offered.vct },
|
|
237
|
+
credential: {
|
|
238
|
+
vct: 'Hello',
|
|
239
|
+
iss: 'example.com',
|
|
240
|
+
iat: 123,
|
|
241
|
+
// Defines what can be disclosed (optional)
|
|
242
|
+
__disclosureFrame: {
|
|
243
|
+
name: true,
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
newCNonce: 'new-c-nonce',
|
|
247
|
+
}),
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const credentials = await client.acquireCredentials({
|
|
251
|
+
credentialIdentifier: offered.vct,
|
|
252
|
+
// format: 'vc+sd-jwt',
|
|
253
|
+
alg,
|
|
254
|
+
jwk,
|
|
255
|
+
proofCallbacks: {
|
|
256
|
+
// When using sd-jwt for real, this jwt should include a jwk
|
|
257
|
+
signCallback: async () => 'ey.ja.ja',
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
expect(credentials).toEqual({
|
|
262
|
+
notification_id: expect.any(String),
|
|
263
|
+
access_token: 'ey.val.ue',
|
|
264
|
+
c_nonce: 'new-c-nonce',
|
|
265
|
+
c_nonce_expires_in: 300,
|
|
266
|
+
credential: 'sd-jwt',
|
|
267
|
+
// format: 'vc+sd-jwt',
|
|
268
|
+
});
|
|
269
|
+
},
|
|
270
|
+
UNIT_TEST_TIMEOUT,
|
|
271
|
+
);
|
|
169
272
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sphereon/oid4vci-client",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1-unstable.2+27bc1d9",
|
|
4
4
|
"description": "OpenID for Verifiable Credential Issuance (OpenID4VCI) client",
|
|
5
5
|
"source": "lib/index.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"build": "tsc"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@sphereon/oid4vci-common": "0.12.
|
|
18
|
+
"@sphereon/oid4vci-common": "0.12.1-unstable.2+27bc1d9",
|
|
19
19
|
"@sphereon/ssi-types": "0.25.1-unstable.87",
|
|
20
20
|
"cross-fetch": "^3.1.8",
|
|
21
21
|
"debug": "^4.3.4"
|
|
@@ -69,5 +69,5 @@
|
|
|
69
69
|
"OIDC4VCI",
|
|
70
70
|
"OID4VCI"
|
|
71
71
|
],
|
|
72
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "27bc1d9522fa74d8016dced63fa415efb6c4eebc"
|
|
73
73
|
}
|