mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 15:02:05 -03:30
[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:
parent
cc3ab86544
commit
7a3fd3404c
@ -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)
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user