Skip to content

Commit 65c766b

Browse files
committed
Require that both RS256 and ES256 must be supported if the signature algorithm is not configured
1 parent 1cb4964 commit 65c766b

File tree

9 files changed

+245
-13
lines changed

9 files changed

+245
-13
lines changed

spec/src/main/asciidoc/configuration.asciidoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,8 @@ return Public Key text in one of the supported formats.
356356
#### `mp.jwt.verify.publickey.algorithm`
357357

358358
The `mp.jwt.verify.publickey.algorithm` configuration property allows for specifying which Public Key Signature Algorithm
359-
is supported by the MP JWT endpoint. This property can be be set to either `RS256` or `ES256`. Default value is `RS256`.
359+
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.
360+
360361
Support for the other asymmetric signature algorithms such as `RS512`, `ES512` and others is optional.
361362

362363
`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.

tck/src/main/java/org/eclipse/microprofile/jwt/tck/util/MpJwtTestVersion.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
* loading the META-INF/MPJWTTESTVERSION resource from the test war and converting it to the MpJwtTestVersion value.
2525
*/
2626
public enum MpJwtTestVersion {
27-
MPJWT_V_1_0, MPJWT_V_1_1, MPJWT_V_1_2, MPJWT_V_2_1;
27+
MPJWT_V_1_0, MPJWT_V_1_1, MPJWT_V_1_2, MPJWT_V_2_1, MPJWT_V_2_2;
2828

2929
public static final String VERSION_LOCATION = "META-INF/MPJWTTESTVERSION";
3030
public static final String MANIFEST_NAME = "MPJWTTESTVERSION";

tck/src/test/java/org/eclipse/microprofile/jwt/tck/config/PublicKeyEndpoint.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.security.interfaces.ECPublicKey;
3232
import java.security.interfaces.RSAPublicKey;
3333
import java.util.Base64;
34+
import java.util.List;
3435
import java.util.Optional;
3536
import java.util.logging.Logger;
3637

@@ -73,8 +74,8 @@ public class PublicKeyEndpoint {
7374
private Optional<String> location;
7475

7576
@Inject
76-
@ConfigProperty(name = Names.VERIFIER_PUBLIC_KEY_ALGORITHM, defaultValue = "RS256")
77-
private String algorithm;
77+
@ConfigProperty(name = Names.VERIFIER_PUBLIC_KEY_ALGORITHM)
78+
private List<String> algorithm;
7879

7980
@Inject
8081
@ConfigProperty(name = Names.ISSUER)
@@ -116,15 +117,15 @@ public JsonObject verifyKeyAsPEM() {
116117

117118
// Check the key exists and is a valid PEM public key
118119
try {
119-
if ("RS256".equals(algorithm)) {
120+
if (algorithm.contains("RS256")) {
120121
PublicKey publicKey = SimpleTokenUtils.decodePublicKey(key.orElse("bad-key"));
121122
if (publicKey instanceof RSAPublicKey) {
122123
msg += " | key as PEM PASS";
123124
pass = true;
124125
} else {
125126
pass = false;
126127
}
127-
} else if ("ES256".equals(algorithm)) {
128+
} else if (algorithm.contains("ES256")) {
128129
PublicKey publicKey = SimpleTokenUtils.decodeECPublicKey(key.orElse("bad-key"));
129130
if (publicKey instanceof ECPublicKey) {
130131
msg += " | key as PEM PASS";
@@ -161,7 +162,7 @@ public JsonObject verifyKeyLocationAsPEMResource() {
161162
try {
162163
String pemValue = SimpleTokenUtils.readResource(locationValue);
163164
log.info(String.format("verifyKeyLocationAsPEMResource, locationValue=%s", pemValue));
164-
if ("RS256".equals(algorithm)) {
165+
if (algorithm.contains("RS256")) {
165166
PublicKey publicKey = SimpleTokenUtils.decodePublicKey(pemValue);
166167
if (publicKey instanceof RSAPublicKey) {
167168
log.info(String.format("verifyKeyLocationAsPEMResource, publicKey=%s", publicKey));
@@ -170,7 +171,7 @@ public JsonObject verifyKeyLocationAsPEMResource() {
170171
} else {
171172
pass = false;
172173
}
173-
} else if ("ES256".equals(algorithm)) {
174+
} else if (algorithm.contains("ES256")) {
174175
PublicKey publicKey = SimpleTokenUtils.decodeECPublicKey(pemValue);
175176
if (publicKey instanceof ECPublicKey) {
176177
log.info(String.format("verifyKeyLocationAsPEMResource, publicKey=%s", publicKey));
@@ -360,7 +361,7 @@ public JsonObject verifyKeyLocationAsJWKResource(@QueryParam("kid") String kid)
360361
StringBuilder msgBuilder = new StringBuilder();
361362
JsonObject jwk = Json.createReader(new StringReader(jwkValue)).readObject();
362363
if (verifyJWK(jwk, kid, msgBuilder)) {
363-
if ("RS256".equals(algorithm)) {
364+
if (algorithm.contains("RS256")) {
364365
PublicKey publicKey = SimpleTokenUtils.decodeJWKSPublicKey(jwkValue);
365366
log.info(String.format("verifyKeyLocationAsJWKResource, publicKey=%s", publicKey));
366367
}
@@ -452,7 +453,7 @@ public JsonObject verifyKeyLocationAsJWKSUrl(@QueryParam("kid") String kid) {
452453
log.info(String.format("verifyKeyLocationAsJWKSUrl, locationValue=%s", jwksContents.toString()));
453454
StringBuilder msgBuilder = new StringBuilder();
454455
if (verifyJWKS(jwksContents.toString(), kid, msgBuilder)) {
455-
if ("RS256".equals(algorithm)) {
456+
if (algorithm.contains("RS256")) {
456457
PublicKey publicKey = SimpleTokenUtils.decodeJWKSPublicKey(jwksContents.toString());
457458
log.info(String.format("verifyKeyLocationAsJWKSResource, publicKey=%s", publicKey));
458459
}
@@ -602,7 +603,7 @@ private boolean verifyJWK(JsonObject key, String kid, StringBuilder msg) {
602603

603604
boolean pass = true;
604605

605-
String expectedKty = "RS256".equals(algorithm) ? "RSA" : "EC";
606+
String expectedKty = algorithm.contains("RS256") ? "RSA" : "EC";
606607
if (!key.getJsonString("kty").getString().equals(expectedKty)) {
607608
msg.append("key != " + expectedKty);
608609
pass = false;
@@ -620,7 +621,7 @@ private boolean verifyJWK(JsonObject key, String kid, StringBuilder msg) {
620621
msg.append("alg != " + algorithm);
621622
pass = false;
622623
}
623-
if ("RS256".equals(algorithm)) {
624+
if (algorithm.contains("RS256")) {
624625
if (!key.getJsonString("e").getString().equals("AQAB")) {
625626
msg.append("e != AQAB");
626627
pass = false;
@@ -629,7 +630,7 @@ private boolean verifyJWK(JsonObject key, String kid, StringBuilder msg) {
629630
msg.append("n != tL6HShqY5H4y56rsCo7VdhT9...");
630631
pass = false;
631632
}
632-
} else if ("ES256".equals(algorithm)) {
633+
} else if (algorithm.contains("ES256")) {
633634
if (!key.getJsonString("crv").getString().equals("P-256")) {
634635
msg.append("crv != P-256");
635636
pass = false;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) 2016-2018 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* You may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
package org.eclipse.microprofile.jwt.tck.config;
21+
22+
import java.io.StringReader;
23+
24+
import org.eclipse.microprofile.jwt.Claim;
25+
import org.eclipse.microprofile.jwt.ClaimValue;
26+
import org.eclipse.microprofile.jwt.Claims;
27+
28+
import jakarta.annotation.security.RolesAllowed;
29+
import jakarta.enterprise.context.RequestScoped;
30+
import jakarta.inject.Inject;
31+
import jakarta.json.Json;
32+
import jakarta.json.JsonObject;
33+
import jakarta.json.JsonReader;
34+
import jakarta.ws.rs.GET;
35+
import jakarta.ws.rs.Path;
36+
import jakarta.ws.rs.Produces;
37+
import jakarta.ws.rs.core.MediaType;
38+
39+
/**
40+
* The common endpoint used by the various config tests
41+
*/
42+
@RequestScoped
43+
@Path("/endp")
44+
public class RS256OrES256Endpoint {
45+
@Inject
46+
@Claim(standard = Claims.raw_token)
47+
private ClaimValue<String> rawToken;
48+
49+
@GET
50+
@Path("/verifyToken")
51+
@Produces(MediaType.TEXT_PLAIN)
52+
@RolesAllowed("Tester")
53+
public String verifyToken() {
54+
return getAlgorithm();
55+
}
56+
57+
private String getAlgorithm() {
58+
JsonReader jsonReader = Json.createReader(new StringReader(rawToken.getValue().split(".")[0]));
59+
JsonObject headers = jsonReader.readObject();
60+
return headers.getString("alg");
61+
}
62+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Copyright (c) 2016-2020 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* You may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
package org.eclipse.microprofile.jwt.tck.config;
21+
22+
import static jakarta.ws.rs.core.MediaType.TEXT_PLAIN;
23+
import static org.eclipse.microprofile.jwt.tck.TCKConstants.TEST_GROUP_CONFIG;
24+
25+
import java.io.IOException;
26+
import java.net.HttpURLConnection;
27+
import java.net.URL;
28+
import java.security.PrivateKey;
29+
30+
import org.eclipse.microprofile.jwt.tck.container.jaxrs.TCKApplication;
31+
import org.eclipse.microprofile.jwt.tck.util.MpJwtTestVersion;
32+
import org.eclipse.microprofile.jwt.tck.util.TokenUtils;
33+
import org.jboss.arquillian.container.test.api.Deployment;
34+
import org.jboss.arquillian.container.test.api.RunAsClient;
35+
import org.jboss.arquillian.test.api.ArquillianResource;
36+
import org.jboss.arquillian.testng.Arquillian;
37+
import org.jboss.shrinkwrap.api.ShrinkWrap;
38+
import org.jboss.shrinkwrap.api.asset.StringAsset;
39+
import org.jboss.shrinkwrap.api.spec.WebArchive;
40+
import org.testng.Assert;
41+
import org.testng.Reporter;
42+
import org.testng.annotations.Test;
43+
44+
import jakarta.ws.rs.client.ClientBuilder;
45+
import jakarta.ws.rs.client.WebTarget;
46+
import jakarta.ws.rs.core.HttpHeaders;
47+
import jakarta.ws.rs.core.Response;
48+
49+
/**
50+
* Validate that if mp.jwt.verify.publickey.algorithm is not configured, then both RS256 and ES256 signatures must be
51+
* accepted.
52+
*/
53+
public class RsaAndEcSignatureAlgorithmTest extends Arquillian {
54+
55+
/**
56+
* The base URL for the container under test
57+
*/
58+
@ArquillianResource
59+
private URL baseURL;
60+
61+
/**
62+
* Create a CDI aware base web application archive
63+
*
64+
* @return the base base web application archive
65+
* @throws IOException
66+
* - on resource failure
67+
*/
68+
@Deployment()
69+
public static WebArchive createDeployment() throws IOException {
70+
URL config =
71+
RsaAndEcSignatureAlgorithmTest.class.getResource("/META-INF/microprofile-config-rsa-ec.properties");
72+
73+
WebArchive webArchive = ShrinkWrap
74+
.create(WebArchive.class, "RsaAndEcSignatureAlgorithmTest.war")
75+
.addAsManifestResource(new StringAsset(MpJwtTestVersion.MPJWT_V_2_2.name()),
76+
MpJwtTestVersion.MANIFEST_NAME)
77+
.addClass(RS256OrES256Endpoint.class)
78+
.addClass(TCKApplication.class)
79+
.addClass(SimpleTokenUtils.class)
80+
.addAsWebInfResource("beans.xml", "beans.xml")
81+
.addAsManifestResource(config, "microprofile-config.properties");
82+
return webArchive;
83+
}
84+
85+
@RunAsClient
86+
@Test(groups = TEST_GROUP_CONFIG, description = "Validate that the ES256 signed token is accepted")
87+
public void testES256Token() throws Exception {
88+
Reporter.log("testES256Token, expect HTTP_OK");
89+
90+
PrivateKey privateKey = TokenUtils.readECPrivateKey("/ecPrivateKey.pem");
91+
String kid = "eckey";
92+
String token = TokenUtils.signClaims(privateKey, kid, "/Token1.json");
93+
94+
String uri = baseURL.toExternalForm() + "endp/verifyToken";
95+
WebTarget echoEndpointTarget = ClientBuilder.newClient()
96+
.target(uri);
97+
Response response =
98+
echoEndpointTarget.request(TEXT_PLAIN).header(HttpHeaders.AUTHORIZATION, "Bearer " + token).get();
99+
Assert.assertEquals(response.getStatus(), HttpURLConnection.HTTP_OK);
100+
String replyString = response.readEntity(String.class);
101+
Assert.assertEquals("ES256", replyString);
102+
}
103+
104+
@RunAsClient
105+
@Test(groups = TEST_GROUP_CONFIG, description = "Validate that the RS256 signed token is accepted")
106+
public void testRS256Token() throws Exception {
107+
Reporter.log("testRS256Token, expect HTTP_OK");
108+
109+
PrivateKey privateKey = TokenUtils.readPrivateKey("/privateKey4k.pem");
110+
String kid = "rskey";
111+
String token = TokenUtils.signClaims(privateKey, kid, "/Token1.json");
112+
113+
String uri = baseURL.toExternalForm() + "endp/verifyToken";
114+
WebTarget echoEndpointTarget = ClientBuilder.newClient()
115+
.target(uri);
116+
Response response =
117+
echoEndpointTarget.request(TEXT_PLAIN).header(HttpHeaders.AUTHORIZATION, "Bearer " + token).get();
118+
Assert.assertEquals(response.getStatus(), HttpURLConnection.HTTP_OK);
119+
String replyString = response.readEntity(String.class);
120+
Assert.assertEquals("RS256", replyString);
121+
}
122+
123+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#
2+
# Copyright (c) 2011-2022 Contributors to the Eclipse Foundation
3+
#
4+
# See the NOTICE file(s) distributed with this work for additional
5+
# information regarding copyright ownership.
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# You may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
#
19+
20+
# A reference to the publicKey4k.pem contents embedded location
21+
mp.jwt.verify.publickey.location=/rs256es256.jwk
22+
mp.jwt.verify.issuer=https://server.example.com

tck/src/test/resources/rs256es256.jwk

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"keys": [
3+
{
4+
"kty": "RSA",
5+
"use": "sig",
6+
"alg": "RS256",
7+
"kid": "rskey",
8+
"e": "AQAB",
9+
"n": "tL6HShqY5H4y56rsCo7VdhT9_eLQwsJpKWg66j98XsB_qc5ZxkJ25GXCzpjR0ZvzAxMNlj1hrMORaKVzz2_5axZgF1eZfzgrNyQ9rtGaBtMNAB20jLsoYp5psRTaYxKeOiLHPr3956ukSRUF9YfJGSamrvGOwC8h6zbq6uaydv-FVJXijlMD_iCggUfoirtVOWK_X1IzV7covxcGzT0X019_4RbtjLdnvqZnGqmpHQpBEItI-4gNvaKR8NDWUxAjO_v-oOKR5nEUnDWcQSCxKmyQrVJtHr9PBwWrHzTSx4k1L1hLf-AWXAdy_r6c0Lzgt5knmZTyWDG2-n8SlrXxHHxFO1Wz8H_OKBzTAf8zIuj2lkXYo-M6aoJM7qQmTys80dtYvnaHGSl-jpe2plMbS9RS4XcHH7vCqJc9acBnp9CvLgjOmA0b5Rc0WyN4sn1SDFYe6HZcVo4YGTbtTTlwgu_ozQ1x-xpTAaU0mWkHMwT0CO79rPORjhDXokEuduvtp6VUiAaoFF6Y3QQLf6O3P9p8yghpBBLb460lEQqOHQQGP0EK46cU81dlcD5lYE0TayDzb9pZZWUyjIE4ElzyW7wgI4xw7czdBalN-IhXKfGUCqIDVh7X7JpmskZMaRixf424yBcZLntEejZy59yLDSssHMc_bqnBraXuo8JBEPk"
10+
},
11+
{
12+
"kty": "EC",
13+
"use": "sig",
14+
"alg": "ES256",
15+
"kid": "eckey",
16+
"crv":"P-256",
17+
"x":"w4HohvwOj21FBQE1PrJOAlPRQMyWimmXH9rIHa7YMTU",
18+
"y":"osZEjUhZa79-kClcGm79eX0q_QFLlrA99MhkzNy6MtI"
19+
}
20+
]
21+
}

tck/src/test/resources/suites/tck-base-suite.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
<class name="org.eclipse.microprofile.jwt.tck.config.ECPublicKeyAsPEMTest" />
7373
<class name="org.eclipse.microprofile.jwt.tck.config.ECPublicKeyAsPEMLocationTest" />
7474
<class name="org.eclipse.microprofile.jwt.tck.config.ECPublicKeyAsJWKLocationTest" />
75+
<class name="org.eclipse.microprofile.jwt.tck.config.RsaAndEcSignatureAlgorithmTest" />
7576
<class name="org.eclipse.microprofile.jwt.tck.config.IssValidationTest" />
7677
<class name="org.eclipse.microprofile.jwt.tck.config.IssValidationFailTest" />
7778
<class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.CookieTokenTest" />

tck/src/test/resources/suites/tck-full-suite.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
<class name="org.eclipse.microprofile.jwt.tck.config.ECPublicKeyAsPEMTest" />
7373
<class name="org.eclipse.microprofile.jwt.tck.config.ECPublicKeyAsPEMLocationTest" />
7474
<class name="org.eclipse.microprofile.jwt.tck.config.ECPublicKeyAsJWKLocationTest" />
75+
<class name="org.eclipse.microprofile.jwt.tck.config.RsaAndEcSignatureAlgorithmTest" />
7576
<class name="org.eclipse.microprofile.jwt.tck.config.IssValidationTest" />
7677
<class name="org.eclipse.microprofile.jwt.tck.config.IssValidationFailTest" />
7778
<class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.CookieTokenTest" />

0 commit comments

Comments
 (0)