diff --git a/src/main/java/io/jsonwebtoken/SigningKeyResolver.java b/src/main/java/io/jsonwebtoken/SigningKeyResolver.java index b068db9d2..789a9c2c8 100644 --- a/src/main/java/io/jsonwebtoken/SigningKeyResolver.java +++ b/src/main/java/io/jsonwebtoken/SigningKeyResolver.java @@ -16,6 +16,7 @@ package io.jsonwebtoken; import java.security.Key; +import java.util.Collection; /** * A {@code SigningKeyResolver} can be used by a {@link io.jsonwebtoken.JwtParser JwtParser} to find a signing key that @@ -60,6 +61,17 @@ public interface SigningKeyResolver { */ Key resolveSigningKey(JwsHeader header, Claims claims); + /** + * Returns a collection signing key that should be used to attempt to validate a digital signature for the Claims JWS with the specified + * header and claims. This allows for a key rotation scenario to support multiple keys during an overlap period. + * + * @param header the header of the JWS to validate + * @param claims the claims (body) of the JWS to validate + * @return the signing key that should be used to validate a digital signature for the Claims JWS with the specified + * header and claims. + */ + Collection resolveSigningKeys(JwsHeader header, Claims claims); + /** * Returns the signing key that should be used to validate a digital signature for the Plaintext JWS with the * specified header and plaintext payload. @@ -70,4 +82,16 @@ public interface SigningKeyResolver { * specified header and plaintext payload. */ Key resolveSigningKey(JwsHeader header, String plaintext); + + /** + * Returns the signing key that should be used to attempt to validate a digital signature for the Plaintext JWS with the + * specified header and plaintext payload. This allows for a key rotation scenario to support multiple keys during an overlap period. + * + * @param header the header of the JWS to validate + * @param plaintext the plaintext body of the JWS to validate + * @return the signing key that should be used to validate a digital signature for the Plaintext JWS with the + * specified header and plaintext payload. + */ + Collection resolveSigningKeys(JwsHeader header, String plaintext); + } diff --git a/src/main/java/io/jsonwebtoken/SigningKeyResolverAdapter.java b/src/main/java/io/jsonwebtoken/SigningKeyResolverAdapter.java index 1be7ec556..53da6db44 100644 --- a/src/main/java/io/jsonwebtoken/SigningKeyResolverAdapter.java +++ b/src/main/java/io/jsonwebtoken/SigningKeyResolverAdapter.java @@ -19,6 +19,8 @@ import javax.crypto.spec.SecretKeySpec; import java.security.Key; +import java.util.ArrayList; +import java.util.Collection; /** * An Adapter implementation of the @@ -48,9 +50,28 @@ public Key resolveSigningKey(JwsHeader header, Claims claims) { "Override the resolveSigningKey(JwsHeader, Claims) method instead and return a " + "Key instance appropriate for the " + alg.name() + " algorithm."); byte[] keyBytes = resolveSigningKeyBytes(header, claims); + if (keyBytes == null) + return null; return new SecretKeySpec(keyBytes, alg.getJcaName()); } + @Override + public Collection resolveSigningKeys(JwsHeader header, Claims claims) { + SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm()); + Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, Claims) implementation cannot be " + + "used for asymmetric key algorithms (RSA, Elliptic Curve). " + + "Override the resolveSigningKey(JwsHeader, Claims) method instead and return a " + + "Key instance appropriate for the " + alg.name() + " algorithm."); + Collection keysBytes = resolveSigningKeysBytes(header, claims); + if (keysBytes == null) + return null; + Collection keys = new ArrayList(); + for (byte[] keyBytes: keysBytes) + keys.add(new SecretKeySpec(keyBytes, alg.getJcaName())); + + return keys; + } + @Override public Key resolveSigningKey(JwsHeader header, String plaintext) { SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm()); @@ -59,9 +80,28 @@ public Key resolveSigningKey(JwsHeader header, String plaintext) { "Override the resolveSigningKey(JwsHeader, String) method instead and return a " + "Key instance appropriate for the " + alg.name() + " algorithm."); byte[] keyBytes = resolveSigningKeyBytes(header, plaintext); + if (keyBytes == null) + return null; return new SecretKeySpec(keyBytes, alg.getJcaName()); } + @Override + public Collection resolveSigningKeys(JwsHeader header, String plaintext) { + SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm()); + Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, String) implementation cannot be " + + "used for asymmetric key algorithms (RSA, Elliptic Curve). " + + "Override the resolveSigningKey(JwsHeader, String) method instead and return a " + + "Key instance appropriate for the " + alg.name() + " algorithm."); + Collection keysBytes = resolveSigningKeysBytes(header, plaintext); + if (keysBytes == null) + return null; + Collection keys = new ArrayList(); + for (byte[] keyBytes: keysBytes) + keys.add(new SecretKeySpec(keyBytes, alg.getJcaName())); + + return keys; + } + /** * Convenience method invoked by {@link #resolveSigningKey(JwsHeader, Claims)} that obtains the necessary signing * key bytes. This implementation simply throws an exception: if the JWS parsed is a Claims JWS, you must @@ -81,6 +121,25 @@ public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { "resolveSigningKeyBytes(JwsHeader, Claims) method."); } + /** + * Convenience method invoked by {@link #resolveSigningKey(JwsHeader, Claims)} that obtains the necessary signing + * key bytes. This implementation simply throws an exception: if the JWS parsed is a Claims JWS, you must + * override this method or the {@link #resolveSigningKey(JwsHeader, Claims)} method instead. + * + *

NOTE: You cannot override this method when validating RSA signatures. If you expect RSA signatures, + * you must override the {@link #resolveSigningKey(JwsHeader, Claims)} method instead.

+ * + * @param header the parsed {@link JwsHeader} + * @param claims the parsed {@link Claims} + * @return the signing key bytes to use to verify the JWS signature. + */ + public Collection resolveSigningKeysBytes(JwsHeader header, Claims claims) { + throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " + + "Claims JWS signing key resolution. Consider overriding either the " + + "resolveSigningKey(JwsHeader, Claims) method or, for HMAC algorithms, the " + + "resolveSigningKeyBytes(JwsHeader, Claims) method."); + } + /** * Convenience method invoked by {@link #resolveSigningKey(JwsHeader, String)} that obtains the necessary signing * key bytes. This implementation simply throws an exception: if the JWS parsed is a plaintext JWS, you must @@ -96,4 +155,20 @@ public byte[] resolveSigningKeyBytes(JwsHeader header, String payload) { "resolveSigningKey(JwsHeader, String) method or, for HMAC algorithms, the " + "resolveSigningKeyBytes(JwsHeader, String) method."); } + + /** + * Convenience method invoked by {@link #resolveSigningKey(JwsHeader, String)} that obtains the necessary signing + * key bytes. This implementation simply throws an exception: if the JWS parsed is a plaintext JWS, you must + * override this method or the {@link #resolveSigningKey(JwsHeader, String)} method instead. + * + * @param header the parsed {@link JwsHeader} + * @param payload the parsed String plaintext payload + * @return the signing key bytes to use to verify the JWS signature. + */ + public Collection resolveSigningKeysBytes(JwsHeader header, String payload) { + throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " + + "plaintext JWS signing key resolution. Consider overriding either the " + + "resolveSigningKey(JwsHeader, String) method or, for HMAC algorithms, the " + + "resolveSigningKeyBytes(JwsHeader, String) method."); + } } diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index 4e4b9c79c..937c74029 100644 --- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -49,6 +49,8 @@ import java.io.IOException; import java.security.Key; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.Map; @@ -61,9 +63,9 @@ public class DefaultJwtParser implements JwtParser { private ObjectMapper objectMapper = new ObjectMapper(); - private byte[] keyBytes; + private Collection keyBytes; - private Key key; + private Collection keys; private SigningKeyResolver signingKeyResolver; @@ -141,21 +143,27 @@ public JwtParser setAllowedClockSkewSeconds(long seconds) { @Override public JwtParser setSigningKey(byte[] key) { Assert.notEmpty(key, "signing key cannot be null or empty."); - this.keyBytes = key; + if (this.keyBytes == null) + this.keyBytes = new ArrayList(); + this.keyBytes.add(key); return this; } @Override public JwtParser setSigningKey(String base64EncodedKeyBytes) { Assert.hasText(base64EncodedKeyBytes, "signing key cannot be null or empty."); - this.keyBytes = TextCodec.BASE64.decode(base64EncodedKeyBytes); + if (this.keyBytes == null) + this.keyBytes = new ArrayList(); + this.keyBytes.add(TextCodec.BASE64.decode(base64EncodedKeyBytes)); return this; } @Override public JwtParser setSigningKey(Key key) { Assert.notNull(key, "signing key cannot be null."); - this.key = key; + if (this.keys == null) + this.keys = new ArrayList(); + this.keys.add(key); return this; } @@ -297,26 +305,56 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, throw new MalformedJwtException(msg); } - if (key != null && keyBytes != null) { + if (keys != null && keyBytes != null) { throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either."); - } else if ((key != null || keyBytes != null) && signingKeyResolver != null) { - String object = key != null ? "a key object" : "key bytes"; + } else if ((keys != null || keyBytes != null) && signingKeyResolver != null) { + String object = keys != null ? "a key object" : "key bytes"; throw new IllegalStateException("A signing key resolver and " + object + " cannot both be specified. Choose either."); } //digitally signed, let's assert the signature: - Key key = this.key; + Collection keys = this.keys; + UnsupportedJwtException signingKeyResolverException = null; - if (key == null) { //fall back to keyBytes + if (keys == null) { //fall back to keyBytes - byte[] keyBytes = this.keyBytes; + //byte[] keyBytes = this.keyBytes; if (Objects.isEmpty(keyBytes) && signingKeyResolver != null) { //use the signingKeyResolver + keys = new ArrayList(); if (claims != null) { - key = signingKeyResolver.resolveSigningKey(jwsHeader, claims); + try { + Key key = this.signingKeyResolver.resolveSigningKey(jwsHeader, claims); + if (key != null) + keys.add(key); + } catch (UnsupportedJwtException e) { + signingKeyResolverException = e; + } + try { + Collection keyList = this.signingKeyResolver.resolveSigningKeys(jwsHeader, claims); + if (!Objects.isEmpty(keyList)) + keys.addAll(keyList); + } catch (UnsupportedJwtException e) { + signingKeyResolverException = e; + } } else { - key = signingKeyResolver.resolveSigningKey(jwsHeader, payload); + try { + Key key = this.signingKeyResolver.resolveSigningKey(jwsHeader, payload); + if (key != null) + keys.add(key); + } catch (UnsupportedJwtException e) { + signingKeyResolverException = e; + } + try { + Collection keyList = this.signingKeyResolver.resolveSigningKeys(jwsHeader, payload); + if (!Objects.isEmpty(keyList)) + keys.addAll(keyList); + } catch (UnsupportedJwtException e) { + signingKeyResolverException = e; + } } + if (keys.size() == 0 && signingKeyResolverException != null) + throw signingKeyResolverException; } if (!Objects.isEmpty(keyBytes)) { @@ -324,31 +362,40 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, Assert.isTrue(algorithm.isHmac(), "Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance."); - key = new SecretKeySpec(keyBytes, algorithm.getJcaName()); + keys = new ArrayList(); + for (byte[] bytes: this.keyBytes) + keys.add(new SecretKeySpec(bytes, algorithm.getJcaName())); } } - - Assert.notNull(key, "A signing key must be specified if the specified JWT is digitally signed."); + + Assert.notNull(keys, "A signing key must be specified if the specified JWT is digitally signed."); //re-create the jwt part without the signature. This is what needs to be signed for verification: String jwtWithoutSignature = base64UrlEncodedHeader + SEPARATOR_CHAR + base64UrlEncodedPayload; - JwtSignatureValidator validator; - try { - validator = createSignatureValidator(algorithm, key); - } catch (IllegalArgumentException e) { - String algName = algorithm.getValue(); - String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " + - "algorithm, but the specified signing key of type " + key.getClass().getName() + - " may not be used to validate " + algName + " signatures. Because the specified " + - "signing key reflects a specific and expected algorithm, and the JWT does not reflect " + - "this algorithm, it is likely that the JWT was not expected and therefore should not be " + - "trusted. Another possibility is that the parser was configured with the incorrect " + - "signing key, but this cannot be assumed for security reasons."; - throw new UnsupportedJwtException(msg, e); + boolean signatureOk = false; + for (Key key: keys) { + JwtSignatureValidator validator; + try { + validator = createSignatureValidator(algorithm, key); + } catch (IllegalArgumentException e) { + String algName = algorithm.getValue(); + String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " + + "algorithm, but the specified signing key of type " + key.getClass().getName() + + " may not be used to validate " + algName + " signatures. Because the specified " + + "signing key reflects a specific and expected algorithm, and the JWT does not reflect " + + "this algorithm, it is likely that the JWT was not expected and therefore should not be " + + "trusted. Another possibility is that the parser was configured with the incorrect " + + "signing key, but this cannot be assumed for security reasons."; + throw new UnsupportedJwtException(msg, e); + } + + if (validator.isValid(jwtWithoutSignature, base64UrlEncodedDigest)) { + signatureOk = true; + break; + } } - - if (!validator.isValid(jwtWithoutSignature, base64UrlEncodedDigest)) { + if (!signatureOk) { String msg = "JWT signature does not match locally computed signature. JWT validity cannot be " + "asserted and should not be trusted."; throw new SignatureException(msg); diff --git a/src/main/java/io/jsonwebtoken/lang/Objects.java b/src/main/java/io/jsonwebtoken/lang/Objects.java index eb475ac69..3038b5bb9 100644 --- a/src/main/java/io/jsonwebtoken/lang/Objects.java +++ b/src/main/java/io/jsonwebtoken/lang/Objects.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.lang.reflect.Array; import java.util.Arrays; +import java.util.Collection; public final class Objects { @@ -95,6 +96,16 @@ public static boolean isEmpty(Object[] array) { return (array == null || array.length == 0); } + /** + * Determine whether the given collection is empty: + * i.e. null or of zero length. + * + * @param array the Collection to check + */ + public static boolean isEmpty(Collection collection) { + return (collection == null || collection.isEmpty()); + } + /** * Returns {@code true} if the specified byte array is null or of zero length, {@code false} otherwise. * diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy index 187711fec..17071a7ae 100644 --- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy +++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy @@ -606,6 +606,35 @@ class JwtParserTest { assertEquals jws.getBody().getSubject(), subject } + @Test + void testParseClaimsWithSigningKeysResolver() { + + String subject = 'Joe' + + byte[] key = randomKey() + byte[] key2 = randomKey() + + String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() + + def signingKeyResolver = new SigningKeyResolverAdapter() { + @Override + byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { + return null + } + @Override + Collection resolveSigningKeysBytes(JwsHeader header, Claims claims) { + ArrayList list = new ArrayList() + list.add(key2) + list.add(key) + return list + } + } + + Jws jws = Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact) + + assertEquals jws.getBody().getSubject(), subject + } + @Test void testParseClaimsWithSigningKeyResolverInvalidKey() { @@ -630,6 +659,37 @@ class JwtParserTest { } } + @Test + void testParseClaimsWithSigningKeyResolverInvalidKeys() { + + String subject = 'Joe' + + byte[] key = randomKey() + + String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() + + def signingKeyResolver = new SigningKeyResolverAdapter() { + @Override + byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { + return null + } + @Override + Collection resolveSigningKeysBytes(JwsHeader header, Claims claims) { + ArrayList list = new ArrayList() + list.add(randomKey()) + list.add(randomKey()) + return list + } + } + + try { + Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact) + fail() + } catch (SignatureException se) { + assertEquals se.getMessage(), 'JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.' + } + } + @Test void testParseClaimsWithSigningKeyResolverAndKey() { @@ -654,6 +714,37 @@ class JwtParserTest { } } + @Test + void testParseClaimsWithSigningKeyResolverAndKeys() { + + String subject = 'Joe' + + SecretKeySpec key = new SecretKeySpec(randomKey(), "HmacSHA256") + + String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() + + def signingKeyResolver = new SigningKeyResolverAdapter() { + @Override + byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { + return null + } + @Override + Collection resolveSigningKeysBytes(JwsHeader header, Claims claims) { + ArrayList list = new ArrayList() + list.add(randomKey()) + list.add(randomKey()) + return list + } + } + + try { + Jwts.parser().setSigningKey(key).setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact) + fail() + } catch (IllegalStateException ise) { + assertEquals ise.getMessage(), 'A signing key resolver and a key object cannot both be specified. Choose either.' + } + } + @Test void testParseClaimsWithSigningKeyResolverAndKeyBytes() { @@ -678,6 +769,37 @@ class JwtParserTest { } } + @Test + void testParseClaimsWithSigningKeyResolverAndKeysBytes() { + + String subject = 'Joe' + + byte[] key = randomKey() + + String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() + + def signingKeyResolver = new SigningKeyResolverAdapter() { + @Override + byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { + return null + } + @Override + Collection resolveSigningKeysBytes(JwsHeader header, Claims claims) { + ArrayList list = new ArrayList() + list.add(randomKey()) + list.add(randomKey()) + return list + } + } + + try { + Jwts.parser().setSigningKey(key).setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact) + fail() + } catch (IllegalStateException ise) { + assertEquals ise.getMessage(), 'A signing key resolver and key bytes cannot both be specified. Choose either.' + } + } + @Test void testParseClaimsWithNullSigningKeyResolver() { @@ -716,6 +838,59 @@ class JwtParserTest { } } + @Test + void testParseClaimsWithSigningKeyResolverAdapterWithNoKey() { + + + String subject = 'Joe' + + byte[] key = randomKey() + + String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() + + def signingKeyResolver = new SigningKeyResolverAdapter() { + @Override + byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { + return null + } + } + + try { + Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact) + fail() + } catch (UnsupportedJwtException ex) { + assertEquals ex.getMessage(), 'The specified SigningKeyResolver implementation does not support ' + + 'Claims JWS signing key resolution. Consider overriding either the resolveSigningKey(JwsHeader, Claims) method ' + + 'or, for HMAC algorithms, the resolveSigningKeyBytes(JwsHeader, Claims) method.' + } + } + + @Test + void testParseClaimsWithSigningKeyResolverAdapterWithNoKeys() { + + String subject = 'Joe' + + byte[] key = randomKey() + + String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() + + def signingKeyResolver = new SigningKeyResolverAdapter() { + @Override + Collection resolveSigningKeysBytes(JwsHeader header, Claims claims) { + return null + } + } + + try { + Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact) + fail() + } catch (UnsupportedJwtException ex) { + assertEquals ex.getMessage(), 'The specified SigningKeyResolver implementation does not support ' + + 'Claims JWS signing key resolution. Consider overriding either the resolveSigningKey(JwsHeader, Claims) method ' + + 'or, for HMAC algorithms, the resolveSigningKeyBytes(JwsHeader, Claims) method.' + } + } + @Test void testParseClaimsJwsWithNumericTypes() { byte[] key = randomKey() @@ -772,6 +947,35 @@ class JwtParserTest { assertEquals jws.getBody(), inputPayload } + @Test + void testParsePlaintextJwsWithSigningKeyResolverAdapterMultipleKeys() { + + String inputPayload = 'Hello world!' + + byte[] key = randomKey() + byte[] key2 = randomKey() + + String compact = Jwts.builder().setPayload(inputPayload).signWith(SignatureAlgorithm.HS256, key).compact() + + def signingKeyResolver = new SigningKeyResolverAdapter() { + @Override + byte[] resolveSigningKeyBytes(JwsHeader header, String payload) { + return null + } + @Override + Collection resolveSigningKeysBytes(JwsHeader header, String payload) { + ArrayList list = new ArrayList() + list.add(key2) + list.add(key) + return list + } + } + + Jws jws = Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact) + + assertEquals jws.getBody(), inputPayload + } + @Test void testParsePlaintextJwsWithSigningKeyResolverInvalidKey() { @@ -796,6 +1000,37 @@ class JwtParserTest { } } + @Test + void testParsePlaintextJwsWithSigningKeyResolverInvalidMultipleKey() { + + String inputPayload = 'Hello world!' + + byte[] key = randomKey() + + String compact = Jwts.builder().setPayload(inputPayload).signWith(SignatureAlgorithm.HS256, key).compact() + + def signingKeyResolver = new SigningKeyResolverAdapter() { + @Override + byte[] resolveSigningKeyBytes(JwsHeader header, String payload) { + return null + } + @Override + Collection resolveSigningKeysBytes(JwsHeader header, String payload) { + ArrayList list = new ArrayList() + list.add(randomKey()) + list.add(randomKey()) + return list + } + } + + try { + Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact) + fail() + } catch (SignatureException se) { + assertEquals se.getMessage(), 'JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.' + } + } + @Test void testParsePlaintextJwsWithInvalidSigningKeyResolverAdapter() { @@ -817,6 +1052,58 @@ class JwtParserTest { } } + @Test + void testParsePlaintextJwsWithSigningKeyResolverAdapterWithNoKey() { + + String inputPayload = 'Hello world!' + + byte[] key = randomKey() + + String compact = Jwts.builder().setPayload(inputPayload).signWith(SignatureAlgorithm.HS256, key).compact() + + def signingKeyResolver = new SigningKeyResolverAdapter() { + @Override + byte[] resolveSigningKeyBytes(JwsHeader header, String payload) { + return null + } + } + + try { + Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact) + fail() + } catch (UnsupportedJwtException ex) { + assertEquals ex.getMessage(), 'The specified SigningKeyResolver implementation does not support plaintext ' + + 'JWS signing key resolution. Consider overriding either the resolveSigningKey(JwsHeader, String) ' + + 'method or, for HMAC algorithms, the resolveSigningKeyBytes(JwsHeader, String) method.' + } + } + + @Test + void testParsePlaintextJwsWithSigningKeyResolverAdapterWithNoKeys() { + + String inputPayload = 'Hello world!' + + byte[] key = randomKey() + + String compact = Jwts.builder().setPayload(inputPayload).signWith(SignatureAlgorithm.HS256, key).compact() + + def signingKeyResolver = new SigningKeyResolverAdapter() { + @Override + Collection resolveSigningKeysBytes(JwsHeader header, String payload) { + return null + } + } + + try { + Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact) + fail() + } catch (UnsupportedJwtException ex) { + assertEquals ex.getMessage(), 'The specified SigningKeyResolver implementation does not support plaintext ' + + 'JWS signing key resolution. Consider overriding either the resolveSigningKey(JwsHeader, String) ' + + 'method or, for HMAC algorithms, the resolveSigningKeyBytes(JwsHeader, String) method.' + } + } + @Test void testParseRequireDontAllowNullClaimName() { def expectedClaimValue = 'A Most Awesome Claim Value' @@ -1286,7 +1573,7 @@ class JwtParserTest { @Test void testParseRequireExpiration_Success() { // expire in the future - def expiration = new Date(System.currentTimeMillis() + 10000) + Date expiration = new Date(System.currentTimeMillis() + 10000) byte[] key = randomKey()