8000 Issue #8 Add ability to resolve signing key based on Jws embedded values... by josebarrueta · Pull Request #9 · jwtk/jjwt · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Issue #8 Add ability to resolve signing key based on Jws embedded values... #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 20, 2014
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 src/main/java/io/jsonwebtoken/JwtParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ public interface JwtParser {
*/
JwtParser setSigningKey(Key key);

/**
* Sets the {@link SigningKeyResolver} used to resolve the <code>signing key</code> using the parsed {@link JwsHeader}
* and/or the {@link Claims}. If the specified JWT string is not a JWS (no signature), this resolver is not used.
* <p/>
* <p>This method will set the signing key resolver to be used in case a signing key is not provided by any of the other methods.</p>
* <p/>
* <p>This is a convenience method: the {@code jwsSignatureKeyResolver} is used after a Jws has been parsed and either the
* {@link JwsHeader} or the {@link Claims} embedded in the {@link Jws} can be used to resolve the signing key.
* </p>
*
* @param signingKeyResolver the signing key resolver used to retrieve the signing key.
* @return the parser for method chaining.
*/
JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver);

/**
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
* otherwise.
Expand Down
58 changes: 58 additions & 0 deletions src/main/java/io/jsonwebtoken/SigningKeyResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* 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 io.jsonwebtoken;

/**
* A JwsSigningKeyResolver is invoked by a {@link io.jsonwebtoken.JwtParser JwtParser} if it's provided and the
* JWT being parsed is signed.
* <p/>
* Implementations of this interfaces must be provided to {@link io.jsonwebtoken.JwtParser JwtParser} when the values
* embedded in the JWS need to be used to determine the <code>signing key</code> used to sign the JWS.
*
* @since 0.4
*/
public interface SigningKeyResolver {

/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} parsed a {@link Jws} and needs
* to resolve the signing key, based on a value embedded in the {@link JwsHeader} and/or the {@link Claims}
* <p/>
* <p>This method will only be invoked if an implementation is provided.</p>
* <p/>
* <p>Note that this key <em>MUST</em> be a valid key for the signature algorithm found in the JWT header
* (as the {@code alg} header parameter).</p>
*
* @param header the parsed {@link JwsHeader}
* @param claims the parsed {@link Claims}
* @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary.
*/
byte[] resolveSigningKey(JwsHeader header, Claims claims);

/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} parsed a {@link Jws} and needs
* to resolve the signing key, based on a value embedded in the {@link JwsHeader} and/or the plaintext payload.
* <p/>
* <p>This method will only be invoked if an implementation is provided.</p>
* <p/>
* <p>Note that this key <em>MUST</em> be a valid key for the signature algorithm found in the JWT header
* (as the {@code alg} header parameter).</p>
*
* @param header the parsed {@link JwsHeader}
* @param payload the jws plaintext payload.
* @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary.
*/
byte[] resolveSigningKey(JwsHeader header, String payload);
}
41 changes: 41 additions & 0 deletions src/main/java/io/jsonwebtoken/SigningKeyResolverAdapter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* 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 io.jsonwebtoken;

