Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/develop' into feat/sd-jwt-issu…
Browse files Browse the repository at this point in the history
…ance-and-test
  • Loading branch information
TimoGlastra committed Dec 7, 2023
2 parents 0d928cc + 1595df2 commit 3a8598c
Show file tree
Hide file tree
Showing 19 changed files with 430 additions and 149 deletions.
4 changes: 0 additions & 4 deletions packages/callback-example/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline

**Note:** Version bump only for package @sphereon/oid4vci-callback-example





## [0.7.3](https://github.com/Sphereon-Opensource/OID4VCI/compare/v0.7.2...v0.7.3) (2023-09-30)

**Note:** Version bump only for package @sphereon/oid4vci-callback-example
Expand Down
4 changes: 0 additions & 4 deletions packages/client/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline

**Note:** Version bump only for package @sphereon/oid4vci-client





## [0.7.3](https://github.com/Sphereon-Opensource/OID4VCI/compare/v0.7.2...v0.7.3) (2023-09-30)

**Note:** Version bump only for package @sphereon/oid4vci-client
Expand Down
17 changes: 9 additions & 8 deletions packages/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,10 @@ console.log(client.getAccessTokenEndpoint()); // https://auth.research.identipro

The OID4VCI Server metadata contains information about token endpoints, credential endpoints, as well as additional
information about supported Credentials, and their cryptographic suites and formats.
The code above already retrieved the metadata, so it will not be fetched again. If you however not used
The code above already retrieved the metadata, so it will not be fetched again, and this method places the data in another variable. If you however have not used
the `retrieveServerMetadata` option, you can use this method to fetch it from the Issuer:

```typescript
import { OpenID4VCIClient } from '@sphereon/oid4vci-client';

const metadata = await client.retrieveServerMetadata();
```

Expand Down Expand Up @@ -111,6 +109,9 @@ the [Proof of Posession](#proof-of-possession) chapter for more information.
The Proof of Possession using a signature callback function. The example uses the `jose` library.

```typescript
import * as jose from 'jose';
import { DIDDocument } from 'did-resolver';

const { privateKey, publicKey } = await jose.generateKeyPair('ES256');

// Must be JWS
Expand All @@ -121,10 +122,10 @@ async function signCallback(args: Jwt, kid: string): Promise<string> {
.setIssuer(kid)
.setAudience(args.payload.aud)
.setExpirationTime('2h')
.sign(keypair.privateKey);
.sign(privateKey);
}

const callbacks: ProofOfPossessionCallbacks = {
const callbacks: ProofOfPossessionCallbacks<DIDDocument> = {
signCallback,
};
```
Expand All @@ -133,14 +134,14 @@ Now it is time to get the actual credential

```typescript
const credentialResponse = await client.acquireCredentials({
credentialType: 'OpenBadgeCredential',
credentialTypes: 'OpenBadgeCredential',
proofCallbacks: callbacks,
format: 'jwt_vc',
format: 'jwt_vc_json',
alg: Alg.ES256K,
kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1',
});
console.log(credentialResponse.credential);
// JWT format. (LDP/JSON-LD is also supported by the client)
// JWT format. (LDP / JSON-LD ('ldp_vc' / 'jwt_vc_json-ld') is also supported by the client)
// eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL2V4YW1wbGVzL3YxIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiVW5pdmVyc2l0eURlZ3JlZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiaHR0cHM6Ly9leGFtcGxlLmVkdS9pc3N1ZXJzLzU2NTA0OSIsImlzc3VhbmNlRGF0ZSI6IjIwMTAtMDEtMDFUMDA6MDA6MDBaIiwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6ZXhhbXBsZTplYmZlYjFmNzEyZWJjNmYxYzI3NmUxMmVjMjEiLCJkZWdyZWUiOnsidHlwZSI6IkJhY2hlbG9yRGVncmVlIiwibmFtZSI6IkJhY2hlbG9yIG9mIFNjaWVuY2UgYW5kIEFydHMifX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSJ9.z5vgMTK1nfizNCg5N-niCOL3WUIAL7nXy-nGhDZYO_-PNGeE-0djCpWAMH8fD8eWSID5PfkPBYkx_dfLJnQ7NA
```

Expand Down
2 changes: 1 addition & 1 deletion packages/client/lib/__tests__/MattrE2E.spec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const jwk: JWK = {
// priv hex: 913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5
const did = `did:key:z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`;
const kid = `${did}#z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`;
describe('OID4VCI-Client using Mattr issuer should', () => {
describe.skip('OID4VCI-Client using Mattr issuer should', () => {
async function test(format: 'ldp_vc' | 'jwt_vc_json') {
const offer = await getCredentialOffer(format);
const client = await OpenID4VCIClient.fromURI({
Expand Down
148 changes: 73 additions & 75 deletions packages/client/lib/__tests__/SphereonE2E.spec.test.ts
Original file line number Diff line number Diff line change
@@ -1,171 +1,169 @@
import * as crypto from 'crypto'
import * as crypto from 'crypto';

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 { v4 } from 'uuid'
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 { v4 } from 'uuid';

import { OpenID4VCIClient } from '..';

import { OpenID4VCIClient } from '..'
export const UNIT_TEST_TIMEOUT = 30000;

export const UNIT_TEST_TIMEOUT = 30000

const ISSUER_URL = 'https://ssi.sphereon.com/pf3'
const ISSUER_URL = 'https://ssi.sphereon.com/pf3';

const jwk: JWK = {
crv: 'Ed25519',
d: 'kTRm0aONHYwNPA-w_DtjMHUIWjE3K70qgCIhWojZ0eU',
x: 'NeA0d8sp86xRh3DczU4m5wPNIbl0HCSwOBcMN3sNmdk',
kty: 'OKP'
}
kty: 'OKP',
};

// pub hex: 35e03477cb29f3ac518770dccd4e26e703cd21b9741c24b038170c377b0d99d9
// priv hex: 913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5
const did = `did:key:z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`;
const kid = `${did}#z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`
const kid = `${did}#z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`;
describe('OID4VCI-Client using Sphereon issuer should', () => {
async function test(format: 'ldp_vc' | 'jwt_vc_json') {
debug.enable('*')
const offer = await getCredentialOffer(format)
debug.enable('*');
const offer = await getCredentialOffer(format);
const client = await OpenID4VCIClient.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()
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'
})
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)
signCallback: proofOfPossessionCallbackFunction,
},
});
expect(credentialResponse.credential).toBeDefined();
const wrappedVC = CredentialMapper.toWrappedVerifiableCredential(credentialResponse.credential!);
expect(format.startsWith(wrappedVC.format)).toEqual(true);
}

xit(
'succeed in a full flow with the client using OpenID4VCI version 11 and ldp_vc',
async () => {
await test('ldp_vc')
await test('ldp_vc');
},
UNIT_TEST_TIMEOUT
)
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')
await test('jwt_vc_json');
},
UNIT_TEST_TIMEOUT
)
})
UNIT_TEST_TIMEOUT,
);
});

