Skip to content

Commit

Permalink
[ELY-2564] Add the ability to disable OIDC access token typ claim val…
Browse files Browse the repository at this point in the history
…idation via a system property
  • Loading branch information
fjuma committed Jun 14, 2023
1 parent d541f51 commit 8a930f7
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 1 deletion.
20 changes: 20 additions & 0 deletions http/oidc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@
<artifactId>slf4j-jboss-logmanager</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
Expand All @@ -148,6 +153,21 @@
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public class Oidc {
static final String KEYCLOAK_CLIENT_CLUSTER_HOST = "client_cluster_host";
static final String KEYCLOAK_QUERY_BEARER_TOKEN = "k_query_bearer_token";
static final String DEFAULT_TOKEN_SIGNATURE_ALGORITHM = "RS256";
public static final String DISABLE_TYP_CLAIM_VALIDATION_PROPERTY_NAME = "wildfly.elytron.oidc.disable.typ.claim.validation";


// keycloak-specific request parameter used to specify the identifier of the identity provider that should be used to authenticate a user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@

import static org.wildfly.security.http.oidc.ElytronMessages.log;
import static org.wildfly.security.http.oidc.IDToken.AT_HASH;
import static org.wildfly.security.http.oidc.Oidc.DISABLE_TYP_CLAIM_VALIDATION_PROPERTY_NAME;
import static org.wildfly.security.http.oidc.Oidc.INVALID_AT_HASH_CLAIM;
import static org.wildfly.security.http.oidc.Oidc.INVALID_ISSUED_FOR_CLAIM;
import static org.wildfly.security.http.oidc.Oidc.INVALID_TYPE_CLAIM;
import static org.wildfly.security.http.oidc.Oidc.getJavaAlgorithmForHash;
import static org.wildfly.security.jose.jwk.JWKUtil.BASE64_URL;

import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedAction;
import java.util.Arrays;

import javax.crypto.SecretKey;
Expand All @@ -52,6 +55,17 @@
*/
public class TokenValidator {

static final boolean DISABLE_TYP_CLAIM_VALIDATION_PROPERTY;

static {
DISABLE_TYP_CLAIM_VALIDATION_PROPERTY = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return Boolean.parseBoolean(System.getProperty(DISABLE_TYP_CLAIM_VALIDATION_PROPERTY_NAME, "false"));
}
});
}

