Skip to content

Commit

Permalink
Merge pull request #114 from Sphereon-Opensource/develop
Browse files Browse the repository at this point in the history
New release
  • Loading branch information
nklomp authored Jun 19, 2024
2 parents 4d46a72 + a0d8ad3 commit b7a0808
Show file tree
Hide file tree
Showing 98 changed files with 10,276 additions and 4,021 deletions.
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"packages": ["packages/*"],
"version": "0.10.3",
"version": "0.11.0",
"npmClient": "pnpm",
"command": {
"publish": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sphereon/oid4vci-workspace",
"version": "0.5.0",
"version": "0.11.0",
"description": "OpenID for Verifiable Credential Issuance workspace",
"author": "Sphereon",
"license": "Apache-2.0",
Expand Down
30 changes: 28 additions & 2 deletions packages/callback-example/lib/IssuerCallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Ed25519Signature2020 } from '@digitalcredentials/ed25519-signature-2020
import { Ed25519VerificationKey2020 } from '@digitalcredentials/ed25519-verification-key-2020'
import { securityLoader } from '@digitalcredentials/security-document-loader'
import vc from '@digitalcredentials/vc'
import { CredentialRequestV1_0_11 } from '@sphereon/oid4vci-common'
import { CredentialRequest, CredentialRequestV1_0_11 } from '@sphereon/oid4vci-common'
import { CredentialIssuanceInput } from '@sphereon/oid4vci-issuer'
import { CompactSdJwtVc, W3CVerifiableCredential } from '@sphereon/ssi-types'

