Skip to content

Commit

Permalink
Require that both RS256 and ES256 must be supported if the signature …
Browse files Browse the repository at this point in the history
…algorithm is not configured
  • Loading branch information
sberyozkin committed Sep 26, 2024
1 parent 1cb4964 commit edc4b7f
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 2 deletions.
3 changes: 2 additions & 1 deletion spec/src/main/asciidoc/configuration.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,8 @@ return Public Key text in one of the supported formats.
#### `mp.jwt.verify.publickey.algorithm`

The `mp.jwt.verify.publickey.algorithm` configuration property allows for specifying which Public Key Signature Algorithm
is supported by the MP JWT endpoint. This property can be be set to either `RS256` or `ES256`. Default value is `RS256`.
is supported by the MP JWT endpoint. This property can be be set to either `RS256` or `ES256`. If `mp.jwt.verify.publickey.algorithm` is not set then both `RS256` and `ES256` must be accepted.

Support for the other asymmetric signature algorithms such as `RS512`, `ES512` and others is optional.

`mp.jwt.verify.publickey.algorithm` will provide an additional hint how to read the Public Key in the PKCS#8 PEM format as both RSA and EC Public Keys in the PKCS#8 PEM format may only have a standard `-----BEGIN PUBLIC KEY-----` header and footer.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
* loading the META-INF/MPJWTTESTVERSION resource from the test war and converting it to the MpJwtTestVersion value.
*/
public enum MpJwtTestVersion {
MPJWT_V_1_0, MPJWT_V_1_1, MPJWT_V_1_2, MPJWT_V_2_1;
MPJWT_V_1_0, MPJWT_V_1_1, MPJWT_V_1_2, MPJWT_V_2_1, MPJWT_V_2_2;

public static final String VERSION_LOCATION = "META-INF/MPJWTTESTVERSION";
public static final String MANIFEST_NAME = "MPJWTTESTVERSION";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2016-2018 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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.eclipse.microprofile.jwt.tck.config;

import java.io.StringReader;

import org.eclipse.microprofile.jwt.Claim;
import org.eclipse.microprofile.jwt.ClaimValue;
import org.eclipse.microprofile.jwt.Claims;

import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import jakarta.json.JsonReader;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

/**
* The common endpoint used by the various config tests
*/
@RequestScoped
@Path("/endp")
public class RS256OrES256Endpoint {
@Inject
@Claim(standard = Claims.raw_token)
private ClaimValue<String> rawToken;

@GET
@Path("/verifyToken")
@Produces(MediaType.TEXT_PLAIN)
@RolesAllowed("Tester")
public String verifyRS256Token() {
return getAlgorithm();
}

private String getAlgorithm() {
JsonReader jsonReader = Json.createReader(new StringReader(rawToken.getValue().split(".")[0]));
JsonObject headers = jsonReader.readObject();
return headers.getString("alg");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright (c) 2016-2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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.eclipse.microprofile.jwt.tck.config;

import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN;
import static org.eclipse.microprofile.jwt.tck.TCKConstants.TEST_GROUP_CONFIG;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.PrivateKey;

import org.eclipse.microprofile.jwt.tck.container.jaxrs.TCKApplication;
import org.eclipse.microprofile.jwt.tck.util.MpJwtTestVersion;
import org.eclipse.microprofile.jwt.tck.util.TokenUtils;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.Assert;
import org.testng.Reporter;
import org.testng.annotations.Test;

import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;

/**
* Validate that if mp.jwt.verify.signature.algorithm is not configured, then both RS256 and ES256 signatures must be
* accepted.
*/
public class RsaAndEcSignatureAlgorithmTest extends Arquillian {

/**
* The base URL for the container under test
*/
@ArquillianResource
private URL baseURL;

/**
* Create a CDI aware base web application archive
*
* @return the base base web application archive
* @throws IOException
* - on resource failure
*/
@Deployment()
public static WebArchive createDeployment() throws IOException {
URL config =
RsaAndEcSignatureAlgorithmTest.class.getResource("/META-INF/microprofile-config-rsa-ec.properties");

WebArchive webArchive = ShrinkWrap
.create(WebArchive.class, "RsaAndEcSignatureAlgorithmTest.war")
.addAsManifestResource(new StringAsset(MpJwtTestVersion.MPJWT_V_2_2.name()),
MpJwtTestVersion.MANIFEST_NAME)
.addClass(RS256OrES256Endpoint.class)
.addClass(TCKApplication.class)
.addClass(SimpleTokenUtils.class)
.addAsWebInfResource("beans.xml", "beans.xml")
.addAsManifestResource(config, "microprofile-config.properties");
return webArchive;
}

@RunAsClient
@Test(groups = TEST_GROUP_CONFIG, description = "Validate that the ES256 signed token is accepted")
public void testES256Token() throws Exception {
Reporter.log("testES256Token, expect HTTP_OK");

PrivateKey privateKey = TokenUtils.readECPrivateKey("/ecPrivateKey.pem");
String kid = "eckey";
String token = TokenUtils.signClaims(privateKey, kid, "/Token1.json");

String uri = baseURL.toExternalForm() + "endp/verifyToken";
WebTarget echoEndpointTarget = ClientBuilder.newClient()
.target(uri);
Response response =
echoEndpointTarget.request(TEXT_PLAIN).header(HttpHeaders.AUTHORIZATION, "Bearer " + token).get();
Assert.assertEquals(response.getStatus(), HttpURLConnection.HTTP_OK);
String replyString = response.readEntity(String.class);
Assert.assertEquals("ES256", replyString);
}

@RunAsClient
@Test(groups = TEST_GROUP_CONFIG, description = "Validate that the RS256 signed token is accepted")
public void testRS256Token() throws Exception {
Reporter.log("testRS256Token, expect HTTP_OK");

PrivateKey privateKey = TokenUtils.readECPrivateKey("/privateKey4k.pem");
String kid = "rskey";
String token = TokenUtils.signClaims(privateKey, kid, "/Token1.json");

String uri = baseURL.toExternalForm() + "endp/verifyToken";
WebTarget echoEndpointTarget = ClientBuilder.newClient()
.target(uri);
Response response =
echoEndpointTarget.request(TEXT_PLAIN).header(HttpHeaders.AUTHORIZATION, "Bearer " + token).get();
Assert.assertEquals(response.getStatus(), HttpURLConnection.HTTP_OK);
String replyString = response.readEntity(String.class);
Assert.assertEquals("RS256", replyString);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#
# Copyright (c) 2011-2022 Contributors to the Eclipse Foundation
#
# See the NOTICE file(s) distributed with this work for additional
# information regarding copyright ownership.
#
# 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.
#

# A reference to the publicKey4k.pem contents embedded location
mp.jwt.verify.publickey.location=/rs256es256.jwk
mp.jwt.verify.issuer=https://server.example.com
21 changes: 21 additions & 0 deletions tck/src/test/resources/rs256es256.jwk
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"kid": "rskey",
"e": "AQAB",
"n": "tL6HShqY5H4y56rsCo7VdhT9_eLQwsJpKWg66j98XsB_qc5ZxkJ25GXCzpjR0ZvzAxMNlj1hrMORaKVzz2_5axZgF1eZfzgrNyQ9rtGaBtMNAB20jLsoYp5psRTaYxKeOiLHPr3956ukSRUF9YfJGSamrvGOwC8h6zbq6uaydv-FVJXijlMD_iCggUfoirtVOWK_X1IzV7covxcGzT0X019_4RbtjLdnvqZnGqmpHQpBEItI-4gNvaKR8NDWUxAjO_v-oOKR5nEUnDWcQSCxKmyQrVJtHr9PBwWrHzTSx4k1L1hLf-AWXAdy_r6c0Lzgt5knmZTyWDG2-n8SlrXxHHxFO1Wz8H_OKBzTAf8zIuj2lkXYo-M6aoJM7qQmTys80dtYvnaHGSl-jpe2plMbS9RS4XcHH7vCqJc9acBnp9CvLgjOmA0b5Rc0WyN4sn1SDFYe6HZcVo4YGTbtTTlwgu_ozQ1x-xpTAaU0mWkHMwT0CO79rPORjhDXokEuduvtp6VUiAaoFF6Y3QQLf6O3P9p8yghpBBLb460lEQqOHQQGP0EK46cU81dlcD5lYE0TayDzb9pZZWUyjIE4ElzyW7wgI4xw7czdBalN-IhXKfGUCqIDVh7X7JpmskZMaRixf424yBcZLntEejZy59yLDSssHMc_bqnBraXuo8JBEPk"
},
{
"kty": "EC",
"use": "sig",
"alg": "ES256",
"kid": "eckey",
"crv":"P-256",
"x":"w4HohvwOj21FBQE1PrJOAlPRQMyWimmXH9rIHa7YMTU",
"y":"osZEjUhZa79-kClcGm79eX0q_QFLlrA99MhkzNy6MtI"
}
]
}
1 change: 1 addition & 0 deletions tck/src/test/resources/suites/tck-base-suite.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<class name="org.eclipse.microprofile.jwt.tck.config.ECPublicKeyAsPEMTest" />
<class name="org.eclipse.microprofile.jwt.tck.config.ECPublicKeyAsPEMLocationTest" />
<class name="org.eclipse.microprofile.jwt.tck.config.ECPublicKeyAsJWKLocationTest" />
<class name="org.eclipse.microprofile.jwt.tck.config.RsaAndEcSignatureAlgorithmTest" />
<class name="org.eclipse.microprofile.jwt.tck.config.IssValidationTest" />
<class name="org.eclipse.microprofile.jwt.tck.config.IssValidationFailTest" />
<class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.CookieTokenTest" />
Expand Down
1 change: 1 addition & 0 deletions tck/src/test/resources/suites/tck-full-suite.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<class name="org.eclipse.microprofile.jwt.tck.config.ECPublicKeyAsPEMTest" />
<class name="org.eclipse.microprofile.jwt.tck.config.ECPublicKeyAsPEMLocationTest" />
<class name="org.eclipse.microprofile.jwt.tck.config.ECPublicKeyAsJWKLocationTest" />
<class name="org.eclipse.microprofile.jwt.tck.config.RsaAndEcSignatureAlgorithmTest" />
<class name="org.eclipse.microprofile.jwt.tck.config.IssValidationTest" />
<class name="org.eclipse.microprofile.jwt.tck.config.IssValidationFailTest" />
<class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.CookieTokenTest" />
Expand Down

0 comments on commit edc4b7f

Please sign in to comment.