Skip to content

Commit 2109d9a

Browse files
committed
Merge remote-tracking branch 'origin/develop' into develop
2 parents f044336 + 3502496 commit 2109d9a

7 files changed

Lines changed: 88 additions & 15 deletions

File tree

packages/client/lib/CredentialRequestClient.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,12 +267,11 @@ export class CredentialRequestClient {
267267
}
268268
response.access_token = requestToken
269269

270-
/* TODO SSISDK-85
271-
if ((uniformRequest.credential_subject_issuance && response.successBody) || response.successBody?.credential_subject_issuance) {
270+
if ((uniformRequest.credential_subject_issuance && response.successBody) || response.successBody?.credential_subject_issuance) {
272271
if (JSON.stringify(uniformRequest.credential_subject_issuance) !== JSON.stringify(response.successBody?.credential_subject_issuance)) {
273272
throw Error('Subject signing was requested, but issuer did not provide the options in its response')
274273
}
275-
}*/
274+
}
276275
logger.debug(`Credential endpoint ${credentialEndpoint} response:\r\n${JSON.stringify(response, null, 2)}`)
277276

278277
return {

packages/client/lib/MetadataClientV1_0_15.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export class MetadataClientV1_0_15 {
5555
let credential_endpoint: string | undefined
5656
let nonce_endpoint: string | undefined
5757
let deferred_credential_endpoint: string | undefined
58+
let notification_endpoint: string | undefined
5859
let authorization_endpoint: string | undefined
5960
let authorization_challenge_endpoint: string | undefined
6061
let authorizationServerType: AuthorizationServerType = 'OID4VCI'
@@ -66,6 +67,7 @@ export class MetadataClientV1_0_15 {
6667
credential_endpoint = credentialIssuerMetadata.credential_endpoint
6768
nonce_endpoint = credentialIssuerMetadata.nonce_endpoint
6869
deferred_credential_endpoint = credentialIssuerMetadata.deferred_credential_endpoint
70+
notification_endpoint = credentialIssuerMetadata.notification_endpoint
6971
if (credentialIssuerMetadata.token_endpoint) {
7072
token_endpoint = credentialIssuerMetadata.token_endpoint
7173
}
@@ -140,6 +142,15 @@ export class MetadataClientV1_0_15 {
140142
deferred_credential_endpoint = authMetadata.deferred_credential_endpoint
141143
}
142144
}
145+
if (authMetadata.notification_endpoint) {
146+
if (notification_endpoint && authMetadata.notification_endpoint !== notification_endpoint) {
147+
logger.debug(
148+
`Credential issuer has a different notification_endpoint (${notification_endpoint}) from the Authorization Server (${authMetadata.notification_endpoint}). Will use the issuer value`,
149+
)
150+
} else {
151+
notification_endpoint = authMetadata.notification_endpoint
152+
}
153+
}
143154
}
144155

145156
if (!authorization_endpoint) {
@@ -182,6 +193,7 @@ export class MetadataClientV1_0_15 {
182193
display: ci.display ?? [],
183194
...(nonce_endpoint && { nonce_endpoint }),
184195
...(deferred_credential_endpoint && { deferred_credential_endpoint }),
196+
...(notification_endpoint && { notification_endpoint }),
185197
}
186198

187199
logger.debug(`Issuer ${issuer} token endpoint ${token_endpoint}, credential endpoint ${credential_endpoint}`)
@@ -192,6 +204,7 @@ export class MetadataClientV1_0_15 {
192204
token_endpoint,
193205
credential_endpoint,
194206
authorization_challenge_endpoint,
207+
notification_endpoint,
195208
authorizationServerType,
196209
credentialIssuerMetadata: v15CredentialIssuerMetadata,
197210
authorizationServerMetadata: authMetadata,

packages/issuer/lib/VcIssuer.ts

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
OID4VCICredentialFormat,
3535
OpenId4VCIVersion,
3636
PRE_AUTH_GRANT_LITERAL,
37+
ProofOfPossession,
3738
QRCodeOpts,
3839
StatusListOpts,
3940
TokenErrorResponse,
@@ -54,7 +55,7 @@ import { LOG } from './index'
5455
const shortUUID = ShortUUID()
5556

5657
export class VcIssuer {
57-
private readonly _issuerMetadata: CredentialIssuerMetadataOptsV1_0_15
58+
private _issuerMetadata: CredentialIssuerMetadataOptsV1_0_15 // TODO SSISDK-87 create proper solution to update issuer metadata
5859
private readonly _authorizationServerMetadata: AuthorizationServerMetadata
5960
private readonly _defaultCredentialOfferBaseUri?: string
6061
private readonly _credentialSignerCallback?: CredentialSignerCallback
@@ -675,16 +676,70 @@ export class VcIssuer {
675676
try {
676677
if (format && !supportedIssuanceFormats.includes(format)) {
677678
throw Error(`Format ${format} not supported yet`)
678-
} else if (typeof this._jwtVerifyCallback !== 'function' && typeof jwtVerifyCallback !== 'function') {
679-
throw new Error(JWT_VERIFY_CONFIG_ERROR)
680-
} else if (!credentialRequest.proof) {
681-
throw Error('Proof of possession is required. No proof value present in credential request')
682679
}
683680

684-
const jwtVerifyResult = jwtVerifyCallback
685-
? await jwtVerifyCallback(credentialRequest.proof)
686-
: // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
687-
await this._jwtVerifyCallback!(credentialRequest.proof)
681+
const verifyFn: JWTVerifyCallback | undefined = jwtVerifyCallback ?? this._jwtVerifyCallback
682+
if (typeof verifyFn !== 'function') {
683+
throw Error(JWT_VERIFY_CONFIG_ERROR)
684+
}
685+
686+
const credReq = credentialRequest as CredentialRequestV1_0_15
687+
688+
// Validate request structure (mutually exclusive)
689+
if (credReq.proof && credReq.proofs) {
690+
throw Error('Credential request may not contain both proof and proofs parameters')
691+
}
692+
693+
// Normalize candidates into a single array of ProofOfPossession objects
694+
const proofCandidates: Array<ProofOfPossession> = []
695+
696+
if (credReq.proof) {
697+
proofCandidates.push(credReq.proof)
698+
} else if (credReq.proofs) {
699+
// Handle "proofs": prioritize 'jwt' as it's the only fully supported type
700+
if (Array.isArray(credReq.proofs.jwt)) {
701+
// Map to ProofOfPossession objects, handling both string and object formats
702+
for (const jwtProof of credReq.proofs.jwt) {
703+
if (typeof jwtProof === 'string') {
704+
// Handle case where jwt array contains strings instead of ProofOfPossession objects
705+
proofCandidates.push({
706+
proof_type: 'jwt',
707+
jwt: jwtProof,
708+
})
709+
} else if (jwtProof && typeof jwtProof === 'object' && 'jwt' in jwtProof) {
710+
// Handle proper ProofOfPossession object
711+
proofCandidates.push(jwtProof)
712+
}
713+
}
714+
}
715+
716+
// Check if there are no supported proofs found
717+
if (proofCandidates.length === 0) {
718+
const availableTypes = Object.keys(credReq.proofs).join(', ')
719+
throw Error(`No supported proof types found in request. Available: [${availableTypes}]`)
720+
}
721+
} else {
722+
throw Error('Proof of possession is required. No proof or proofs value present in credential request')
723+
}
724+
725+
// Execute verification
726+
let jwtVerifyResult: JwtVerifyResult | undefined
727+
const validationErrors: string[] = []
728+
729+
for (const proof of proofCandidates) {
730+
try {
731+
jwtVerifyResult = await verifyFn({ jwt: proof.jwt })
732+
break
733+
} catch (error) {
734+
const msg = error instanceof Error ? error.message : String(error)
735+
validationErrors.push(msg)
736+
}
737+
}
738+
739+
// Check success
740+
if (!jwtVerifyResult) {
741+
throw Error(`Unable to verify any provided proofs. Errors: ${validationErrors.join('; ')}`)
742+
}
688743

689744
const { didDocument, did, jwt } = jwtVerifyResult
690745
const { header, payload } = jwt
@@ -863,6 +918,11 @@ export class VcIssuer {
863918
return this._issuerMetadata
864919
}
865920

921+
// TODO SSISDK-87 create proper solution to update issuer metadata
922+
public set issuerMetadata(value: CredentialIssuerMetadataOptsV1_0_15) {
923+
this._issuerMetadata = value;
924+
}
925+
866926
public get authorizationServerMetadata() {
867927
return this._authorizationServerMetadata
868928
}

packages/issuer/lib/builder/DisplayBuilder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class DisplayBuilder {
2121

2222
withLogo(logo: ImageInfo) {
2323
if (logo) {
24-
if (!logo.url) {
24+
if (!logo.uri) {
2525
throw Error(`logo without url will not work`)
2626
}
2727
}

packages/oid4vci-common/lib/types/Generic.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export type CredentialOfferMode = 'VALUE' | 'REFERENCE'
2323
* Important Note: please be aware that these Common interfaces are based on versions v1_0.11 and v1_0.09
2424
*/
2525
export interface ImageInfo {
26-
url?: string
26+
uri?: string
2727
alt_text?: string
2828

2929
[key: string]: unknown

packages/oid4vci-common/lib/types/ServerMetadata.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ export interface EndpointMetadata {
148148
token_endpoint: string
149149
credential_endpoint: string
150150
deferred_credential_endpoint?: string
151+
notification_endpoint?: string
151152
authorization_server?: string
152153
authorization_endpoint?: string // Can be undefined in pre-auth flow
153154
authorization_challenge_endpoint?: string

packages/oid4vci-common/lib/types/v1_0_15.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export type CredentialConfigurationSupportedCommonV1_0_15 = {
7373
}
7474

7575
export interface CredentialConfigurationSupportedSdJwtVcV1_0_15 extends CredentialConfigurationSupportedCommonV1_0_15 {
76-
format: 'dc+sd-jwt' // REQUIRED. Updated format identifier for SD-JWT VC to align with the media type in draft -06 of [I-D.ietf-oauth-sd-jwt-vc]
76+
format: 'dc+sd-jwt' | 'vc+sd-jwt' // REQUIRED. Updated format identifier for SD-JWT VC to align with the media type in draft -06 of [I-D.ietf-oauth-sd-jwt-vc]
7777
vct: string // REQUIRED. String designating the type of a Credential, as defined in [I-D.ietf-oauth-sd-jwt-vc].
7878
claims?: ClaimsDescriptionV1_0_15[] // OPTIONAL. Array of claims description objects using claims path pointers as defined in Appendix C.
7979
order?: string[] // OPTIONAL. An array of claims.display.name values that lists them in the order they should be displayed by the Wallet.

0 commit comments

Comments
 (0)