From 222631e8530fea060b0acb3073d834a6f2616e23 Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Thu, 22 Jan 2026 18:00:12 +0530 Subject: [PATCH 1/6] WIP:issuance changes Signed-off-by: shitrerohit --- .../dtos/oid4vc-issuer-template.dto.ts | 4 +- apps/oid4vc-issuance/constant/issuance.ts | 5 +- .../interfaces/oid4vc-issuance.interfaces.ts | 8 +- .../libs/helpers/issuer.metadata.ts | 152 ++++++++++++------ .../src/oid4vc-issuance.service.ts | 10 +- apps/x509/src/interfaces/x509.interface.ts | 1 + apps/x509/src/repositories/x509.repository.ts | 3 +- apps/x509/src/x509.service.ts | 22 +-- libs/enum/src/enum.ts | 6 +- .../migration.sql | 2 + libs/prisma-service/prisma/schema.prisma | 1 + 11 files changed, 146 insertions(+), 68 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20260119085820_added_key_id_in_x509_certificates_table/migration.sql diff --git a/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts b/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts index d3231f10f..7f660d254 100644 --- a/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts +++ b/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts @@ -172,7 +172,7 @@ export class MdocNamespaceDto { export class MdocTemplateDto { @ApiProperty({ description: 'Document type (required when format is "mso_mdoc"; must NOT be provided when format is "vc+sd-jwt")', - example: 'org.iso.23220.photoID.1' + example: 'org.iso.18013.5.1.mDL' }) //@ValidateIf((o: CreateCredentialTemplateDto) => 'mso_mdoc' === o.format) @IsString() @@ -230,7 +230,7 @@ export class CreateCredentialTemplateDto { format: CredentialFormat; @ValidateIf((o: CreateCredentialTemplateDto) => CredentialFormat.SdJwtVc === o.format) - @IsEmpty({ message: 'doctype must not be provided when format is "vc+sd-jwt"' }) + @IsEmpty({ message: 'doctype must not be provided when format is "dc+sd-jwt"' }) readonly _doctypeAbsentGuard?: unknown; @ValidateIf((o: CreateCredentialTemplateDto) => CredentialFormat.Mdoc === o.format) diff --git a/apps/oid4vc-issuance/constant/issuance.ts b/apps/oid4vc-issuance/constant/issuance.ts index d32498997..c1adf5aa2 100644 --- a/apps/oid4vc-issuance/constant/issuance.ts +++ b/apps/oid4vc-issuance/constant/issuance.ts @@ -2,5 +2,8 @@ import { AccessTokenSignerKeyType } from '../interfaces/oid4vc-issuance.interfac export const dpopSigningAlgValuesSupported = ['RS256', 'ES256', 'EdDSA']; export const credentialConfigurationsSupported = {}; -export const accessTokenSignerKeyType = 'ed25519' as AccessTokenSignerKeyType; +export const accessTokenSignerKeyType = { kty: 'OKP', crv: 'Ed25519' } as { + kty: string; + crv: AccessTokenSignerKeyType; +}; export const batchCredentialIssuanceDefault = 0; diff --git a/apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces.ts b/apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces.ts index e3ba6413e..be9613ed6 100644 --- a/apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces.ts +++ b/apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces.ts @@ -36,7 +36,7 @@ export interface CredentialConfiguration { doctype?: string; scope: string; claims: Claim[]; - credential_signing_alg_values_supported: string[]; + credential_signing_alg_values_supported: string[] | number[]; cryptographic_binding_methods_supported: string[]; display: Display[]; } @@ -54,7 +54,7 @@ export interface AuthorizationServerConfig { export interface IssuerCreation { authorizationServerUrl: string; issuerId: string; - accessTokenSignerKeyType?: AccessTokenSignerKeyType; + accessTokenSignerKeyType?: { kty: string; crv: string }; display: Display[]; dpopSigningAlgValuesSupported?: string[]; authorizationServerConfigs: AuthorizationServerConfig; @@ -67,7 +67,7 @@ export interface IssuerInitialConfig { display: Display[] | {}; // eslint-disable-next-line @typescript-eslint/ban-types authorizationServerConfigs: AuthorizationServerConfig | {}; - accessTokenSignerKeyType: AccessTokenSignerKeyType; + accessTokenSignerKeyType: { kty: string; crv: string }; dpopSigningAlgValuesSupported: string[]; batchCredentialIssuance?: object; credentialConfigurationsSupported: object; @@ -87,7 +87,7 @@ export interface initialIssuerDetails { } export enum AccessTokenSignerKeyType { - ED25519 = 'ed25519' + ED25519 = 'Ed25519' } export interface IssuerUpdation { diff --git a/apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts b/apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts index 96f5282c0..b2b17f9b8 100644 --- a/apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts +++ b/apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts @@ -3,12 +3,7 @@ import { oidc_issuer, Prisma } from '@prisma/client'; import { batchCredentialIssuanceDefault } from '../../constant/issuance'; import { CreateOidcCredentialOffer } from '../../interfaces/oid4vc-issuer-sessions.interfaces'; import { IssuerResponse } from 'apps/oid4vc-issuance/interfaces/oid4vc-issuance.interfaces'; -import { - Claim, - CredentialAttribute, - MdocTemplate, - SdJwtTemplate -} from 'apps/oid4vc-issuance/interfaces/oid4vc-template.interfaces'; +import { Claim, MdocTemplate, SdJwtTemplate } from 'apps/oid4vc-issuance/interfaces/oid4vc-template.interfaces'; import { CredentialFormat } from '@credebl/enum/enum'; type AttributeDisplay = { name: string; locale: string }; @@ -44,10 +39,14 @@ type CredentialConfig = { vct?: string; scope: string; doctype?: string; - claims: Claim[]; - credential_signing_alg_values_supported: string[]; + + credential_signing_alg_values_supported: string[] | number[]; cryptographic_binding_methods_supported: string[]; - display: { name: string; description?: string; locale?: string }[]; + + credential_metadata: { + claims: Claim[]; + display: CredentialDisplayItem[]; + }; }; type CredentialConfigurationsSupported = { @@ -55,8 +54,9 @@ type CredentialConfigurationsSupported = { }; // ---- Static Lists (as requested) ---- -const STATIC_CREDENTIAL_ALGS = ['ES256', 'EdDSA'] as const; -const STATIC_BINDING_METHODS = ['did:key'] as const; +const STATIC_CREDENTIAL_ALGS_FOR_SDJWT = ['ES256', 'EdDSA'] as const; +const STATIC_CREDENTIAL_ALGS_FOR_MDOC = [-7, -9] as const; +const STATIC_BINDING_METHODS = ['did:key', 'did:web', 'did:jwk', 'jwk'] as const; // Safe coercion helpers function coerceJsonObject(v: Prisma.JsonValue): T | null { @@ -243,35 +243,93 @@ export function encodeIssuerPublicId(publicIssuerId: string): string { /** * Recursively builds a nested claims object from a list of attributes. */ -function buildNestedClaims(attributes: CredentialAttribute[]): Record { - const claims: Record = {}; +// function buildNestedClaims(attributes: CredentialAttribute[]): Record { +// const claims: Record = {}; + +// for (const attr of attributes) { +// const node: Claim = {}; + +// // ✅ include display info +// if (attr.display?.length) { +// node.display = attr.display.map((d) => ({ +// name: d.name, +// locale: d.locale +// })); +// } + +// // ✅ include mandatory flag +// if (attr.mandatory) { +// node.mandatory = true; +// } + +// // ✅ handle nested children recursively +// if (attr.children?.length) { +// const childClaims = buildNestedClaims(attr.children); +// Object.assign(node, childClaims); // merge children into current node +// } + +// claims[attr.key] = node; +// } + +// return claims; +// } +// function generateObjects( +// namespace: string, +// attributes: any[], +// parentPath: string[] = [] +// ): any[] { +// const result: any[] = []; + +// for (const attr of attributes) { +// const currentPath = [...parentPath, attr.key]; + +// // If display exists, create object +// if (attr.display) { +// result.push({ +// path: [namespace, ...currentPath], +// display: attr.display +// }); +// } + +// // Recurse into children +// if (attr.children?.length) { +// result.push( +// ...generateObjects(namespace, attr.children, currentPath) +// ); +// } +// } +// console.log('generateObjects - result', JSON.stringify(result), '\n\n\n'); +// return result; +// } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function generateClaims(attributes: any[], namespace?: string, parentPath: string[] = []): Claim[] { + const result: Claim[] = []; for (const attr of attributes) { - const node: Claim = {}; - - // ✅ include display info - if (attr.display?.length) { - node.display = attr.display.map((d) => ({ - name: d.name, - locale: d.locale - })); + const currentPath = [...parentPath, attr.key]; + + const path = namespace ? [namespace, ...currentPath] : currentPath; + + const claim: Claim = { path }; + + if (attr.display) { + claim.display = attr.display; } - // ✅ include mandatory flag - if (attr.mandatory) { - node.mandatory = true; + if (true === attr.mandatory) { + claim.mandatory = true; } - // ✅ handle nested children recursively + // Always push the claim (even if it only has path) + result.push(claim); + + // Handle nested children if (attr.children?.length) { - const childClaims = buildNestedClaims(attr.children); - Object.assign(node, childClaims); // merge children into current node + result.push(...generateClaims(attr.children, namespace, currentPath)); } - - claims[attr.key] = node; } - return claims; + return result; } /** @@ -279,25 +337,24 @@ function buildNestedClaims(attributes: CredentialAttribute[]): Record { +function buildClaimsFromTemplate(template: SdJwtTemplate | MdocTemplate): Record | Claim[] { // ✅ MDOC case — handle namespaces if ((template as MdocTemplate).namespaces) { const mdocTemplate = template as MdocTemplate; - //TODO: Remove any type // eslint-disable-next-line @typescript-eslint/no-explicit-any - const claims: Record = {}; - + const claims: Claim[] = []; for (const ns of mdocTemplate.namespaces) { - claims[ns.namespace] = buildNestedClaims(ns.attributes); - } + const generated = generateClaims(ns.attributes, ns.namespace); + claims.push(...generated); + } return claims; } - // ✅ SD-JWT case — flat attributes const sdjwtTemplate = template as SdJwtTemplate; - return buildNestedClaims(sdjwtTemplate.attributes); + const claims = generateClaims(sdjwtTemplate.attributes); + return claims; } //TODO: Fix this eslint issue @@ -316,14 +373,17 @@ export function buildSdJwtCredentialConfig(name: string, template: SdJwtTemplate format: CredentialFormat.SdJwtVc, scope: credentialScope, vct: template.vct, - credential_signing_alg_values_supported: [...STATIC_CREDENTIAL_ALGS], + credential_signing_alg_values_supported: [...STATIC_CREDENTIAL_ALGS_FOR_SDJWT], cryptographic_binding_methods_supported: [...STATIC_BINDING_METHODS], // proof_types_supported: { // jwt: { // proof_signing_alg_values_supported: ['ES256'] // } // }, - claims + credential_metadata: { + claims, + display: [] + } } }; } @@ -339,7 +399,6 @@ export function buildMdocCredentialConfig(name: string, template: MdocTemplate) const credentialScope = `openid4vc:${template.doctype}-${formatSuffix}`; const claims = buildClaimsFromTemplate(template); - // for (const ns of template.namespaces) { // claims.push(...buildClaimsFromAttributes(ns.attributes, [ns.namespace])); // } @@ -349,9 +408,12 @@ export function buildMdocCredentialConfig(name: string, template: MdocTemplate) format: CredentialFormat.Mdoc, scope: credentialScope, doctype: template.doctype, - credential_signing_alg_values_supported: [...STATIC_CREDENTIAL_ALGS], + credential_signing_alg_values_supported: [...STATIC_CREDENTIAL_ALGS_FOR_MDOC], cryptographic_binding_methods_supported: [...STATIC_BINDING_METHODS], - claims + credential_metadata: { + claims, + display: [] + } } }; } @@ -387,7 +449,6 @@ export function buildCredentialConfigurationsSupported(templateRows: any): Recor templateToBuild, format === CredentialFormat.Mdoc ? CredentialFormat.Mdoc : CredentialFormat.SdJwtVc ); - const appearanceJson = coerceJsonObject(templateRow.appearance); // Prepare the display configuration @@ -406,12 +467,11 @@ export function buildCredentialConfigurationsSupported(templateRows: any): Recor // eslint-disable-next-line prefer-destructuring const dynamicKey = Object.keys(credentialConfig)[0]; - Object.assign(credentialConfig[dynamicKey], { + Object.assign(credentialConfig[dynamicKey].credential_metadata, { display: displayConfigurations }); Object.assign(credentialConfigMap, credentialConfig); } - return credentialConfigMap; // ✅ Return flat map, not nested object } diff --git a/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts b/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts index bc78d9620..0650fedaa 100644 --- a/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts +++ b/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts @@ -298,6 +298,7 @@ export class Oid4vcIssuanceService { const { name, description, format, canBeRevoked, appearance, signerOption } = credentialTemplate; const checkNameExist = await this.oid4vcIssuanceRepository.getTemplateByNameForIssuer(name, issuerId); + console.log('checkNameExist', checkNameExist); if (0 < checkNameExist.length) { throw new ConflictException(ResponseMessages.oidcTemplate.error.templateNameAlreadyExist); } @@ -313,6 +314,7 @@ export class Oid4vcIssuanceService { }; // Persist in DB const createdTemplate = await this.oid4vcIssuanceRepository.createTemplate(issuerId, metadata); + console.log('createdTemplate', createdTemplate); if (!createdTemplate) { throw new InternalServerErrorException(ResponseMessages.oidcTemplate.error.createFailed); } @@ -321,15 +323,19 @@ export class Oid4vcIssuanceService { try { const issuerTemplateConfig = await this.buildOidcIssuerConfig(issuerId); const agentDetails = await this.oid4vcIssuanceRepository.getAgentEndPoint(orgId); + console.log('agentDetails', agentDetails); if (!agentDetails) { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); } const { agentEndPoint } = agentDetails; const issuerDetails = await this.oid4vcIssuanceRepository.getOidcIssuerDetailsById(issuerId); + console.log('issuerDetails', issuerDetails); if (!issuerDetails) { throw new NotFoundException(ResponseMessages.oidcTemplate.error.issuerDetailsNotFound); } const url = getAgentUrl(agentEndPoint, CommonConstants.OIDC_ISSUER_TEMPLATE, issuerDetails.publicIssuerId); + console.log('url', url); + console.log('issuerTemplateConfig', JSON.stringify(issuerTemplateConfig)); createTemplateOnAgent = await this._createOIDCTemplate(issuerTemplateConfig, url, orgId); } catch (agentError) { try { @@ -751,10 +757,12 @@ export class Oid4vcIssuanceService { async buildOidcIssuerConfig(issuerId: string) { try { const issuerDetails = await this.oid4vcIssuanceRepository.getOidcIssuerDetailsById(issuerId); + console.log('step-1 issuerDetails', issuerDetails); const templates = await this.oid4vcIssuanceRepository.getTemplatesByIssuerId(issuerId); + console.log('step-2 templates', templates); const credentialConfigurationsSupported = buildCredentialConfigurationsSupported(templates); - + console.log('step-3 credentialConfigurationsSupported', credentialConfigurationsSupported); return buildIssuerPayload({ credentialConfigurationsSupported }, issuerDetails); } catch (error) { this.logger.error(`[buildOidcIssuerPayload] - error: ${JSON.stringify(error)}`); diff --git a/apps/x509/src/interfaces/x509.interface.ts b/apps/x509/src/interfaces/x509.interface.ts index bfae58ad9..cb7c444fd 100644 --- a/apps/x509/src/interfaces/x509.interface.ts +++ b/apps/x509/src/interfaces/x509.interface.ts @@ -10,6 +10,7 @@ export interface CreateX509CertificateEntity { certificateBase64: string; createdBy: string; lastChangedBy: string; + keyId?: string; } export interface UpdateCertificateStatusDto { diff --git a/apps/x509/src/repositories/x509.repository.ts b/apps/x509/src/repositories/x509.repository.ts index 037c2d4f0..78226a263 100644 --- a/apps/x509/src/repositories/x509.repository.ts +++ b/apps/x509/src/repositories/x509.repository.ts @@ -57,7 +57,8 @@ export class X509CertificateRepository { expiry: createDto.expiry, certificateBase64: createDto.certificateBase64, createdBy: createDto.createdBy, - lastChangedBy: createDto.lastChangedBy + lastChangedBy: createDto.lastChangedBy, + keyId: createDto.keyId } }); diff --git a/apps/x509/src/x509.service.ts b/apps/x509/src/x509.service.ts index 0c3f1bd52..23e4324b4 100644 --- a/apps/x509/src/x509.service.ts +++ b/apps/x509/src/x509.service.ts @@ -89,7 +89,8 @@ export class X509CertificateService extends BaseService { validFrom: options.validity.notBefore, expiry: options.validity.notAfter, createdBy: user.id, - lastChangedBy: user.id + lastChangedBy: user.id, + keyId: certificate.response.keyId }; return await this.x509CertificateRepository.create(createDto); @@ -161,24 +162,24 @@ export class X509CertificateService extends BaseService { this.logger.log(`Decoded certificate`); this.logger.debug(`certificate data:`, JSON.stringify(decodedResult)); - const { publicKey } = decodedResult.response; + const { publicJwk } = decodedResult.response; const decodedCert = decodedResult.response.x509Certificate; this.logger.log(`Start validating certificate`); - const isValidKeyType = Object.values(x5cKeyType).includes(publicKey.keyType as x5cKeyType); - + const crv = publicJwk?.jwk?.jwk?.crv; + const isValidKeyType = Object.values(x5cKeyType).includes(crv as x5cKeyType); if (!isValidKeyType) { this.logger.error(`keyType is not valid for importing certificate`); throw new InternalServerErrorException(ResponseMessages.x509.error.import); } - const validFrom = new Date(decodedCert.notBefore); - const expiry = new Date(decodedCert.notAfter); + const validFrom = new Date(decodedCert.asn.tbsCertificate.validity.notBefore.utcTime); + const expiry = new Date(decodedCert.asn.tbsCertificate.validity.notAfter.utcTime); const certificateDateCheckDto: CertificateDateCheckDto = { orgId, validFrom, expiry, - keyType: publicKey.keyType, + keyType: crv, status: x5cRecordStatus.Active }; const collisionForActiveRecords = await this.x509CertificateRepository.hasDateCollision(certificateDateCheckDto); @@ -208,13 +209,14 @@ export class X509CertificateService extends BaseService { this.logger.log(`Successfully imported certificate in wallet `); const createDto: CreateX509CertificateEntity = { orgId, - certificateBase64: certificate.response.issuerCertficicate, - keyType: publicKey.keyType, + certificateBase64: certificate.response.issuerCertificate, + keyType: crv, status: certStatus, validFrom, expiry, createdBy: user.id, - lastChangedBy: user.id + lastChangedBy: user.id, + keyId: certificate.response.keyId }; this.logger.log(`Now adding certificate in platform for org : ${orgId} `); diff --git a/libs/enum/src/enum.ts b/libs/enum/src/enum.ts index 189d31a11..6e60e8119 100644 --- a/libs/enum/src/enum.ts +++ b/libs/enum/src/enum.ts @@ -286,8 +286,8 @@ export enum OpenId4VcIssuanceSessionState { } export enum x5cKeyType { - Ed25519 = 'ed25519', - P256 = 'p256' + Ed25519 = 'Ed25519', + P256 = 'P-256' } export enum x5cRecordStatus { @@ -319,7 +319,7 @@ export enum X509ExtendedKeyUsage { } export enum CredentialFormat { - SdJwtVc = 'vc+sd-jwt', + SdJwtVc = 'dc+sd-jwt', Mdoc = 'mso_mdoc' } diff --git a/libs/prisma-service/prisma/migrations/20260119085820_added_key_id_in_x509_certificates_table/migration.sql b/libs/prisma-service/prisma/migrations/20260119085820_added_key_id_in_x509_certificates_table/migration.sql new file mode 100644 index 000000000..c6bb4f500 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20260119085820_added_key_id_in_x509_certificates_table/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "x509_certificates" ADD COLUMN "keyId" TEXT; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index c40e2e962..a0285c691 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -662,6 +662,7 @@ model x509_certificates { expiry DateTime certificateBase64 String isImported Boolean @default(false) + keyId String? createdAt DateTime @default(now()) createdBy String @db.Uuid From 4704a388a79422d531205de457ef6c47fb42a730 Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Tue, 27 Jan 2026 07:45:13 +0530 Subject: [PATCH 2/6] WIP:changes required for SD-JWT issuance Signed-off-by: shitrerohit --- .../dtos/oid4vc-issuer-template.dto.ts | 4 ++-- .../oid4vc-issuer-sessions.interfaces.ts | 1 + .../helpers/credential-sessions.builder.ts | 2 +- .../libs/helpers/issuer.metadata.ts | 22 ++++++++++++------- .../src/oid4vc-issuance.service.ts | 6 +++-- libs/common/src/interfaces/x509.interface.ts | 1 + 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts b/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts index 7f660d254..1bbb6de32 100644 --- a/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts +++ b/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer-template.dto.ts @@ -171,7 +171,7 @@ export class MdocNamespaceDto { } export class MdocTemplateDto { @ApiProperty({ - description: 'Document type (required when format is "mso_mdoc"; must NOT be provided when format is "vc+sd-jwt")', + description: 'Document type (required when format is "mso_mdoc"; must NOT be provided when format is "dc+sd-jwt")', example: 'org.iso.18013.5.1.mDL' }) //@ValidateIf((o: CreateCredentialTemplateDto) => 'mso_mdoc' === o.format) @@ -189,7 +189,7 @@ export class MdocTemplateDto { export class SdJwtTemplateDto { @ApiProperty({ description: - 'Verifiable Credential Type (required when format is "vc+sd-jwt"; must NOT be provided when format is "mso_mdoc")', + 'Verifiable Credential Type (required when format is "dc+sd-jwt"; must NOT be provided when format is "mso_mdoc")', example: 'BirthCertificateCredential-sdjwt' }) // @ValidateIf((o: CreateCredentialTemplateDto) => 'vc+sd-jwt' === o.format) diff --git a/apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts b/apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts index cfc6f3a3d..e463eda2b 100644 --- a/apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts +++ b/apps/oid4vc-issuance/interfaces/oid4vc-issuer-sessions.interfaces.ts @@ -17,6 +17,7 @@ export interface ISignerOption { method: SignerMethodOption; did?: string; x5c?: string[]; + keyId?: string; } export enum AuthenticationType { diff --git a/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts b/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts index dac66df5e..e568f9d0e 100644 --- a/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts +++ b/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts @@ -135,7 +135,7 @@ export const DEFAULT_TXCODE = { /** Map DB format string -> API enum */ function mapDbFormatToApiFormat(dbFormat: string): CredentialFormat { const normalized = (dbFormat ?? '').toLowerCase(); - if (['sd-jwt', 'vc+sd-jwt', 'sdjwt', 'sd+jwt-vc'].includes(normalized)) { + if (['sd-jwt', 'dc+sd-jwt', 'sdjwt', 'sd+jwt-vc'].includes(normalized)) { return CredentialFormat.SdJwtVc; } if ('mso_mdoc' === normalized || 'mso-mdoc' === normalized || 'mdoc' === normalized) { diff --git a/apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts b/apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts index b2b17f9b8..fc78db1d8 100644 --- a/apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts +++ b/apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts @@ -56,7 +56,8 @@ type CredentialConfigurationsSupported = { // ---- Static Lists (as requested) ---- const STATIC_CREDENTIAL_ALGS_FOR_SDJWT = ['ES256', 'EdDSA'] as const; const STATIC_CREDENTIAL_ALGS_FOR_MDOC = [-7, -9] as const; -const STATIC_BINDING_METHODS = ['did:key', 'did:web', 'did:jwk', 'jwk'] as const; +const STATIC_BINDING_METHODS_FOR_SDJWT = ['jwk'] as const; +const STATIC_BINDING_METHODS_FOR_MDOC = ['cose_key'] as const; // We need to test 'did:key', 'did:web', 'did:jwk', 'jwk', // Safe coercion helpers function coerceJsonObject(v: Prisma.JsonValue): T | null { @@ -374,12 +375,12 @@ export function buildSdJwtCredentialConfig(name: string, template: SdJwtTemplate scope: credentialScope, vct: template.vct, credential_signing_alg_values_supported: [...STATIC_CREDENTIAL_ALGS_FOR_SDJWT], - cryptographic_binding_methods_supported: [...STATIC_BINDING_METHODS], - // proof_types_supported: { - // jwt: { - // proof_signing_alg_values_supported: ['ES256'] - // } - // }, + cryptographic_binding_methods_supported: [...STATIC_BINDING_METHODS_FOR_SDJWT], + proof_types_supported: { + jwt: { + proof_signing_alg_values_supported: ['ES256', 'EdDSA'] + } + }, credential_metadata: { claims, display: [] @@ -409,7 +410,12 @@ export function buildMdocCredentialConfig(name: string, template: MdocTemplate) scope: credentialScope, doctype: template.doctype, credential_signing_alg_values_supported: [...STATIC_CREDENTIAL_ALGS_FOR_MDOC], - cryptographic_binding_methods_supported: [...STATIC_BINDING_METHODS], + cryptographic_binding_methods_supported: [...STATIC_BINDING_METHODS_FOR_MDOC], + proof_types_supported: { + jwt: { + proof_signing_alg_values_supported: ['ES256', 'EdDSA'] + } + }, credential_metadata: { claims, display: [] diff --git a/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts b/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts index 0650fedaa..82a89939c 100644 --- a/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts +++ b/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts @@ -554,7 +554,8 @@ export class Oid4vcIssuanceService { } signerOptions.push({ method: SignerMethodOption.X5C, - x5c: [activeCertificate.certificateBase64] + x5c: [activeCertificate.certificateBase64], + keyId: activeCertificate.keyId }); activeCertificateDetails.push(activeCertificate); } @@ -570,7 +571,8 @@ export class Oid4vcIssuanceService { } signerOptions.push({ method: SignerMethodOption.X5C, - x5c: [activeCertificate.certificateBase64] + x5c: [activeCertificate.certificateBase64], + keyId: activeCertificate.keyId }); activeCertificateDetails.push(activeCertificate); } diff --git a/libs/common/src/interfaces/x509.interface.ts b/libs/common/src/interfaces/x509.interface.ts index 01e9cd2f7..04f65402d 100644 --- a/libs/common/src/interfaces/x509.interface.ts +++ b/libs/common/src/interfaces/x509.interface.ts @@ -213,6 +213,7 @@ export interface X509CertificateRecord { lastChangedBy: string; createdAt: Date; lastChangedDateTime: Date; + keyId: string; } export interface IX509SearchCriteria extends IPaginationSortingDto { From 3a4cc3458f7db6a6836e4b6bc334c5ddc54deedf Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Tue, 27 Jan 2026 07:56:43 +0530 Subject: [PATCH 3/6] fix/removed console log Signed-off-by: shitrerohit --- apps/oid4vc-issuance/src/oid4vc-issuance.service.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts b/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts index 82a89939c..c34b22165 100644 --- a/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts +++ b/apps/oid4vc-issuance/src/oid4vc-issuance.service.ts @@ -298,7 +298,6 @@ export class Oid4vcIssuanceService { const { name, description, format, canBeRevoked, appearance, signerOption } = credentialTemplate; const checkNameExist = await this.oid4vcIssuanceRepository.getTemplateByNameForIssuer(name, issuerId); - console.log('checkNameExist', checkNameExist); if (0 < checkNameExist.length) { throw new ConflictException(ResponseMessages.oidcTemplate.error.templateNameAlreadyExist); } @@ -314,7 +313,6 @@ export class Oid4vcIssuanceService { }; // Persist in DB const createdTemplate = await this.oid4vcIssuanceRepository.createTemplate(issuerId, metadata); - console.log('createdTemplate', createdTemplate); if (!createdTemplate) { throw new InternalServerErrorException(ResponseMessages.oidcTemplate.error.createFailed); } @@ -323,19 +321,15 @@ export class Oid4vcIssuanceService { try { const issuerTemplateConfig = await this.buildOidcIssuerConfig(issuerId); const agentDetails = await this.oid4vcIssuanceRepository.getAgentEndPoint(orgId); - console.log('agentDetails', agentDetails); if (!agentDetails) { throw new NotFoundException(ResponseMessages.issuance.error.agentEndPointNotFound); } const { agentEndPoint } = agentDetails; const issuerDetails = await this.oid4vcIssuanceRepository.getOidcIssuerDetailsById(issuerId); - console.log('issuerDetails', issuerDetails); if (!issuerDetails) { throw new NotFoundException(ResponseMessages.oidcTemplate.error.issuerDetailsNotFound); } const url = getAgentUrl(agentEndPoint, CommonConstants.OIDC_ISSUER_TEMPLATE, issuerDetails.publicIssuerId); - console.log('url', url); - console.log('issuerTemplateConfig', JSON.stringify(issuerTemplateConfig)); createTemplateOnAgent = await this._createOIDCTemplate(issuerTemplateConfig, url, orgId); } catch (agentError) { try { @@ -759,12 +753,9 @@ export class Oid4vcIssuanceService { async buildOidcIssuerConfig(issuerId: string) { try { const issuerDetails = await this.oid4vcIssuanceRepository.getOidcIssuerDetailsById(issuerId); - console.log('step-1 issuerDetails', issuerDetails); const templates = await this.oid4vcIssuanceRepository.getTemplatesByIssuerId(issuerId); - console.log('step-2 templates', templates); const credentialConfigurationsSupported = buildCredentialConfigurationsSupported(templates); - console.log('step-3 credentialConfigurationsSupported', credentialConfigurationsSupported); return buildIssuerPayload({ credentialConfigurationsSupported }, issuerDetails); } catch (error) { this.logger.error(`[buildOidcIssuerPayload] - error: ${JSON.stringify(error)}`); From cc91e3f01817061f6a79508c7813c52f745650e8 Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Tue, 27 Jan 2026 11:11:14 +0530 Subject: [PATCH 4/6] fix/resolved code rabbit ai comments Signed-off-by: shitrerohit --- apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer.dto.ts | 3 ++- apps/issuance/constant/issuance.ts | 2 +- .../libs/helpers/credential-sessions.builder.ts | 2 +- libs/common/src/interfaces/x509.interface.ts | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer.dto.ts b/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer.dto.ts index 2675d7514..74eafc674 100644 --- a/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer.dto.ts +++ b/apps/api-gateway/src/oid4vc-issuance/dtos/oid4vc-issuer.dto.ts @@ -1,6 +1,6 @@ /* eslint-disable camelcase */ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsString, IsOptional, IsArray, ValidateNested, IsUrl, IsInt } from 'class-validator'; +import { IsString, IsOptional, IsArray, ValidateNested, IsUrl, IsInt, Min } from 'class-validator'; import { Type } from 'class-transformer'; export class LogoDto { @@ -96,6 +96,7 @@ export class IssuerCreationDto { }) @IsOptional() @IsInt({ message: 'batchCredentialIssuanceSize must be an integer' }) + @Min(1, { message: 'batchCredentialIssuanceSize must be greater than 0' }) batchCredentialIssuanceSize?: number; @ApiProperty({ diff --git a/apps/issuance/constant/issuance.ts b/apps/issuance/constant/issuance.ts index d32498997..bb547ce5e 100644 --- a/apps/issuance/constant/issuance.ts +++ b/apps/issuance/constant/issuance.ts @@ -2,5 +2,5 @@ import { AccessTokenSignerKeyType } from '../interfaces/oid4vc-issuance.interfac export const dpopSigningAlgValuesSupported = ['RS256', 'ES256', 'EdDSA']; export const credentialConfigurationsSupported = {}; -export const accessTokenSignerKeyType = 'ed25519' as AccessTokenSignerKeyType; +export const accessTokenSignerKeyType = 'Ed25519' as AccessTokenSignerKeyType; export const batchCredentialIssuanceDefault = 0; diff --git a/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts b/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts index e568f9d0e..aecdda133 100644 --- a/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts +++ b/apps/oid4vc-issuance/libs/helpers/credential-sessions.builder.ts @@ -459,7 +459,7 @@ export function buildCredentialOfferPayload( throw new UnprocessableEntityException(`${validationError.errors.join(', ')}`); } - const templateFormat = (templateRecord as any).format ?? 'vc+sd-jwt'; + const templateFormat = (templateRecord as any).format ?? 'dc+sd-jwt'; const apiFormat = mapDbFormatToApiFormat(templateFormat); if (apiFormat === CredentialFormat.SdJwtVc) { return buildSdJwtCredential(credentialRequest, templateRecord, signerOptions, activeCertificateDetails); diff --git a/libs/common/src/interfaces/x509.interface.ts b/libs/common/src/interfaces/x509.interface.ts index 04f65402d..df2015a88 100644 --- a/libs/common/src/interfaces/x509.interface.ts +++ b/libs/common/src/interfaces/x509.interface.ts @@ -213,7 +213,7 @@ export interface X509CertificateRecord { lastChangedBy: string; createdAt: Date; lastChangedDateTime: Date; - keyId: string; + keyId?: string; } export interface IX509SearchCriteria extends IPaginationSortingDto { From 0e13a81f60924a61a4fbc8a92819256b97435795 Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Tue, 27 Jan 2026 12:05:12 +0530 Subject: [PATCH 5/6] fix:removed unused variables Signed-off-by: shitrerohit --- apps/issuance/constant/issuance.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/issuance/constant/issuance.ts b/apps/issuance/constant/issuance.ts index bb547ce5e..fac68e2ce 100644 --- a/apps/issuance/constant/issuance.ts +++ b/apps/issuance/constant/issuance.ts @@ -1,6 +1,3 @@ -import { AccessTokenSignerKeyType } from '../interfaces/oid4vc-issuance.interfaces'; - export const dpopSigningAlgValuesSupported = ['RS256', 'ES256', 'EdDSA']; export const credentialConfigurationsSupported = {}; -export const accessTokenSignerKeyType = 'Ed25519' as AccessTokenSignerKeyType; export const batchCredentialIssuanceDefault = 0; From 3cc2bb99459a40b476f75d1d3bc0eb698da57943 Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Thu, 29 Jan 2026 12:13:38 +0530 Subject: [PATCH 6/6] feat: update codebase for Credo 6.1 compatibility Signed-off-by: shitrerohit --- .../libs/helpers/issuer.metadata.ts | 2 +- ...oid4vp-verification-sessions.interfaces.ts | 1 + .../src/oid4vc-verification.service.ts | 34 ++++++++++++------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts b/apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts index fc78db1d8..4d3b2d851 100644 --- a/apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts +++ b/apps/oid4vc-issuance/libs/helpers/issuer.metadata.ts @@ -56,7 +56,7 @@ type CredentialConfigurationsSupported = { // ---- Static Lists (as requested) ---- const STATIC_CREDENTIAL_ALGS_FOR_SDJWT = ['ES256', 'EdDSA'] as const; const STATIC_CREDENTIAL_ALGS_FOR_MDOC = [-7, -9] as const; -const STATIC_BINDING_METHODS_FOR_SDJWT = ['jwk'] as const; +const STATIC_BINDING_METHODS_FOR_SDJWT = ['did:key', 'did:web', 'did:jwk', 'jwk'] as const; const STATIC_BINDING_METHODS_FOR_MDOC = ['cose_key'] as const; // We need to test 'did:key', 'did:web', 'did:jwk', 'jwk', // Safe coercion helpers diff --git a/apps/oid4vc-verification/interfaces/oid4vp-verification-sessions.interfaces.ts b/apps/oid4vc-verification/interfaces/oid4vp-verification-sessions.interfaces.ts index b6db519a3..381c96470 100644 --- a/apps/oid4vc-verification/interfaces/oid4vp-verification-sessions.interfaces.ts +++ b/apps/oid4vc-verification/interfaces/oid4vp-verification-sessions.interfaces.ts @@ -18,6 +18,7 @@ export interface DidSigner { export interface X5cSigner { method: SignerMethodOption.X5C; x5c: string[]; + keyId: string; } export type RequestSigner = DidSigner | X5cSigner; diff --git a/apps/oid4vc-verification/src/oid4vc-verification.service.ts b/apps/oid4vc-verification/src/oid4vc-verification.service.ts index 87f8ca802..b4046041c 100644 --- a/apps/oid4vc-verification/src/oid4vc-verification.service.ts +++ b/apps/oid4vc-verification/src/oid4vc-verification.service.ts @@ -234,16 +234,10 @@ export class Oid4vpVerificationService extends BaseService { method: SignerMethodOption.DID, didUrl: orgDid }; - } else if ( - sessionRequest.requestSigner.method === SignerOption.X509_P256 || - sessionRequest.requestSigner.method === SignerOption.X509_ED25519 - ) { + } else if (sessionRequest.requestSigner.method === SignerOption.X509_P256) { this.logger.debug('X5C based request signer method selected'); - const activeCertificate = await this.oid4vpRepository.getCurrentActiveCertificate( - orgId, - sessionRequest.requestSigner.method - ); + const activeCertificate = await this.oid4vpRepository.getCurrentActiveCertificate(orgId, x5cKeyType.P256); this.logger.debug(`activeCertificate=${JSON.stringify(activeCertificate)}`); if (!activeCertificate) { @@ -254,14 +248,30 @@ export class Oid4vpVerificationService extends BaseService { requestSigner = { method: SignerMethodOption.X5C, // "x5c" - x5c: [activeCertificate.certificateBase64] // array with PEM/DER base64 + x5c: [activeCertificate.certificateBase64], // array with PEM/DER base64 + keyId: activeCertificate.keyId }; activeCertificateDetails.push(activeCertificate); + } else if (sessionRequest.requestSigner.method === SignerOption.X509_ED25519) { + this.logger.debug('X5C based request signer method selected'); + + const activeCertificate = await this.oid4vpRepository.getCurrentActiveCertificate(orgId, x5cKeyType.Ed25519); + this.logger.debug(`activeCertificate=${JSON.stringify(activeCertificate)}`); + + if (!activeCertificate) { + throw new NotFoundException( + `No active certificate(${sessionRequest.requestSigner.method}}) found for issuer` + ); + } + requestSigner = { + method: SignerMethodOption.X5C, // "x5c" + x5c: [activeCertificate.certificateBase64], // array with PEM/DER base64 + keyId: activeCertificate.keyId + }; } else { throw new BadRequestException(`Unsupported requestSigner method: ${sessionRequest.requestSigner.method}`); } - // assign the single object (not an array) sessionRequest.requestSigner = requestSigner; @@ -357,14 +367,14 @@ export class Oid4vpVerificationService extends BaseService { resolvedSigner = { method: SignerMethodOption.X5C, - x5c: [activeCertificate.certificateBase64] + x5c: [activeCertificate.certificateBase64], + keyId: activeCertificate.keyId }; } else { throw new BadRequestException(`Unsupported requestSigner method: ${signerOption}`); } sessionRequest.requestSigner = resolvedSigner; - const url = getAgentUrl(agentEndPoint, CommonConstants.OID4VP_VERIFICATION_SESSION); this.logger.debug(`[createIntentBasedVerificationPresentation] calling agent URL=${url}`);