Incorrect Signature algorithms presented by Client Authenticator

closes #15853

Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
mposolda 2023-04-20 16:35:09 +02:00 committed by Marek Posolda
parent 513c00bcd9
commit dc3b037e3a
11 changed files with 96 additions and 9 deletions

View file

@ -34,7 +34,11 @@ public class CryptoInfoRepresentation {
private String cryptoProvider; private String cryptoProvider;
private List<String> supportedKeystoreTypes; private List<String> supportedKeystoreTypes;
public static CryptoInfoRepresentation create() { private List<String> clientSignatureSymmetricAlgorithms;
private List<String> clientSignatureAsymmetricAlgorithms;
public static CryptoInfoRepresentation create(List<String> clientSignatureSymmetricAlgorithms, List<String> clientSignatureAsymmetricAlgorithms) {
CryptoInfoRepresentation info = new CryptoInfoRepresentation(); CryptoInfoRepresentation info = new CryptoInfoRepresentation();
CryptoProvider cryptoProvider = CryptoIntegration.getProvider(); CryptoProvider cryptoProvider = CryptoIntegration.getProvider();
@ -42,6 +46,8 @@ public class CryptoInfoRepresentation {
info.supportedKeystoreTypes = CryptoIntegration.getProvider().getSupportedKeyStoreTypes() info.supportedKeystoreTypes = CryptoIntegration.getProvider().getSupportedKeyStoreTypes()
.map(KeystoreUtil.KeystoreFormat::toString) .map(KeystoreUtil.KeystoreFormat::toString)
.collect(Collectors.toList()); .collect(Collectors.toList());
info.clientSignatureSymmetricAlgorithms = clientSignatureSymmetricAlgorithms;
info.clientSignatureAsymmetricAlgorithms = clientSignatureAsymmetricAlgorithms;
return info; return info;
} }
@ -61,4 +67,20 @@ public class CryptoInfoRepresentation {
public void setSupportedKeystoreTypes(List<String> supportedKeystoreTypes) { public void setSupportedKeystoreTypes(List<String> supportedKeystoreTypes) {
this.supportedKeystoreTypes = supportedKeystoreTypes; this.supportedKeystoreTypes = supportedKeystoreTypes;
} }
public List<String> getClientSignatureSymmetricAlgorithms() {
return clientSignatureSymmetricAlgorithms;
}
public void setClientSignatureSymmetricAlgorithms(List<String> clientSignatureSymmetricAlgorithms) {
this.clientSignatureSymmetricAlgorithms = clientSignatureSymmetricAlgorithms;
}
public List<String> getClientSignatureAsymmetricAlgorithms() {
return clientSignatureAsymmetricAlgorithms;
}
public void setClientSignatureAsymmetricAlgorithms(List<String> clientSignatureAsymmetricAlgorithms) {
this.clientSignatureAsymmetricAlgorithms = clientSignatureAsymmetricAlgorithms;
}
} }

View file

@ -53,7 +53,7 @@
"count": "Specifies how many clients can be created using the token", "count": "Specifies how many clients can be created using the token",
"client-authenticator-type": "Client Authenticator used for authentication of this client against Keycloak server", "client-authenticator-type": "Client Authenticator used for authentication of this client against Keycloak server",
"registration-access-token": "The registration access token provides access for clients to the client registration service.", "registration-access-token": "The registration access token provides access for clients to the client registration service.",
"signature-algorithm": "JWA algorithm, which the client needs to use when signing a JWT for authentication. If left blank, the client is allowed to use any algorithm.", "signature-algorithm": "JWA algorithm, which the client needs to use when signing a JWT for authentication. If left blank, the client is allowed to use any appropriate algorithm for the particular client authenticator.",
"anonymousAccessPolicies": "Those Policies are used when the Client Registration Service is invoked by unauthenticated request. This means that the request does not contain Initial Access Token nor Bearer Token.", "anonymousAccessPolicies": "Those Policies are used when the Client Registration Service is invoked by unauthenticated request. This means that the request does not contain Initial Access Token nor Bearer Token.",
"authenticatedAccessPolicies": "Those Policies are used when Client Registration Service is invoked by authenticated request. This means that the request contains Initial Access Token or Bearer Token.", "authenticatedAccessPolicies": "Those Policies are used when Client Registration Service is invoked by authenticated request. This means that the request contains Initial Access Token or Bearer Token.",
"allowRegexComparison": "If OFF, then the Subject DN from given client certificate must exactly match the given DN from the 'Subject DN' property as described in the RFC8705 specification. The Subject DN can be in the RFC2553 or RFC1779 format. If ON, then the Subject DN from given client certificate should match regex specified by 'Subject DN' property.", "allowRegexComparison": "If OFF, then the Subject DN from given client certificate must exactly match the given DN from the 'Subject DN' property as described in the RFC8705 specification. The Subject DN can be in the RFC2553 or RFC1779 format. If ON, then the Subject DN from given client certificate should match regex specified by 'Subject DN' property.",

View file

@ -185,7 +185,9 @@ export const Credentials = ({ client, save, refresh }: CredentialsProps) => {
/> />
</FormGroup> </FormGroup>
{(clientAuthenticatorType === "client-jwt" || {(clientAuthenticatorType === "client-jwt" ||
clientAuthenticatorType === "client-secret-jwt") && <SignedJWT />} clientAuthenticatorType === "client-secret-jwt") && (
<SignedJWT clientAuthenticatorType={clientAuthenticatorType} />
)}
{clientAuthenticatorType === "client-jwt" && ( {clientAuthenticatorType === "client-jwt" && (
<FormGroup> <FormGroup>
<Alert variant="info" isInline title={t("signedJWTConfirm")} /> <Alert variant="info" isInline title={t("signedJWTConfirm")} />

View file

@ -10,14 +10,21 @@ import {
import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { HelpItem } from "ui-shared"; import { HelpItem } from "ui-shared";
import { convertAttributeNameToForm, sortProviders } from "../../util"; import { convertAttributeNameToForm } from "../../util";
import { FormFields } from "../ClientDetails"; import { FormFields } from "../ClientDetails";
export const SignedJWT = () => { type SignedJWTProps = {
clientAuthenticatorType: string;
};
export const SignedJWT = ({ clientAuthenticatorType }: SignedJWTProps) => {
const { control } = useFormContext(); const { control } = useFormContext();
const providers = sortProviders( const { cryptoInfo } = useServerInfo();
useServerInfo().providers!.clientSignature.providers const providers =
); clientAuthenticatorType === "client-jwt"
? cryptoInfo?.clientSignatureAsymmetricAlgorithms ?? []
: cryptoInfo?.clientSignatureSymmetricAlgorithms ?? [];
const { t } = useTranslation("clients"); const { t } = useTranslation("clients");
const [open, isOpen] = useState(false); const [open, isOpen] = useState(false);

View file

@ -73,4 +73,6 @@ export interface ProtocolMapperTypeRepresentation {
export interface CryptoInfoRepresentation { export interface CryptoInfoRepresentation {
cryptoProvider: string; cryptoProvider: string;
supportedKeystoreTypes: string[]; supportedKeystoreTypes: string[];
clientSignatureSymmetricAlgorithms: string[];
clientSignatureAsymmetricAlgorithms: string[];
} }

View file

@ -28,4 +28,8 @@ public interface ClientSignatureVerifierProvider extends Provider {
@Override @Override
default void close() { default void close() {
} }
String getAlgorithm();
boolean isAsymmetricAlgorithm();
} }

View file

@ -35,4 +35,13 @@ public class AsymmetricClientSignatureVerifierProvider implements ClientSignatur
return new ClientAsymmetricSignatureVerifierContext(session, client, input); return new ClientAsymmetricSignatureVerifierContext(session, client, input);
} }
@Override
public String getAlgorithm() {
return algorithm;
}
@Override
public boolean isAsymmetricAlgorithm() {
return true;
}
} }

View file

@ -18,4 +18,14 @@ public class ECDSAClientSignatureVerifierProvider implements ClientSignatureVeri
public SignatureVerifierContext verifier(ClientModel client, JWSInput input) throws VerificationException { public SignatureVerifierContext verifier(ClientModel client, JWSInput input) throws VerificationException {
return new ClientECDSASignatureVerifierContext(session, client, input); return new ClientECDSASignatureVerifierContext(session, client, input);
} }
@Override
public String getAlgorithm() {
return algorithm;
}
@Override
public boolean isAsymmetricAlgorithm() {
return true;
}
} }

View file

@ -34,4 +34,14 @@ public class MacSecretClientSignatureVerifierProvider implements ClientSignature
public SignatureVerifierContext verifier(ClientModel client, JWSInput input) throws VerificationException { public SignatureVerifierContext verifier(ClientModel client, JWSInput input) throws VerificationException {
return new ClientMacSignatureVerifierContext(session, client, algorithm); return new ClientMacSignatureVerifierContext(session, client, algorithm);
} }
@Override
public String getAlgorithm() {
return algorithm;
}
@Override
public boolean isAsymmetricAlgorithm() {
return false;
}
} }

View file

@ -23,6 +23,7 @@ import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.broker.social.SocialIdentityProvider; import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.common.Profile; import org.keycloak.common.Profile;
import org.keycloak.component.ComponentFactory; import org.keycloak.component.ComponentFactory;
import org.keycloak.crypto.ClientSignatureVerifierProvider;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType; import org.keycloak.events.admin.ResourceType;
@ -96,7 +97,21 @@ public class ServerInfoAdminResource {
info.setSystemInfo(SystemInfoRepresentation.create(session.getKeycloakSessionFactory().getServerStartupTimestamp())); info.setSystemInfo(SystemInfoRepresentation.create(session.getKeycloakSessionFactory().getServerStartupTimestamp()));
info.setMemoryInfo(MemoryInfoRepresentation.create()); info.setMemoryInfo(MemoryInfoRepresentation.create());
info.setProfileInfo(ProfileInfoRepresentation.create()); info.setProfileInfo(ProfileInfoRepresentation.create());
info.setCryptoInfo(CryptoInfoRepresentation.create());
// True - asymmetric algorithms, false - symmetric algorithms
Map<Boolean, List<String>> algorithms = session.getAllProviders(ClientSignatureVerifierProvider.class).stream()
.collect(
Collectors.toMap(
ClientSignatureVerifierProvider::isAsymmetricAlgorithm,
clientSignatureVerifier -> Collections.singletonList(clientSignatureVerifier.getAlgorithm()),
(l1, l2) -> listCombiner(l1, l2)
.stream()
.sorted()
.collect(Collectors.toList()),
HashMap::new
)
);
info.setCryptoInfo(CryptoInfoRepresentation.create(algorithms.get(false), algorithms.get(true)));
setSocialProviders(info); setSocialProviders(info);
setIdentityProviders(info); setIdentityProviders(info);

View file

@ -19,6 +19,7 @@ package org.keycloak.testsuite.admin;
import org.junit.Test; import org.junit.Test;
import org.keycloak.common.Version; import org.keycloak.common.Version;
import org.keycloak.crypto.Algorithm;
import org.keycloak.keys.Attributes; import org.keycloak.keys.Attributes;
import org.keycloak.keys.GeneratedRsaKeyProviderFactory; import org.keycloak.keys.GeneratedRsaKeyProviderFactory;
import org.keycloak.keys.KeyProvider; import org.keycloak.keys.KeyProvider;
@ -68,6 +69,11 @@ public class ServerInfoTest extends AbstractKeycloakTest {
assertNotNull(info.getSystemInfo()); assertNotNull(info.getSystemInfo());
assertNotNull(info.getCryptoInfo()); assertNotNull(info.getCryptoInfo());
Assert.assertNames(info.getCryptoInfo().getSupportedKeystoreTypes(), KeystoreUtils.getSupportedKeystoreTypes()); Assert.assertNames(info.getCryptoInfo().getSupportedKeystoreTypes(), KeystoreUtils.getSupportedKeystoreTypes());
Assert.assertNames(info.getCryptoInfo().getClientSignatureSymmetricAlgorithms(), Algorithm.HS256, Algorithm.HS384, Algorithm.HS512);
Assert.assertNames(info.getCryptoInfo().getClientSignatureAsymmetricAlgorithms(),
Algorithm.ES256, Algorithm.ES384, Algorithm.ES512,
Algorithm.PS256, Algorithm.PS384, Algorithm.PS512,
Algorithm.RS256, Algorithm.RS384, Algorithm.RS512);
ComponentTypeRepresentation rsaGeneratedProviderInfo = info.getComponentTypes().get(KeyProvider.class.getName()) ComponentTypeRepresentation rsaGeneratedProviderInfo = info.getComponentTypes().get(KeyProvider.class.getName())
.stream() .stream()