[OID4VCI] Add UI support for vc.credential_signing_alg and vc.credential_build_config.hash_algorithm in OID4VCI client scopes (#44851)

Closes #44849

Signed-off-by: forkimenjeckayang <forkimenjeckayang@gmail.com>
This commit is contained in:
forkimenjeckayang 2026-01-08 17:19:56 +01:00 committed by GitHub
parent cc3ab86544
commit 7a3fd3404c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 185 additions and 0 deletions

View File

@ -3650,6 +3650,10 @@ supportedFormats=Supported Formats
supportedFormatsHelp=The format of the verifiable credential. Currently supported formats: SD-JWT VC (dc+sd-jwt), JWT VC (jwt_vc).
credentialDisplay=Credential Display
credentialDisplayHelp=JSON array of objects containing display metadata for wallets (name, logo, colors, etc.). Example: [{"name": "IdentityCredential", "locale": "en-US", "logo": {"uri": "https://example.com/logo.png", "alt_text": "Logo"}, "background_color": "#12107c", "text_color": "#FFFFFF"}]
credentialSigningAlgorithm=Credential Signing Algorithm
credentialSigningAlgorithmHelp=Signing algorithm used to sign credentials (for example "ES256"). Leave blank to use the realm defaults derived from available keys.
hashAlgorithm=Hash Algorithm
hashAlgorithmHelp=Hash algorithm used for SD-JWT credentials (for example "SHA-256"). Defaults to "SHA-256" if not specified.
supportedCredentialTypes=Supported Credential Types
supportedCredentialTypesHelp=Comma-separated list of credential types (e.g., "VerifiableCredential,UniversityDegreeCredential"). Used in the credential definition for JWT VC and SD-JWT formats.
verifiableCredentialType=Verifiable Credential Type (VCT)

View File

@ -77,6 +77,17 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
[serverInfo],
);
// Get available hash algorithms from server info
const hashAlgorithms = serverInfo?.providers?.hash?.providers
? Object.keys(serverInfo.providers.hash.providers)
: [];
// Get available asymmetric signature algorithms from server info
const asymmetricSigAlgOptions = useMemo(
() => serverInfo?.cryptoInfo?.clientSignatureAsymmetricAlgorithms ?? [],
[serverInfo],
);
// Fetch realm keys for signing_key_id dropdown
const [realmKeys, setRealmKeys] = useState<KeyMetadataRepresentation[]>([]);
@ -367,6 +378,45 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => {
options={keyOptions}
/>
)}
{asymmetricSigAlgOptions.length > 0 && (
<SelectControl
id="kc-credential-signing-alg"
name={convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
"attributes.vc.credential_signing_alg",
)}
label={t("credentialSigningAlgorithm")}
labelIcon={t("credentialSigningAlgorithmHelp")}
controller={{
defaultValue:
clientScope?.attributes?.["vc.credential_signing_alg"] ??
"",
}}
options={asymmetricSigAlgOptions.map((alg) => ({
key: alg,
value: alg,
}))}
/>
)}
{hashAlgorithms.length > 0 && (
<SelectControl
id="kc-hash-algorithm"
name={convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
"attributes.vc.credential_build_config.hash_algorithm",
)}
label={t("hashAlgorithm")}
labelIcon={t("hashAlgorithmHelp")}
controller={{
defaultValue:
clientScope?.attributes?.[
"vc.credential_build_config.hash_algorithm"
] ?? "SHA-256",
}}
options={hashAlgorithms.map((alg) => ({
key: alg,
value: alg,
}))}
/>
)}
<TextAreaControl
name={convertAttributeNameToForm<ClientScopeDefaultOptionalType>(
"attributes.vc.display",

View File

@ -67,6 +67,8 @@ const OID4VCI_FIELDS = {
FORMAT: "#kc-vc-format",
TOKEN_JWS_TYPE: "attributes.vc🍺credential_build_config🍺token_jws_type",
SIGNING_KEY_ID: "#kc-signing-key-id",
SIGNING_ALGORITHM: "#kc-credential-signing-alg",
HASH_ALGORITHM: "#kc-hash-algorithm",
DISPLAY: "attributes.vc🍺display",
SUPPORTED_CREDENTIAL_TYPES: "attributes.vc🍺supported_credential_types",
VERIFIABLE_CREDENTIAL_TYPE: "attributes.vc🍺verifiable_credential_type",
@ -80,6 +82,8 @@ const TEST_VALUES = {
CREDENTIAL_ID: "test-cred-identifier",
ISSUER_DID: "did:key:test123",
EXPIRY_SECONDS: "86400",
SIGNING_ALG: "ES256",
HASH_ALGORITHM: "SHA-384",
TOKEN_JWS_TYPE: "dc+sd-jwt",
VISIBLE_CLAIMS: "id,iat,nbf,exp,jti,given_name",
DISPLAY:
@ -124,6 +128,8 @@ test.describe("OID4VCI Client Scope Functionality", () => {
).toBeVisible();
await expect(page.locator(OID4VCI_FIELDS.FORMAT)).toBeVisible();
await expect(page.getByTestId(OID4VCI_FIELDS.TOKEN_JWS_TYPE)).toBeVisible();
await expect(page.locator(OID4VCI_FIELDS.SIGNING_ALGORITHM)).toBeVisible();
await expect(page.locator(OID4VCI_FIELDS.HASH_ALGORITHM)).toBeVisible();
await expect(page.getByTestId(OID4VCI_FIELDS.DISPLAY)).toBeVisible();
});
@ -154,6 +160,18 @@ test.describe("OID4VCI Client Scope Functionality", () => {
.getByTestId(OID4VCI_FIELDS.TOKEN_JWS_TYPE)
.fill(TEST_VALUES.TOKEN_JWS_TYPE);
await selectItem(
page,
OID4VCI_FIELDS.SIGNING_ALGORITHM,
TEST_VALUES.SIGNING_ALG,
);
await selectItem(
page,
OID4VCI_FIELDS.HASH_ALGORITHM,
TEST_VALUES.HASH_ALGORITHM,
);
await page.getByTestId(OID4VCI_FIELDS.DISPLAY).fill(TEST_VALUES.DISPLAY);
await page
.getByTestId(OID4VCI_FIELDS.SUPPORTED_CREDENTIAL_TYPES)
@ -181,6 +199,12 @@ test.describe("OID4VCI Client Scope Functionality", () => {
await expect(page.locator("#kc-vc-format")).toContainText(
"JWT VC (jwt_vc)",
);
await expect(page.locator(OID4VCI_FIELDS.SIGNING_ALGORITHM)).toContainText(
TEST_VALUES.SIGNING_ALG,
);
await expect(page.locator(OID4VCI_FIELDS.HASH_ALGORITHM)).toContainText(
TEST_VALUES.HASH_ALGORITHM,
);
await expect(page.getByTestId(OID4VCI_FIELDS.DISPLAY)).toHaveValue(
TEST_VALUES.DISPLAY,
);
@ -237,6 +261,8 @@ test.describe("OID4VCI Client Scope Functionality", () => {
page.getByTestId(OID4VCI_FIELDS.EXPIRY_IN_SECONDS),
).toBeHidden();
await expect(page.locator(OID4VCI_FIELDS.FORMAT)).toBeHidden();
await expect(page.locator(OID4VCI_FIELDS.SIGNING_ALGORITHM)).toBeHidden();
await expect(page.locator(OID4VCI_FIELDS.HASH_ALGORITHM)).toBeHidden();
await expect(page.getByTestId(OID4VCI_FIELDS.DISPLAY)).toBeHidden();
});
@ -354,6 +380,13 @@ test.describe("OID4VCI Client Scope Functionality", () => {
await page
.getByTestId(OID4VCI_FIELDS.CREDENTIAL_IDENTIFIER)
.fill(TEST_VALUES.CREDENTIAL_ID);
await selectItem(
page,
OID4VCI_FIELDS.SIGNING_ALGORITHM,
TEST_VALUES.SIGNING_ALG,
);
await page.getByTestId(OID4VCI_FIELDS.DISPLAY).fill(TEST_VALUES.DISPLAY);
await page
.getByTestId(OID4VCI_FIELDS.SUPPORTED_CREDENTIAL_TYPES)
@ -381,6 +414,9 @@ test.describe("OID4VCI Client Scope Functionality", () => {
await expect(
page.getByTestId(OID4VCI_FIELDS.VERIFIABLE_CREDENTIAL_TYPE),
).toHaveValue(TEST_VALUES.VERIFIABLE_CREDENTIAL_TYPE);
await expect(page.locator(OID4VCI_FIELDS.SIGNING_ALGORITHM)).toContainText(
TEST_VALUES.SIGNING_ALG,
);
await expect(page.getByTestId(OID4VCI_FIELDS.VISIBLE_CLAIMS)).toHaveValue(
TEST_VALUES.VISIBLE_CLAIMS,
);
@ -454,4 +490,99 @@ test.describe("OID4VCI Client Scope Functionality", () => {
await expect(page.getByTestId(OID4VCI_FIELDS.TOKEN_JWS_TYPE)).toBeVisible();
});
test("should display signing algorithm dropdown with available algorithms", async ({
page,
}) => {
await using testBed = await createTestBed();
await createClientScopeAndSelectProtocolAndFormat(
page,
testBed,
"SD-JWT VC (dc+sd-jwt)",
);
await expect(page.locator(OID4VCI_FIELDS.SIGNING_ALGORITHM)).toBeVisible();
await page.locator(OID4VCI_FIELDS.SIGNING_ALGORITHM).click();
await expect(page.getByRole("option", { name: "RS256" })).toBeVisible();
await expect(page.getByRole("option", { name: "ES256" })).toBeVisible();
});
test("should display hash algorithm dropdown with available algorithms", async ({
page,
}) => {
await using testBed = await createTestBed();
await createClientScopeAndSelectProtocolAndFormat(
page,
testBed,
"SD-JWT VC (dc+sd-jwt)",
);
await expect(page.locator(OID4VCI_FIELDS.HASH_ALGORITHM)).toBeVisible();
await page.locator(OID4VCI_FIELDS.HASH_ALGORITHM).click();
await expect(page.getByRole("option", { name: "SHA-256" })).toBeVisible();
await expect(page.getByRole("option", { name: "SHA-384" })).toBeVisible();
await expect(page.getByRole("option", { name: "SHA-512" })).toBeVisible();
});
test("should save and persist hash algorithm value", async ({ page }) => {
await using testBed = await createTestBed();
const testClientScopeName = `oid4vci-hash-alg-test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
await createClientScopeAndSelectProtocolAndFormat(
page,
testBed,
"SD-JWT VC (dc+sd-jwt)",
);
await page
.getByTestId(OID4VCI_FIELDS.CREDENTIAL_CONFIGURATION_ID)
.fill(TEST_VALUES.CREDENTIAL_CONFIG);
await page.getByTestId("name").fill(testClientScopeName);
await selectItem(
page,
OID4VCI_FIELDS.HASH_ALGORITHM,
TEST_VALUES.HASH_ALGORITHM,
);
await clickSaveButton(page);
await expect(page.getByText("Client scope created")).toBeVisible();
await navigateBackAndVerifyClientScope(page, testBed, testClientScopeName);
await expect(page.locator(OID4VCI_FIELDS.HASH_ALGORITHM)).toContainText(
TEST_VALUES.HASH_ALGORITHM,
);
});
test("should default to SHA-256 when hash algorithm is not set", async ({
page,
}) => {
await using testBed = await createTestBed();
const testClientScopeName = `oid4vci-hash-default-test-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
await createClientScopeAndSelectProtocolAndFormat(
page,
testBed,
"SD-JWT VC (dc+sd-jwt)",
);
await page
.getByTestId(OID4VCI_FIELDS.CREDENTIAL_CONFIGURATION_ID)
.fill(TEST_VALUES.CREDENTIAL_CONFIG);
await page.getByTestId("name").fill(testClientScopeName);
await clickSaveButton(page);
await expect(page.getByText("Client scope created")).toBeVisible();
await navigateBackAndVerifyClientScope(page, testBed, testClientScopeName);
await expect(page.locator(OID4VCI_FIELDS.HASH_ALGORITHM)).toContainText(
"SHA-256",
);
});
});