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 <dpalmer@redhat.com>
This commit is contained in:
Douglas Palmer 2025-06-11 11:39:40 -07:00 committed by Marek Posolda
parent 30979dc873
commit 1183157d86
9 changed files with 70 additions and 4 deletions

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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.

View File

@ -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 (
<Form className="pf-v5-u-pt-lg">
@ -91,6 +93,25 @@ export const KeyForm = ({
{format !== CERT_PEM && (
<StoreSettings hidePassword={useFile} isSaml={isSaml} />
)}
<SelectControl
name="keySize"
label={t("keySize")}
labelIcon={t("keySizeHelp")}
controller={{
defaultValue: keySizes[0],
}}
menuAppendTo="parent"
options={keySizes}
/>
<NumberControl
name="validity"
label={t("validity")}
labelIcon={t("validityHelp")}
controller={{
defaultValue: 3,
rules: { required: t("required"), min: 1, max: 10 },
}}
/>
</Form>
);
};

View File

@ -8,4 +8,6 @@ export default interface KeyStoreConfig {
keyAlias?: string;
realmAlias?: string;
format?: string;
keySize?: number;
validity?: number;
}

View File

@ -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);

View File

@ -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);

View File

@ -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());

View File

@ -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);