8000 Unhandled NPE with alg:none JWT in Bearer Authentication by mabartos · Pull Request #48798 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions services/src/main/java/org/keycloak/crypto/CryptoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.Map;
import java.util.stream.Collectors;

import org.keycloak.common.VerificationException;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.util.Strings;
Expand All @@ -36,6 +37,20 @@
*/
public class CryptoUtils {

/**
* Looks up a {@link SignatureProvider} for the given algorithm, throwing if none is registered.
*/
public static SignatureProvider getSignatureProvider(KeycloakSession session, String algorithm) throws VerificationException {
if (algorithm == null) {
throw new VerificationException("Missing token algorithm");
}
SignatureProvider signatureProvider = session.getProvider(SignatureProvider.class, algorithm);
if (signatureProvider == null) {
throw new VerificationException("Unsupported token algorithm: " + algorithm);
}
return signatureProvider;
}

/**
* Returns the supported asymmetric signature algorithms.
* This method discovers all available SignatureProvider implementations and filters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import org.keycloak.common.Profile;
import org.keycloak.common.Profile.Feature;
import org.keycloak.common.VerificationException;
import org.keycloak.crypto.SignatureProvider;
import org.keycloak.crypto.CryptoUtils;
import org.keycloak.crypto.SignatureVerifierContext;
import org.keycloak.http.HttpRequest;
import org.keycloak.models.Constants;
Expand Down Expand Up @@ -181,7 +181,7 @@ public static InviteOrgActionToken parseInvitationToken(KeycloakSession session,
.withChecks(TokenVerifier.IS_ACTIVE,
new TokenVerifier.RealmUrlCheck(Urls.realmIssuer(context.getUri().getBaseUri(), realm.getName())));

SignatureVerifierContext verifierContext = session.getProvider(SignatureProvider.class, verifier.getHeader().getAlgorithm().name()).verifier(verifier.getHeader().getKeyId());
SignatureVerifierContext verifierContext = CryptoUtils.getSignatureProvider(session, verifier.getHeader().getAlgorithm().name()).verifier(verifier.getHeader().getKeyId());
verifier.verifierContext(verifierContext);

return verifier.verify().getToken();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import org.keycloak.OAuth2Constants;
import org.keycloak.TokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.crypto.SignatureProvider;
import org.keycloak.crypto.CryptoUtils;
import org.keycloak.crypto.SignatureVerifierContext;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
Expand Down Expand Up @@ -226,7 +226,7 @@ protected boolean verifyToken(String tokenStr) {
TokenVerifier<T> verifier = TokenVerifier.create(tokenStr, getTokenClass())
.realmUrl(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));

SignatureVerifierContext verifierContext = session.getProvider(SignatureProvider.class, verifier.getHeader().getAlgorithm().name()).verifier(verifier.getHeader().getKeyId());
SignatureVerifierContext verifierContext = CryptoUtils.getSignatureProvider(session, verifier.getHeader().getAlgorithm().name()).verifier(verifier.getHeader().getKeyId());
verifier.verifierContext(verifierContext);

this.token = verifier.verify().getToken();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.keycloak.common.VerificationException;
import org.keycloak.crypto.CekManagementProvider;
import org.keycloak.crypto.ContentEncryptionProvider;
import org.keycloak.crypto.CryptoUtils;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.crypto.SignatureProvider;
import org.keycloak.crypto.SignatureSignerContext;
Expand Down Expand Up @@ -191,7 +192,7 @@ private Response issueUserInfo() {

verifier = DPoPUtil.withDPoPVerifier(verifier, realm, new DPoPUtil.Validator(session).request(request).uriInfo(session.getContext().getUri()).accessToken(tokenForUserInfo.getToken()));

SignatureVerifierContext verifier 8000 Context = session.getProvider(SignatureProvider.class, verifier.getHeader().getAlgorithm().name()).verifier(verifier.getHeader().getKeyId());
SignatureVerifierContext verifierContext = CryptoUtils.getSignatureProvider(session, verifier.getHeader().getAlgorithm().name()).verifier(verifier.getHeader().getKeyId());
verifier.verifierContext(verifierContext);

token = verifier.verify().getToken();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.keycloak.TokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.SecretGenerator;
import org.keycloak.crypto.CryptoUtils;
import org.keycloak.crypto.SignatureProvider;
import org.keycloak.crypto.SignatureSignerContext;
import org.keycloak.crypto.SignatureVerifierContext;
Expand Down Expand Up @@ -99,7 +100,7 @@ public static TokenVerification verifyToken(KeycloakSession session, RealmModel
TokenVerifier<AccessToken> verifier = TokenVerifier.create(token, AccessToken.class)
.withChecks(new TokenVerifier.RealmUrlCheck(getIssuer(session, realm)), TokenVerifier.IS_ACTIVE, new TokenRevocationCheck(session));

SignatureVerifierContext verifierContext = session.getProvider(SignatureProvider.class, verifier.getHeader().getAlgorithm().name()).verifier(verifier.getHeader().getKeyId());
SignatureVerifierContext verifierContext = CryptoUtils.getSignatureProvider(session, verifier.getHeader().getAlgorithm().name()).verifier(verifier.getHeader().getKeyId());
verifier.verifierContext(verifierContext);

kid = verifierContext.getKid();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
import org.keycloak.common.util.Time;
import org.keycloak.cookie.CookieProvider;
import org.keycloak.cookie.CookieType;
import org.keycloak.crypto.CryptoUtils;
import org.keycloak.crypto.SignatureProvider;
import org.keycloak.crypto.SignatureVerifierContext;
import org.keycloak.events.Details;
Expand Down Expand Up @@ -262,7 +263,7 @@ public static boolean expireUserSessionCookie(KeycloakSession session, UserSessi
String kid = verifier.getHeader().getKeyId();
String algorithm = verifier.getHeader().getAlgorithm().name();

SignatureVerifierContext signatureVerifier = session.getProvider(SignatureProvider.class, algorithm).verifier(kid);
SignatureVerifierContext signatureVerifier = CryptoUtils.getSignatureProvider(session, algorithm).verifier(kid);
verifier.verifierContext(signatureVerifier);

AccessToken token = verifier.verify().getToken();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.keycloak.common.Profile;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Time;
import org.keycloak.crypto.CryptoUtils;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.crypto.SignatureProvider;
import org.keycloak.crypto.SignatureVerifierContext;
Expand Down Expand Up @@ -209,7 +210,7 @@ private static DPoP validateDPoP(KeycloakSession session, URI uri, String method

key.setAlgorithm(header.getAlgorithm().name());

SignatureVerifierContext signatureVerifier = session.getProvider(SignatureProvider.class, algorithm).verifier(key);
SignatureVerifierContext signatureVerifier = CryptoUtils.getSignatureProvider(session, algorithm).verifier(key);
verifier.verifierContext(signatureVerifier);
verifier.withChecks(
DPoPClaimsCheck.INSTANCE,
Expand Down
40 changes: 40 additions & 0 deletions services/src/test/java/org/keycloak/crypto/CryptoUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.keycloak.crypto;

import org.keycloak.common.VerificationException;
import org.keycloak.utils.AbstractUtilSessionTest;

import org.junit.jupiter.api.Test;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class CryptoUtilsTest extends AbstractUtilSessionTest {

@Test
public void getSignatureProviderReturnsProviderForSupportedAlgorithm() throws VerificationException {
assertNotNull(CryptoUtils.getSignatureProvider(session, "RS256"));
}

@Test
public void getSignatureProviderThrowsForNoneAlgorithm() {
VerificationException thrown = assertThrows(VerificationException.class,
() -> CryptoUtils.getSignatureProvider(session, "none"));
assertThat(thrown.getMessage(), containsString("none"));
}

@Test
public void getSignatureProviderThrowsForUnknownAlgorithm() {
VerificationException thrown = assertThrows(VerificationException.class,
() -> CryptoUtils. 10BC8 getSignatureProvider(session, "FAKE256"));
assertThat(thrown.getMessage(), containsString("FAKE256"));
}

@Test
public void getSignatureProviderThrowsForNullAlgorithm() {
assertThrows(VerificationException.class,
() -> CryptoUtils.getSignatureProvider(session, null));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,22 @@
import java.nio.charset.StandardCharsets;
import java.util.Base64;

import org.keycloak.common.Profile;
import org.keycloak.common.VerificationException;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.http.HttpRequest;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.resteasy.HttpRequestImpl;
import org.keycloak.services.resteasy.ResteasyKeycloakSession;
import org.keycloak.services.resteasy.ResteasyKeycloakSessionFactory;
import org.keycloak.utils.AbstractUtilSessionTest;

import org.jboss.resteasy.mock.MockHttpRequest;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class DPoPUtilTests {
public class DPoPUtilTests extends AbstractUtilSessionTest {
private static final String URL = "http://localhost/test";
private static final long IAT = 1_000_000_000L;

private static KeycloakSession session;

@BeforeAll
public static void beforeAll() {
Profile.defaults();
CryptoIntegration.init(CryptoProvider.class.getClassLoader());
ResteasyKeycloakSessionFactory sessionFactory = new ResteasyKeycloakSessionFactory();
sessionFactory.init();
session = new ResteasyKeycloakSession(sessionFactory);
}

@Test
public void testRejectsUnsupportedJwkKeyType() {
String jwkJson = "{\"kty\":\"foo\",\"kid\":\"invalid-kid\",\"alg\":\"RS256\"}";
Expand Down
10BC8
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.keycloak.utils;

import org.keycloak.common.Profile;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.crypto.CryptoProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.resteasy.ResteasyKeycloakSession;
import org.keycloak.services.resteasy.ResteasyKeycloakSessionFactory;

import org.junit.jupiter.api.BeforeAll;

/**
* Abstract class for tests using some test session
*/
public abstract class AbstractUtilSessionTest {

protected static KeycloakSession session;

@BeforeAll
public static void beforeAll() {
Profile.defaults();
CryptoIntegration.init(CryptoProvider.class.getClassLoader());
ResteasyKeycloakSessionFactory sessionFactory = new ResteasyKeycloakSessionFactory();
sessionFactory.init();
session = new ResteasyKeycloakSession(sessionFactory);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.keycloak.tests.oauth;

import java.io.IOException;

import org.keycloak.OAuthErrorException;
import org.keycloak.common.util.SecretGenerator;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.oauth.OAuthClient;
import org.keycloak.testframework.oauth.annotations.InjectOAuthClient;
import org.keycloak.testsuite.util.oauth.IntrospectionResponse;
import org.keycloak.testsuite.util.oauth.UserInfoResponse;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

@KeycloakIntegrationTest
public class TokenInputValidationTest {

@InjectOAuthClient
OAuthClient oauth;

@Test
public void userInfoRejectsAlgNoneToken() {
String noneToken = new JWSBuilder().jsonContent(createDefaultToken()).none();
UserInfoResponse response = oauth.doUserInfoRequest(noneToken);

assertEquals(401, response.getStatusCode());
assertFalse(response.isSuccess());
assertEquals(OAuthErrorException.INVALID_TOKEN, response.getError());
}

@Test
public void introspectionRejectsAlgNoneToken() throws IOException {
String noneToken = new JWSBuilder().jsonContent(createDefaultToken()).none();
IntrospectionResponse response = oauth.doIntrospectionAccessTokenRequest(noneToken);

assertFalse(response.asJsonNode().get("active").asBoolean());
}

private JsonWebToken createDefaultToken() {
JsonWebToken token = new JsonWebToken();
token.id(SecretGenerator.getInstance().generateSecureID());
token.issuer("http://127.0.0.1:8500");
token.audience(oauth.getEndpoints().getIssuer());
token.iat((long) Time.currentTime());
token.exp((long) (Time.currentTime() + 300));
token.subject("attacker");
return token;
}

}
Loading
0