/**
* An <a href="http://en.wikipedia.org/wiki/Adapter_pattern">Adapter</a> implementation of the
* {@link SigningKeyResolver} interface that allows subclasses to process only the type of Jws body that
* are known/expected for a particular case.
*
* <p>All of the methods in this implementation throw exceptions: overridden methods represent
* scenarios expected by calling code in known situations. It would be unexpected to receive a JWS or JWT that did
* not match parsing expectations, so all non-overridden methods throw exceptions to indicate that the JWT
* input was unexpected.</p>
*
* @since 0.4
*/
public class SigningKeyResolverAdapter implements SigningKeyResolver {

@Override
public byte[] resolveSigningKey(JwsHeader header, Claims claims) {
throw new UnsupportedJwtException("Resolving signing keys with claims are not supported.");
}

@Override
public byte[] resolveSigningKey(JwsHeader header, String payload) {
throw new UnsupportedJwtException("Resolving signing keys with plaintext payload are not supported.");
}
}
25 changes: 24 additions & 1 deletion src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtHandler;
import io.jsonwebtoken.JwtHandlerAdapter;
Expand Down Expand Up @@ -55,6 +56,8 @@ public class DefaultJwtParser implements JwtParser {

private Key key;

private SigningKeyResolver signingKeyResolver;

@Override
public JwtParser setSigningKey(byte[] key) {
Assert.notEmpty(key, "signing key cannot be null or empty.");
Expand All @@ -76,6 +79,13 @@ public JwtParser setSigningKey(Key key) {
return this;
}

@Override
public JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver) {
Assert.notNull(signingKeyResolver, "jwsSigningKeyResolver cannot be null.");
this.signingKeyResolver = signingKeyResolver;
return this;
}

@Override
public boolean isSigned(String jwt) {

Expand Down Expand Up @@ -234,14 +244,27 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException,

if (key != 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 ";
throw new IllegalStateException("A signing key resolver object and" + object + "cannot both be specified. Choose either.");
}

//digitally sign 8000 ed, let's assert the signature:
Key key = this.key;

if (key == null) { //fall back to keyBytes

if (!Objects.isEmpty(this.keyBytes)) {
byte[] keyBytes = this.keyBytes;

if (Objects.isEmpty(keyBytes) && signingKeyResolver != null) { //use the signingKeyResolver
if (claims != null) {
keyBytes = signingKeyResolver.resolveSigningKey(jwsHeader, claims);
} else {
keyBytes = signingKeyResolver.resolveSigningKey(jwsHeader, payload);
}
}

if (!Objects.isEmpty(keyBytes)) {

Assert.isTrue(!algorithm.isRsa(),
"Key bytes cannot be specified for RSA signatures. Please specify a PublicKey or PrivateKey instance.");
Expand Down
200 changes: 200 additions & 0 deletions src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -501,4 +501,204 @@ class JwtParserTest {
}
}

// ========================================================================
// parseClaimsJws with signingKey resolver.
// ========================================================================

@Test
void testParseClaimsWithSigningKeyResolver() {

String subject = 'Joe'

byte[] key = randomKey()

String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()

def signingKeyResolver = new SigningKeyResolverAdapter() {
@Override
byte[] resolveSigningKey(JwsHeader header, Claims claims) {
return key
}
}

Jws jws = Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact)

assertEquals jws.getBody().getSubject(), subject
}

@Test
void testParseClaimsWithSigningKeyResolverInvalidKey() {

String subject = 'Joe'

byte[] key = randomKey()

String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()

def signingKeyResolver = new SigningKeyResolverAdapter() {
@Override
byte[] resolveSigningKey(JwsHeader header, Claims claims) {
return randomKey()
}
}

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() {

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[] resolveSigningKey(JwsHeader header, Claims claims) {
return randomKey()
}
}

try {
Jwts.parser().setSigningKey(key).setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact);
fail()
} catch (IllegalStateException ise) {
assertEquals ise.getMessage(), 'A signing key resolver object and a key object cannot both be specified. Choose either.'
}
}

@Test
void testParseClaimsWithSigningKeyResolverAndKeyBytes() {

String subject = 'Joe'

byte[] key = randomKey()

String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()

def signingKeyResolver = new SigningKeyResolverAdapter() {
@Override
byte[] resolveSigningKey(JwsHeader header, Claims claims) {
return randomKey()
}
}

try {
Jwts.parser().setSigningKey(key).setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact);
fail()
} catch (IllegalStateException ise) {
assertEquals ise.getMessage(), 'A signing key resolver object and key bytes cannot both be specified. Choose either.'
}
}

@Test
void testParseClaimsWithNullSigningKeyResolver() {

String subject = 'Joe'

byte[] key = randomKey()

String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()

try {
Jwts.parser().setSigningKeyResolver(null).parseClaimsJws(compact);
fail()
} catch (IllegalArgumentException iae) {
assertEquals iae.getMessage(), 'jwsSigningKeyResolver cannot be null.'
}
}

@Test
void testParseClaimsWithInvalidSigningKeyResolverAdapter() {

String subject = 'Joe'

byte[] key = randomKey()

String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()

def signingKeyResolver = new SigningKeyResolverAdapter()

try {
Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact);
fail()
} catch (UnsupportedJwtException ex) {
assertEquals ex.getMessage(), 'Resolving signing keys with claims are not supported.'
}
}

// ========================================================================
// parsePlaintextJws with signingKey resolver.
// ========================================================================

@Test
void testParsePlaintextJwsWithSigningKeyResolverAdapter() {

String inputPayload = 'Hello world!'

byte[] key = randomKey()

String compact = Jwts.builder().setPayload(inputPayload).signWith(SignatureAlgorithm.HS256, key).compact()

def signingKeyResolver = new SigningKeyResolverAdapter() {
@Override
byte[] resolveSigningKey(JwsHeader header, String payload) {
return key
}
}

Jws<String> jws = Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact);

assertEquals jws.getBody(), inputPayload
}

@Test
void testParsePlaintextJwsWithSigningKeyResolverInvalidKey() {

String inputPayload = 'Hello world!'

byte[] key = randomKey()

String compact = Jwts.builder().setPayload(inputPayload).signWith(SignatureAlgorithm.HS256, key).compact()

def signingKeyResolver = new SigningKeyResolverAdapter() {
@Override
byte[] resolveSigningKey(JwsHeader header, String payload) {
return randomKey()
}
}

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() {

String payload = 'Hello world!'

byte[] key = randomKey()

String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, key).compact()

def signingKeyResolver = new SigningKeyResolverAdapter()

try {
Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact);
fail()
} catch (UnsupportedJwtException ex) {
assertEquals ex.getMessage(), 'Resolving signing keys with plaintext payload are not supported.'
}
}
}
0