interface CreateCredentialOfferResponse {
uri: string,
userPinRequired: boolean
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'
'Content-Type': 'application/json',
},

//make sure to serialize your JSON body
body: JSON.stringify({
'credentials': ['GuestCredential'],
'grants': {
credentials: ['GuestCredential'],
grants: {
'urn:ietf:params:oauth:grant-type:pre-authorized_code': {
'pre-authorized_code': v4().substring(0, 10),
'user_pin_required': false
}
user_pin_required: false,
},
},
'credentialDataSupplierInput': { 'firstName': 'Hello', 'lastName': 'World', 'email': '[email protected]' }
})
})
credentialDataSupplierInput: { firstName: 'Hello', lastName: 'World', email: '[email protected]' },
}),
});

return (await credentialOffer.json()) as CreateCredentialOfferResponse
return (await credentialOffer.json()) as CreateCredentialOfferResponse;
}

async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> {
const importedJwk = await importJWK(jwk, 'EdDSA')
const importedJwk = await importJWK(jwk, 'EdDSA');
return await new SignJWT({ ...args.payload })
.setProtectedHeader({ ...args.header, kid: kid! })
.setIssuer(kid!)
.setIssuedAt()
.setExpirationTime('2h')
.sign(importedJwk)
.sign(importedJwk);
}


describe('ismapolis bug report #63, https://github.com/Sphereon-Opensource/OID4VC-demo/issues/63, should', () => {
it('work as expected provided a correct JWT is supplied', async () => {
debug.enable('*')
const { uri } = await getCredentialOffer('jwt_vc_json')
const client = await OpenID4VCIClient.fromURI({ uri: uri, clientId: 'test-clientID' })
const metadata = await client.retrieveServerMetadata()
console.log(JSON.stringify(metadata))
debug.enable('*');
const { uri } = await getCredentialOffer('jwt_vc_json');
const client = await OpenID4VCIClient.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)}`)
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)
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')
throw Error('aud required');
} else if (!kid) {
throw Error('kid required')
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)
.sign(edPrivateKey);
}

const callbacks: ProofOfPossessionCallbacks<never> = {
signCallback: signCallback
}
signCallback: signCallback,
};

const credentialResponse = await client.acquireCredentials({
credentialTypes: 'GuestCredential',
proofCallbacks: callbacks,
format: 'jwt_vc_json',
alg: Alg.ES256K,
kid: didDocument.verificationMethod[0].id,
jti: v4()
})
console.log(JSON.stringify(credentialResponse.credential))
})
})
jti: v4(),
});
console.log(JSON.stringify(credentialResponse.credential));
});
});
4 changes: 0 additions & 4 deletions packages/common/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline

**Note:** Version bump only for package @sphereon/oid4vci-common





## [0.7.3](https://github.com/Sphereon-Opensource/OID4VCI/compare/v0.7.2...v0.7.3) (2023-09-30)

**Note:** Version bump only for package @sphereon/oid4vci-common
Expand Down
1 change: 1 addition & 0 deletions packages/common/lib/types/Generic.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export type CredentialDataSupplierInput = any;

export type CreateCredentialOfferURIResult = {
uri: string;
qrCodeDataUri?: string;
session: CredentialOfferSession;
userPin?: string;
userPinLength?: number;
Expand Down
Loading

0 comments on commit 3a8598c

Please sign in to comment.