@sphereon/oid4vci-client
Version:
OpenID for Verifiable Credential Issuance (OpenID4VCI) client
146 lines (126 loc) • 6.01 kB
text/typescript
import { Alg, Jwt } from '@sphereon/oid4vci-common';
import { toJwk } from '@sphereon/ssi-sdk-ext.key-utils';
import { CredentialMapper } from '@sphereon/ssi-types';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
import { from } from '@trust/keyto';
import { fetch } from 'cross-fetch';
import debug from 'debug';
import { base64url, importJWK, JWK, SignJWT } from 'jose';
import * as u8a from 'uint8arrays';
import { OpenID4VCIClientV1_0_11 } from '..';
export const UNIT_TEST_TIMEOUT = 30000;
const ISSUER_URL = 'https://conformance-test.ebsi.eu/conformance/v3/issuer-mock';
const AUTH_URL = 'https://conformance-test.ebsi.eu/conformance/v3/auth-mock';
const jwk: JWK = {
alg: 'ES256',
use: 'sig',
kty: 'EC',
crv: 'P-256',
x: 'hUWYK06qFvdudydiqnEhVJhZ-73jcLtuzH8kIyNOSHE',
y: 'UZf7oUkJdo65SQekMD5ssiRclEimG2SmlsjXf3QwQJo',
d: 'zDeeo3K0Pk8dofeKcajvJYxMZ1vijx_cVDJQl1IpbAM',
};
console.log(`JWK (private/orig): ${JSON.stringify(jwk, null, 2)}`);
const privateKey = from(jwk, 'jwk').toString('blk', 'private');
const publicKey = from(jwk, 'jwk').toString('blk', 'public');
console.log(`Private key: ${privateKey}`);
console.log(`Public key: ${publicKey}`);
console.log(`Private key (b64): ${base64url.encode(u8a.fromString(privateKey, 'base16'))}`);
console.log(`JWK (private 2) ${JSON.stringify(toJwk(privateKey, 'Secp256r1', { isPrivateKey: true }))}`);
console.log(`JWK (public 2) ${JSON.stringify(toJwk(publicKey, 'Secp256r1', { isPrivateKey: false }))}`);
// const DID_METHOD = 'did:key'
const DID =
'did:key:z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrm54tL4pRrDDhR1QJ5RHPMXUq5MzYpZL2k35vya5eMiNxschNy9AJ74CC3MmcYiZJGZfyhWQ6qDgTVcDSHdquwPYvLDut383JbrgYdZYYSC2merTMgmQtUi3huYhaky1qE';
const DID_URL_ENCODED =
'did%3Akey%3Az2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrm54tL4pRrDDhR1QJ5RHPMXUq5MzYpZL2k35vya5eMiNxschNy9AJ74CC3MmcYiZJGZfyhWQ6qDgTVcDSHdquwPYvLDut383JbrgYdZYYSC2merTMgmQtUi3huYhaky1qE';
// const PRIVATE_KEY_HEX = '7dd923e40f4615ac496119f7e793cc2899e99b64b88ca8603db986700089532b'
// const PUBLIC_KEY_HEX =
// '04a23cb4c83901acc2eb0f852599610de0caeac260bf8ed05e7f902eaac0f9c8d74dd4841b94d13424d32af8ec0e9976db9abfa7e3a59e10d565c5d4d901b4be63'
// pub hex: 35e03477cb29f3ac518770dccd4e26e703cd21b9741c24b038170c377b0d99d9
// priv hex: 913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5
// const did = `did:key:z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`;
const kid = `${DID}#z2dmzD81cgPx8Vki7JbuuMmFYrWPgYoytykUZ3eyqht1j9Kbrm54tL4pRrDDhR1QJ5RHPMXUq5MzYpZL2k35vya5eMiNxschNy9AJ74CC3MmcYiZJGZfyhWQ6qDgTVcDSHdquwPYvLDut383JbrgYdZYYSC2merTMgmQtUi3huYhaky1qE`;
// const jw = jose.importKey()
// EBSI returning a 500 in credential endpoint all of a sudden
describe.skip('OID4VCI-Client using Sphereon issuer should', () => {
async function test(credentialType: 'CTWalletCrossPreAuthorisedInTime' | 'CTWalletCrossPreAuthorisedDeferred' | 'CTWalletCrossAuthorisedInTime') {
debug.enable('*');
const offer = await getCredentialOffer(credentialType);
const client = await OpenID4VCIClientV1_0_11.fromURI({
uri: offer,
kid,
alg: Alg.ES256,
clientId: DID_URL_ENCODED,
});
expect(client.credentialOffer).toBeDefined();
expect(client.endpointMetadata).toBeDefined();
expect(client.getCredentialEndpoint()).toEqual(`${ISSUER_URL}/credential`);
expect(client.getAccessTokenEndpoint()).toEqual(`${AUTH_URL}/token`);
if (credentialType !== 'CTWalletCrossPreAuthorisedInTime') {
const url = await client.createAuthorizationRequestUrl({
authorizationRequest: {
redirectUri: 'openid4vc%3A',
},
});
const result = await fetch(url);
console.log(result.text());
}
const accessToken = await client.acquireAccessToken({ pin: '0891' });
// console.log(accessToken);
expect(accessToken).toMatchObject({
expires_in: 86400,
// scope: 'GuestCredential',
token_type: 'Bearer',
});
const format = 'jwt_vc';
const credentialResponse = await client.acquireCredentials({
credentialTypes: client.getCredentialOfferTypes()[0],
format,
proofCallbacks: {
signCallback: proofOfPossessionCallbackFunction,
},
kid,
deferredCredentialAwait: true,
deferredCredentialIntervalInMS: 5000,
});
console.log(JSON.stringify(credentialResponse, null, 2));
expect(credentialResponse.credential).toBeDefined();
const wrappedVC = CredentialMapper.toWrappedVerifiableCredential(credentialResponse.credential!);
expect(format.startsWith(wrappedVC.format)).toEqual(true);
}
// Current conformance tests is not stable as changes are being applied it seems
it(
'succeed in a full flow with the client using OpenID4VCI version 11 and jwt_vc_json',
async () => {
await test('CTWalletCrossPreAuthorisedInTime');
await test('CTWalletCrossPreAuthorisedDeferred');
// await test('CTWalletCrossAuthorisedInTime');
},
UNIT_TEST_TIMEOUT,
);
});
async function getCredentialOffer(
credentialType: 'CTWalletCrossPreAuthorisedInTime' | 'CTWalletCrossAuthorisedInTime' | 'CTWalletCrossPreAuthorisedDeferred',
): Promise<string> {
const credentialOffer = await fetch(
`https://conformance-test.ebsi.eu/conformance/v3/issuer-mock/initiate-credential-offer?credential_type=${credentialType}&client_id=${DID_URL_ENCODED}&credential_offer_endpoint=openid-credential-offer%3A%2F%2F`,
{
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
);
return await credentialOffer.text();
}
async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> {
const importedJwk = await importJWK(jwk);
return await new SignJWT({ ...args.payload })
.setProtectedHeader({ ...args.header, kid: kid! })
.setIssuer(DID)
.setIssuedAt()
.setExpirationTime('2m')
.sign(importedJwk);
}