/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
*******************************************************************************/
package org.cloudfoundry.identity.uaa.oauth.token;
import org.cloudfoundry.identity.uaa.oauth.TokenKeyEndpoint;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.util.MapCollector;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneProvisioning;
import org.cloudfoundry.identity.uaa.zone.MultitenancyFixture;
import org.cloudfoundry.identity.uaa.zone.TokenPolicy;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import java.security.Principal;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class TokenKeyEndpointTests {
private TokenKeyEndpoint tokenKeyEndpoint = new TokenKeyEndpoint();
private Authentication validUaaResource;
@Before
public void setUp() throws Exception {
validUaaResource = new UsernamePasswordAuthenticationToken("client_id",null, Collections.singleton(new SimpleGrantedAuthority("uaa.resource")));
}
@After
public void cleanUp() throws Exception {
IdentityZoneHolder.clear();
}
@Test
public void sharedSecretIsReturnedFromTokenKeyEndpoint() throws Exception {
configureKeysForDefaultZone(Collections.singletonMap("someKeyId", "someKey"));
VerificationKeyResponse response = tokenKeyEndpoint.getKey(validUaaResource);
assertEquals("HS256", response.getAlgorithm());
assertEquals("someKey", response.getKey());
assertEquals("someKeyId", response.getId());
assertEquals("MAC", response.getType());
assertEquals("sig", response.getUse().name());
}
private void configureKeysForDefaultZone(Map<String,String> keys) {
IdentityZoneProvisioning provisioning = mock(IdentityZoneProvisioning.class);
IdentityZoneHolder.setProvisioning(provisioning);
IdentityZone zone = IdentityZone.getUaa();
IdentityZoneConfiguration config = new IdentityZoneConfiguration();
TokenPolicy tokenPolicy = new TokenPolicy();
tokenPolicy.setKeys(keys);
config.setTokenPolicy(tokenPolicy);
zone.setConfig(config);
when(provisioning.retrieve("uaa")).thenReturn(zone);
}
@Test(expected = AccessDeniedException.class)
public void sharedSecretCannotBeAnonymouslyRetrievedFromTokenKeyEndpoint() throws Exception {
configureKeysForDefaultZone(Collections.singletonMap("anotherKeyId", "someKey"));
assertEquals("{alg=HMACSHA256, value=someKey}",
tokenKeyEndpoint.getKey(
new AnonymousAuthenticationToken("anon", "anonymousUser", AuthorityUtils
.createAuthorityList("ROLE_ANONYMOUS"))).toString());
}
@Test
public void responseIsBackwardCompatibleWithMap() {
configureKeysForDefaultZone(Collections.singletonMap("literallyAnything", "someKey"));
VerificationKeyResponse response = tokenKeyEndpoint.getKey(validUaaResource);
String serialized = JsonUtils.writeValueAsString(response);
Map<String, String> deserializedMap = JsonUtils.readValue(serialized, Map.class);
assertEquals("HS256", deserializedMap.get("alg"));
assertEquals("someKey", deserializedMap.get("value"));
assertEquals("MAC", deserializedMap.get("kty"));
assertEquals("sig", deserializedMap.get("use"));
}
@Test
public void keyIsReturnedForZone() {
IdentityZone zone = MultitenancyFixture.identityZone("test-zone", "test");
IdentityZoneConfiguration config = new IdentityZoneConfiguration();
TokenPolicy tokenPolicy = new TokenPolicy();
Map<String, String> keys = new HashMap<>();
String signingKey1 = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOQIBAAJAcPh8sj6TdTGYUTAn7ywyqNuzPD8pNtmSFVm87yCIhKDdIdEQ+g8H\n" +
"xq8zBWtMN9uaxyEomLXycgTbnduW6YOpyQIDAQABAkAE2qiBAC9V2cuxsWAF5uBG\n" +
"YSpSbGRY9wBP6oszuzIigLgWwxYwqGSS/Euovn1/BZEQL1JLc8tRp+Zn34JfLrAB\n" +
"AiEAz956b8BHk2Inbp2FcOvJZI4XVEah5ITY+vTvYFTQEz0CIQCLIN4t+ehu/qIS\n" +
"fj94nT9LhKPJKMwqhZslC0tIJ4OpfQIhAKaruHhKMBnYpc1nuEsmg8CAvevxBnX4\n" +
"nxH5usX+uyfxAiA0l7olWyEYRD10DDFmINs6auuXMUrskBDz0e8lWXqV6QIgJSkM\n" +
"L5WgVmzexrNmKxmGQQhNzfgO0Lk7o+iNNZXbkxw=\n" +
"-----END RSA PRIVATE KEY-----";
keys.put("key1", signingKey1);
tokenPolicy.setKeys(keys);
config.setTokenPolicy(tokenPolicy);
zone.setConfig(config);
IdentityZoneHolder.set(zone);
VerificationKeyResponse response = tokenKeyEndpoint.getKey(mock(Principal.class));
Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
Base64.Decoder decoder = Base64.getUrlDecoder();
assertEquals(response.getModulus(), encoder.encodeToString(decoder.decode(response.getModulus())));
assertEquals(response.getExponent(), encoder.encodeToString(decoder.decode((response.getExponent()))));
assertEquals("RS256", response.getAlgorithm());
assertEquals("key1", response.getId());
assertEquals("RSA", response.getType());
assertEquals("sig", response.getUse().name());
}
@Test
public void defaultZonekeyIsReturned_ForZoneWithNoKeys() {
configureKeysForDefaultZone(Collections.singletonMap("someKeyId", "someKey"));
IdentityZone zone = MultitenancyFixture.identityZone("test-zone", "test");
IdentityZoneHolder.set(zone);
VerificationKeyResponse response = tokenKeyEndpoint.getKey(validUaaResource);
assertEquals("HS256", response.getAlgorithm());
assertEquals("someKey", response.getKey());
assertEquals("someKeyId", response.getId());
assertEquals("MAC", response.getType());
assertEquals("sig", response.getUse().name());
}
@Test
public void listResponseContainsAllPublicKeysWhenUnauthenticated() throws Exception {
String signingKey1 = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOQIBAAJAcPh8sj6TdTGYUTAn7ywyqNuzPD8pNtmSFVm87yCIhKDdIdEQ+g8H\n" +
"xq8zBWtMN9uaxyEomLXycgTbnduW6YOpyQIDAQABAkAE2qiBAC9V2cuxsWAF5uBG\n" +
"YSpSbGRY9wBP6oszuzIigLgWwxYwqGSS/Euovn1/BZEQL1JLc8tRp+Zn34JfLrAB\n" +
"AiEAz956b8BHk2Inbp2FcOvJZI4XVEah5ITY+vTvYFTQEz0CIQCLIN4t+ehu/qIS\n" +
"fj94nT9LhKPJKMwqhZslC0tIJ4OpfQIhAKaruHhKMBnYpc1nuEsmg8CAvevxBnX4\n" +
"nxH5usX+uyfxAiA0l7olWyEYRD10DDFmINs6auuXMUrskBDz0e8lWXqV6QIgJSkM\n" +
"L5WgVmzexrNmKxmGQQhNzfgO0Lk7o+iNNZXbkxw=\n" +
"-----END RSA PRIVATE KEY-----";
String signingKey2 = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOQIBAAJBAKIuxhxq0SyeITbTw3SeyHz91eB6xEwRn9PPgl+klu4DRUmVs0h+\n" +
"UlVjXSTLiJ3r1bJXVded4JzVvNSh5Nw+7zsCAwEAAQJAYeVH8klL39nHhLfIiHF7\n" +
"5W63FhwktyIATrM4KBFKhXn8i29l76qVqX88LAYpeULric8fGgNoSaYVsHWIOgDu\n" +
"cQIhAPCJ7hu7OgqvyIGWRp2G2qjKfQVqSntG9HNSt9MhaXKjAiEArJt+PoF0AQFR\n" +
"R9O/XULmxR0OUYhkYZTr5eCo7kNscokCIDSv0aLrYKxEkqOn2fHZPv3n1HiiLoxQ\n" +
"H20/OhqZ3/IHAiBSn3/31am8zW+l7UM+Fkc29aij+KDsYQfmmvriSp3/2QIgFtiE\n" +
"Jkd0KaxkobLdyDrW13QnEaG5TXO0Y85kfu3nP5o=\n" +
"-----END RSA PRIVATE KEY-----";
String signingKey3 = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOgIBAAJBAOnndOyLh8axLMyjX+gCglBCeU5Cumjxz9asho5UvO8zf03PWciZ\n" +
"DGWce+B+n23E1IXbRKHWckCY0UH7fEgbrKkCAwEAAQJAGR9aCJoH8EhRVn1prKKw\n" +
"Wmx5WPWDzgfC2fzXyuvBCzPZNMQqOxWT9ajr+VysuyFZbz+HGJDqpf9Jl+fcIIUJ\n" +
"LQIhAPTn319kLU0QzoNBSB53tPhdNbzggBpW/Xv6B52XqGwPAiEA9IAAFu7GVymQ\n" +
"/neMHM7/umMFGFFbdq8E2pohLyjcg8cCIQCZWfv/0k2ffQ+jFqSfF1wFTPBSRc1R\n" +
"MPlmwSg1oPpANwIgHngBCtqQnvYQGpX9QO3O0oRaczBYTI789Nz2O7FE4asCIGEy\n" +
"SkbkWTex/hl+l0wdNErz/yBxP8esbPukOUqks/if\n" +
"-----END RSA PRIVATE KEY-----";
Map<String, String> keysForUaaZone = new HashMap<>();
keysForUaaZone.put("RsaKey1", signingKey1);
keysForUaaZone.put("thisIsASymmetricKeyThatShouldNotShowUp", "ItHasSomeTextThatIsNotPEM");
keysForUaaZone.put("RsaKey2", signingKey2);
keysForUaaZone.put("RsaKey3", signingKey3);
configureKeysForDefaultZone(keysForUaaZone);
VerificationKeysListResponse keysResponse = tokenKeyEndpoint.getKeys(null);
List<VerificationKeyResponse> keys = keysResponse.getKeys();
List<String> keyIds = keys.stream().map(k -> k.getId()).collect(Collectors.toList());
assertThat(keyIds, containsInAnyOrder("RsaKey1", "RsaKey2", "RsaKey3"));
HashMap<String, VerificationKeyResponse> keysMap = keys.stream().collect(new MapCollector<>(k -> k.getId(), k -> k));
VerificationKeyResponse key1Response = keysMap.get("RsaKey1");
VerificationKeyResponse key2Response = keysMap.get("RsaKey2");
VerificationKeyResponse key3Response = keysMap.get("RsaKey3");
byte[] bytes = "Text for testing of private/public key match".getBytes();
RsaSigner rsaSigner = new RsaSigner(signingKey1);
RsaVerifier rsaVerifier = new RsaVerifier(key1Response.getKey());
rsaVerifier.verify(bytes, rsaSigner.sign(bytes));
rsaSigner = new RsaSigner(signingKey2);
rsaVerifier = new RsaVerifier(key2Response.getKey());
rsaVerifier.verify(bytes, rsaSigner.sign(bytes));
rsaSigner = new RsaSigner(signingKey3);
rsaVerifier = new RsaVerifier(key3Response.getKey());
rsaVerifier.verify(bytes, rsaSigner.sign(bytes));
//ensure that none of the keys are padded
keys.stream().forEach(
key ->
assertFalse("Invalid padding for key:"+key.getKid(),
key.getExponent().endsWith("=") ||
key.getModulus().endsWith("="))
);
}
@Test
public void listResponseContainsAllKeysWhenAuthenticated() throws Exception {
String signingKey1 = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOQIBAAJAcPh8sj6TdTGYUTAn7ywyqNuzPD8pNtmSFVm87yCIhKDdIdEQ+g8H\n" +
"xq8zBWtMN9uaxyEomLXycgTbnduW6YOpyQIDAQABAkAE2qiBAC9V2cuxsWAF5uBG\n" +
"YSpSbGRY9wBP6oszuzIigLgWwxYwqGSS/Euovn1/BZEQL1JLc8tRp+Zn34JfLrAB\n" +
"AiEAz956b8BHk2Inbp2FcOvJZI4XVEah5ITY+vTvYFTQEz0CIQCLIN4t+ehu/qIS\n" +
"fj94nT9LhKPJKMwqhZslC0tIJ4OpfQIhAKaruHhKMBnYpc1nuEsmg8CAvevxBnX4\n" +
"nxH5usX+uyfxAiA0l7olWyEYRD10DDFmINs6auuXMUrskBDz0e8lWXqV6QIgJSkM\n" +
"L5WgVmzexrNmKxmGQQhNzfgO0Lk7o+iNNZXbkxw=\n" +
"-----END RSA PRIVATE KEY-----";
String signingKey2 = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOQIBAAJBAKIuxhxq0SyeITbTw3SeyHz91eB6xEwRn9PPgl+klu4DRUmVs0h+\n" +
"UlVjXSTLiJ3r1bJXVded4JzVvNSh5Nw+7zsCAwEAAQJAYeVH8klL39nHhLfIiHF7\n" +
"5W63FhwktyIATrM4KBFKhXn8i29l76qVqX88LAYpeULric8fGgNoSaYVsHWIOgDu\n" +
"cQIhAPCJ7hu7OgqvyIGWRp2G2qjKfQVqSntG9HNSt9MhaXKjAiEArJt+PoF0AQFR\n" +
"R9O/XULmxR0OUYhkYZTr5eCo7kNscokCIDSv0aLrYKxEkqOn2fHZPv3n1HiiLoxQ\n" +
"H20/OhqZ3/IHAiBSn3/31am8zW+l7UM+Fkc29aij+KDsYQfmmvriSp3/2QIgFtiE\n" +
"Jkd0KaxkobLdyDrW13QnEaG5TXO0Y85kfu3nP5o=\n" +
"-----END RSA PRIVATE KEY-----";
String signingKey3 = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOgIBAAJBAOnndOyLh8axLMyjX+gCglBCeU5Cumjxz9asho5UvO8zf03PWciZ\n" +
"DGWce+B+n23E1IXbRKHWckCY0UH7fEgbrKkCAwEAAQJAGR9aCJoH8EhRVn1prKKw\n" +
"Wmx5WPWDzgfC2fzXyuvBCzPZNMQqOxWT9ajr+VysuyFZbz+HGJDqpf9Jl+fcIIUJ\n" +
"LQIhAPTn319kLU0QzoNBSB53tPhdNbzggBpW/Xv6B52XqGwPAiEA9IAAFu7GVymQ\n" +
"/neMHM7/umMFGFFbdq8E2pohLyjcg8cCIQCZWfv/0k2ffQ+jFqSfF1wFTPBSRc1R\n" +
"MPlmwSg1oPpANwIgHngBCtqQnvYQGpX9QO3O0oRaczBYTI789Nz2O7FE4asCIGEy\n" +
"SkbkWTex/hl+l0wdNErz/yBxP8esbPukOUqks/if\n" +
"-----END RSA PRIVATE KEY-----";
Map<String, String> keysForUaaZone = new HashMap<>();
keysForUaaZone.put("RsaKey1", signingKey1);
keysForUaaZone.put("RsaKey2", signingKey2);
keysForUaaZone.put("RsaKey3", signingKey3);
keysForUaaZone.put("SymmetricKey", "ItHasSomeTextThatIsNotPEM");
configureKeysForDefaultZone(keysForUaaZone);
VerificationKeysListResponse keysResponse = tokenKeyEndpoint.getKeys(validUaaResource);
List<VerificationKeyResponse> keys = keysResponse.getKeys();
List<String> keyIds = keys.stream().map(k -> k.getId()).collect(Collectors.toList());
assertThat(keyIds, containsInAnyOrder("RsaKey1", "RsaKey2", "RsaKey3", "SymmetricKey"));
VerificationKeyResponse symKeyResponse = keys.stream().filter(k -> k.getId().equals("SymmetricKey")).findAny().get();
assertEquals("ItHasSomeTextThatIsNotPEM", symKeyResponse.getKey());
}
@Test
public void tokenKeyEndpoint_ReturnsAllKeysForZone() throws Exception {
IdentityZone zone = MultitenancyFixture.identityZone("test-zone", "test");
IdentityZoneConfiguration config = new IdentityZoneConfiguration();
TokenPolicy tokenPolicy = new TokenPolicy();
Map<String, String> keys = new HashMap<>();
String signingKey1 = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOQIBAAJAcPh8sj6TdTGYUTAn7ywyqNuzPD8pNtmSFVm87yCIhKDdIdEQ+g8H\n" +
"xq8zBWtMN9uaxyEomLXycgTbnduW6YOpyQIDAQABAkAE2qiBAC9V2cuxsWAF5uBG\n" +
"YSpSbGRY9wBP6oszuzIigLgWwxYwqGSS/Euovn1/BZEQL1JLc8tRp+Zn34JfLrAB\n" +
"AiEAz956b8BHk2Inbp2FcOvJZI4XVEah5ITY+vTvYFTQEz0CIQCLIN4t+ehu/qIS\n" +
"fj94nT9LhKPJKMwqhZslC0tIJ4OpfQIhAKaruHhKMBnYpc1nuEsmg8CAvevxBnX4\n" +
"nxH5usX+uyfxAiA0l7olWyEYRD10DDFmINs6auuXMUrskBDz0e8lWXqV6QIgJSkM\n" +
"L5WgVmzexrNmKxmGQQhNzfgO0Lk7o+iNNZXbkxw=\n" +
"-----END RSA PRIVATE KEY-----";
String signingKey2 = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOQIBAAJBAKIuxhxq0SyeITbTw3SeyHz91eB6xEwRn9PPgl+klu4DRUmVs0h+\n" +
"UlVjXSTLiJ3r1bJXVded4JzVvNSh5Nw+7zsCAwEAAQJAYeVH8klL39nHhLfIiHF7\n" +
"5W63FhwktyIATrM4KBFKhXn8i29l76qVqX88LAYpeULric8fGgNoSaYVsHWIOgDu\n" +
"cQIhAPCJ7hu7OgqvyIGWRp2G2qjKfQVqSntG9HNSt9MhaXKjAiEArJt+PoF0AQFR\n" +
"R9O/XULmxR0OUYhkYZTr5eCo7kNscokCIDSv0aLrYKxEkqOn2fHZPv3n1HiiLoxQ\n" +
"H20/OhqZ3/IHAiBSn3/31am8zW+l7UM+Fkc29aij+KDsYQfmmvriSp3/2QIgFtiE\n" +
"Jkd0KaxkobLdyDrW13QnEaG5TXO0Y85kfu3nP5o=\n" +
"-----END RSA PRIVATE KEY-----";
keys.put("key1", signingKey1);
keys.put("key2", signingKey2);
tokenPolicy.setKeys(keys);
config.setTokenPolicy(tokenPolicy);
zone.setConfig(config);
IdentityZoneHolder.set(zone);
VerificationKeysListResponse keysResponse = tokenKeyEndpoint.getKeys(mock(Principal.class));
List<VerificationKeyResponse> keysForZone = keysResponse.getKeys();
List<String> keyIds = keysForZone.stream().map(k -> k.getId()).collect(Collectors.toList());
assertThat(keyIds, containsInAnyOrder("key1", "key2"));
}
}