/*
* ****************************************************************************
* 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.jwt;
import org.cloudfoundry.identity.uaa.oauth.jwk.JsonWebKey;
import org.cloudfoundry.identity.uaa.oauth.jwk.JsonWebKeyHelper;
import org.cloudfoundry.identity.uaa.oauth.jwk.JsonWebKeySet;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.security.jwt.crypto.sign.InvalidSignatureException;
import org.springframework.security.jwt.crypto.sign.MacSigner;
import org.springframework.security.jwt.crypto.sign.SignatureVerifier;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.Collections.singletonMap;
import static org.cloudfoundry.identity.uaa.oauth.jwk.JsonWebKey.KeyType.MAC;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
public class ChainedSignatureVerifierTests {
private Signer signer;
private Signer invalidSigner;
private String rsaSigningKey;
private String invalidRsaSigningKey;
private String content;
private Jwt signedValidContent;
private Jwt signedInvalidContent;
private JsonWebKey validKey;
private JsonWebKey invalidKey;
private ChainedSignatureVerifier verifier;
@Before
public void setup() {
rsaSigningKey = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOQIBAAJAcjAgsHEfrUxeTFwQPb17AkZ2Im4SfZdpY8Ada9pZfxXz1PZSqv9T\n" +
"PTMAzNx+EkzMk2IMYN+uNm1bfDzaxVdz+QIDAQABAkBoR39y4rw0/QsY3PKQD5xo\n" +
"hYSZCMCmJUI/sFCuECevIFY4h6q9KBP+4Set96f7Bgs9wJWVvCMx/nJ6guHAjsIB\n" +
"AiEAywVOoCGIZ2YzARXWYcMRYZ89hxoHh8kZ+QMthRSZieECIQCP/GWQYgyofAQA\n" +
"BtM8YwThXEV+S3KtuCn4IAQ89gqdGQIgULBASpZpPyc4OEM0nFBKFTGT46EtwwLj\n" +
"RrvDmLPSPiECICQi9FqIQSUH+vkGvX0qXM8ymT5ZMS7oSaA8aNPj7EYBAiEAx5V3\n" +
"2JGEulMY3bK1PVGYmtsXF1gq6zbRMoollMCRSMg=\n" +
"-----END RSA PRIVATE KEY-----";
signer = new CommonSigner("valid", rsaSigningKey);
invalidRsaSigningKey = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIBOgIBAAJBAJnlBG4lLmUiHslsKDODfd0MqmGZRNUOhn7eO3cKobsFljUKzRQe\n" +
"GB7LYMjPavnKccm6+jWSXutpzfAc9A9wXG8CAwEAAQJADwwdiseH6cuURw2UQLUy\n" +
"sVJztmdOG6b375+7IMChX6/cgoF0roCPP0Xr70y1J4TXvFhjcwTgm4RI+AUiIDKw\n" +
"gQIhAPQHwHzdYG1639Qz/TCHzuai0ItwVC1wlqKpat+CaqdZAiEAoXFyS7249mRu\n" +
"xtwRAvxKMe+eshHvG2le+ZDrM/pz8QcCIQCzmCDpxGL7L7sbCUgFN23l/11Lwdex\n" +
"uXKjM9wbsnebwQIgeZIbVovUp74zaQ44xT3EhVwC7ebxXnv3qAkIBMk526sCIDVg\n" +
"z1jr3KEcaq9zjNJd9sKBkqpkVSqj8Mv+Amq+YjBA\n" +
"-----END RSA PRIVATE KEY-----";
invalidSigner = new CommonSigner("invalid", invalidRsaSigningKey);
content = new RandomValueStringGenerator(1024 * 4).generate();
signedValidContent = JwtHelper.encode(content, signer);
signedInvalidContent = JwtHelper.encode(content, invalidSigner);
validKey = JsonWebKeyHelper.fromPEMPrivateKey(rsaSigningKey);
invalidKey = JsonWebKeyHelper.fromPEMPrivateKey(invalidRsaSigningKey);
}
@Test
public void test_single_key_valid() {
verifier = new ChainedSignatureVerifier(new JsonWebKeySet<>(Arrays.asList(validKey)));
signedValidContent.verifySignature(verifier);
}
@Test(expected = InvalidSignatureException.class)
public void test_single_key_invalid() {
verifier = new ChainedSignatureVerifier(new JsonWebKeySet<>(Arrays.asList(invalidKey)));
signedValidContent.verifySignature(verifier);
}
@Test
public void test_multi_key_first_valid() {
verifier = new ChainedSignatureVerifier(new JsonWebKeySet<>(Arrays.asList(validKey, invalidKey)));
signedValidContent.verifySignature(verifier);
}
@Test
public void test_multi_key_last_valid() {
verifier = new ChainedSignatureVerifier(new JsonWebKeySet<>(Arrays.asList(invalidKey, validKey)));
signedValidContent.verifySignature(verifier);
}
@Test(expected = InvalidSignatureException.class)
public void test_multi_key_invalid() {
verifier = new ChainedSignatureVerifier(new JsonWebKeySet<>(Arrays.asList(invalidKey, invalidKey)));
signedValidContent.verifySignature(verifier);
}
@Test
public void check_that_we_use_common_signer() {
Map<String,Object> p = new HashMap<>();
p.put("kty",MAC.name());
p.put("kid", "macid");
p.put("value", "test-mac-key");
JsonWebKey macKey = new JsonWebKey(p);
verifier = new ChainedSignatureVerifier(new JsonWebKeySet<>(Arrays.asList(validKey, invalidKey, macKey)));
List<SignatureVerifier> delegates = new ArrayList((List<SignatureVerifier>) ReflectionTestUtils.getField(verifier, verifier.getClass(), "delegates"));
assertNotNull(delegates);
assertEquals(3, delegates.size());
int pos = 0;
for (SignatureVerifier v : delegates) {
assertTrue("Checking "+(pos++), v instanceof CommonSignatureVerifier);
}
}
@Test
public void unsupported_key_types_are_ignored() {
Map<String,Object> p = new HashMap<>();
p.put("kty","EC");
p.put("kid", "ecid");
p.put("x", "test-ec-key-x");
p.put("y", "test-ec-key-y");
p.put("use", "sig");
p.put("crv", "test-crv");
Map<String,Object> q = new HashMap<>();
q.put("kty","oct");
q.put("k", "octkeyvalue");
JsonWebKeySet keySet = JsonUtils.convertValue(singletonMap("keys", Arrays.asList(validKey, p, q)), JsonWebKeySet.class);
verifier = new ChainedSignatureVerifier(keySet);
List<SignatureVerifier> delegates = new ArrayList((List<SignatureVerifier>) ReflectionTestUtils.getField(verifier, verifier.getClass(), "delegates"));
assertNotNull(delegates);
assertEquals(1, delegates.size());
int pos = 0;
for (SignatureVerifier v : delegates) {
assertTrue("Checking "+(pos++), v instanceof CommonSignatureVerifier);
}
}
@Test(expected = IllegalArgumentException.class)
public void no_supported_key_types_causes_error() {
Map<String,Object> p = new HashMap<>();
p.put("kty","EC");
p.put("kid", "ecid");
p.put("x", "test-ec-key-x");
p.put("y", "test-ec-key-y");
p.put("use", "sig");
p.put("crv", "test-crv");
Map<String,Object> q = new HashMap<>();
q.put("kty","oct");
q.put("k", "octkeyvalue");
JsonWebKeySet keySet = JsonUtils.convertValue(singletonMap("keys", Arrays.asList(p, q)), JsonWebKeySet.class);
verifier = new ChainedSignatureVerifier(keySet);
}
@Test
public void test_multi_key_both_valid() {
Map<String,Object> p = new HashMap<>();
p.put("kty",MAC.name());
p.put("value", "mac-content");
JsonWebKey jsonWebKey = new JsonWebKey(p);
verifier = new ChainedSignatureVerifier(new JsonWebKeySet<>(Arrays.asList(validKey, jsonWebKey)));
signedValidContent.verifySignature(verifier);
List<SignatureVerifier> delegates = new ArrayList((List<SignatureVerifier>) ReflectionTestUtils.getField(verifier, verifier.getClass(), "delegates"));
assertNotNull(delegates);
assertEquals(2, delegates.size());
assertEquals("HMACSHA256", delegates.get(1).algorithm());
//ensure the second signer never gets invoked upon success
delegates.remove(1);
MacSigner macSigner = mock(MacSigner.class);
delegates.add(macSigner);
ReflectionTestUtils.setField(verifier, "delegates", delegates);
signedValidContent.verifySignature(verifier);
Mockito.verifyZeroInteractions(macSigner);
}
}