Skip to content

Commit

Permalink
feat: Open the signing algorithm list in the credential issuance proc…
Browse files Browse the repository at this point in the history
…ess, refs #88

Signed-off-by: Vincent Kelleher <vincent.kelleher@gaia-x.eu>
  • Loading branch information
Vincent Kelleher committed Feb 21, 2024
1 parent 6e76f57 commit d9b17af
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 43 deletions.
4 changes: 2 additions & 2 deletions packages/common/lib/types/OpenID4VCIErrors.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Alg } from './CredentialIssuance.types';
import { Alg } from './CredentialIssuance.types'

export const BAD_PARAMS = 'Wrong parameters provided';
export const URL_NOT_VALID = 'Request url is not valid';
export const JWS_NOT_VALID = 'JWS is not valid';
export const PROOF_CANT_BE_CONSTRUCTED = "Proof can't be constructed.";
export const NO_JWT_PROVIDED = 'No JWT provided';
export const TYP_ERROR = 'Typ must be "openid4vci-proof+jwt"';
export const ALG_ERROR = `Algorithm is a required field and must be one of: ${Object.keys(Alg).join(', ')}`;
export const ALG_ERROR = `Algorithm is a required field, you are free to use the signing algorithm of your choice or one of the following: ${Object.keys(Alg).join(', ')}`;
export const KID_JWK_X5C_ERROR = 'Only one must be present: kid, jwk or x5c';
export const KID_DID_NO_DID_ERROR = 'A DID value needs to be returned when kid is present';
export const DID_NO_DIDDOC_ERROR = 'A DID Document needs to be resolved when a DID is encountered';
Expand Down
12 changes: 8 additions & 4 deletions packages/issuer/lib/VcIssuer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
Alg,
ALG_ERROR,
AUD_ERROR,
CNonceState,
Expand Down Expand Up @@ -33,14 +32,19 @@ import {
toUniformCredentialOfferRequest,
TYP_ERROR,
UniformCredentialRequest,
URIState,
URIState
} from '@sphereon/oid4vci-common'
import { CompactSdJwtVc, CredentialMapper, W3CVerifiableCredential } from '@sphereon/ssi-types'
import { v4 } from 'uuid'

import { assertValidPinNumber, createCredentialOfferObject, createCredentialOfferURIFromObject } from './functions'
import { LookupStateManager } from './state-manager'
import { CredentialDataSupplier, CredentialDataSupplierArgs, CredentialIssuanceInput, CredentialSignerCallback } from './types'
import {
CredentialDataSupplier,
CredentialDataSupplierArgs,
CredentialIssuanceInput,
CredentialSignerCallback
} from './types'

const SECOND = 1000

