Skip to content

Commit

Permalink
Support JWK with certificate chain
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasz-walkiewicz authored and kokosing committed Jan 7, 2021
1 parent 8e3b9af commit bc312ab
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 49 deletions.
Expand Up @@ -13,6 +13,7 @@
*/
package io.trino.server.security.jwt;

import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Strings;
Expand All @@ -26,6 +27,7 @@
import java.security.interfaces.RSAPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -52,84 +54,54 @@ public static Map<String, PublicKey> decodeKeys(String jwkJson)
.collect(toImmutableMap(JwkPublicKey::getKeyId, Function.identity()));
}

public static Optional<? extends JwkPublicKey> tryDecodeJwkKey(Map<String, String> properties)
public static Optional<? extends JwkPublicKey> tryDecodeJwkKey(Key key)
{
String keyId = properties.get("kid");
if (Strings.isNullOrEmpty(keyId)) {
// key id is required to index the key
// key id is required to index the key
if (key.getKid().isEmpty() || key.getKid().get().isEmpty()) {
return Optional.empty();
}

String keyType = properties.get("kty");
switch (keyType) {
String keyId = key.getKid().get();
switch (key.getKty()) {
case "RSA":
return tryDecodeRsaKey(keyId, properties);
return tryDecodeRsaKey(keyId, key);
case "EC":
return tryDecodeEcKey(keyId, properties);
return tryDecodeEcKey(keyId, key);
default:
// ignore non unknown keys
return Optional.empty();
}
}

public static Optional<JwkRsaPublicKey> tryDecodeRsaKey(String keyId, Map<String, String> properties)
public static Optional<JwkRsaPublicKey> tryDecodeRsaKey(String keyId, Key key)
{
// alg field is optional so not verified
// use field is optional so not verified

String encodedModulus = properties.get("n");
if (Strings.isNullOrEmpty(encodedModulus)) {
log.error("JWK RSA key %s does not contain the required modulus field 'n'", keyId);
return Optional.empty();
}
String encodedExponent = properties.get("e");
if (Strings.isNullOrEmpty(encodedExponent)) {
log.error("JWK RSA key %s does not contain the required exponent field 'e'", keyId);
return Optional.empty();
}

Optional<BigInteger> modulus = decodeBigint(keyId, "modulus", encodedModulus);
Optional<BigInteger> modulus = key.getStringProperty("n").flatMap(encodedModulus -> decodeBigint(keyId, "modulus", encodedModulus));
if (modulus.isEmpty()) {
return Optional.empty();
}
Optional<BigInteger> exponent = decodeBigint(keyId, "exponent", encodedExponent);
Optional<BigInteger> exponent = key.getStringProperty("e").flatMap(encodedExponent -> decodeBigint(keyId, "exponent", encodedExponent));
if (exponent.isEmpty()) {
return Optional.empty();
}
return Optional.of(new JwkRsaPublicKey(keyId, exponent.get(), modulus.get()));
}

public static Optional<JwkEcPublicKey> tryDecodeEcKey(String keyId, Map<String, String> properties)
public static Optional<JwkEcPublicKey> tryDecodeEcKey(String keyId, Key key)
{
// alg field is optional so not verified
// use field is optional so not verified

String curveName = properties.get("crv");
if (Strings.isNullOrEmpty(curveName)) {
log.error("JWK EC key %s does not contain the required curve field 'crv'", keyId);
return Optional.empty();
}
String encodedX = properties.get("x");
if (Strings.isNullOrEmpty(encodedX)) {
log.error("JWK EC key %s does not contain the required x coordinate field 'x'", keyId);
return Optional.empty();
}
String encodedY = properties.get("y");
if (Strings.isNullOrEmpty(encodedY)) {
log.error("JWK EC key %s does not contain the required y coordinate field 'y'", keyId);
return Optional.empty();
}

Optional<ECParameterSpec> curve = EcCurve.tryGet(curveName);
Optional<String> curveName = key.getStringProperty("crv");
Optional<ECParameterSpec> curve = curveName.flatMap(EcCurve::tryGet);
if (curve.isEmpty()) {
log.error("JWK EC %s curve '%s' is not supported", keyId, curveName);
return Optional.empty();
}
Optional<BigInteger> x = decodeBigint(keyId, "x", encodedX);
Optional<BigInteger> x = key.getStringProperty("x").flatMap(encodedX -> decodeBigint(keyId, "x", encodedX));
if (x.isEmpty()) {
return Optional.empty();
}
Optional<BigInteger> y = decodeBigint(keyId, "y", encodedY);
Optional<BigInteger> y = key.getStringProperty("y").flatMap(encodedY -> decodeBigint(keyId, "y", encodedY));
if (y.isEmpty()) {
return Optional.empty();
}
Expand Down Expand Up @@ -259,17 +231,58 @@ public byte[] getEncoded()

public static class Keys
{
private final List<Map<String, String>> keys;
private final List<Key> keys;

@JsonCreator
public Keys(@JsonProperty("keys") List<Map<String, String>> keys)
public Keys(@JsonProperty("keys") List<Key> keys)
{
this.keys = ImmutableList.copyOf(requireNonNull(keys, "keys is null"));
}

public List<Map<String, String>> getKeys()
public List<Key> getKeys()
{
return keys;
}
}

public static class Key
{
private final String kty;
private final Optional<String> kid;
private final Map<String, Object> other = new HashMap<>();

@JsonCreator
public Key(
@JsonProperty("kty") String kty,
@JsonProperty("kid") Optional<String> kid)
{
this.kty = requireNonNull(kty, "kty is null");
this.kid = requireNonNull(kid, "kid is null");
}

public String getKty()
{
return kty;
}

public Optional<String> getKid()
{
return kid;
}

public Optional<String> getStringProperty(String name)
{
Object value = other.get(name);
if (value instanceof String && !Strings.isNullOrEmpty((String) value)) {
return Optional.of((String) value);
}
return Optional.empty();
}

@JsonAnySetter
public void set(String name, Object value)
{
other.put(name, value);
}
}
}
Expand Up @@ -56,6 +56,18 @@ public class TestJwkService
" \"x\": \"W9pnAHwUz81LldKjL3BzxO1iHe1Pc0fO6rHkrybVy6Y\",\n" +
" \"y\": \"XKSNmn_xajgOvWuAiJnWx5I46IwPVJJYPaEpsX3NPZg\",\n" +
" \"alg\": \"ES256\"\n" +
" },\n" +
" {\n" +
" \"alg\": \"RS256\",\n" +
" \"kty\": \"RSA\",\n" +
" \"use\": \"sig\",\n" +
" \"x5c\": [\n" +
" \"MIIC+DCCAeCgAwIBAgIJBIGjYW6hFpn2MA0GCSqGSIb3DQEBBQUAMCMxITAfBgNVBAMTGGN1c3RvbWVyLWRlbW9zLmF1dGgwLmNvbTAeFw0xNjExMjIyMjIyMDVaFw0zMDA4MDEyMjIyMDVaMCMxITAfBgNVBAMTGGN1c3RvbWVyLWRlbW9zLmF1dGgwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMnjZc5bm/eGIHq09N9HKHahM7Y31P0ul+A2wwP4lSpIwFrWHzxw88/7Dwk9QMc+orGXX95R6av4GF+Es/nG3uK45ooMVMa/hYCh0Mtx3gnSuoTavQEkLzCvSwTqVwzZ+5noukWVqJuMKNwjL77GNcPLY7Xy2/skMCT5bR8UoWaufooQvYq6SyPcRAU4BtdquZRiBT4U5f+4pwNTxSvey7ki50yc1tG49Per/0zA4O6Tlpv8x7Red6m1bCNHt7+Z5nSl3RX/QYyAEUX1a28VcYmR41Osy+o2OUCXYdUAphDaHo4/8rbKTJhlu8jEcc1KoMXAKjgaVZtG/v5ltx6AXY0CAwEAAaMvMC0wDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQUQxFG602h1cG+pnyvJoy9pGJJoCswDQYJKoZIhvcNAQEFBQADggEBAGvtCbzGNBUJPLICth3mLsX0Z4z8T8iu4tyoiuAshP/Ry/ZBnFnXmhD8vwgMZ2lTgUWwlrvlgN+fAtYKnwFO2G3BOCFw96Nm8So9sjTda9CCZ3dhoH57F/hVMBB0K6xhklAc0b5ZxUpCIN92v/w+xZoz1XQBHe8ZbRHaP1HpRM4M7DJk2G5cgUCyu3UBvYS41sHvzrxQ3z7vIePRA4WF4bEkfX12gvny0RsPkrbVMXX1Rj9t6V7QXrbPYBAO+43JvDGYawxYVvLhz+BJ45x50GFQmHszfY3BR9TPK8xmMmQwtIvLu1PMttNCs7niCYkSiUv2sc2mlq1i3IashGkkgmo=\"\n" +
" ],\n" +
" \"n\": \"yeNlzlub94YgerT030codqEztjfU_S6X4DbDA_iVKkjAWtYfPHDzz_sPCT1Axz6isZdf3lHpq_gYX4Sz-cbe4rjmigxUxr-FgKHQy3HeCdK6hNq9ASQvMK9LBOpXDNn7mei6RZWom4wo3CMvvsY1w8tjtfLb-yQwJPltHxShZq5-ihC9irpLI9xEBTgG12q5lGIFPhTl_7inA1PFK97LuSLnTJzW0bj096v_TMDg7pOWm_zHtF53qbVsI0e3v5nmdKXdFf9BjIARRfVrbxVxiZHjU6zL6jY5QJdh1QCmENoejj_ytspMmGW7yMRxzUqgxcAqOBpVm0b-_mW3HoBdjQ\",\n" +
" \"e\": \"AQAB\",\n" +
" \"kid\": \"test-certificate-chain\",\n" +
" \"x5t\": \"NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg\"\n" +
" }\n" +
" ]\n" +
"}";
Expand Down Expand Up @@ -185,8 +197,9 @@ private static void assertEmptyKeys(JwkService service)
private static void assertTestKeys(JwkService service)
{
Map<String, PublicKey> keys = service.getKeys();
assertEquals(keys.size(), 2);
assertEquals(keys.size(), 3);
assertTrue(keys.containsKey("test-rsa"));
assertTrue(keys.containsKey("test-ec"));
assertTrue(keys.containsKey("test-certificate-chain"));
}
}

0 comments on commit bc312ab

Please sign in to comment.