Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:cloudfoundry/uaa into refactor/m…
Browse files Browse the repository at this point in the history
…ockTimeInstant
  • Loading branch information
strehle committed May 6, 2023
2 parents a05a567 + 5c6f7bd commit d38d14a
Show file tree
Hide file tree
Showing 15 changed files with 334 additions and 285 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,29 @@
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.nimbusds.jose.HeaderParameterNames;
import com.nimbusds.jose.jwk.JWKParameterNames;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.BaseNCodec;
import org.cloudfoundry.identity.uaa.util.UaaStringUtils;

import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static org.apache.commons.codec.binary.BaseNCodec.PEM_CHUNK_SIZE;
import static org.cloudfoundry.identity.uaa.oauth.jwk.JsonWebKey.KeyType.MAC;
import static org.cloudfoundry.identity.uaa.oauth.jwk.JsonWebKey.KeyType.RSA;
import static org.springframework.util.StringUtils.hasText;
import static org.cloudfoundry.identity.uaa.oauth.jwk.JsonWebKey.KeyType.oct;

/**
* See https://tools.ietf.org/html/rfc7517
Expand All @@ -48,7 +51,8 @@
@JsonSerialize(using = JsonWebKeySerializer.class)
public class JsonWebKey {

private static final java.util.Base64.Encoder base64encoder = java.util.Base64.getMimeEncoder(BaseNCodec.PEM_CHUNK_SIZE, "\n".getBytes());
private static final Base64.Encoder base64encoder = Base64.getMimeEncoder(PEM_CHUNK_SIZE, "\n".getBytes(Charset.defaultCharset()));
private static final Base64.Decoder base64decoder = Base64.getUrlDecoder();

// value is not defined in RFC 7517
public static final String PUBLIC_KEY_VALUE = "value";
Expand All @@ -58,9 +62,12 @@ public enum KeyUse {
enc
}

// RFC 7518
public enum KeyType {
RSA,
MAC
EC,
MAC,
oct
}

public enum KeyOperation {
Expand All @@ -81,7 +88,7 @@ public JsonWebKey(Map<String, Object> json) {
throw new IllegalArgumentException("kty field is required");
}
KeyType.valueOf((String) json.get(JWKParameterNames.KEY_TYPE));
this.json = new HashMap(json);
this.json = new HashMap<>(json);
}

public Map<String, Object> getKeyProperties() {
Expand Down Expand Up @@ -122,7 +129,7 @@ public JsonWebKey setX5t(String x5t) {
public final KeyUse getUse() {
String use = (String) getKeyProperties().get(JWKParameterNames.PUBLIC_KEY_USE);
KeyUse result = null;
if (hasText(use)) {
if (UaaStringUtils.isNotEmpty(use)) {
result = KeyUse.valueOf(use);
}
return result;
Expand Down Expand Up @@ -153,10 +160,10 @@ public String getAlgorithm() {
public String getValue() {
String result = (String) getKeyProperties().get(PUBLIC_KEY_VALUE);
if (result == null) {
if (RSA.equals(getKty())) {
result = pemEncodePublicKey(getRsaPublicKey(this));
if (RSA == getKty()) {
result = pemEncodePublicKey(getRsaPublicKey(this)).orElse(UaaStringUtils.EMPTY_STRING);
this.json.put(PUBLIC_KEY_VALUE, result);
} else if (MAC.equals(getKty())) {
} else if (MAC == getKty() || oct == getKty()) {
result = (String) getKeyProperties().get(JWKParameterNames.OCT_KEY_VALUE);
this.json.put(PUBLIC_KEY_VALUE, result);
}
Expand All @@ -172,27 +179,29 @@ public Set<KeyOperation> getKeyOps() {
return result.stream().map(KeyOperation::valueOf).collect(Collectors.toSet());
}

public static String pemEncodePublicKey(PublicKey publicKey) {
public static Optional<String> pemEncodePublicKey(PublicKey publicKey) {
if (publicKey == null) {
return Optional.empty();
}
String begin = "-----BEGIN PUBLIC KEY-----\n";
String end = "\n-----END PUBLIC KEY-----";
byte[] data = publicKey.getEncoded();
String base64encoded = new String(base64encoder.encode(data));

return begin + base64encoded + end;
return Optional.of(begin + base64encoder.encodeToString(publicKey.getEncoded()) + end);
}

public static PublicKey getRsaPublicKey(JsonWebKey key) {
final Base64 decoder = new Base64(true);
protected static PublicKey getRsaPublicKey(JsonWebKey key) {
String e = (String) key.getKeyProperties().get(JWKParameterNames.RSA_EXPONENT);
String n = (String) key.getKeyProperties().get(JWKParameterNames.RSA_MODULUS);
BigInteger modulus = new BigInteger(1, decoder.decode(n.getBytes(StandardCharsets.UTF_8)));
BigInteger exponent = new BigInteger(1, decoder.decode(e.getBytes(StandardCharsets.UTF_8)));
try {
return KeyFactory.getInstance(RSA.name()).generatePublic(
new RSAPublicKeySpec(modulus, exponent)
);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e1) {
throw new IllegalStateException(e1);

if (e != null && n != null) {
BigInteger modulus = new BigInteger(1, base64decoder.decode(n.getBytes(StandardCharsets.UTF_8)));
BigInteger exponent = new BigInteger(1, base64decoder.decode(e.getBytes(StandardCharsets.UTF_8)));
try {
return KeyFactory.getInstance(RSA.name()).generatePublic(new RSAPublicKeySpec(modulus, exponent));
} catch (InvalidKeySpecException | NoSuchAlgorithmException e1) {
throw new IllegalStateException(e1);
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ public static boolean isNullOrEmpty(final String input) {
return input == null || input.length() == 0;
}

public static boolean isNotEmpty(final String input) { return !isNullOrEmpty(input); }

public static String convertISO8859_1_to_UTF_8(String s) {
if (s==null) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.junit.jupiter.api.Test;

import static org.cloudfoundry.identity.uaa.oauth.jwk.JsonWebKey.KeyType.EC;
import static org.cloudfoundry.identity.uaa.oauth.jwk.JsonWebKey.KeyType.oct;
import static org.cloudfoundry.identity.uaa.test.ModelTestUtils.getResourceAsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
Expand All @@ -17,6 +19,10 @@ class JsonWebKeyDeserializerTest {
private static final String uaaLegacyJwkSet = getResourceAsString(JsonWebKeyDeserializerTest.class, "JwkSet-LegacyUaa.json");
// Keycloak server configuration https://www.keycloak.org/docs/latest/server_admin/, e.g. jwks_uri: http://localhost:8080/realms/{realm-name}/protocol/openid-connect/certs
private static final String keyCloakJwkSet = getResourceAsString(JsonWebKeyDeserializerTest.class, "JwkSet-Keycloak.json");
// HMAC standard attributes
private static final String keyOctedJwkSet = getResourceAsString(JsonWebKeyDeserializerTest.class, "JwkSet-Hmac.json");
// elliptic cure
private static final String keyECJwkSet = getResourceAsString(JsonWebKeyDeserializerTest.class, "JwkSet-ECProvider.json");

@Test
void testWebKeysMicrosoft() {
Expand Down Expand Up @@ -63,4 +69,38 @@ void testWebKeysKeycloak() {
assertEquals("Zv-dxo0VbAZrjp7gBP97yyjdxC8", key.getX5t());
}
}

@Test
void testWebKeysOcted() {
JsonWebKeySet<JsonWebKey> keys = JsonUtils.readValue(keyOctedJwkSet, new TypeReference<JsonWebKeySet<JsonWebKey>>() {
});
assertNotNull(keys);
assertNotNull(keys.getKeys());
assertEquals(1, keys.getKeys().size());
for (JsonWebKey key : keys.getKeys()) {
assertNotNull(key);
assertEquals(oct, key.getKty());
assertEquals("tokenKey", key.getValue());
assertEquals("legacy-token-key", key.getKid());
}
}

@Test
void testWebKeysEllipticCurve() {
JsonWebKeySet<JsonWebKey> keys = JsonUtils.readValue(keyECJwkSet, new TypeReference<JsonWebKeySet<JsonWebKey>>() {
});
assertNotNull(keys);
assertNotNull(keys.getKeys());
assertEquals(1, keys.getKeys().size());
for (JsonWebKey key : keys.getKeys()) {
assertNotNull(key);
assertNull(key.getValue());
assertEquals(EC, key.getKty());
assertEquals("ES256", key.getAlgorithm());
assertEquals("ec-key-1", key.getKid());
assertEquals("gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", key.getKeyProperties().get("x"));
assertEquals("SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps", key.getKeyProperties().get("y"));
assertEquals("P-256", key.getKeyProperties().get("crv"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,12 @@ void isNullOrEmpty_ShouldReturnTrue(final String input) {
Assertions.assertThat(UaaStringUtils.isNullOrEmpty(input)).isTrue();
}

@ParameterizedTest
@NullAndEmptySource
void isNotEmpty_ShouldReturnFalse(final String input) {
Assertions.assertThat(UaaStringUtils.isNotEmpty(input)).isFalse();
}

@ParameterizedTest
@ValueSource(strings = { " ", " ", "\t", "\n", "abc" })
void isNullOrEmpty_ShouldReturnFalse(final String input) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"keys": [
{
"kty": "EC",
"use": "sig",
"kid": "ec-key-1",
"crv": "P-256",
"alg": "ES256",
"x": "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0",
"y": "SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"keys": [
{
"kty": "oct",
"alg": "HS256",
"key_ops": ["verify"],
"k": "tokenKey",
"kid": "legacy-token-key"
}
]
}
Loading

0 comments on commit d38d14a

Please sign in to comment.