Validate kid in Cloud Foundry token header
Instead of validating the signature against all the public keys, we can validate it only against the public key with the kid that matches the one in the token header. Closes gh-8126
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.springframework.boot.actuate.cloudfoundry;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -162,13 +162,13 @@ public class CloudFoundrySecurityServiceTests {
|
||||
+ "kqwIn7Glry9n9Suxygbf8g5AzpWcusZgDLIIZ7JTUldBb8qU2a0Dl4mvLZOn4wPo\n"
|
||||
+ "jfj9Cw2QICsc5+Pwf21fP+hzf+1WSRHbnYv8uanRO0gZ8ekGaghM/2H6gqJbo2nI\n"
|
||||
+ "JwIDAQAB\n-----END PUBLIC KEY-----";
|
||||
String responseBody = "{\"keys\" : [ {\"value\" : \""
|
||||
String responseBody = "{\"keys\" : [ {\"kid\":\"test-key\",\"value\" : \""
|
||||
+ tokenKeyValue.replace("\n", "\\n") + "\"} ]}";
|
||||
this.server.expect(requestTo(UAA_URL + "/token_keys"))
|
||||
.andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON));
|
||||
List<String> tokenKeys = this.securityService.fetchTokenKeys();
|
||||
Map<String, String> tokenKeys = this.securityService.fetchTokenKeys();
|
||||
this.server.verify();
|
||||
assertThat(tokenKeys).containsExactly(tokenKeyValue);
|
||||
assertThat(tokenKeys.get("test-key")).isEqualTo(tokenKeyValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -178,7 +178,7 @@ public class CloudFoundrySecurityServiceTests {
|
||||
String responseBody = "{\"keys\": []}";
|
||||
this.server.expect(requestTo(UAA_URL + "/token_keys"))
|
||||
.andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON));
|
||||
List<String> tokenKeys = this.securityService.fetchTokenKeys();
|
||||
Map<String, String> tokenKeys = this.securityService.fetchTokenKeys();
|
||||
this.server.verify();
|
||||
assertThat(tokenKeys).hasSize(0);
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ public class TokenTests {
|
||||
assertThat(token.getExpiry()).isEqualTo(2147483647);
|
||||
assertThat(token.getIssuer()).isEqualTo("http://localhost:8080/uaa/oauth/token");
|
||||
assertThat(token.getSignatureAlgorithm()).isEqualTo("RS256");
|
||||
assertThat(token.getKeyId()).isEqualTo("key-id");
|
||||
assertThat(token.getContent()).isEqualTo(content.getBytes());
|
||||
assertThat(token.getSignature())
|
||||
.isEqualTo(Base64Utils.decodeFromString(signature));
|
||||
@@ -100,7 +101,7 @@ public class TokenTests {
|
||||
|
||||
@Test
|
||||
public void getIssuerWhenIssIsNullShouldThrowException() throws Exception {
|
||||
String header = "{\"alg\": \"RS256\", \"kid\": \"key-id\", \"typ\": \"JWT\"}";
|
||||
String header = "{\"alg\": \"RS256\", \"kid\": \"key-id\", \"typ\": \"JWT\"}";
|
||||
String claims = "{\"exp\": 2147483647}";
|
||||
Token token = createToken(header, claims);
|
||||
this.thrown
|
||||
@@ -108,6 +109,16 @@ public class TokenTests {
|
||||
token.getIssuer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getKidWhenKidIsNullShouldThrowException() throws Exception {
|
||||
String header = "{\"alg\": \"RS256\", \"typ\": \"JWT\"}";
|
||||
String claims = "{\"exp\": 2147483647}";
|
||||
Token token = createToken(header, claims);
|
||||
this.thrown
|
||||
.expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN));
|
||||
token.getKeyId();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getExpiryWhenExpIsNullShouldThrowException() throws Exception {
|
||||
String header = "{\"alg\": \"RS256\", \"kid\": \"key-id\", \"typ\": \"JWT\"}";
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.security.Signature;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.junit.Before;
|
||||
@@ -82,10 +82,10 @@ public class TokenValidatorTests {
|
||||
+ "r3F7aM9YpErzeYLrl0GhQr9BVJxOvXcVd4kmY+XkiCcrkyS1cnghnllh+LCwQu1s\n"
|
||||
+ "YwIDAQAB\n-----END PUBLIC KEY-----";
|
||||
|
||||
private static final List<String> INVALID_KEYS = Collections
|
||||
.singletonList(INVALID_KEY);
|
||||
private static final Map<String, String> INVALID_KEYS = Collections
|
||||
.singletonMap("invalid-key", INVALID_KEY);
|
||||
|
||||
private static final List<String> VALID_KEYS = Collections.singletonList(VALID_KEY);
|
||||
private static final Map<String, String> VALID_KEYS = Collections.singletonMap("valid-key", VALID_KEY);
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
@@ -94,25 +94,25 @@ public class TokenValidatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateTokenWhenSignatureValidationFailsTwiceShouldThrowException()
|
||||
public void validateTokenWhenKidValidationFailsTwiceShouldThrowException()
|
||||
throws Exception {
|
||||
ReflectionTestUtils.setField(this.tokenValidator, "tokenKeys", INVALID_KEYS);
|
||||
given(this.securityService.fetchTokenKeys()).willReturn(INVALID_KEYS);
|
||||
String header = "{\"alg\": \"RS256\", \"kid\": \"key-id\",\"typ\": \"JWT\"}";
|
||||
String header = "{\"alg\": \"RS256\", \"kid\": \"valid-key\",\"typ\": \"JWT\"}";
|
||||
String claims = "{\"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}";
|
||||
this.thrown.expect(
|
||||
AuthorizationExceptionMatcher.withReason(Reason.INVALID_SIGNATURE));
|
||||
AuthorizationExceptionMatcher.withReason(Reason.INVALID_KEY_ID));
|
||||
this.tokenValidator.validate(
|
||||
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateTokenWhenSignatureValidationSucceedsInTheSecondAttempt()
|
||||
public void validateTokenWhenKidValidationSucceedsInTheSecondAttempt()
|
||||
throws Exception {
|
||||
ReflectionTestUtils.setField(this.tokenValidator, "tokenKeys", INVALID_KEYS);
|
||||
given(this.securityService.fetchTokenKeys()).willReturn(VALID_KEYS);
|
||||
given(this.securityService.getUaaUrl()).willReturn("http://localhost:8080/uaa");
|
||||
String header = "{ \"alg\": \"RS256\", \"kid\": \"key-id\",\"typ\": \"JWT\"}";
|
||||
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\",\"typ\": \"JWT\"}";
|
||||
String claims = "{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}";
|
||||
this.tokenValidator.validate(
|
||||
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
|
||||
@@ -123,7 +123,7 @@ public class TokenValidatorTests {
|
||||
public void validateTokenShouldFetchTokenKeysIfNull() throws Exception {
|
||||
given(this.securityService.fetchTokenKeys()).willReturn(VALID_KEYS);
|
||||
given(this.securityService.getUaaUrl()).willReturn("http://localhost:8080/uaa");
|
||||
String header = "{ \"alg\": \"RS256\", \"kid\": \"key-id\",\"typ\": \"JWT\"}";
|
||||
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\",\"typ\": \"JWT\"}";
|
||||
String claims = "{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}";
|
||||
this.tokenValidator.validate(
|
||||
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
|
||||
@@ -131,17 +131,29 @@ public class TokenValidatorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateTokenWhenSignatureValidShouldNotFetchTokenKeys()
|
||||
public void validateTokenWhenValidShouldNotFetchTokenKeys()
|
||||
throws Exception {
|
||||
ReflectionTestUtils.setField(this.tokenValidator, "tokenKeys", VALID_KEYS);
|
||||
given(this.securityService.getUaaUrl()).willReturn("http://localhost:8080/uaa");
|
||||
String header = "{ \"alg\": \"RS256\", \"kid\": \"key-id\",\"typ\": \"JWT\"}";
|
||||
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\",\"typ\": \"JWT\"}";
|
||||
String claims = "{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}";
|
||||
this.tokenValidator.validate(
|
||||
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
|
||||
verify(this.securityService, Mockito.never()).fetchTokenKeys();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateTokenWhenSignatureInvalidShouldThrowException() throws Exception {
|
||||
ReflectionTestUtils.setField(this.tokenValidator, "tokenKeys", Collections.singletonMap("valid-key", INVALID_KEY));
|
||||
given(this.securityService.getUaaUrl()).willReturn("http://localhost:8080/uaa");
|
||||
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\",\"typ\": \"JWT\"}";
|
||||
String claims = "{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"actuator.read\"]}";
|
||||
this.thrown.expect(
|
||||
AuthorizationExceptionMatcher.withReason(Reason.INVALID_SIGNATURE));
|
||||
this.tokenValidator.validate(
|
||||
new Token(getSignedToken(header.getBytes(), claims.getBytes())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateTokenWhenTokenAlgorithmIsNotRS256ShouldThrowException()
|
||||
throws Exception {
|
||||
@@ -158,7 +170,7 @@ public class TokenValidatorTests {
|
||||
public void validateTokenWhenExpiredShouldThrowException() throws Exception {
|
||||
given(this.securityService.fetchTokenKeys()).willReturn(VALID_KEYS);
|
||||
given(this.securityService.fetchTokenKeys()).willReturn(VALID_KEYS);
|
||||
String header = "{ \"alg\": \"RS256\", \"kid\": \"key-id\", \"typ\": \"JWT\"}";
|
||||
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\", \"typ\": \"JWT\"}";
|
||||
String claims = "{ \"jti\": \"0236399c350c47f3ae77e67a75e75e7d\", \"exp\": 1477509977, \"scope\": [\"actuator.read\"]}";
|
||||
this.thrown
|
||||
.expect(AuthorizationExceptionMatcher.withReason(Reason.TOKEN_EXPIRED));
|
||||
@@ -170,7 +182,7 @@ public class TokenValidatorTests {
|
||||
public void validateTokenWhenIssuerIsNotValidShouldThrowException() throws Exception {
|
||||
given(this.securityService.fetchTokenKeys()).willReturn(VALID_KEYS);
|
||||
given(this.securityService.getUaaUrl()).willReturn("http://other-uaa.com");
|
||||
String header = "{ \"alg\": \"RS256\", \"kid\": \"key-id\", \"typ\": \"JWT\", \"scope\": [\"actuator.read\"]}";
|
||||
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\", \"typ\": \"JWT\", \"scope\": [\"actuator.read\"]}";
|
||||
String claims = "{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\"}";
|
||||
this.thrown
|
||||
.expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_ISSUER));
|
||||
@@ -183,7 +195,7 @@ public class TokenValidatorTests {
|
||||
throws Exception {
|
||||
given(this.securityService.fetchTokenKeys()).willReturn(VALID_KEYS);
|
||||
given(this.securityService.getUaaUrl()).willReturn("http://localhost:8080/uaa");
|
||||
String header = "{ \"alg\": \"RS256\", \"kid\": \"key-id\", \"typ\": \"JWT\"}";
|
||||
String header = "{ \"alg\": \"RS256\", \"kid\": \"valid-key\", \"typ\": \"JWT\"}";
|
||||
String claims = "{ \"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\", \"scope\": [\"foo.bar\"]}";
|
||||
this.thrown.expect(
|
||||
AuthorizationExceptionMatcher.withReason(Reason.INVALID_AUDIENCE));
|
||||
|
||||
Reference in New Issue
Block a user