UNPKG

@sphereon/oid4vci-client

Version:

OpenID for Verifiable Credential Issuance (OpenID4VCI) client

173 lines (151 loc) • 5.96 kB
import * as crypto from 'crypto'; import { uuidv4 } from '@sphereon/oid4vc-common'; import { Alg, Jwt, ProofOfPossessionCallbacks } from '@sphereon/oid4vci-common'; import { CredentialMapper } from '@sphereon/ssi-types'; import * as didts from '@transmute/did-key.js'; import { fetch } from 'cross-fetch'; import debug from 'debug'; import { importJWK, JWK, SignJWT } from 'jose'; import { OpenID4VCIClientV1_0_11 } from '..'; export const UNIT_TEST_TIMEOUT = 60000; const ISSUER_URL = 'https://ssi.sphereon.com/pf3'; const jwk: JWK = { crv: 'Ed25519', d: 'kTRm0aONHYwNPA-w_DtjMHUIWjE3K70qgCIhWojZ0eU', x: 'NeA0d8sp86xRh3DczU4m5wPNIbl0HCSwOBcMN3sNmdk', kty: 'OKP', }; // pub hex: 35e03477cb29f3ac518770dccd4e26e703cd21b9741c24b038170c377b0d99d9 // priv hex: 913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5 const did = `did:key:z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`; const kid = `${did}#z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`; // Sphereon infra down rn describe.skip('OID4VCI-Client using Sphereon issuer should', () => { async function test(format: 'ldp_vc' | 'jwt_vc_json') { debug.enable('*'); const offer = await getCredentialOffer(format); const client = await OpenID4VCIClientV1_0_11.fromURI({ uri: offer.uri, kid, alg: Alg.EdDSA, }); expect(client.credentialOffer).toBeDefined(); expect(client.endpointMetadata).toBeDefined(); expect(client.getCredentialEndpoint()).toEqual(`${ISSUER_URL}/credentials`); expect(client.getAccessTokenEndpoint()).toEqual(`${ISSUER_URL}/token`); const accessToken = await client.acquireAccessToken(); // console.log(accessToken); expect(accessToken).toMatchObject({ expires_in: 300, // scope: 'GuestCredential', token_type: 'bearer', }); const credentialResponse = await client.acquireCredentials({ credentialTypes: 'GuestCredential', format, proofCallbacks: { signCallback: proofOfPossessionCallbackFunction, }, }); expect(credentialResponse.credential).toBeDefined(); const wrappedVC = CredentialMapper.toWrappedVerifiableCredential(credentialResponse.credential!); expect(format.startsWith(wrappedVC.format)).toEqual(true); } it( 'succeed in a full flow with the client using OpenID4VCI version 11 and ldp_vc', async () => { await test('ldp_vc'); }, UNIT_TEST_TIMEOUT, ); it( 'succeed in a full flow with the client using OpenID4VCI version 11 and jwt_vc_json', async () => { await test('jwt_vc_json'); }, UNIT_TEST_TIMEOUT, ); }); interface CreateCredentialOfferResponse { uri: string; userPinRequired: boolean; } async function getCredentialOffer(format: 'ldp_vc' | 'jwt_vc_json'): Promise<CreateCredentialOfferResponse> { const credentialOffer = await fetch('https://ssi.sphereon.com/pf3/webapp/credential-offers', { method: 'post', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, //make sure to serialize your JSON body body: JSON.stringify({ credentials: ['GuestCredential'], grants: { 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { 'pre-authorized_code': uuidv4().substring(0, 10), user_pin_required: false, }, }, credentialDataSupplierInput: { firstName: 'Hello', lastName: 'World', email: 'hello.world@example.com' }, }), }); return (await credentialOffer.json()) as CreateCredentialOfferResponse; } async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> { const importedJwk = await importJWK(jwk, 'EdDSA'); return await new SignJWT({ ...args.payload }) .setProtectedHeader({ ...args.header, kid: kid! }) .setIssuer(kid!) .setIssuedAt() .setExpirationTime('2h') .sign(importedJwk); } //fixme: bring back this test describe('ismapolis bug report #63, https://github.com/Sphereon-Opensource/OID4VC-demo/issues/63, should', () => { // Sphereon infra is not working currently it.skip('work as expected provided a correct JWT is supplied', async () => { debug.enable('*'); const { uri } = await getCredentialOffer('jwt_vc_json'); const client = await OpenID4VCIClientV1_0_11.fromURI({ uri: uri, clientId: 'test-clientID' }); const metadata = await client.retrieveServerMetadata(); console.log(JSON.stringify(metadata)); //2. Adquire acces token from authorization server endpoint const accessToken = await client.acquireAccessToken({}); console.log(`Access token: ${JSON.stringify(accessToken)}`); //3. Create DID needed for later proof of possession const { keys, didDocument } = await didts.jwk.generate({ type: 'secp256k1', // 'P-256', 'P-384', 'X25519', 'secp256k1' accept: 'application/did+json', secureRandom: () => { return crypto.randomBytes(32); }, }); const edPrivateKey = await importJWK(keys[0].privateKeyJwk); async function signCallback(args: Jwt, kid?: string): Promise<string> { if (!args.payload.aud) { throw Error('aud required'); } else if (!kid) { throw Error('kid required'); } return await new SignJWT({ ...args.payload }) .setProtectedHeader({ alg: args.header.alg, kid, typ: 'openid4vci-proof+jwt' }) .setIssuedAt() .setIssuer(kid) .setAudience(args.payload.aud) .setExpirationTime('2h') .sign(edPrivateKey); } const callbacks: ProofOfPossessionCallbacks = { signCallback: signCallback, }; const credentialResponse = await client.acquireCredentials({ credentialTypes: 'GuestCredential', proofCallbacks: callbacks, format: 'jwt_vc_json', alg: Alg.ES256K, kid: didDocument.verificationMethod[0].id, jti: uuidv4(), }); console.log(JSON.stringify(credentialResponse.credential)); }); });