/*
* Copyright 2012-2017 the original author or authors.
*
* 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 org.springframework.security.oauth2.provider.token.store.jwk;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.*;
import static org.springframework.security.oauth2.provider.token.store.jwk.JwkAttributes.KEYS;
/**
* @author Joe Grandja
*/
public class JwkSetConverterTest {
private final JwkSetConverter converter = new JwkSetConverter();
private final ObjectMapper objectMapper = new ObjectMapper();
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void convertWhenJwkSetStreamIsNullThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("Invalid JWK Set Object.");
this.converter.convert(null);
}
@Test
public void convertWhenJwkSetStreamIsEmptyThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("Invalid JWK Set Object.");
this.converter.convert(new ByteArrayInputStream(new byte[0]));
}
@Test
public void convertWhenJwkSetStreamNotAnObjectThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("Invalid JWK Set Object.");
this.converter.convert(new ByteArrayInputStream("".getBytes()));
}
@Test
public void convertWhenJwkSetStreamHasMissingKeysAttributeThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("Invalid JWK Set Object.");
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
this.converter.convert(this.asInputStream(jwkSetObject));
}
@Test
public void convertWhenJwkSetStreamHasInvalidKeysAttributeThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("Invalid JWK Set Object. The JWK Set MUST have a keys attribute.");
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
jwkSetObject.put(KEYS + "-invalid", new Map[0]);
this.converter.convert(this.asInputStream(jwkSetObject));
}
@Test
public void convertWhenJwkSetStreamHasInvalidJwkElementsThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("Invalid JWK Set Object. The JWK Set MUST have an array of JWK(s).");
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
jwkSetObject.put(JwkAttributes.KEYS, "");
this.converter.convert(this.asInputStream(jwkSetObject));
}
@Test
public void convertWhenJwkSetStreamHasEmptyJwkElementsThenReturnEmptyJwkSet() throws Exception {
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
jwkSetObject.put(JwkAttributes.KEYS, new Map[0]);
Set<JwkDefinition> jwkSet = this.converter.convert(this.asInputStream(jwkSetObject));
assertTrue("JWK Set NOT empty", jwkSet.isEmpty());
}
@Test
public void convertWhenJwkSetStreamHasEmptyJwkElementThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("unknown (kty) is currently not supported.");
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
Map<String, Object> jwkObject = new HashMap<String, Object>();
jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject});
this.converter.convert(this.asInputStream(jwkSetObject));
}
@Test
public void convertWhenJwkSetStreamHasJwkElementWithECKeyTypeThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("EC (kty) is currently not supported.");
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
Map<String, Object> jwkObject = this.createJwkObject(JwkDefinition.KeyType.EC);
jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject});
this.converter.convert(this.asInputStream(jwkSetObject));
}
@Test
public void convertWhenJwkSetStreamHasJwkElementWithOCTKeyTypeThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("oct (kty) is currently not supported.");
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
Map<String, Object> jwkObject = this.createJwkObject(JwkDefinition.KeyType.OCT);
jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject});
this.converter.convert(this.asInputStream(jwkSetObject));
}
@Test
public void convertWhenJwkSetStreamHasJwkElementWithMissingKeyIdAttributeThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("kid is a required attribute for a JWK.");
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
Map<String, Object> jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, null);
jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject});
this.converter.convert(this.asInputStream(jwkSetObject));
}
@Test
public void convertWhenJwkSetStreamHasJwkElementWithMissingPublicKeyUseAttributeThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("unknown (use) is currently not supported.");
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
Map<String, Object> jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1");
jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject});
this.converter.convert(this.asInputStream(jwkSetObject));
}
@Test
public void convertWhenJwkSetStreamHasJwkElementWithENCPublicKeyUseAttributeThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("enc (use) is currently not supported.");
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
Map<String, Object> jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1", JwkDefinition.PublicKeyUse.ENC);
jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject});
this.converter.convert(this.asInputStream(jwkSetObject));
}
@Test
public void convertWhenJwkSetStreamHasJwkElementWithMissingAlgorithmAttributeThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("unknown (alg) is currently not supported.");
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
Map<String, Object> jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1", JwkDefinition.PublicKeyUse.SIG);
jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject});
this.converter.convert(this.asInputStream(jwkSetObject));
}
@Test
public void convertWhenJwkSetStreamHasJwkElementWithMissingRSAModulusAttributeThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("n is a required attribute for a RSA JWK.");
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
Map<String, Object> jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1",
JwkDefinition.PublicKeyUse.SIG, JwkDefinition.CryptoAlgorithm.RS256);
jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject});
this.converter.convert(this.asInputStream(jwkSetObject));
}
@Test
public void convertWhenJwkSetStreamHasJwkElementWithMissingRSAExponentAttributeThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("e is a required attribute for a RSA JWK.");
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
Map<String, Object> jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1",
JwkDefinition.PublicKeyUse.SIG, JwkDefinition.CryptoAlgorithm.RS256, "AMh-pGAj9vX2gwFDyrXot1f2YfHgh8h0Qx6w9IqLL");
jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject});
this.converter.convert(this.asInputStream(jwkSetObject));
}
@Test
public void convertWhenJwkSetStreamIsValidThenReturnJwkSet() throws Exception {
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
Map<String, Object> jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1",
JwkDefinition.PublicKeyUse.SIG, JwkDefinition.CryptoAlgorithm.RS256, "AMh-pGAj9vX2gwFDyrXot1f2YfHgh8h0Qx6w9IqLL", "AQAB");
jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject});
Set<JwkDefinition> jwkSet = this.converter.convert(this.asInputStream(jwkSetObject));
assertNotNull(jwkSet);
assertEquals("JWK Set NOT size=1", 1, jwkSet.size());
Map<String, Object> jwkObject2 = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-2",
JwkDefinition.PublicKeyUse.SIG, JwkDefinition.CryptoAlgorithm.RS512, "AMh-pGAj9vX2gwFDyrXot1f2YfHgh8h0Qx6w9IqLL", "AQAB");
jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject, jwkObject2});
jwkSet = this.converter.convert(this.asInputStream(jwkSetObject));
assertNotNull(jwkSet);
assertEquals("JWK Set NOT size=2", 2, jwkSet.size());
}
@Test
public void convertWhenJwkSetStreamHasDuplicateJwkElementsThenThrowJwkException() throws Exception {
this.thrown.expect(JwkException.class);
this.thrown.expectMessage("Duplicate JWK found in Set: key-id-1 (kid)");
Map<String, Object> jwkSetObject = new HashMap<String, Object>();
Map<String, Object> jwkObject = this.createJwkObject(JwkDefinition.KeyType.RSA, "key-id-1",
JwkDefinition.PublicKeyUse.SIG, JwkDefinition.CryptoAlgorithm.RS256, "AMh-pGAj9vX2gwFDyrXot1f2YfHgh8h0Qx6w9IqLL", "AQAB");
jwkSetObject.put(JwkAttributes.KEYS, new Map[] {jwkObject, jwkObject});
this.converter.convert(this.asInputStream(jwkSetObject));
}
private Map<String, Object> createJwkObject(JwkDefinition.KeyType keyType) {
return this.createJwkObject(keyType, null);
}
private Map<String, Object> createJwkObject(JwkDefinition.KeyType keyType, String keyId) {
return this.createJwkObject(keyType, keyId, null);
}
private Map<String, Object> createJwkObject(JwkDefinition.KeyType keyType,
String keyId,
JwkDefinition.PublicKeyUse publicKeyUse) {
return this.createJwkObject(keyType, keyId, publicKeyUse, null);
}
private Map<String, Object> createJwkObject(JwkDefinition.KeyType keyType,
String keyId,
JwkDefinition.PublicKeyUse publicKeyUse,
JwkDefinition.CryptoAlgorithm algorithm) {
return this.createJwkObject(keyType, keyId, publicKeyUse, algorithm, null);
}
private Map<String, Object> createJwkObject(JwkDefinition.KeyType keyType,
String keyId,
JwkDefinition.PublicKeyUse publicKeyUse,
JwkDefinition.CryptoAlgorithm algorithm,
String rsaModulus) {
return this.createJwkObject(keyType, keyId, publicKeyUse, algorithm, rsaModulus, null);
}
private Map<String, Object> createJwkObject(JwkDefinition.KeyType keyType,
String keyId,
JwkDefinition.PublicKeyUse publicKeyUse,
JwkDefinition.CryptoAlgorithm algorithm,
String rsaModulus,
String rsaExponent) {
Map<String, Object> jwkObject = new HashMap<String, Object>();
jwkObject.put(JwkAttributes.KEY_TYPE, keyType.value());
if (keyId != null) {
jwkObject.put(JwkAttributes.KEY_ID, keyId);
}
if (publicKeyUse != null) {
jwkObject.put(JwkAttributes.PUBLIC_KEY_USE, publicKeyUse.value());
}
if (algorithm != null) {
jwkObject.put(JwkAttributes.ALGORITHM, algorithm.headerParamValue());
}
if (rsaModulus != null) {
jwkObject.put(JwkAttributes.RSA_PUBLIC_KEY_MODULUS, rsaModulus);
}
if (rsaExponent != null) {
jwkObject.put(JwkAttributes.RSA_PUBLIC_KEY_EXPONENT, rsaExponent);
}
return jwkObject;
}
private InputStream asInputStream(Map<String, Object> content) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
this.objectMapper.writeValue(out, content);
return new ByteArrayInputStream(out.toByteArray());
}
}