Expand All @@ -15,7 +15,7 @@ export const generateDid = async () => {
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const getIssuerCallback = (credential: CredentialIssuanceInput, keyPair: any, verificationMethod: string) => {
export const getIssuerCallbackV1_0_11 = (credential: CredentialIssuanceInput, keyPair: any, verificationMethod: string) => {
if (!credential) {
throw new Error('A credential needs to be provided')
}
Expand All @@ -33,6 +33,32 @@ export const getIssuerCallback = (credential: CredentialIssuanceInput, keyPair:
return await vc.issue({ credential, suite, documentLoader })
}
}

export const getIssuerCallbackV1_0_13 = (
credential: CredentialIssuanceInput,
credentialRequest: CredentialRequest,
keyPair: any,
verificationMethod: string,
) => {
if (!credential) {
throw new Error('A credential needs to be provided')
}

return async (_opts: {
credentialRequest: CredentialRequest
credential: CredentialIssuanceInput
format?: string
jwtVerifyResult: any // Adjust type if necessary
}): Promise<W3CVerifiableCredential | CompactSdJwtVc> => {
const documentLoader = securityLoader().build()
const verificationKey: any = Array.from(keyPair.values())[0]
const keys = await Ed25519VerificationKey2020.from({ ...verificationKey })
const suite = new Ed25519Signature2020({ key: keys })
suite.verificationMethod = verificationMethod
return await vc.issue({ credential, suite, documentLoader })
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const verifyCredential = async (credential: W3CVerifiableCredential, keyPair: any, verificationMethod: string): Promise<any> => {
const documentLoader = securityLoader().build()
Expand Down
56 changes: 34 additions & 22 deletions packages/callback-example/lib/__tests__/issuerCallback.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { CredentialRequestClient, CredentialRequestClientBuilder, ProofOfPossess
import {
Alg,
CNonceState,
CredentialSupported,
CredentialConfigurationSupportedV1_0_13,
CredentialIssuerMetadataV1_0_13,
CredentialRequest,
IssuerCredentialSubjectDisplay,
IssueStatus,
Jwt,
Expand All @@ -13,17 +15,18 @@ import {
ProofOfPossession,
} from '@sphereon/oid4vci-common'
import { CredentialOfferSession } from '@sphereon/oid4vci-common/dist'
import { CredentialSupportedBuilderV1_11, VcIssuer, VcIssuerBuilder } from '@sphereon/oid4vci-issuer'
import { CredentialSupportedBuilderV1_13, VcIssuer, VcIssuerBuilder } from '@sphereon/oid4vci-issuer'
import { MemoryStates } from '@sphereon/oid4vci-issuer'
import { CredentialDataSupplierResult } from '@sphereon/oid4vci-issuer/dist/types'
import { ICredential, IProofPurpose, IProofType, W3CVerifiableCredential } from '@sphereon/ssi-types'
import { DIDDocument } from 'did-resolver'
import * as jose from 'jose'
import { v4 } from 'uuid'

import { generateDid, getIssuerCallback, verifyCredential } from '../IssuerCallback'
import { generateDid, getIssuerCallbackV1_0_11, getIssuerCallbackV1_0_13, verifyCredential } from '../IssuerCallback'

const INITIATION_TEST_URI =
'openid-initiate-issuance://?credential_type=OpenBadgeCredential&issuer=https%3A%2F%2Fjff%2Ewalt%2Eid%2Fissuer-api%2Foidc%2F&pre-authorized_code=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhOTUyZjUxNi1jYWVmLTQ4YjMtODIxYy00OTRkYzgyNjljZjAiLCJwcmUtYXV0aG9yaXplZCI6dHJ1ZX0.YE5DlalcLC2ChGEg47CQDaN1gTxbaQqSclIVqsSAUHE&user_pin_required=false'
'openid-credential-offer://?credential_offer=%7B%22credential_issuer%22:%22https://credential-issuer.example.com%22,%22credential_configuration_ids%22:%5B%22UniversityDegreeCredential%22%5D,%22grants%22:%7B%22urn:ietf:params:oauth:grant-type:pre-authorized_code%22:%7B%22pre-authorized_code%22:%22oaKazRN8I0IbtZ0C7JuMn5%22,%22tx_code%22:%7B%22input_mode%22:%22text%22,%22description%22:%22Please%20enter%20the%20serial%20number%20of%20your%20physical%20drivers%20license%22%7D%7D%7D%7D'
const IDENTIPROOF_ISSUER_URL = 'https://example.com/credential'
const kid = 'did:example:ebfeb1f712ebc6f1c276e12ec21#keys-1'
let keypair: KeyPair // Proof of Possession JWT
Expand All @@ -37,7 +40,7 @@ async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promi
}
return await new jose.SignJWT({ ...args.payload })
.setProtectedHeader({ ...args.header })
.setIssuedAt(args.payload.iat ?? Math.round(+new Date()/1000))
.setIssuedAt(args.payload.iat ?? Math.round(+new Date() / 1000))
.setIssuer(kid)
.setAudience(args.payload.aud)
.setExpirationTime('2h')
Expand Down Expand Up @@ -85,12 +88,14 @@ describe('issuerCallback', () => {
const clientId = 'sphereon:wallet'

beforeAll(async () => {
const credentialsSupported: CredentialSupported = new CredentialSupportedBuilderV1_11()
.withCryptographicSuitesSupported('ES256K')
const credentialsSupported: Record<string, CredentialConfigurationSupportedV1_0_13> = new CredentialSupportedBuilderV1_13()
.withCredentialSigningAlgValuesSupported('ES256K')
.withCryptographicBindingMethod('did')
.withFormat('jwt_vc_json')
.withTypes('VerifiableCredential')
.withId('UniversityDegree_JWT')
.withCredentialName('UniversityDegree_JWT')
.withCredentialDefinition({
type: ['VerifiableCredential', 'UniversityDegree_JWT'],
})
.withCredentialSupportedDisplay({
name: 'University Credential',
locale: 'en-US',
Expand All @@ -113,6 +118,7 @@ describe('issuerCallback', () => {
createdAt: +new Date(),
lastUpdatedAt: +new Date(),
status: IssueStatus.OFFER_CREATED,
notification_id: v4(),
userPin: '123456',
credentialOffer: {
credential_offer: {
Expand Down Expand Up @@ -141,14 +147,14 @@ describe('issuerCallback', () => {
const nonces = new MemoryStates<CNonceState>()
await nonces.set('test_value', { cNonce: 'test_value', createdAt: +new Date(), issuerState: 'existing-state' })
vcIssuer = new VcIssuerBuilder<DIDDocument>()
.withAuthorizationServer('https://authorization-server')
.withAuthorizationServers('https://authorization-server')
.withCredentialEndpoint('https://credential-endpoint')
.withCredentialIssuer(IDENTIPROOF_ISSUER_URL)
.withIssuerDisplay({
name: 'example issuer',
locale: 'en-US',
})
.withCredentialsSupported(credentialsSupported)
.withCredentialConfigurationsSupported(credentialsSupported)
.withCredentialOfferStateManager(stateManager)
.withCNonceStateManager(nonces)
.withJWTVerifyCallback(verifyCallbackFunction)
Expand Down Expand Up @@ -192,7 +198,7 @@ describe('issuerCallback', () => {
credentialSubject: {},
issuanceDate: new Date().toISOString(),
}
const vc = await getIssuerCallback(credential, didKey.keyPairs, didKey.didDocument.verificationMethod[0].id)({})
const vc = await getIssuerCallbackV1_0_11(credential, didKey.keyPairs, didKey.didDocument.verificationMethod[0].id)({})
expect(vc).toEqual({
'@context': ['https://www.w3.org/2018/credentials/v1', 'https://w3id.org/security/suites/ed25519-2020/v1'],
credentialSubject: {},
Expand All @@ -211,11 +217,15 @@ describe('issuerCallback', () => {
expect.objectContaining({ verified: true }),
)
})

it('Should pass requesting a verifiable credential using the client', async () => {
const credReqClient = (await CredentialRequestClientBuilder.fromURI({ uri: INITIATION_TEST_URI }))
.withCredentialEndpoint('https://oidc4vci.demo.spruceid.com/credential')
.withCredentialEndpointFromMetadata({
credential_configurations_supported: { VeriCred: { format: 'jwt_vc_json' } },
} as unknown as CredentialIssuerMetadataV1_0_13)
.withFormat('jwt_vc_json')
.withCredentialType('credentialType')
.withCredentialIdentifier('VeriCred')
.withToken('token')

const jwt: Jwt = {
Expand All @@ -236,37 +246,39 @@ describe('issuerCallback', () => {
callbacks: {
signCallback: proofOfPossessionCallbackFunction,
},
version: OpenId4VCIVersion.VER_1_0_11,
version: OpenId4VCIVersion.VER_1_0_13,
})
.withClientId(clientId)
.withKid(kid)
.build()

const credentialRequestClient = new CredentialRequestClient(credReqClient)
const credentialRequest = await credentialRequestClient.createCredentialRequest({
credentialTypes: ['VerifiableCredential'],
format: 'jwt_vc_json',
const credentialRequest: CredentialRequest = await credentialRequestClient.createCredentialRequest({
credentialIdentifier: 'VerifiableCredential',
// format: 'jwt_vc_json',
proofInput: proof,
version: OpenId4VCIVersion.VER_1_0_11,
version: OpenId4VCIVersion.VER_1_0_13,
})

expect(credentialRequest).toEqual({
format: 'jwt_vc_json',
// format: 'jwt_vc_json',
proof: {
jwt: expect.stringContaining('eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFj'),
proof_type: 'jwt',
},
types: ['VerifiableCredential'],
credential_identifier: 'VerifiableCredential',
})

const credentialResponse = await vcIssuer.issueCredential({
credentialRequest: credentialRequest,
credential,
responseCNonce: state,
credentialSignerCallback: getIssuerCallback(credential, didKey.keyPairs, didKey.didDocument.verificationMethod[0].id),
credentialSignerCallback: getIssuerCallbackV1_0_13(credential, credentialRequest, didKey.keyPairs, didKey.didDocument.verificationMethod[0].id),
})

expect(credentialResponse).toEqual({
c_nonce: expect.any(String),
notification_id: expect.any(String),
c_nonce_expires_in: 300,
credential: {
'@context': ['https://www.w3.org/2018/credentials/v1', 'https://w3id.org/security/suites/ed25519-2020/v1'],
Expand All @@ -284,7 +296,7 @@ describe('issuerCallback', () => {
},
type: ['VerifiableCredential'],
},
format: 'jwt_vc_json',
// format: 'jwt_vc_json',
})

await expect(
Expand Down
2 changes: 1 addition & 1 deletion packages/callback-example/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { getIssuerCallback, generateDid, verifyCredential } from './IssuerCallback'
export { getIssuerCallbackV1_0_11, generateDid, verifyCredential } from './IssuerCallback'
2 changes: 1 addition & 1 deletion packages/callback-example/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sphereon/oid4vci-callback-example",
"version": "0.10.3",
"version": "0.11.0",
"description": "OpenID 4 Verifiable Credential Issuance issuer callback example",
"source": "lib/index.ts",
"main": "dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/client/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ Release with support for the pre-authorized code flow only!
- Documentation updates/fixes

- Fixes:
- The acquireCredential in the OpenID4VCIClient was not using the access token, resulting in auth issues.
- The acquireCredential in the OpenID4VCIClientV1_0_13 was not using the access token, resulting in auth issues.

## v0.3.1 - 2022-11-20

Expand Down
29 changes: 24 additions & 5 deletions packages/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ This initiates the client using a URI obtained from the Issuer using a link (URL
already fetching the Server Metadata

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

// The client is initiated from a URI. This URI is provided by the Issuer, typically as a URL or QR code.
const client = await OpenID4VCIClient.fromURI({
const client = await OpenID4VCIClientV1_0_13.fromURI({
uri: 'openid-initiate-issuance://?issuer=https%3A%2F%2Fissuer.research.identiproof.io&credential_type=OpenBadgeCredentialUrl&pre-authorized_code=4jLs9xZHEfqcoow0kHE7d1a8hUk6Sy-5bVSV2MqBUGUgiFFQi-ImL62T-FmLIo8hKA1UdMPH0lM1xAgcFkJfxIw9L-lI3mVs0hRT8YVwsEM1ma6N3wzuCdwtMU4bcwKp&user_pin_required=true',
kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#key-1', // Our DID. You can defer this also to when the acquireCredential method is called
alg: Alg.ES256, // The signing Algorithm we will use. You can defer this also to when the acquireCredential method is called
Expand All @@ -68,6 +68,25 @@ console.log(client.getCredentialEndpoint()); // https://issuer.research.identipr
console.log(client.getAccessTokenEndpoint()); // https://auth.research.identiproof.io/oauth2/token
```

Using https scheme

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

// The client is initiated from a URI. This URI is provided by the Issuer, typically as a URL or QR code.
const client = await OpenID4VCIClientV1_0_13.fromURI({
uri: 'https://launchpad.vii.electron.mattrlabs.io?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Flaunchpad.vii.electron.mattrlabs.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22ldp_vc%22%2C%22types%22%3A%5B%22OpenBadgeCredential%22%5D%7D%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22UPZohaodPlLBnGsqB02n2tIupCIg8nKRRUEUHWA665X%22%7D%7D%7D',
kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21#key-1', // Our DID. You can defer this also to when the acquireCredential method is called
alg: Alg.ES256, // The signing Algorithm we will use. You can defer this also to when the acquireCredential method is called
clientId: 'test-clientId', // The clientId if the Authrozation Service requires it. If a clientId is needed you can defer this also to when the acquireAccessToken method is called
retrieveServerMetadata: true, // Already retrieve the server metadata. Can also be done afterwards by invoking a method yourself.
});

console.log(client.getIssuer()); // https://launchpad.vii.electron.mattrlabs.io
console.log(client.getCredentialEndpoint()); // https://launchpad.vii.electron.mattrlabs.io/credential
console.log(client.getAccessTokenEndpoint()); // https://launchpad.vii.electron.mattrlabs.io/oauth2/token
```

## Server metadata

The OID4VCI Server metadata contains information about token endpoints, credential endpoints, as well as additional
Expand Down Expand Up @@ -187,15 +206,15 @@ The OpenID4VCI spec defines a server metadata object that contains information a
support. Next to this predefined endpoint there are also the well-known locations for OpenID Connect Discovery
configuration and
Oauth2 Authorization Server configuration. These contain for instance the token endpoints.
The MetadataClient checks the OpenID4VCI well-known location for the medata and existence of a token endpoint. If the
The MetadataClientV1_0_13 checks the OpenID4VCI well-known location for the medata and existence of a token endpoint. If the
OpenID4VCI well-known location is not found, the OIDC/OAuth2 well-known locations will be tried:

Example:

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

const metadata = await MetadataClient.retrieveAllMetadataFromCredentialOffer(initiationRequestWithUrl);
const metadata = await MetadataClientV1_0_13.retrieveAllMetadataFromCredentialOffer(initiationRequestWithUrl);

console.log(metadata);
/**
Expand Down
Loading

0 comments on commit b7a0808

Please sign in to comment.