From 1183157d862f9ccc66e1d6b36018e1aea6d49046 Mon Sep 17 00:00:00 2001 From: Douglas Palmer Date: Wed, 11 Jun 2025 11:39:40 -0700 Subject: [PATCH] Key generation for client authentication is always RSA 2048 with a 10-year validity, regardless of the selected algorithm Closes #38620 Signed-off-by: Douglas Palmer --- .../representations/KeyStoreConfig.java | 18 ++++++++++++++++ .../ClientAttributeCertificateResource.java | 1 + .../admin/messages/messages_en.properties | 4 +++- .../src/clients/keys/GenerateKeyDialog.tsx | 21 +++++++++++++++++++ .../src/defs/keystoreConfig.ts | 2 ++ .../models/utils/KeycloakModelUtils.java | 12 +++++++++-- .../ClientAttributeCertificateResource.java | 12 ++++++++++- .../admin/client/CredentialsTest.java | 2 ++ .../AbstractClientAuthSignedJWTTest.java | 2 ++ 9 files changed, 70 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/keycloak/representations/KeyStoreConfig.java b/core/src/main/java/org/keycloak/representations/KeyStoreConfig.java index 782669472c3..0364417eb5b 100644 --- a/core/src/main/java/org/keycloak/representations/KeyStoreConfig.java +++ b/core/src/main/java/org/keycloak/representations/KeyStoreConfig.java @@ -29,6 +29,8 @@ public class KeyStoreConfig { protected String keyAlias; protected String realmAlias; protected String format; + protected Integer keySize; + protected Integer validity; public Boolean isRealmCertificate() { return realmCertificate; @@ -77,4 +79,20 @@ public class KeyStoreConfig { public void setFormat(String format) { this.format = format; } + + public Integer getKeySize() { + return keySize; + } + + public void setKeySize(Integer keySize) { + this.keySize = keySize; + } + + public Integer getValidity() { + return validity; + } + + public void setValidity(Integer validity) { + this.validity = validity; + } } diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java index 02e47e6e53b..b84076889b9 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/ClientAttributeCertificateResource.java @@ -17,6 +17,7 @@ package org.keycloak.admin.client.resource; +import jakarta.ws.rs.QueryParam; import org.keycloak.representations.KeyStoreConfig; import org.keycloak.representations.idm.CertificateRepresentation; diff --git a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties index 58d8db1031a..8ff9d7e2490 100644 --- a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties +++ b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties @@ -480,7 +480,7 @@ userInfoResponseEncryptionKeyManagementAlgorithmHelp=JWA Algorithm used for key authnContextDeclRefsHelp=Ordered list of requested AuthnContext DeclRefs. inherent=Inherited tableTitle=Attributes groups -generateNewKeys=Generate new keys +generateNewKeys=Generate RSA keys updateClientPolicySuccess=Client policy updated unlock=Unlock validateRealm=You must enter a realm @@ -826,6 +826,8 @@ removeImported=Remove imported endpoints=Endpoints roleSaveError=Could not save role\: {{error}} keySize=Key size +validity=Certificate expiration +validityHelp=Number of years the generated certificate is valid for. membershipUserLdapAttributeHelp=Used only if the Membership Attribute Type is UID. It is the name of the LDAP attribute on the user, which is used for membership mappings. It is typically 'uid'. For example, if the value of 'Membership User LDAP Attribute' is 'uid' and the LDAP group has 'memberUid\: john', it is expected that particular LDAP user will have the attribute 'uid\: john'. samlCapabilityConfig=SAML capabilities accessTokenSignatureAlgorithmHelp=JWA algorithm used for signing access tokens. diff --git a/js/apps/admin-ui/src/clients/keys/GenerateKeyDialog.tsx b/js/apps/admin-ui/src/clients/keys/GenerateKeyDialog.tsx index ceda13c9c77..53da3c825cf 100644 --- a/js/apps/admin-ui/src/clients/keys/GenerateKeyDialog.tsx +++ b/js/apps/admin-ui/src/clients/keys/GenerateKeyDialog.tsx @@ -1,6 +1,7 @@ import type KeyStoreConfig from "@keycloak/keycloak-admin-client/lib/defs/keystoreConfig"; import { HelpItem, + NumberControl, SelectControl, FileUploadControl, } from "@keycloak/keycloak-ui-shared"; @@ -59,6 +60,7 @@ export const KeyForm = ({ ...(cryptoInfo?.supportedKeystoreTypes ?? []), ...(hasPem ? [CERT_PEM] : []), ]; + const keySizes = ["4096", "3072", "2048"]; return (
@@ -91,6 +93,25 @@ export const KeyForm = ({ {format !== CERT_PEM && ( )} + + ); }; diff --git a/js/libs/keycloak-admin-client/src/defs/keystoreConfig.ts b/js/libs/keycloak-admin-client/src/defs/keystoreConfig.ts index 5c11410ed80..09f89df643a 100644 --- a/js/libs/keycloak-admin-client/src/defs/keystoreConfig.ts +++ b/js/libs/keycloak-admin-client/src/defs/keystoreConfig.ts @@ -8,4 +8,6 @@ export default interface KeyStoreConfig { keyAlias?: string; realmAlias?: string; format?: string; + keySize?: number; + validity?: number; } diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java index f1294feb279..2bed9e075d5 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java @@ -65,6 +65,7 @@ import jakarta.transaction.InvalidTransactionException; import jakarta.transaction.SystemException; import jakarta.transaction.Transaction; import javax.crypto.spec.SecretKeySpec; +import java.math.BigInteger; import java.security.Key; import java.security.KeyPair; import java.security.PrivateKey; @@ -72,6 +73,7 @@ import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Base64; +import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -218,8 +220,14 @@ public final class KeycloakModelUtils { } public static CertificateRepresentation generateKeyPairCertificate(String subject) { - KeyPair keyPair = KeyUtils.generateRsaKeyPair(2048); - X509Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, subject); + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.YEAR, 3); + return generateKeyPairCertificate(subject, 4096, calendar); + } + + public static CertificateRepresentation generateKeyPairCertificate(String subject, int keysize, Calendar endDate) { + KeyPair keyPair = KeyUtils.generateRsaKeyPair(keysize); + X509Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, subject, BigInteger.valueOf(System.currentTimeMillis()), endDate.getTime()); String privateKeyPem = PemUtils.encodeKey(keyPair.getPrivate()); String certPem = PemUtils.encodeCertificate(certificate); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java index 6c0f1595d50..e86c235aa78 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java @@ -18,6 +18,7 @@ package org.keycloak.services.resources.admin; import com.google.common.base.Strings; +import jakarta.ws.rs.QueryParam; import org.eclipse.microprofile.openapi.annotations.Operation; import org.eclipse.microprofile.openapi.annotations.extensions.Extension; import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; @@ -64,6 +65,7 @@ import java.security.PrivateKey; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; +import java.util.Calendar; import java.util.Set; import java.util.stream.Collectors; @@ -340,7 +342,15 @@ public class ClientAttributeCertificateResource { throw new ErrorResponseException("password-missing", "Need to specify a store password for jks generation and download", Response.Status.BAD_REQUEST); } - CertificateRepresentation info = KeycloakModelUtils.generateKeyPairCertificate(client.getClientId()); + CertificateRepresentation info; + if (config.getKeySize() <= 0 || config.getValidity() <= 0) { + info = KeycloakModelUtils.generateKeyPairCertificate(client.getClientId()); + } + else { + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.YEAR, config.getValidity()); + info = KeycloakModelUtils.generateKeyPairCertificate(client.getClientId(), config.getKeySize(), calendar); + } byte[] rtn = getKeystore(config, info.getPrivateKey(), info.getCertificate()); info.setPrivateKey(null); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java index 24ce7100852..64cf7dcb190 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/CredentialsTest.java @@ -240,6 +240,8 @@ public class CredentialsTest extends AbstractClientTest { config.setKeyAlias("alias"); config.setKeyPassword("keyPass"); config.setStorePassword("storePass"); + config.setKeySize(4096); + config.setValidity(3); byte[] result = certRsc.generateAndGetKeystore(config); KeyStore keyStore = CryptoIntegration.getProvider().getKeyStore(preferredKeystoreType); keyStore.load(new ByteArrayInputStream(result), "storePass".toCharArray()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AbstractClientAuthSignedJWTTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AbstractClientAuthSignedJWTTest.java index d4ddba5eb42..76963c04a24 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AbstractClientAuthSignedJWTTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AbstractClientAuthSignedJWTTest.java @@ -389,6 +389,8 @@ public abstract class AbstractClientAuthSignedJWTTest extends AbstractKeycloakTe keyStoreConfig.setKeyPassword(keyPassword); keyStoreConfig.setStorePassword(storePassword); keyStoreConfig.setKeyAlias(keyAlias); + keyStoreConfig.setKeySize(4096); + keyStoreConfig.setValidity(3); client = getClient(testRealm.getRealm(), client.getId()).toRepresentation(); final String certOld = client.getAttributes().get(JWTClientAuthenticator.CERTIFICATE_ATTR);