Expand Down Expand Up @@ -463,7 +467,7 @@ export class VcIssuer<DIDDoc extends object> {

if (typ !== 'openid4vci-proof+jwt') {
throw Error(TYP_ERROR)
} else if (!alg || !(alg in Alg)) {
} else if (!alg) {
throw Error(ALG_ERROR)
} else if (!([kid, jwk, x5c].filter((x) => !!x).length === 1)) {
// only 1 is allowed, but need to look into whether jwk and x5c are allowed together
Expand Down
184 changes: 147 additions & 37 deletions packages/issuer/lib/__tests__/VcIssuer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { OpenID4VCIClient } from '@sphereon/oid4vci-client'
import {
Alg,
ALG_ERROR,
CredentialOfferSession,
CredentialSupported,
IssuerCredentialSubjectDisplay,
IssueStatus,
STATE_MISSING_ERROR,
STATE_MISSING_ERROR
} from '@sphereon/oid4vci-common'
import { IProofPurpose, IProofType } from '@sphereon/ssi-types'
import { DIDDocument } from 'did-resolver'
Expand All @@ -16,13 +17,36 @@ import { MemoryStates } from '../state-manager'

const IDENTIPROOF_ISSUER_URL = 'https://issuer.research.identiproof.io'

const verifiableCredential = {
'@context': [
'https://www.w3.org/2018/credentials/v1',
'https://w3id.org/security/suites/jws-2020/v1'
],
id: 'http://university.example/credentials/1872',
type: [
'VerifiableCredential',
'ExampleAlumniCredential'
],
issuer: 'https://university.example/issuers/565049',
issuanceDate: new Date().toISOString(),
credentialSubject: {
id: 'did:example:ebfeb1f712ebc6f1c276e12ec21',
alumniOf: {
id: 'did:example:c276e12ec21ebfeb1f712ebc6f1',
name: 'Example University'
}
}
}

describe('VcIssuer', () => {
let vcIssuer: VcIssuer<DIDDocument>
const issuerState = 'previously-created-state'
const clientId = 'sphereon:wallet'
const preAuthorizedCode = 'test_code'

beforeAll(async () => {
const jwtVerifyCallback: jest.Mock = jest.fn()

beforeEach(async () => {
jest.clearAllMocks()
const credentialsSupported: CredentialSupported = new CredentialSupportedBuilderV1_11()
.withCryptographicSuitesSupported('ES256K')
Expand Down Expand Up @@ -105,29 +129,7 @@ describe('VcIssuer', () => {
},
}),
)
.withJWTVerifyCallback(() =>
Promise.resolve({
did: 'did:example:1234',
kid: 'did:example:1234#auth',
alg: 'ES256k',
didDocument: {
'@context': 'https://www.w3.org/ns/did/v1',
id: 'did:example:1234',
},
jwt: {
header: {
typ: 'openid4vci-proof+jwt',
alg: Alg.ES256K,
kid: 'test-kid',
},
payload: {
aud: 'https://credential-issuer',
iat: +new Date(),
nonce: 'test-nonce',
},
},
}),
)
.withJWTVerifyCallback(jwtVerifyCallback)
.build()
})

Expand Down Expand Up @@ -272,6 +274,29 @@ describe('VcIssuer', () => {

// Of course this doesn't work. The state is part of the proof to begin with
it('should fail issuing credential if an invalid state is used', async () => {
jwtVerifyCallback.mockResolvedValue({
did: 'did:example:1234',
kid: 'did:example:1234#auth',
alg: Alg.ES256K,
didDocument: {
'@context': 'https://www.w3.org/ns/did/v1',
id: 'did:example:1234',
},
jwt: {
header: {
typ: 'openid4vci-proof+jwt',
alg: Alg.ES256K,
kid: 'test-kid',
},
payload: {
aud: IDENTIPROOF_ISSUER_URL,
iat: +new Date(),
nonce: 'test-nonce',
},
}
}
)

await expect(
vcIssuer.issueCredential({
credentialRequest: {
Expand All @@ -287,23 +312,65 @@ describe('VcIssuer', () => {
).rejects.toThrow(Error(STATE_MISSING_ERROR + ' (test-nonce)'))
})

// Of course this doesn't work. The state is part of the proof to begin with
xit('should issue credential if a valid state is passed in', async () => {
await expect(
it.each([...Object.values<string>(Alg), 'CUSTOM'])('should issue %s signed credential if a valid state is passed in', async (alg: string) => {
jwtVerifyCallback.mockResolvedValue({
did: 'did:example:1234',
kid: 'did:example:1234#auth',
alg: alg,
didDocument: {
'@context': 'https://www.w3.org/ns/did/v1',
id: 'did:example:1234',
},
jwt: {
header: {
typ: 'openid4vci-proof+jwt',
alg: alg,
kid: 'test-kid',
},
payload: {
aud: IDENTIPROOF_ISSUER_URL,
iat: +new Date(),
nonce: 'test-nonce',
},
}
}
)

let createdAt = +new Date()
await vcIssuer.cNonces.set('test-nonce', {
cNonce: 'test-nonce',
preAuthorizedCode: 'test-pre-authorized-code',
createdAt: createdAt
})
await vcIssuer.credentialOfferSessions.set('test-pre-authorized-code', {
createdAt: createdAt,
preAuthorizedCode: 'test-pre-authorized-code',
credentialOffer: {
credential_offer: {
credential_issuer: 'did:key:test',
credentials: []
}
},
lastUpdatedAt: createdAt,
status: IssueStatus.ACCESS_TOKEN_CREATED
})

expect(
vcIssuer.issueCredential({
credential: verifiableCredential,
credentialRequest: {
types: ['VerifiableCredential'],
format: 'jwt_vc_json',
proof: {
proof_type: 'jwt',
jwt: 'ye.ye.ye',
},
jwt: 'ye.ye.ye'
}
},
// issuerState,
}),
newCNonce: 'new-test-nonce'
})
).resolves.toEqual({
c_nonce: expect.any(String),
c_nonce_expires_in: 90000,
c_nonce: 'new-test-nonce',
c_nonce_expires_in: 300000,
credential: {
'@context': ['https://www.w3.org/2018/credentials/v1'],
credentialSubject: {},
Expand All @@ -314,11 +381,54 @@ describe('VcIssuer', () => {
jwt: 'ye.ye.ye',
proofPurpose: 'assertionMethod',
type: 'JwtProof2020',
verificationMethod: 'sdfsdfasdfasdfasdfasdfassdfasdf',
verificationMethod: 'sdfsdfasdfasdfasdfasdfassdfasdf'
},
type: ['VerifiableCredential'],
type: ['VerifiableCredential']
},
format: 'jwt_vc_json',
format: 'jwt_vc_json'
})
})

it('should fail issuing credential if the signing algorithm is missing', async () => {
let createdAt = +new Date()
await vcIssuer.cNonces.set('test-nonce', {
cNonce: 'test-nonce',
preAuthorizedCode: 'test-pre-authorized-code',
createdAt: createdAt
})

jwtVerifyCallback.mockResolvedValue({
did: 'did:example:1234',
kid: 'did:example:1234#auth',
alg: undefined,
didDocument: {
'@context': 'https://www.w3.org/ns/did/v1',
id: 'did:example:1234',
},
jwt: {
header: {
typ: 'openid4vci-proof+jwt',
alg: undefined,
kid: 'test-kid',
},
payload: {
aud: IDENTIPROOF_ISSUER_URL,
iat: +new Date(),
nonce: 'test-nonce',
},
}
}
)

expect(vcIssuer.issueCredential({
credentialRequest: {
types: ['VerifiableCredential'],
format: 'jwt_vc_json',
proof: {
proof_type: 'jwt',
jwt: 'ye.ye.ye',
},
},
})).rejects.toThrow(Error(ALG_ERROR))
})
})

0 comments on commit d9b17af

Please sign in to comment.