private static final int HEADER_INDEX = 0;
private JwtConsumerBuilder jwtConsumerBuilder;
private OidcClientConfiguration clientConfiguration;
Expand Down Expand Up @@ -98,7 +112,9 @@ public AccessToken parseAndVerifyToken(final String bearerToken) throws OidcExce
try {
JwtContext jwtContext = setVerificationKey(bearerToken, jwtConsumerBuilder);
jwtConsumerBuilder.setRequireSubject();
jwtConsumerBuilder.registerValidator(new TypeValidator("Bearer"));
if (! DISABLE_TYP_CLAIM_VALIDATION_PROPERTY) {
jwtConsumerBuilder.registerValidator(new TypeValidator("Bearer"));
}
if (clientConfiguration.isVerifyTokenAudience()) {
jwtConsumerBuilder.setExpectedAudience(clientConfiguration.getResourceName());
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2023 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.wildfly.security.http.oidc;

import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.RSASSASigner;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;

import javax.json.Json;
import javax.json.JsonObjectBuilder;

import mockit.Mock;
import mockit.MockUp;

/**
* Base test class for typ claim validation tests.
*
* @author <a href="mailto:fjuma@redhat.com">Farah Juma</a>
*/
public class TypClaimValidationBaseTest {

public static String ISSUER_URL = "http://localhost:8080/realms/myrealm";
public static String SUBJECT = "bf8ce366-0a74-4628-bd9a-1e69084ae558";

/**
* The issuerUrl gets set using OIDC discovery. Since this test class isn't
* making use of the Keycloak OpenID provider, we are mocking the return
* value for the issuer URL.
*
* @param issuerUrl
*/
protected static void mockIssuerUrl(String issuerUrl) {
Class<?> classToMock;
try {
classToMock = Class.forName("org.wildfly.security.http.oidc.OidcClientConfiguration",
true, OidcClientConfiguration.class.getClassLoader());
} catch (ClassNotFoundException e) {
throw new NoClassDefFoundError(e.getMessage());
}
new MockUp<Object>(classToMock) {
@Mock
public String getIssuerUrl() {
return issuerUrl;
}
};
}

protected static AccessToken testTokenValidationWithoutTypClaim() throws Exception {
KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
HardcodedPublicKeyLocator hardcodedPublicKeyLocator = new HardcodedPublicKeyLocator(keyPair.getPublic());

OidcClientConfiguration clientConfiguration = new OidcClientConfiguration();
clientConfiguration.setClientId("clientWithoutTyp");
clientConfiguration.setPublicKeyLocator(hardcodedPublicKeyLocator);
clientConfiguration.setProviderUrl(ISSUER_URL);
clientConfiguration.setPublicClient(true);
clientConfiguration.setPrincipalAttribute("preferred_username");
clientConfiguration.setSSLRequired(Oidc.SSLRequired.EXTERNAL);

TokenValidator tokenValidator = TokenValidator.builder(clientConfiguration).build();
return tokenValidator.parseAndVerifyToken(createJwt(keyPair, 60, "1"));
}

private static String createJwt(KeyPair keyPair, int expirationOffset, String kid) throws Exception {
PrivateKey privateKey = keyPair.getPrivate();
JWSSigner signer = new RSASSASigner(privateKey);
JsonObjectBuilder claimsBuilder = createClaims(expirationOffset);

JWSHeader.Builder headerBuilder = new JWSHeader.Builder(JWSAlgorithm.RS256)
.type(new JOSEObjectType("jwt"));
if (kid != null) {
headerBuilder.keyID(kid);
}

JWSObject jwsObject = new JWSObject(headerBuilder.build(), new Payload(claimsBuilder.build().toString()));
jwsObject.sign(signer);
return jwsObject.serialize();
}

private static JsonObjectBuilder createClaims(int expirationOffset) {
// typ claim not included
return Json.createObjectBuilder()
.add("sub", SUBJECT)
.add("iss", ISSUER_URL)
.add("aud", "account")
.add("exp", (System.currentTimeMillis() / 1000) + expirationOffset)
.add("azp", "app")
.add("scope", "profile email")
.add("preferred_username", "alice");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2023 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.wildfly.security.http.oidc;

import static org.junit.Assert.assertEquals;
import static org.wildfly.common.Assert.assertNotNull;
import static org.wildfly.security.http.oidc.Oidc.DISABLE_TYP_CLAIM_VALIDATION_PROPERTY_NAME;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

/**
* Tests for disabling typ claim validation.
*
* @author <a href="mailto:fjuma@redhat.com">Farah Juma</a>
*/
public class TypClaimValidationDisabledTest extends TypClaimValidationBaseTest {

private static String DISABLE_TYP_CLAIM_VALIDATION_PROPERTY;

@BeforeClass
public static void setUp() {
mockIssuerUrl(ISSUER_URL);
DISABLE_TYP_CLAIM_VALIDATION_PROPERTY = System.setProperty(DISABLE_TYP_CLAIM_VALIDATION_PROPERTY_NAME, "true");
}

@AfterClass
public static void tearDown() {
if (DISABLE_TYP_CLAIM_VALIDATION_PROPERTY == null) {
System.clearProperty(DISABLE_TYP_CLAIM_VALIDATION_PROPERTY_NAME);
} else {
System.setProperty(DISABLE_TYP_CLAIM_VALIDATION_PROPERTY_NAME, DISABLE_TYP_CLAIM_VALIDATION_PROPERTY);
}
}

@Test
public void testTokenWithoutTypClaimWithTypClaimValidationDisabled() throws Exception {
AccessToken accessToken = testTokenValidationWithoutTypClaim();
assertNotNull(accessToken);
assertEquals(ISSUER_URL, accessToken.getIssuer());
assertEquals("bf8ce366-0a74-4628-bd9a-1e69084ae558", accessToken.getSubject());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2023 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.wildfly.security.http.oidc;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.junit.BeforeClass;
import org.junit.Test;

/**
* Tests for typ claim validation.
*
* @author <a href="mailto:fjuma@redhat.com">Farah Juma</a>
*/
public class TypClaimValidationEnabledTest extends TypClaimValidationBaseTest {

private static String ISSUER_URL = "http://localhost:8080/realms/myrealm";

@BeforeClass
public static void setUp() {
mockIssuerUrl(ISSUER_URL);
}

@Test
public void testTokenWithoutTypClaimWithTypClaimValidationEnabled() throws Exception {
try {
testTokenValidationWithoutTypClaim();
fail("Expected exception not thrown");
} catch (OidcException e) {
assertTrue(e.getMessage().contains("Invalid bearer token"));
}
}
}

0 comments on commit 8a930f7

Please sign in to comment.