Sanity checkpoint so I don't lose work.

This commit is contained in:
Les Hazlewood 2021-09-12 01:23:01 -07:00
parent 3f4e40ad27
commit 5819aa2f4b
71 changed files with 2511 additions and 979 deletions

View File

@ -15,6 +15,8 @@
*/
package io.jsonwebtoken.lang;
import java.util.List;
/**
* @since 0.6
*/
@ -22,6 +24,14 @@ public final class Arrays {
private Arrays(){} //prevent instantiation
public static <T> int length(T[] a) {
return a == null ? 0 : a.length;
}
public static <T> List<T> asList(T[] a) {
return a == null ? Collections.<T>emptyList() : java.util.Arrays.asList(a);
}
public static int length(byte[] bytes) {
return bytes != null ? bytes.length : 0;
}

View File

@ -128,10 +128,11 @@ public final class Assert {
* @param message the exception message to use if the assertion fails
* @see Strings#hasText
*/
public static void hasText(String text, String message) {
public static String hasText(String text, String message) {
if (!Strings.hasText(text)) {
throw new IllegalArgumentException(message);
}
return text;
}
/**
@ -268,10 +269,11 @@ public final class Assert {
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the map is <code>null</code> or has no entries
*/
public static void notEmpty(Map map, String message) {
public static <T extends Map<?,?>> T notEmpty(T map, String message) {
if (Collections.isEmpty(map)) {
throw new IllegalArgumentException(message);
}
return map;
}
/**
@ -310,13 +312,14 @@ public final class Assert {
* @throws IllegalArgumentException if the object is not an instance of clazz
* @see Class#isInstance
*/
public static void isInstanceOf(Class type, Object obj, String message) {
public static <T> T isInstanceOf(Class<T> type, Object obj, String message) {
notNull(type, "Type to check against must not be null");
if (!type.isInstance(obj)) {
throw new IllegalArgumentException(message +
"Object of class [" + (obj != null ? obj.getClass().getName() : "null") +
"] must be an instance of " + type);
}
return type.cast(obj);
}
/**

View File

@ -20,9 +20,11 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public final class Collections {
@ -32,6 +34,14 @@ public final class Collections {
return java.util.Collections.emptyList();
}
public static <T> Set<T> emptySet() {
return java.util.Collections.emptySet();
}
public static <K,V> Map<K,V> emptyMap() {
return java.util.Collections.emptyMap();
}
public static <T> List<T> of(T... elements) {
if (elements == null || elements.length == 0) {
return java.util.Collections.emptyList();
@ -39,6 +49,14 @@ public final class Collections {
return java.util.Collections.unmodifiableList(Arrays.asList(elements));
}
public static <T> Set<T> setOf(T... elements) {
if (elements == null || elements.length == 0) {
return java.util.Collections.emptySet();
}
Set<T> set = new LinkedHashSet<>(Arrays.asList(elements));
return java.util.Collections.unmodifiableSet(set);
}
/**
* Return <code>true</code> if the supplied Collection is <code>null</code>
* or empty. Otherwise, return <code>false</code>.

View File

@ -5,9 +5,9 @@ import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.List;
public interface AsymmetricJwk<V, K extends Key> extends Jwk<V, K> {
public interface AsymmetricJwk<K extends Key> extends Jwk<K> {
String getUse();
String getPublicKeyUse();
URI getX509Url();

View File

@ -1,10 +1,11 @@
package io.jsonwebtoken.security;
import java.net.URI;
import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.List;
public interface AsymmetricJwkMutator<T extends AsymmetricJwkMutator<T>> {
public interface AsymmetricJwkBuilder<K extends Key, J extends AsymmetricJwk<K>, T extends AsymmetricJwkBuilder<K, J, T>> extends JwkBuilder<K, J, T> {
T setPublicKeyUse(String use);

View File

@ -3,5 +3,5 @@ package io.jsonwebtoken.security;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
public interface EcPrivateJwk<V> extends PrivateJwk<V, ECPrivateKey, ECPublicKey, EcPublicJwk<V>> {
public interface EcPrivateJwk extends PrivateJwk<ECPrivateKey, ECPublicKey, EcPublicJwk> {
}

View File

@ -3,5 +3,5 @@ package io.jsonwebtoken.security;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
public interface EcPrivateJwkBuilder<V> extends PrivateJwkBuilder<ECPrivateKey, ECPublicKey, EcPublicJwk<V>, EcPrivateJwk<V>, EcPrivateJwkBuilder<V>> {
public interface EcPrivateJwkBuilder extends PrivateJwkBuilder<ECPrivateKey, ECPublicKey, EcPublicJwk, EcPrivateJwk, EcPrivateJwkBuilder> {
}

View File

@ -2,5 +2,5 @@ package io.jsonwebtoken.security;
import java.security.interfaces.ECPublicKey;
public interface EcPublicJwk<V> extends PublicJwk<V, ECPublicKey> {
public interface EcPublicJwk extends PublicJwk<ECPublicKey> {
}

View File

@ -3,5 +3,5 @@ package io.jsonwebtoken.security;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
public interface EcPublicJwkBuilder<V> extends PublicJwkBuilder<ECPublicKey, ECPrivateKey, EcPublicJwk<V>, EcPrivateJwk<V>, EcPrivateJwkBuilder<V>, EcPublicJwkBuilder<V>> {
public interface EcPublicJwkBuilder extends PublicJwkBuilder<ECPublicKey, ECPrivateKey, EcPublicJwk, EcPrivateJwk, EcPrivateJwkBuilder, EcPublicJwkBuilder> {
}

View File

@ -7,15 +7,13 @@ import java.util.Set;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface Jwk<V, K extends Key> extends Identifiable, Map<String,V> {
String getType();
Set<String> getOperations();
public interface Jwk<K extends Key> extends Identifiable, Map<String,Object> {
String getAlgorithm();
String getId();
Set<String> getOperations();
String getType();
K toKey();
}

View File

@ -8,7 +8,7 @@ import java.util.Set;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface JwkBuilder<K extends Key, J extends Jwk<?, K>, T extends JwkBuilder<K, J, T>> {
public interface JwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilder<K, J, T>> {
T put(String name, Object value);

View File

@ -2,44 +2,14 @@ package io.jsonwebtoken.security;
import io.jsonwebtoken.lang.Classes;
import javax.crypto.SecretKey;
import java.security.Key;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
/**
* @since JJWT_RELEASE_VERSION
*/
public class Jwks {
private static final String BUILDER_CLASS_NAME = "io.jsonwebtoken.impl.security.DefaultJwkBuilder";
private static final Class<?>[] KEY_ARGS = new Class[]{Key.class};
private static final String CNAME = "io.jsonwebtoken.impl.security.DefaultProtoJwkBuilder";
public static SecretJwkBuilder builder(SecretKey key) {
return Classes.newInstance(BUILDER_CLASS_NAME, KEY_ARGS, key);
}
public static RsaPublicJwkBuilder<?> builder(RSAPublicKey key) {
return Classes.newInstance(BUILDER_CLASS_NAME, KEY_ARGS, key);
}
public static EcPublicJwkBuilder<?> builder(ECPublicKey key) {
return Classes.newInstance(BUILDER_CLASS_NAME, KEY_ARGS, key);
}
public static RsaPrivateJwkBuilder<?> builder(RSAPrivateKey key) {
return Classes.newInstance(BUILDER_CLASS_NAME, KEY_ARGS, key);
}
public static EcPrivateJwkBuilder<?> builder(ECPrivateKey key) {
return Classes.newInstance(BUILDER_CLASS_NAME, KEY_ARGS, key);
}
public static Jwk<?, ?> forValues(Map<String, ?> values) {
JwkBuilder<?, ?, ?> builder = Classes.newInstance(BUILDER_CLASS_NAME);
return builder.putAll(values).build();
public static ProtoJwkBuilder<?, ?, ?> builder() {
return Classes.newInstance(CNAME);
}
}

View File

@ -4,7 +4,7 @@ import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
public interface PrivateJwk<V, K extends PrivateKey, L extends PublicKey, M extends PublicJwk<V, L>> extends AsymmetricJwk<V, K> {
public interface PrivateJwk<K extends PrivateKey, L extends PublicKey, M extends PublicJwk<L>> extends AsymmetricJwk<K> {
M toPublicJwk();

View File

@ -3,7 +3,9 @@ package io.jsonwebtoken.security;
import java.security.PrivateKey;
import java.security.PublicKey;
public interface PrivateJwkBuilder<K extends PrivateKey, L extends PublicKey, J extends PublicJwk<?, L>, M extends PrivateJwk<?, K, L, J>, T extends PrivateJwkBuilder<K, L, J, M, T>> extends AsymmetricJwkMutator<T>, JwkBuilder<K, M, T> {
public interface PrivateJwkBuilder<K extends PrivateKey, L extends PublicKey,
J extends PublicJwk<L>, M extends PrivateJwk<K, L, J>,
T extends PrivateJwkBuilder<K, L, J, M, T>> extends AsymmetricJwkBuilder<K, M, T> {
T setPublicKey(L publicKey);
}

View File

@ -0,0 +1,26 @@
package io.jsonwebtoken.security;
import javax.crypto.SecretKey;
import java.security.Key;
import java.security.KeyPair;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
public interface ProtoJwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilder<K, J, T>> extends JwkBuilder<K, J, T> {
SecretJwkBuilder setKey(SecretKey key);
RsaPublicJwkBuilder setKey(RSAPublicKey key);
RsaPrivateJwkBuilder setKey(RSAPrivateKey key);
EcPublicJwkBuilder setKey(ECPublicKey key);
EcPrivateJwkBuilder setKey(ECPrivateKey key);
RsaPrivateJwkBuilder setKeyPairRsa(KeyPair keyPair);
EcPrivateJwkBuilder setKeyPairEc(KeyPair keyPair);
}

View File

@ -2,5 +2,5 @@ package io.jsonwebtoken.security;
import java.security.PublicKey;
public interface PublicJwk<V, K extends PublicKey> extends AsymmetricJwk<V, K> {
public interface PublicJwk<K extends PublicKey> extends AsymmetricJwk<K> {
}

View File

@ -3,7 +3,7 @@ package io.jsonwebtoken.security;
import java.security.PrivateKey;
import java.security.PublicKey;
public interface PublicJwkBuilder<K extends PublicKey, L extends PrivateKey, J extends PublicJwk<?, K>, M extends PrivateJwk<?, L, K, J>, P extends PrivateJwkBuilder<L, K, J, M, P>, T extends PublicJwkBuilder<K, L, J, M, P, T>> extends AsymmetricJwkMutator<T>, JwkBuilder<K, J, T> {
public interface PublicJwkBuilder<K extends PublicKey, L extends PrivateKey, J extends PublicJwk<K>, M extends PrivateJwk<L, K, J>, P extends PrivateJwkBuilder<L, K, J, M, P>, T extends PublicJwkBuilder<K, L, J, M, P, T>> extends AsymmetricJwkBuilder<K, J, T> {
P setPrivateKey(L privateKey);
}

View File

@ -3,5 +3,5 @@ package io.jsonwebtoken.security;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
public interface RsaPrivateJwk<V> extends PrivateJwk<V, RSAPrivateKey, RSAPublicKey, RsaPublicJwk<V>> {
public interface RsaPrivateJwk extends PrivateJwk<RSAPrivateKey, RSAPublicKey, RsaPublicJwk> {
}

View File

@ -3,5 +3,5 @@ package io.jsonwebtoken.security;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
public interface RsaPrivateJwkBuilder<V> extends PrivateJwkBuilder<RSAPrivateKey, RSAPublicKey, RsaPublicJwk<V>, RsaPrivateJwk<V>, RsaPrivateJwkBuilder<V>> {
public interface RsaPrivateJwkBuilder extends PrivateJwkBuilder<RSAPrivateKey, RSAPublicKey, RsaPublicJwk, RsaPrivateJwk, RsaPrivateJwkBuilder> {
}

View File

@ -2,5 +2,5 @@ package io.jsonwebtoken.security;
import java.security.interfaces.RSAPublicKey;
public interface RsaPublicJwk<V> extends PublicJwk<V, RSAPublicKey> {
public interface RsaPublicJwk extends PublicJwk<RSAPublicKey> {
}

View File

@ -3,6 +3,6 @@ package io.jsonwebtoken.security;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
public interface RsaPublicJwkBuilder<V> extends PublicJwkBuilder<RSAPublicKey, RSAPrivateKey, RsaPublicJwk<V>, RsaPrivateJwk<V>, RsaPrivateJwkBuilder<V>, RsaPublicJwkBuilder<V>> {
public interface RsaPublicJwkBuilder extends PublicJwkBuilder<RSAPublicKey, RSAPrivateKey, RsaPublicJwk, RsaPrivateJwk, RsaPrivateJwkBuilder, RsaPublicJwkBuilder> {
}

View File

@ -2,5 +2,5 @@ package io.jsonwebtoken.security;
import javax.crypto.SecretKey;
public interface SecretJwk<V> extends Jwk<V, SecretKey> {
public interface SecretJwk extends Jwk<SecretKey> {
}

View File

@ -2,5 +2,5 @@ package io.jsonwebtoken.security;
import javax.crypto.SecretKey;
public interface SecretJwkBuilder extends JwkBuilder<SecretKey, SecretJwk<?>, SecretJwkBuilder> {
public interface SecretJwkBuilder extends JwkBuilder<SecretKey, SecretJwk, SecretJwkBuilder> {
}

View File

@ -60,6 +60,7 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
private Key signatureVerificationKey;
private Key decryptionKey;
private KeyResolver keyResolver;
@SuppressWarnings("deprecation") //TODO: remove for 1.0
private SigningKeyResolver signingKeyResolver;
private CompressionCodecResolver compressionCodecResolver;
@ -184,6 +185,7 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
return this;
}
@SuppressWarnings("deprecation") //TODO: remove for 1.0
@Override
public JwtParserBuilder setSigningKeyResolver(SigningKeyResolver signingKeyResolver) {
Assert.notNull(signingKeyResolver, "SigningKeyResolver cannot be null.");

View File

@ -5,6 +5,6 @@ import io.jsonwebtoken.io.Encoders;
public class Codecs {
static final CodecConverter<byte[], String> BASE64 = new CodecConverter<>(Encoders.BASE64, Decoders.BASE64);
static final CodecConverter<byte[], String> BASE64URL = new CodecConverter<>(Encoders.BASE64URL, Decoders.BASE64URL);
public static final CodecConverter<byte[], String> BASE64 = new CodecConverter<>(Encoders.BASE64, Decoders.BASE64);
public static final CodecConverter<byte[], String> BASE64URL = new CodecConverter<>(Encoders.BASE64URL, Decoders.BASE64URL);
}

View File

@ -0,0 +1,5 @@
package io.jsonwebtoken.impl.lang;
public interface CheckedFunction<T, R> {
R apply(T t) throws Exception;
}

View File

@ -57,7 +57,7 @@ class CollectionConverter<T, C extends Collection<T>> implements Converter<C, Ob
return null;
}
Collection<?> c;
if (value.getClass().isArray() && !value.getClass().getComponentType().isArray()) {
if (value.getClass().isArray() && !value.getClass().getComponentType().isPrimitive()) {
c = Collections.arrayToList(value);
} else if (value instanceof Collection) {
c = (Collection<?>) value;

View File

@ -28,8 +28,4 @@ public final class Converters {
public static <T> Converter<T, Object> forEncoded(Class<T> elementType, Converter<T, String> elementConverter) {
return new EncodedObjectConverter<T>(elementType, elementConverter);
}
public static <T> Converter<List<T>, Object> forEncodedList(Class<T> elementType, Converter<T, String> elementConverter) {
return forList(forEncoded(elementType, elementConverter));
}
}

View File

@ -6,49 +6,42 @@ import java.net.URI;
import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
import java.util.Set;
public abstract class AbstractAsymmetricJwk<V, K extends Key> extends DefaultJwk<K> implements AsymmetricJwk<Object, K> {
abstract class AbstractAsymmetricJwk<K extends Key> extends AbstractJwk<K> implements AsymmetricJwk<K> {
private final String use;
private final URI x509Url;
private final List<X509Certificate> certChain;
private final byte[] x509Sha1Thumbprint;
private final byte[] x509Sha256Thumbprint;
static final String PUBLIC_KEY_USE = "use";
static final String X509_URL = "x5u";
static final String X509_CERT_CHAIN = "x5c";
static final String X509_SHA1_THUMBPRINT = "x5t";
static final String X509_SHA256_THUMBPRINT = "x5t#S256";
AbstractAsymmetricJwk(String type, String use, Set<String> operations, String algorithm, String id, URI x509Url, List<X509Certificate> certChain, byte[] x509Sha1Thumbprint, byte[] x509Sha256Thumbprint, K key, Map<String, ?> values) {
super(type, operations, algorithm, id, key, values);
this.use = use;
this.x509Url = x509Url;
this.certChain = certChain;
this.x509Sha1Thumbprint = x509Sha1Thumbprint;
this.x509Sha256Thumbprint = x509Sha256Thumbprint;
AbstractAsymmetricJwk(JwkContext<K> ctx) {
super(ctx);
}
@Override
public String getUse() {
return this.use;
public String getPublicKeyUse() {
return this.context.getPublicKeyUse();
}
@Override
public URI getX509Url() {
return this.x509Url;
return this.context.getX509Url();
}
@Override
public List<X509Certificate> getX509CertificateChain() {
return this.certChain;
return this.context.getX509CertificateChain();
}
@Override
public byte[] getX509CertificateSha1Thumbprint() {
return this.x509Sha1Thumbprint;
return this.context.getX509CertificateSha1Thumbprint();
}
@Override
public byte[] getX509CertificateSha256Thumbprint() {
return this.x509Sha256Thumbprint;
return this.context.getX509CertificateSha256Thumbprint();
}
}

View File

@ -0,0 +1,246 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.AsymmetricJwk;
import io.jsonwebtoken.security.AsymmetricJwkBuilder;
import io.jsonwebtoken.security.EcPrivateJwk;
import io.jsonwebtoken.security.EcPrivateJwkBuilder;
import io.jsonwebtoken.security.EcPublicJwk;
import io.jsonwebtoken.security.EcPublicJwkBuilder;
import io.jsonwebtoken.security.MalformedKeyException;
import io.jsonwebtoken.security.PrivateJwk;
import io.jsonwebtoken.security.PrivateJwkBuilder;
import io.jsonwebtoken.security.PublicJwk;
import io.jsonwebtoken.security.PublicJwkBuilder;
import io.jsonwebtoken.security.RsaPrivateJwk;
import io.jsonwebtoken.security.RsaPrivateJwkBuilder;
import io.jsonwebtoken.security.RsaPublicJwk;
import io.jsonwebtoken.security.RsaPublicJwkBuilder;
import java.net.URI;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.List;
import java.util.Set;
abstract class AbstractAsymmetricJwkBuilder<K extends Key, J extends AsymmetricJwk<K>,
T extends AsymmetricJwkBuilder<K, J, T>>
extends AbstractJwkBuilder<K, J, T> implements AsymmetricJwkBuilder<K, J, T> {
protected boolean computeX509Sha1Thumbprint;
/**
* Boolean object indicates 3 states: 1) not configured 2) configured as true, 3) configured as false
*/
protected Boolean computeX509Sha256Thumbprint = null;
protected Boolean applyX509KeyUse = null;
private KeyUseStrategy keyUseStrategy = DefaultKeyUseStrategy.INSTANCE;
public AbstractAsymmetricJwkBuilder(JwkContext<K> ctx) {
super(ctx);
}
AbstractAsymmetricJwkBuilder(AbstractAsymmetricJwkBuilder<?, ?, ?> b, K key, Set<String> privateNames) {
super(new DefaultJwkContext<>(b.jwkContext, key, privateNames));
this.computeX509Sha1Thumbprint = b.computeX509Sha1Thumbprint;
this.computeX509Sha256Thumbprint = b.computeX509Sha256Thumbprint;
this.applyX509KeyUse = b.applyX509KeyUse;
this.keyUseStrategy = b.keyUseStrategy;
}
@Override
public T setPublicKeyUse(String use) {
return put(AbstractAsymmetricJwk.PUBLIC_KEY_USE, use);
}
public T setKeyUseStrategy(KeyUseStrategy strategy) {
this.keyUseStrategy = Assert.notNull(strategy, "KeyUseStrategy cannot be null.");
return tthis();
}
@Override
public T setX509CertificateChain(List<X509Certificate> chain) {
this.jwkContext.setX509CertificateChain(chain);
return tthis();
}
@Override
public T setX509Url(URI url) {
this.jwkContext.setX509Url(url);
return tthis();
}
@Override
public T withX509KeyUse(boolean enable) {
this.applyX509KeyUse = enable;
return tthis();
}
@Override
public T withX509Sha1Thumbprint(boolean enable) {
this.computeX509Sha1Thumbprint = enable;
return tthis();
}
@Override
public T withX509Sha256Thumbprint(boolean enable) {
this.computeX509Sha256Thumbprint = enable;
return tthis();
}
private byte[] computeThumbprint(final X509Certificate cert, final String jcaName) {
try {
byte[] encoded = cert.getEncoded();
MessageDigest digest = MessageDigest.getInstance(jcaName);
return digest.digest(encoded);
} catch (CertificateEncodingException e) {
String msg = "Unable to access X509Certificate encoded bytes necessary to compute a " + jcaName +
" thumbprint. Certificate: {" + cert + "}. Cause: " + e.getMessage();
throw new MalformedKeyException(msg, e);
} catch (NoSuchAlgorithmException e) {
String msg = "JCA Algorithm Name '" + jcaName + "' is not available: " + e.getMessage();
throw new IllegalStateException(msg, e);
}
}
@Override
public J build() {
X509Certificate firstCert = null;
List<X509Certificate> chain = this.jwkContext.getX509CertificateChain();
if (!Collections.isEmpty(chain)) {
firstCert = chain.get(0);
}
if (applyX509KeyUse == null) { //if not specified, enable by default if possible:
applyX509KeyUse = firstCert != null && !Strings.hasText(this.jwkContext.getPublicKeyUse());
}
if (computeX509Sha256Thumbprint == null) { //if not specified, enable by default if possible:
computeX509Sha256Thumbprint = firstCert != null && !computeX509Sha1Thumbprint;
}
if (firstCert != null) {
if (applyX509KeyUse) {
KeyUsage usage = new KeyUsage(firstCert);
String use = keyUseStrategy.toJwkValue(usage);
if (Strings.hasText(use)) {
setPublicKeyUse(use);
}
}
if (computeX509Sha1Thumbprint) {
byte[] thumbprint = computeThumbprint(firstCert, "SHA-1");
put(AbstractAsymmetricJwk.X509_SHA1_THUMBPRINT, thumbprint);
}
if (computeX509Sha256Thumbprint) {
byte[] thumbprint = computeThumbprint(firstCert, "SHA-256");
put(AbstractAsymmetricJwk.X509_SHA256_THUMBPRINT, thumbprint);
}
}
return super.build();
}
private abstract static class DefaultPublicJwkBuilder<K extends PublicKey, L extends PrivateKey,
J extends PublicJwk<K>, M extends PrivateJwk<L, K, J>, P extends PrivateJwkBuilder<L, K, J, M, P>,
T extends PublicJwkBuilder<K, L, J, M, P, T>>
extends AbstractAsymmetricJwkBuilder<K, J, T>
implements PublicJwkBuilder<K, L, J, M, P, T> {
public DefaultPublicJwkBuilder(JwkContext<K> ctx) {
super(ctx);
}
@Override
public P setPrivateKey(L privateKey) {
Assert.notNull(privateKey, "PrivateKey argument cannot be null.");
final K publicKey = Assert.notNull(jwkContext.getKey(), "PublicKey cannot be null.");
return newPrivateBuilder(privateKey).setPublicKey(publicKey);
}
protected abstract P newPrivateBuilder(L privateKey);
}
private abstract static class DefaultPrivateJwkBuilder<K extends PrivateKey, L extends PublicKey,
J extends PublicJwk<L>, M extends PrivateJwk<K, L, J>,
T extends PrivateJwkBuilder<K, L, J, M, T>>
extends AbstractAsymmetricJwkBuilder<K, M, T>
implements PrivateJwkBuilder<K, L, J, M, T> {
DefaultPrivateJwkBuilder(JwkContext<K> ctx) {
super(ctx);
}
DefaultPrivateJwkBuilder(DefaultPublicJwkBuilder<L, K, J, M, ?, ?> b, K key, Set<String> privateNames) {
super(b, key, privateNames);
this.jwkContext.setPublicKey(b.jwkContext.getKey());
}
@Override
public T setPublicKey(L publicKey) {
this.jwkContext.setPublicKey(publicKey);
return tthis();
}
}
static class DefaultEcPublicJwkBuilder
extends DefaultPublicJwkBuilder<ECPublicKey, ECPrivateKey, EcPublicJwk, EcPrivateJwk, EcPrivateJwkBuilder, EcPublicJwkBuilder>
implements EcPublicJwkBuilder {
public DefaultEcPublicJwkBuilder(JwkContext<?> src, ECPublicKey key) {
super(new DefaultJwkContext<>(src, key, DefaultEcPrivateJwk.PRIVATE_NAMES));
}
@Override
protected EcPrivateJwkBuilder newPrivateBuilder(ECPrivateKey key) {
return new DefaultEcPrivateJwkBuilder(this, key);
}
}
static class DefaultRsaPublicJwkBuilder
extends DefaultPublicJwkBuilder<RSAPublicKey, RSAPrivateKey, RsaPublicJwk, RsaPrivateJwk, RsaPrivateJwkBuilder, RsaPublicJwkBuilder>
implements RsaPublicJwkBuilder {
DefaultRsaPublicJwkBuilder(JwkContext<?> ctx, RSAPublicKey key) {
super(new DefaultJwkContext<>(ctx, key, DefaultRsaPrivateJwk.PRIVATE_NAMES));
}
@Override
protected RsaPrivateJwkBuilder newPrivateBuilder(RSAPrivateKey key) {
return new DefaultRsaPrivateJwkBuilder(this, key);
}
}
static class DefaultEcPrivateJwkBuilder
extends DefaultPrivateJwkBuilder<ECPrivateKey, ECPublicKey, EcPublicJwk, EcPrivateJwk, EcPrivateJwkBuilder>
implements EcPrivateJwkBuilder {
DefaultEcPrivateJwkBuilder(JwkContext<?> src, ECPrivateKey key) {
super(new DefaultJwkContext<>(src, key, DefaultEcPrivateJwk.PRIVATE_NAMES));
}
DefaultEcPrivateJwkBuilder(DefaultEcPublicJwkBuilder b, ECPrivateKey key) {
super(b, key, DefaultEcPrivateJwk.PRIVATE_NAMES);
}
}
static class DefaultRsaPrivateJwkBuilder
extends DefaultPrivateJwkBuilder<RSAPrivateKey, RSAPublicKey, RsaPublicJwk, RsaPrivateJwk, RsaPrivateJwkBuilder>
implements RsaPrivateJwkBuilder {
DefaultRsaPrivateJwkBuilder(JwkContext<?> src, RSAPrivateKey key) {
super(new DefaultJwkContext<>(src, key, DefaultRsaPrivateJwk.PRIVATE_NAMES));
}
DefaultRsaPrivateJwkBuilder(DefaultRsaPublicJwkBuilder b, RSAPrivateKey key) {
super(b, key, DefaultRsaPrivateJwk.PRIVATE_NAMES);
}
}
}

View File

@ -1,16 +1,14 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.UnsupportedKeyException;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
@ -18,19 +16,12 @@ import java.security.spec.ECFieldFp;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.util.LinkedHashMap;
import java.util.Map;
public class EcJwkConverter<K extends Key & ECKey> extends AbstractJwkConverter<K> {
static final String TYPE_VALUE = "EC";
static final String CURVE_ID = "crv";
static final String X = "x";
static final String Y = "y";
static final String D = "d";
abstract class AbstractEcJwkFactory<K extends Key & ECKey, J extends Jwk<K>> extends AbstractFamilyJwkFactory<K, J> {
private static final BigInteger TWO = new BigInteger("2");
private static final BigInteger THREE = new BigInteger("3");
@ -66,7 +57,7 @@ public class EcJwkConverter<K extends Key & ECKey> extends AbstractJwkConverter<
}
}
private static ECParameterSpec getCurveByJwaId(String jwaCurveId) {
protected static ECParameterSpec getCurveByJwaId(String jwaCurveId) {
ECParameterSpec spec = EC_SPECS_BY_JWA_ID.get(jwaCurveId);
if (spec == null) {
String msg = "Unrecognized JWA curve id '" + jwaCurveId + "'";
@ -75,7 +66,7 @@ public class EcJwkConverter<K extends Key & ECKey> extends AbstractJwkConverter<
return spec;
}
private static String getJwaIdByCurve(EllipticCurve curve) {
protected static String getJwaIdByCurve(EllipticCurve curve) {
String jwaCurveId = JWA_IDS_BY_CURVE.get(curve);
if (jwaCurveId == null) {
String msg = "The specified ECKey curve does not match a JWA standard curve id.";
@ -110,6 +101,7 @@ public class EcJwkConverter<K extends Key & ECKey> extends AbstractJwkConverter<
* <p>
* <code>y<sup>2</sup> = x<sup>3</sup> + ax + b</code>
* </p>
*
* @param curve the Elliptic Curve to check
* @param point a point that may or may not be defined on the specified elliptic curve
* @return {@code true} if a given elliptic curve contains the specified {@code point}, {@code false} otherwise.
@ -126,29 +118,36 @@ public class EcJwkConverter<K extends Key & ECKey> extends AbstractJwkConverter<
// to the equation to account for the restricted field. For a nice overview of the math behind EC curves and
// their application in cryptography, see
// https://web.northeastern.edu/dummit/docs/cryptography_5_elliptic_curves_in_cryptography.pdf
final BigInteger p = ((ECFieldFp)curve.getField()).getP();
final BigInteger p = ((ECFieldFp) curve.getField()).getP();
final BigInteger lhs = y.pow(2).mod(p); //mod p to account for field prime
final BigInteger rhs = x.pow(3).add(a.multiply(x)).add(b).mod(p); //mod p to account for field prime
return lhs.equals(rhs);
}
static ECPublicKey derivePublic(ECPrivateKey key) {
try {
final ECParameterSpec params = key.getParams();
final ECPoint w = multiply(params.getGenerator(), key.getS(), params);
final KeyFactory kg = KeyFactory.getInstance("EC");
return (ECPublicKey) kg.generatePublic(new ECPublicKeySpec(w, params));
} catch (Exception e) {
throw new UnsupportedKeyException("Unable to derive ECPublicKey from specified ECPrivateKey: " + e.getMessage(), e);
}
protected ECPublicKey derivePublic(final JwkContext<ECPrivateKey> ctx) {
final ECPrivateKey key = ctx.getKey();
final ECParameterSpec params = key.getParams();
final ECPoint w = multiply(params.getGenerator(), key.getS(), params);
final ECPublicKeySpec spec = new ECPublicKeySpec(w, params);
return generateKey(ctx, ECPublicKey.class, new CheckedFunction<KeyFactory, ECPublicKey>() {
@Override
public ECPublicKey apply(KeyFactory kf) {
try {
return (ECPublicKey) kf.generatePublic(spec);
} catch (Exception e) {
String msg = "Unable to derive ECPublicKey from ECPrivateKey {" + ctx + "}.";
throw new UnsupportedKeyException(msg);
}
}
});
}
/**
* Multiply a point {@code p} by scalar {@code s} on the curve identified by {@code spec}.
*
* @param p the Elliptic Curve point to multiply
* @param s the scalar value to multiply
* @param p the Elliptic Curve point to multiply
* @param s the scalar value to multiply
* @param spec the domain parameters that identify the Elliptic Curve containing point {@code p}.
*/
private static ECPoint multiply(ECPoint p, BigInteger s, ECParameterSpec spec) {
@ -215,111 +214,7 @@ public class EcJwkConverter<K extends Key & ECKey> extends AbstractJwkConverter<
return new ECPoint(x, y);
}
EcJwkConverter() {
super(TYPE_VALUE);
}
@Override
public boolean supports(Key key) {
return key instanceof ECPublicKey || key instanceof ECPrivateKey;
}
@Override
public K applyFrom(Map<String, ?> jwk) {
Assert.notEmpty(jwk, "JWK map argument cannot be null or empty.");
if (jwk.containsKey(D)) {
return (K) toPrivateKey(jwk);
}
return (K) toPublicKey(jwk);
}
@Override
public Map<String, ?> applyTo(K key) {
if (key instanceof ECPrivateKey) {
return toPrivateJwk((ECPrivateKey) key);
}
Assert.isInstanceOf(ECPublicKey.class, key, "Key argument must be an ECPublicKey or ECPrivateKey instance.");
return toPublicJwk((ECPublicKey)key);
}
private PublicKey toPublicKey(Map<String, ?> jwk) {
String curveId = getRequiredString(jwk, CURVE_ID);
BigInteger x = getRequiredBigInt(jwk, X, false);
BigInteger y = getRequiredBigInt(jwk, Y, false);
ECParameterSpec spec = getCurveByJwaId(curveId);
ECPoint point = new ECPoint(x, y);
if (!contains(spec.getCurve(), point)) {
Map<String,?> msgJwk = sanitize(jwk, D);
String msg = "EC JWK x,y coordinates do not match a point on the '" + curveId + "' elliptic curve. This " +
"could be due simply to an incorrectly-created JWK or possibly an attempted Invalid Curve Attack " +
"(see https://safecurves.cr.yp.to/twist.html for more information). JWK: {" + msgJwk + "}.";
throw new InvalidKeyException(msg);
}
ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, spec);
try {
KeyFactory kf = getKeyFactory();
return kf.generatePublic(pubSpec);
} catch (Exception e) {
Map<String,?> msgJwk = sanitize(jwk, D);
String msg = "Unable to create ECPublicKey from JWK {" + msgJwk + "}: " + e.getMessage();
throw new InvalidKeyException(msg, e);
}
}
public PrivateKey toPrivateKey(Map<String, ?> jwk) {
String curveId = getRequiredString(jwk, CURVE_ID);
BigInteger d = getRequiredBigInt(jwk, D, true);
// We don't actually need the public x,y point coordinates for JVM lookup, but the
// [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2)
// requires them to be present and valid for the private key as well, so we assert that here:
toPublicKey(jwk);
ECParameterSpec spec = getCurveByJwaId(curveId);
ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(d, spec);
try {
KeyFactory kf = getKeyFactory();
return kf.generatePrivate(privateSpec);
} catch (Exception e) {
Map<String,?> msgJwk = sanitize(jwk, D);
String msg = "Unable to create ECPrivateKey from JWK {" + msgJwk + "}: " + e.getMessage();
throw new InvalidKeyException(msg, e);
}
}
public Map<String, String> toPublicJwk(ECPublicKey key) {
Map<String, String> m = newJwkMap();
ECParameterSpec spec = key.getParams();
EllipticCurve curve = spec.getCurve();
ECPoint point = key.getW();
String curveId = getJwaIdByCurve(curve);
m.put(CURVE_ID, curveId);
int fieldSize = curve.getField().getFieldSize();
String x = toOctetString(fieldSize, point.getAffineX());
m.put(X, x);
String y = toOctetString(fieldSize, point.getAffineY());
m.put(Y, y);
return m;
}
public Map<String, ?> toPrivateJwk(ECPrivateKey key) {
ECPublicKey publicKey = derivePublic(key);
Map<String, String> publicJwk = toPublicJwk(publicKey);
Map<String, String> m = new LinkedHashMap<>(publicJwk);
int fieldSize = key.getParams().getCurve().getField().getFieldSize();
String d = toOctetString(fieldSize, key.getS());
m.put(D, d);
return m;
AbstractEcJwkFactory(Class<K> keyType) {
super(DefaultEcPublicJwk.TYPE_VALUE, keyType);
}
}

View File

@ -0,0 +1,146 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.MalformedKeyException;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
abstract class AbstractFamilyJwkFactory<K extends Key, J extends Jwk<K>> implements FamilyJwkFactory<K, J> {
static void malformed(String msg) {
throw new MalformedKeyException(msg);
}
static String getRequiredString(JwkContext<?> ctx, String name) {
Assert.notNull(ctx, "JWK map cannot be null or empty.");
Object value = ctx.get(name);
if (value == null) {
malformed("JWK is missing required case-sensitive '" + name + "' member.");
}
String s = String.valueOf(value);
if (!Strings.hasText(s)) {
malformed("JWK '" + name + "' member cannot be null or empty.");
}
return s;
}
static BigInteger getRequiredBigInt(JwkContext<?> ctx, String name, boolean sensitive) {
String s = getRequiredString(ctx, name);
try {
byte[] bytes = Decoders.BASE64URL.decode(s);
return new BigInteger(1, bytes);
} catch (Exception e) {
String val = sensitive ? AbstractJwk.REDACTED_VALUE : s;
String msg = "Unable to decode JWK member '" + name + "' to BigInteger from value: " + val;
throw new MalformedKeyException(msg, e);
}
}
// Copied from Apache Commons Codec 1.14:
// https://github.com/apache/commons-codec/blob/af7b94750e2178b8437d9812b28e36ac87a455f2/src/main/java/org/apache/commons/codec/binary/Base64.java#L746-L775
static byte[] toUnsignedBytes(BigInteger bigInt) {
Assert.notNull(bigInt, "BigInteger argument cannot be null.");
final int bitlen = bigInt.bitLength();
// round bitlen
final int roundedBitlen = ((bitlen + 7) >> 3) << 3;
final byte[] bigBytes = bigInt.toByteArray();
if (((bitlen % 8) != 0) && (((bitlen / 8) + 1) == (roundedBitlen / 8))) {
return bigBytes;
}
// set up params for copying everything but sign bit
int startSrc = 0;
int len = bigBytes.length;
// if bigInt is exactly byte-aligned, just skip signbit in copy
if ((bitlen % 8) == 0) {
startSrc = 1;
len--;
}
final int startDst = roundedBitlen / 8 - len; // to pad w/ nulls as per spec
final byte[] resizedBytes = new byte[roundedBitlen / 8];
System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len);
return resizedBytes;
}
protected static String encode(BigInteger bigInt) {
byte[] unsigned = toUnsignedBytes(bigInt);
return Encoders.BASE64URL.encode(unsigned);
}
private final String ktyValue;
private final Class<K> keyType;
AbstractFamilyJwkFactory(String ktyValue, Class<K> keyType) {
this.ktyValue = Assert.hasText(ktyValue, "keyType argument cannot be null or empty.");
this.keyType = Assert.notNull(keyType, "keyType class cannot be null.");
}
@Override
public String getId() {
return this.ktyValue;
}
@Override
public boolean supports(JwkContext<?> ctx) {
return supportsKey(ctx.getKey()) || supportsKeyValues(ctx);
}
protected boolean supportsKeyValues(JwkContext<?> ctx) {
return this.ktyValue.equals(ctx.getType());
}
protected boolean supportsKey(Key key) {
return this.keyType.isInstance(key);
}
protected K generateKey(final JwkContext<K> ctx, final CheckedFunction<KeyFactory, K> fn) {
return generateKey(ctx, this.keyType, fn);
}
protected <T extends Key> T generateKey(final JwkContext<?> ctx, final Class<T> type, final CheckedFunction<KeyFactory, T> fn) {
return new JcaTemplate(getId(), ctx.getProvider()).execute(KeyFactory.class, new InstanceCallback<KeyFactory, T>() {
@Override
public T doWithInstance(KeyFactory instance) throws Exception {
try {
return fn.apply(instance);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
String msg = "Unable to create " + type.getSimpleName() + " from JWK {" + ctx + "}: " + e.getMessage();
throw new InvalidKeyException(msg, e);
}
}
});
}
@Override
public final J createJwk(JwkContext<K> ctx) {
Assert.notNull(ctx, "JwkContext argument cannot be null.");
if (!supports(ctx)) { //should be asserted by caller, but assert just in case:
String msg = "Unsupported JwkContext.";
throw new IllegalArgumentException(msg);
}
K key = ctx.getKey();
if (key != null) {
ctx.setType(this.ktyValue);
return createJwkFromKey(ctx);
} else {
return createJwkFromValues(ctx);
}
}
//when called, ctx.getKey() is guaranteed to be non-null
protected abstract J createJwkFromKey(JwkContext<K> ctx);
//when called ctx.getType() is guaranteed to equal this.ktyValue
protected abstract J createJwkFromValues(JwkContext<K> ctx);
}

View File

@ -0,0 +1,141 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.Jwk;
import java.security.Key;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
abstract class AbstractJwk<K extends Key> implements Jwk<K> {
static final String TYPE = "kty";
static final String OPERATIONS = "key_ops";
static final String ALGORITHM = "alg";
static final String ID = "kid";
static final String REDACTED_VALUE = "<redacted>";
public static final String IMMUTABLE_MSG = "JWKs are immutable may not be modified.";
protected final JwkContext<K> context;
AbstractJwk(JwkContext<K> ctx) {
this.context = Assert.notNull(ctx, "JwkContext cannot be null.");
Assert.isTrue(!ctx.isEmpty(), "JwkContext cannot be empty.");
Assert.hasText(ctx.getType(), "JwkContext type cannot be null or empty.");
Assert.notNull(ctx.getKey(), "JwkContext key cannot be null.");
}
@Override
public String getType() {
return this.context.getType();
}
@Override
public Set<String> getOperations() {
return this.context.getOperations();
}
@Override
public String getAlgorithm() {
return this.context.getAlgorithm();
}
@Override
public String getId() {
return this.context.getId();
}
@Override
public K toKey() {
return this.context.getKey();
}
@Override
public int size() {
return this.context.size();
}
@Override
public boolean isEmpty() {
return this.context.isEmpty();
}
@Override
public boolean containsKey(Object key) {
if (key instanceof String) {
return this.context.containsKey((String) key);
}
return false;
}
@Override
public boolean containsValue(Object value) {
return this.context.containsValue(value);
}
@Override
public Object get(Object key) {
if (key instanceof String) {
return this.context.get((String) key);
}
return null;
}
@Override
public Set<String> keySet() {
return this.context.keySet();
}
@Override
public Collection<Object> values() {
return this.context.values();
}
@Override
public Set<Entry<String, Object>> entrySet() {
return this.context.entrySet();
}
private static Object immutable() {
throw new UnsupportedOperationException(IMMUTABLE_MSG);
}
@Override
public Object put(String s, Object o) {
return immutable();
}
@Override
public Object remove(Object o) {
return immutable();
}
@Override
public void putAll(Map<? extends String, ?> m) {
immutable();
}
@Override
public void clear() {
immutable();
}
@Override
public String toString() {
return this.context.toString();
}
@Override
public int hashCode() {
return this.context.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AbstractJwk) {
AbstractJwk<?> other = (AbstractJwk<?>) obj;
return this.context.equals(other.context);
}
return false;
}
}

View File

@ -0,0 +1,96 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.JwkBuilder;
import io.jsonwebtoken.security.SecretJwk;
import io.jsonwebtoken.security.SecretJwkBuilder;
import javax.crypto.SecretKey;
import java.security.Key;
import java.security.Provider;
import java.util.Map;
import java.util.Set;
abstract class AbstractJwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilder<K, J, T>> implements JwkBuilder<K, J, T> {
protected final JwkContext<K> jwkContext;
protected final JwkFactory<K, J> jwkFactory;
@SuppressWarnings("unchecked")
protected AbstractJwkBuilder(JwkContext<K> jwkContext) {
this.jwkContext = Assert.notNull(jwkContext, "JwkContext cannot be null.");
this.jwkFactory = (JwkFactory<K, J>) DispatchingJwkFactory.DEFAULT_INSTANCE;
}
@Override
public T setProvider(Provider provider) {
Assert.notNull(provider, "Provider cannot be null.");
jwkContext.setProvider(provider);
return tthis();
}
@Override
public T put(String name, Object value) {
jwkContext.put(name, value);
return tthis();
}
@Override
public T putAll(Map<String, ?> values) {
jwkContext.putAll(values);
return tthis();
}
@Override
public T setAlgorithm(String alg) {
Assert.hasText(alg, "Algorithm cannot be null or empty.");
jwkContext.setAlgorithm(alg);
return tthis();
}
@Override
public T setId(String id) {
Assert.hasText(id, "Id cannot be null or empty.");
jwkContext.setId(id);
return tthis();
}
@Override
public T setOperations(Set<String> ops) {
Assert.notEmpty(ops, "Operations cannot be null or empty.");
jwkContext.setOperations(ops);
return tthis();
}
@SuppressWarnings("unchecked")
protected final T tthis() {
return (T) this;
}
@Override
public J build() {
assert this.jwkContext != null; //should always exist as there isn't a way to set it outside the constructor
K key = this.jwkContext.getKey();
if (key == null && this.jwkContext.isEmpty()) {
String msg = "A " + Key.class.getName() + " or one or more name/value pairs must be provided to create a JWK.";
throw new IllegalStateException(msg);
}
try {
return jwkFactory.createJwk(this.jwkContext);
} catch (IllegalArgumentException iae) {
//if we get an IAE, it means the builder state wasn't configured enough in order to create
String msg = "Unable to create JWK: " + iae.getMessage();
throw new IllegalStateException(msg, iae);
}
}
static class DefaultSecretJwkBuilder extends AbstractJwkBuilder<SecretKey, SecretJwk, SecretJwkBuilder>
implements SecretJwkBuilder {
public DefaultSecretJwkBuilder(JwkContext<?> ctx, SecretKey key) {
super(new DefaultJwkContext<>(ctx, key, DefaultSecretJwk.PRIVATE_NAMES));
}
}
}

View File

@ -10,6 +10,7 @@ import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@ -20,19 +21,19 @@ abstract class AbstractJwkConverter<K extends Key> implements JwkConverter<K> {
throw new MalformedKeyException(msg);
}
protected static Map<String,?> sanitize(Map<String,?> jwk, String sensitiveKey) {
protected static Map<String, ?> sanitize(final Map<String, ?> jwk, Collection<String> sensitiveKeys) {
//remove any sensitive value that may exist so we don't include it in the exception message
//(which may be printed to logs or the console):
Map<String,?> msgJwk = jwk;
if (jwk.containsKey(sensitiveKey)) {
Map<String,Object> sanitized = new LinkedHashMap<>(jwk);
sanitized.put(sensitiveKey, "<redacted>");
msgJwk = sanitized;
Map<String, Object> msgJwk = new LinkedHashMap<>(jwk);
for(String sensitiveKey : sensitiveKeys) {
if (jwk.containsKey(sensitiveKey)) {
msgJwk.put(sensitiveKey, AbstractJwk.REDACTED_VALUE);
}
}
return msgJwk;
}
static String getRequiredString(Map<String, ?> m, String name) {
static String getRequiredString(Map<?, ?> m, String name) {
Assert.notEmpty(m, "JWK map cannot be null or empty.");
Object value = m.get(name);
if (value == null) {
@ -45,13 +46,13 @@ abstract class AbstractJwkConverter<K extends Key> implements JwkConverter<K> {
return s;
}
static BigInteger getRequiredBigInt(Map<String, ?> m, String name, boolean sensitive) {
static BigInteger getRequiredBigInt(Map<?, ?> m, String name, boolean sensitive) {
String s = getRequiredString(m, name);
try {
byte[] bytes = Decoders.BASE64URL.decode(s);
return new BigInteger(1, bytes);
} catch (Exception e) {
String val = sensitive ? "<redacted>" : s;
String val = sensitive ? AbstractJwk.REDACTED_VALUE : s;
String msg = "Unable to decode JWK member '" + name + "' to BigInteger from value: " + val;
throw new MalformedKeyException(msg, e);
}
@ -108,10 +109,9 @@ abstract class AbstractJwkConverter<K extends Key> implements JwkConverter<K> {
}
}
Map<String,String> newJwkMap() {
Map<String,String> m = new HashMap<>();
m.put("kty", getId());
Map<String, Object> newJwkMap() {
Map<String, Object> m = new HashMap<>();
m.put(AbstractJwk.TYPE, getId());
return m;
}
}

View File

@ -0,0 +1,34 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.PrivateJwk;
import io.jsonwebtoken.security.PublicJwk;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
abstract class AbstractPrivateJwk<K extends PrivateKey, L extends PublicKey, M extends PublicJwk<L>>
extends AbstractAsymmetricJwk<K> implements PrivateJwk<K, L, M> {
private final M publicJwk;
private final KeyPair keyPair;
AbstractPrivateJwk(JwkContext<K> ctx, M pubJwk) {
super(ctx);
this.publicJwk = Assert.notNull(pubJwk, "PublicJwk instance cannot be null.");
L publicKey = Assert.notNull(pubJwk.toKey(), "PublicJwk key instance cannot be null.");
this.context.setPublicKey(publicKey);
this.keyPair = new KeyPair(publicKey, toKey());
}
@Override
public M toPublicJwk() {
return this.publicJwk;
}
@Override
public KeyPair toKeyPair() {
return this.keyPair;
}
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.PublicJwk;
import java.security.PublicKey;
abstract class AbstractPublicJwk<K extends PublicKey> extends AbstractAsymmetricJwk<K> implements PublicJwk<K> {
AbstractPublicJwk(JwkContext<K> ctx) {
super(ctx);
}
}

View File

@ -1,128 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.AsymmetricJwkMutator;
import io.jsonwebtoken.security.MalformedKeyException;
import java.net.URI;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.List;
public abstract class AsymmetricJwkBuilder<K extends Key, J extends AbstractAsymmetricJwk<?, K>, T extends AsymmetricJwkBuilder<K, J, T>> extends DefaultJwkBuilder<K, J, T> implements AsymmetricJwkMutator<T> {
protected String use;
protected List<X509Certificate> x509CertificateChain;
@SuppressWarnings("unused") //used via reflection via SetterFunction in parent class
protected URI x509Url;
@SuppressWarnings("unused") //used via reflection via SetterFunction in parent class
protected byte[] x509Sha1Thumbprint;
@SuppressWarnings("unused") //used via reflection via SetterFunction in parent class
protected byte[] x509Sha256Thumbprint;
protected boolean computeX509Sha1Thumbprint;
/**
* Boolean object indicates 3 states: 1) not configured 2) configured as true, 3) configured as false
*/
protected Boolean computeX509Sha256Thumbprint = null;
protected Boolean applyX509KeyUse = null;
private KeyUseStrategy keyUseStrategy = DefaultKeyUseStrategy.INSTANCE;
public AsymmetricJwkBuilder() {
super();
}
public AsymmetricJwkBuilder(K key) {
super(key);
}
@Override
public T setPublicKeyUse(String use) {
return put(DefaultJwk.USE, use);
}
public T setUseStrategy(KeyUseStrategy strategy) {
this.keyUseStrategy = Assert.notNull(strategy, "KeyUseStrategy cannot be null.");
return tthis();
}
@Override
public T setX509CertificateChain(List<X509Certificate> chain) {
return put(DefaultJwk.X509_CERT_CHAIN, chain);
}
@Override
public T setX509Url(URI url) {
return put(DefaultJwk.X509_URL, url);
}
@Override
public T withX509KeyUse(boolean enable) {
this.applyX509KeyUse = enable;
return tthis();
}
@Override
public T withX509Sha1Thumbprint(boolean enable) {
this.computeX509Sha1Thumbprint = enable;
return tthis();
}
@Override
public T withX509Sha256Thumbprint(boolean enable) {
this.computeX509Sha256Thumbprint = enable;
return tthis();
}
private byte[] computeThumbprint(final X509Certificate cert, final String jcaName) {
try {
byte[] encoded = cert.getEncoded();
MessageDigest digest = MessageDigest.getInstance(jcaName);
return digest.digest(encoded);
} catch (CertificateEncodingException e) {
String msg = "Unable to access X509Certificate encoded bytes necessary to compute a " + jcaName +
" thumbprint. Certificate: {" + cert + "}. Cause: " + e.getMessage();
throw new MalformedKeyException(msg, e);
} catch (NoSuchAlgorithmException e) {
String msg = "JCA Algorithm Name '" + jcaName + "' is not available: " + e.getMessage();
throw new IllegalStateException(msg, e);
}
}
@Override
protected J createJwk() {
X509Certificate firstCert = null;
if (!Collections.isEmpty(this.x509CertificateChain)) {
firstCert = this.x509CertificateChain.get(0);
}
if (applyX509KeyUse == null) { //if not specified, enable by default if possible:
applyX509KeyUse = firstCert != null;
}
if (computeX509Sha256Thumbprint == null) { //if not specified, enable by default if possible:
computeX509Sha256Thumbprint = firstCert != null;
}
if (firstCert != null) {
if (applyX509KeyUse) {
KeyUsage usage = new KeyUsage(firstCert);
String use = keyUseStrategy.toJwkValue(usage);
if (use != null) {
setPublicKeyUse(use);
}
}
if (computeX509Sha1Thumbprint) {
byte[] thumbprint = computeThumbprint(firstCert, "SHA-1");
put(DefaultJwk.X509_SHA1_THUMBPRINT, thumbprint);
}
if (computeX509Sha256Thumbprint) {
byte[] thumbprint = computeThumbprint(firstCert, "SHA-256");
put(DefaultJwk.X509_SHA256_THUMBPRINT, thumbprint);
}
}
return super.createJwk();
}
}

View File

@ -0,0 +1,39 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.Jwk;
import java.security.Key;
class AsymmetricJwkFactory implements FamilyJwkFactory<Key, Jwk<Key>> {
private final String id;
private final FamilyJwkFactory<Key, Jwk<Key>> publicFactory;
private final FamilyJwkFactory<Key, Jwk<Key>> privateFactory;
@SuppressWarnings({"unchecked", "rawtypes"})
AsymmetricJwkFactory(FamilyJwkFactory publicFactory, FamilyJwkFactory privateFactory) {
this.publicFactory = (FamilyJwkFactory<Key, Jwk<Key>>) Assert.notNull(publicFactory, "publicFactory cannot be null.");
this.privateFactory = (FamilyJwkFactory<Key, Jwk<Key>>) Assert.notNull(privateFactory, "privateFactory cannot be null.");
this.id = Assert.notNull(publicFactory.getId(), "publicFactory id cannot be null or empty.");
Assert.isTrue(this.id.equals(privateFactory.getId()), "privateFactory id must equal publicFactory id");
}
@Override
public String getId() {
return this.id;
}
@Override
public boolean supports(JwkContext<?> ctx) {
return this.id.equals(ctx.getType()) || privateFactory.supports(ctx) || publicFactory.supports(ctx);
}
@Override
public Jwk<Key> createJwk(JwkContext<Key> ctx) {
if (privateFactory.supports(ctx)) {
return this.privateFactory.createJwk(ctx);
}
return this.publicFactory.createJwk(ctx);
}
}

View File

@ -0,0 +1,19 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.EcPrivateJwk;
import io.jsonwebtoken.security.EcPublicJwk;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.Set;
class DefaultEcPrivateJwk extends AbstractPrivateJwk<ECPrivateKey, ECPublicKey, EcPublicJwk> implements EcPrivateJwk {
static final String D = "d";
static final Set<String> PRIVATE_NAMES = Collections.setOf(D);
DefaultEcPrivateJwk(JwkContext<ECPrivateKey> ctx, EcPublicJwk pubJwk) {
super(ctx, pubJwk);
}
}

View File

@ -0,0 +1,17 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.EcPublicJwk;
import java.security.interfaces.ECPublicKey;
class DefaultEcPublicJwk extends AbstractPublicJwk<ECPublicKey> implements EcPublicJwk {
static final String TYPE_VALUE = "EC";
static final String CURVE_ID = "crv";
static final String X = "x";
static final String Y = "y";
DefaultEcPublicJwk(JwkContext<ECPublicKey> ctx) {
super(ctx);
}
}

View File

@ -1,64 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.JwtMap;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.Jwk;
import java.security.Key;
import java.util.Map;
import java.util.Set;
class DefaultJwk<K extends Key> extends JwtMap implements Jwk<Object, K> {
static final String TYPE = "kty";
static final String USE = "use";
static final String OPERATIONS = "key_ops";
static final String ALGORITHM = "alg";
static final String ID = "kid";
static final String X509_URL = "x5u";
static final String X509_CERT_CHAIN = "x5c";
static final String X509_SHA1_THUMBPRINT = "x5t";
static final String X509_SHA256_THUMBPRINT = "x5t#S256";
private final String type;
private final Set<String> operations;
private final String algorithm;
private final String id;
private final K key;
DefaultJwk(String type, Set<String> operations, String algorithm, String id, K key, Map<String, ?> values) {
super(values);
this.type = Assert.notNull(Strings.clean(type), "JWK type cannot be null or empty.");
this.operations = operations;
this.algorithm = algorithm;
this.id = id;
this.key = Assert.notNull(key, "Key argument cannot be null.");
}
@Override
public String getType() {
return this.type;
}
@Override
public Set<String> getOperations() {
return this.operations;
}
@Override
public String getAlgorithm() {
return this.algorithm;
}
@Override
public String getId() {
return this.id;
}
@Override
public K toKey() {
return this.key;
}
}

View File

@ -1,244 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.io.CodecConverter;
import io.jsonwebtoken.impl.lang.BiFunction;
import io.jsonwebtoken.impl.lang.Converter;
import io.jsonwebtoken.impl.lang.Converters;
import io.jsonwebtoken.impl.lang.NullSafeConverter;
import io.jsonwebtoken.impl.lang.UriStringConverter;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.Identifiable;
import io.jsonwebtoken.security.JwkBuilder;
import io.jsonwebtoken.security.MalformedKeyException;
import java.lang.reflect.Field;
import java.net.URI;
import java.security.Key;
import java.security.Provider;
import java.security.cert.X509Certificate;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class DefaultJwkBuilder<K extends Key, J extends DefaultJwk<K>, T extends JwkBuilder<K, J, T>> implements JwkBuilder<K, J, T> {
private static final Converter<byte[], Object> THUMBPRINT_CONVERTER =
Converters.forEncoded(byte[].class, CodecConverter.BASE64URL);
private static final Converter<X509Certificate, Object> X509_CONVERTER =
Converters.forEncoded(X509Certificate.class, new JwkX509StringConverter());
private static final Converter<URI, Object> URI_CONVERTER =
Converters.forEncoded(URI.class, new UriStringConverter());
private static final Map<String, SetterFunction<?, DefaultJwkBuilder<?, ?, ?>>> SETTERS;
static {
@SuppressWarnings("unchecked")
List<SetterFunction<?, DefaultJwkBuilder<?, ?, ?>>> fns = Collections.of(
SetterFunction.of(DefaultJwk.TYPE, "type", "Key Type"),
SetterFunction.of(DefaultJwk.USE, "use", "Public Key Use"),
SetterFunction.of(DefaultJwk.OPERATIONS, "operations", "Key Operations", Converters.forSetOf(String.class)),
SetterFunction.of(DefaultJwk.ALGORITHM, "algorithm", "Algorithm"),
SetterFunction.of(DefaultJwk.ID, "id", "Key ID"),
SetterFunction.of(DefaultJwk.X509_URL, "x509Url", "X.509 URL", URI_CONVERTER),
SetterFunction.of(DefaultJwk.X509_CERT_CHAIN, "x509CertificateChain", "X.509 Certificate Chain", Converters.forList(X509_CONVERTER)),
SetterFunction.of(DefaultJwk.X509_SHA1_THUMBPRINT, "x509Sha1Thumbprint", "X.509 Certificate SHA-1 Thumbprint", THUMBPRINT_CONVERTER),
SetterFunction.of(DefaultJwk.X509_SHA256_THUMBPRINT, "x509Sha256Thumbprint", "X.509 Certificate SHA-256 Thumbprint", THUMBPRINT_CONVERTER)
);
Map<String, SetterFunction<?, DefaultJwkBuilder<?, ?, ?>>> s = new LinkedHashMap<>();
for (SetterFunction<?, DefaultJwkBuilder<?, ?, ?>> fn : fns) {
s.put(fn.getId(), fn);
}
SETTERS = s;
}
protected final Map<String, Object> values = new LinkedHashMap<>();
protected Provider provider;
protected String algorithm;
protected String id;
protected Set<String> operations;
protected String type;
protected K key;
protected final Converter<Key, Map<String, ?>> jwkConverter = new DispatchingJwkConverter();
public DefaultJwkBuilder() {
}
@SuppressWarnings("unused") //used via reflection by the Jwks utility class
public DefaultJwkBuilder(K key) {
this();
this.key = Assert.notNull(key, "Key cannot be null.");
}
@Override
public T setProvider(Provider provider) {
this.provider = Assert.notNull(provider, "Provider cannot be null.");
return tthis();
}
protected T nullSafePut(String name, Object value) {
if (DefaultJwk.isReduceableToNull(value)) {
this.values.remove(name);
} else {
this.values.put(name, value);
}
return tthis();
}
private void doPut(String name, Object value) {
assert name != null;
assert value != null; //asserted by caller, guaranteed to be non-null and if a collection, non-empty
SetterFunction<?, DefaultJwkBuilder<?, ?, ?>> fn = SETTERS.get(name);
if (fn != null) {
fn.apply(this, value);
} else {
nullSafePut(name, value);
}
}
@Override
public T put(String name, Object value) {
name = Assert.notNull(Strings.clean(name), "JWK member name cannot be null or empty.");
if (value instanceof String) {
value = Strings.clean((String) value);
} else if (Objects.isArray(value) && !value.getClass().getComponentType().isPrimitive()) {
value = Collections.arrayToList(value);
}
if (DefaultJwk.isReduceableToNull(value)) {
this.values.remove(name);
} else {
doPut(name, value);
}
return tthis();
}
@SuppressWarnings("unchecked")
protected final T tthis() {
return (T) this;
}
@Override
public T putAll(Map<String, ?> values) {
Assert.notEmpty(values, "Values map cannot be null or empty.");
for (Map.Entry<String, ?> entry : values.entrySet()) {
put(entry.getKey(), entry.getValue());
}
return tthis();
}
@Override
public T setAlgorithm(String alg) {
return put(DefaultJwk.ALGORITHM, alg);
}
@Override
public T setId(String id) {
return put(DefaultJwk.ID, id);
}
@Override
public T setOperations(Set<String> ops) {
return put(DefaultJwk.OPERATIONS, ops);
}
@Override
public J build() {
if (this.key == null) { //create one based on values
this.key = (K) jwkConverter.applyFrom(this.values);
} else {
Map<String, ?> jwkValues = jwkConverter.applyTo(this.key);
putAll(jwkValues);
}
return createJwk();
}
@SuppressWarnings("unchecked")
protected J createJwk() {
DefaultJwk<K> jwk = new DefaultJwk<>(this.type, this.operations, this.algorithm, this.id, this.key, this.values);
return (J) jwk;
}
private static class SetterFunction<T, B extends DefaultJwkBuilder<?, ?, ?>> implements BiFunction<B, Object, B>, Identifiable {
private final String id;
private final String fieldName;
private final String title;
private final Converter<T, Object> converter;
public static <B extends DefaultJwkBuilder<?, ?, ?>> SetterFunction<String, B> of(String id, String fieldName, String title) {
return of(id, fieldName, title, Converters.none(String.class));
}
public static <T, B extends DefaultJwkBuilder<?, ?, ?>> SetterFunction<T, B> of(String id, String fieldName, String title, Converter<T, Object> converter) {
return new SetterFunction<>(id, fieldName, title, new NullSafeConverter<>(converter));
}
public SetterFunction(String id, String fieldName, String title, Converter<T, Object> converter) {
this.id = id;
this.fieldName = fieldName;
this.title = title;
this.converter = converter;
}
@Override
public String getId() {
return this.id;
}
@Override
public B apply(B builder, Object value) {
if (value instanceof String) {
value = Strings.clean((String) value);
}
if (DefaultJwk.isReduceableToNull(value)) {
setField(builder, fieldName, null);
builder.values.remove(id);
return builder;
}
T fieldValue;
Object encodedValue;
try {
fieldValue = converter.applyFrom(value);
encodedValue = converter.applyTo(fieldValue);
} catch (Exception e) {
String msg = "Invalid JWK " + title + "('" + id + "') value [" + value + "]: " + e.getMessage();
throw new MalformedKeyException(msg, e);
}
builder.nullSafePut(id, encodedValue);
setField(builder, fieldName, fieldValue);
return builder;
}
private void setField(Object target, String name, Object value) {
try {
Field field = target.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(this, value);
} catch (NoSuchFieldException | IllegalAccessException e) {
String msg = "Unable to access self property via reflection which should always be allowed. This is " +
"likely an internal JJWT programming error. Please report it to the JJWT team immediately. Message: " +
e.getMessage();
throw new IllegalStateException(msg, e);
}
}
}
}

View File

@ -0,0 +1,400 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.JwtMap;
import io.jsonwebtoken.impl.io.CodecConverter;
import io.jsonwebtoken.impl.lang.BiFunction;
import io.jsonwebtoken.impl.lang.Converter;
import io.jsonwebtoken.impl.lang.Converters;
import io.jsonwebtoken.impl.lang.NullSafeConverter;
import io.jsonwebtoken.impl.lang.UriStringConverter;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.Identifiable;
import io.jsonwebtoken.security.MalformedKeyException;
import java.net.URI;
import java.security.Key;
import java.security.Provider;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
private static final Converter<byte[], Object> THUMBPRINT_CONVERTER =
Converters.forEncoded(byte[].class, CodecConverter.BASE64URL);
private static final Converter<X509Certificate, Object> X509_CONVERTER =
Converters.forEncoded(X509Certificate.class, new JwkX509StringConverter());
private static final Converter<URI, Object> URI_CONVERTER =
Converters.forEncoded(URI.class, new UriStringConverter());
private static final Map<String, Canonicalizer<?>> SETTERS;
static {
@SuppressWarnings("RedundantTypeArguments")
List<Canonicalizer<?>> fns = Collections.<Canonicalizer<?>>of(
Canonicalizer.forKey(AbstractJwk.ALGORITHM, "Algorithm"),
Canonicalizer.forKey(AbstractJwk.ID, "Key ID"),
Canonicalizer.forKey(AbstractJwk.OPERATIONS, "Key Operations", Converters.forSetOf(String.class)),
Canonicalizer.forKey(AbstractAsymmetricJwk.PUBLIC_KEY_USE, "Public Key Use"),
Canonicalizer.forKey(AbstractJwk.TYPE, "Key Type"),
Canonicalizer.forKey(AbstractAsymmetricJwk.X509_CERT_CHAIN, "X.509 Certificate Chain", Converters.forList(X509_CONVERTER)),
Canonicalizer.forKey(AbstractAsymmetricJwk.X509_SHA1_THUMBPRINT, "X.509 Certificate SHA-1 Thumbprint", THUMBPRINT_CONVERTER),
Canonicalizer.forKey(AbstractAsymmetricJwk.X509_SHA256_THUMBPRINT, "X.509 Certificate SHA-256 Thumbprint", THUMBPRINT_CONVERTER),
Canonicalizer.forKey(AbstractAsymmetricJwk.X509_URL, "X.509 URL", URI_CONVERTER)
);
Map<String, Canonicalizer<?>> s = new LinkedHashMap<>();
for (Canonicalizer<?> fn : fns) {
s.put(fn.getId(), fn);
}
SETTERS = s;
}
private final Map<String, Object> values;
private final Map<String, Object> canonicalValues;
private final Map<String, Object> redactedValues;
private final Set<String> privateMemberNames;
private K key;
private PublicKey publicKey;
private Provider provider;
public DefaultJwkContext() {
this.values = new LinkedHashMap<>();
this.canonicalValues = new LinkedHashMap<>();
this.redactedValues = new LinkedHashMap<>();
this.privateMemberNames = Collections.emptySet();
}
// public DefaultJwkContext(JwkContext<?> other) {
// //noinspection unchecked
// this(other,
// (Assert.isInstanceOf(DefaultJwkContext.class, other, "JwkContext must be a DefaultJwkContext instance.")).privateMemberNames);
// }
public DefaultJwkContext(JwkContext<?> other, Set<String> privateMemberNames) {
Assert.notNull(other, "JwkContext cannot be null.");
Assert.isInstanceOf(DefaultJwkContext.class, other, "JwkContext must be a DefaultJwkContext instance.");
DefaultJwkContext<?> src = (DefaultJwkContext<?>) other;
this.privateMemberNames = Assert.notEmpty(privateMemberNames, "privateMemberNames cannot be null or empty.");
this.provider = other.getProvider();
this.values = new LinkedHashMap<>(src.values);
this.canonicalValues = new LinkedHashMap<>(src.values);
this.redactedValues = new LinkedHashMap<>(this.values);
//if the key is a PublicKey, we don't even want to redact - we want to fully remove the items that are
//private names (public JWKs should never contain any private key fields, even if redacted):
final Key key = other.getKey();
final boolean remove = (key == null || key instanceof PublicKey);
for (String name : this.privateMemberNames) {
if (remove) {
remove(name);
} else if (this.redactedValues.containsKey(name)) { //otherwise ensure redacted for toString calls:
this.redactedValues.put(name, AbstractJwk.REDACTED_VALUE);
}
}
}
public DefaultJwkContext(JwkContext<?> other, K key, Set<String> privateMemberNames) {
this(other, privateMemberNames);
this.key = Assert.notNull(key, "Key cannot be null.");
}
protected Object nullSafePut(String name, Object value) {
if (JwtMap.isReduceableToNull(value)) {
return remove(name);
} else {
Object redactedValue = this.privateMemberNames.contains(name) ? AbstractJwk.REDACTED_VALUE : value;
this.redactedValues.put(name, redactedValue);
this.canonicalValues.put(name, value);
return this.values.put(name, value);
}
}
@Override
public Object put(String name, Object value) {
name = Assert.notNull(Strings.clean(name), "JWK member name cannot be null or empty.");
if (value instanceof String) {
value = Strings.clean((String) value);
} else if (Objects.isArray(value) && !value.getClass().getComponentType().isPrimitive()) {
value = Collections.arrayToList(value);
}
return doPut(name, value);
}
private Object doPut(String name, Object value) {
assert name != null; //asserted by caller.
Canonicalizer<?> fn = SETTERS.get(name);
if (fn != null) { //Setting a JWA-standard property - let's ensure we can represent it canonically:
return fn.apply(this, value);
} else { //non-standard/custom property:
return nullSafePut(name, value);
}
}
@Override
public void putAll(Map<? extends String, ?> m) {
Assert.notEmpty(m, "JWK values cannot be null or empty.");
for (Map.Entry<? extends String, ?> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
private Object remove(String key) {
this.redactedValues.remove(key);
this.canonicalValues.remove(key);
return this.values.remove(key);
}
@Override
public int size() {
return this.values.size();
}
@Override
public boolean isEmpty() {
return this.values.isEmpty();
}
@Override
public boolean containsKey(String key) {
return this.values.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
return this.values.containsValue(value);
}
@Override
public Object get(String key) {
return this.values.get(key);
}
@Override
public Set<String> keySet() {
return this.values.keySet();
}
@Override
public Collection<Object> values() {
return this.values.values();
}
@Override
public Set<Map.Entry<String, Object>> entrySet() {
return this.values.entrySet();
}
@Override
public Map<String, Object> getValues() {
return this.values;
}
@Override
public String getAlgorithm() {
return (String) this.canonicalValues.get(AbstractJwk.ALGORITHM);
}
@Override
public void setAlgorithm(String algorithm) {
put(AbstractJwk.ALGORITHM, algorithm);
}
@Override
public String getId() {
return (String) this.canonicalValues.get(AbstractJwk.ID);
}
@Override
public void setId(String id) {
put(AbstractJwk.ID, id);
}
@Override
public Set<String> getOperations() {
//noinspection unchecked
return (Set<String>) this.canonicalValues.get(AbstractJwk.OPERATIONS);
}
@Override
public void setOperations(Set<String> ops) {
put(AbstractJwk.OPERATIONS, ops);
}
@Override
public String getType() {
return (String) this.canonicalValues.get(AbstractJwk.TYPE);
}
@Override
public void setType(String type) {
put(AbstractJwk.TYPE, type);
}
@Override
public String getPublicKeyUse() {
return (String) this.canonicalValues.get(AbstractAsymmetricJwk.PUBLIC_KEY_USE);
}
@Override
public void setPublicKeyUse(String use) {
put(AbstractAsymmetricJwk.PUBLIC_KEY_USE, use);
}
@Override
public List<X509Certificate> getX509CertificateChain() {
//noinspection unchecked
return (List<X509Certificate>) this.canonicalValues.get(AbstractAsymmetricJwk.X509_CERT_CHAIN);
}
@Override
public void setX509CertificateChain(List<X509Certificate> x5c) {
put(AbstractAsymmetricJwk.X509_CERT_CHAIN, x5c);
}
@Override
public byte[] getX509CertificateSha1Thumbprint() {
return (byte[]) this.canonicalValues.get(AbstractAsymmetricJwk.X509_SHA1_THUMBPRINT);
}
@Override
public void setX509CertificateSha1Thumbprint(byte[] x5t) {
put(AbstractAsymmetricJwk.X509_SHA1_THUMBPRINT, x5t);
}
@Override
public byte[] getX509CertificateSha256Thumbprint() {
return (byte[]) this.canonicalValues.get(AbstractAsymmetricJwk.X509_SHA256_THUMBPRINT);
}
@Override
public void setX509CertificateSha256Thumbprint(byte[] x5ts256) {
put(AbstractAsymmetricJwk.X509_SHA256_THUMBPRINT, x5ts256);
}
@Override
public URI getX509Url() {
return (URI) this.canonicalValues.get(AbstractAsymmetricJwk.X509_URL);
}
@Override
public void setX509Url(URI url) {
put(AbstractAsymmetricJwk.X509_URL, url);
}
@Override
public K getKey() {
return this.key;
}
@Override
public JwkContext<K> setKey(K key) {
this.key = key;
return this;
}
@Override
public PublicKey getPublicKey() {
return this.publicKey;
}
@Override
public void setPublicKey(PublicKey publicKey) {
this.publicKey = publicKey;
}
@Override
public Provider getProvider() {
return this.provider;
}
@Override
public void setProvider(Provider provider) {
this.provider = provider;
}
@Override
public Set<String> getPrivateMemberNames() {
return this.privateMemberNames;
}
@Override
public int hashCode() {
int hash = 7;
hash = hash * 31 + Objects.nullSafeHashCode(this.key);
hash = hash * 31 + Objects.nullSafeHashCode(this.values);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof DefaultJwkContext) {
DefaultJwkContext<?> c = (DefaultJwkContext<?>) obj;
return Objects.nullSafeEquals(this.key, c.key) &&
Objects.nullSafeEquals(this.values, c.values);
}
return false;
}
@Override
public String toString() {
return this.redactedValues.toString();
}
private static class Canonicalizer<T> implements BiFunction<DefaultJwkContext<?>, Object, T>, Identifiable {
private final String id;
private final String title;
private final Converter<T, Object> converter;
public static Canonicalizer<String> forKey(String id, String title) {
return forKey(id, title, Converters.none(String.class));
}
public static <T> Canonicalizer<T> forKey(String id, String title, Converter<T, Object> converter) {
return new Canonicalizer<>(id, title, new NullSafeConverter<>(converter));
}
public Canonicalizer(String id, String title, Converter<T, Object> converter) {
this.id = id;
this.title = title;
this.converter = converter;
}
@Override
public String getId() {
return this.id;
}
@Override
public T apply(DefaultJwkContext<?> ctx, Object rawValue) {
if (JwtMap.isReduceableToNull(rawValue)) {
//noinspection unchecked
return (T) ctx.remove(id);
}
T canonicalValue;
Object encodedValue;
try {
canonicalValue = converter.applyFrom(rawValue);
encodedValue = converter.applyTo(canonicalValue);
} catch (Exception e) {
String msg = "Invalid JWK " + title + "('" + id + "') value [" + rawValue + "]: " + e.getMessage();
throw new MalformedKeyException(msg, e);
}
ctx.nullSafePut(id, encodedValue);
ctx.canonicalValues.put(id, canonicalValue);
//noinspection unchecked
return (T) encodedValue;
}
}
}

View File

@ -0,0 +1,13 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.Jwk;
import java.security.Key;
public class DefaultJwkFactory<K extends Key, J extends Jwk<K>> implements JwkFactory<K,J> {
@Override
public J createJwk(JwkContext<K> ctx) {
return null;
}
}

View File

@ -0,0 +1,85 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.EcPrivateJwkBuilder;
import io.jsonwebtoken.security.EcPublicJwkBuilder;
import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.JwkBuilder;
import io.jsonwebtoken.security.ProtoJwkBuilder;
import io.jsonwebtoken.security.RsaPrivateJwkBuilder;
import io.jsonwebtoken.security.RsaPublicJwkBuilder;
import io.jsonwebtoken.security.SecretJwkBuilder;
import javax.crypto.SecretKey;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Set;
public class DefaultProtoJwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilder<K, J, T>>
extends AbstractJwkBuilder<K, J, T> implements ProtoJwkBuilder<K, J, T> {
public DefaultProtoJwkBuilder() {
super(new DefaultJwkContext<K>());
}
@Override
public SecretJwkBuilder setKey(SecretKey key) {
return new AbstractJwkBuilder.DefaultSecretJwkBuilder(this.jwkContext, key);
}
@Override
public RsaPublicJwkBuilder setKey(RSAPublicKey key) {
return new AbstractAsymmetricJwkBuilder.DefaultRsaPublicJwkBuilder(this.jwkContext, key);
}
@Override
public RsaPrivateJwkBuilder setKey(RSAPrivateKey key) {
return new AbstractAsymmetricJwkBuilder.DefaultRsaPrivateJwkBuilder(this.jwkContext, key);
}
@Override
public EcPublicJwkBuilder setKey(ECPublicKey key) {
return new AbstractAsymmetricJwkBuilder.DefaultEcPublicJwkBuilder(this.jwkContext, key);
}
@Override
public EcPrivateJwkBuilder setKey(ECPrivateKey key) {
return new AbstractAsymmetricJwkBuilder.DefaultEcPrivateJwkBuilder(this.jwkContext, key);
}
private static <T extends Key> T assertKeyPairChild(Class<T> clazz, Key key) {
String type = PrivateKey.class.isAssignableFrom(clazz) ? "private" : "public";
if (key == null) {
String msg = "KeyPair " + type + " key cannot be null.";
throw new IllegalArgumentException(msg);
}
if (!clazz.isInstance(key)) {
String msg = "The specified KeyPair's " + type + " key must be an instance of " + clazz.getName() +
". Type found: " + key.getClass().getName();
throw new IllegalArgumentException(msg);
}
return clazz.cast(key);
}
@Override
public RsaPrivateJwkBuilder setKeyPairRsa(KeyPair keyPair) {
Assert.notNull(keyPair, "KeyPair cannot be null.");
RSAPublicKey pub = assertKeyPairChild(RSAPublicKey.class, keyPair.getPublic());
RSAPrivateKey priv = assertKeyPairChild(RSAPrivateKey.class, keyPair.getPrivate());
return setKey(priv).setPublicKey(pub);
}
@Override
public EcPrivateJwkBuilder setKeyPairEc(KeyPair keyPair) {
Assert.notNull(keyPair, "KeyPair cannot be null.");
ECPublicKey pub = assertKeyPairChild(ECPublicKey.class, keyPair.getPublic());
ECPrivateKey priv = assertKeyPairChild(ECPrivateKey.class, keyPair.getPrivate());
return setKey(priv).setPublicKey(pub);
}
}

View File

@ -0,0 +1,40 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.RsaPrivateJwk;
import io.jsonwebtoken.security.RsaPublicJwk;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.LinkedHashSet;
import java.util.Set;
class DefaultRsaPrivateJwk extends AbstractPrivateJwk<RSAPrivateKey, RSAPublicKey, RsaPublicJwk> implements RsaPrivateJwk {
static String PRIVATE_EXPONENT = "d";
static String FIRST_PRIME = "p";
static String SECOND_PRIME = "q";
static String FIRST_CRT_EXPONENT = "dp";
static String SECOND_CRT_EXPONENT = "dq";
static String FIRST_CRT_COEFFICIENT = "qi";
static String OTHER_PRIMES_INFO = "oth";
static String PRIME_FACTOR = "r";
static String FACTOR_CRT_EXPONENT = "d";
static String FACTOR_CRT_COEFFICIENT = "t";
static final Set<String> PRIVATE_NAMES = Collections.setOf(
PRIVATE_EXPONENT, FIRST_PRIME, SECOND_PRIME,
FIRST_CRT_EXPONENT, SECOND_CRT_EXPONENT,
FIRST_CRT_COEFFICIENT, OTHER_PRIMES_INFO);
static final Set<String> OPTIONAL_PRIVATE_NAMES;
static {
OPTIONAL_PRIVATE_NAMES = new LinkedHashSet<>(PRIVATE_NAMES);
OPTIONAL_PRIVATE_NAMES.remove(PRIVATE_EXPONENT);
}
DefaultRsaPrivateJwk(JwkContext<RSAPrivateKey> ctx, RsaPublicJwk pubJwk) {
super(ctx, pubJwk);
}
}

View File

@ -0,0 +1,16 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.RsaPublicJwk;
import java.security.interfaces.RSAPublicKey;
class DefaultRsaPublicJwk extends AbstractPublicJwk<RSAPublicKey> implements RsaPublicJwk {
static final String TYPE_VALUE = "RSA";
static final String MODULUS = "n";
static final String PUBLIC_EXPONENT = "e";
DefaultRsaPublicJwk(JwkContext<RSAPublicKey> ctx) {
super(ctx);
}
}

View File

@ -0,0 +1,18 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.SecretJwk;
import javax.crypto.SecretKey;
import java.util.Set;
class DefaultSecretJwk extends AbstractJwk<SecretKey> implements SecretJwk {
static final String TYPE_VALUE = "oct";
static final String K = "k";
static final Set<String> PRIVATE_NAMES = Collections.setOf(K);
DefaultSecretJwk(JwkContext<SecretKey> ctx) {
super(ctx);
}
}

View File

@ -1,64 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.Converter;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.UnsupportedKeyException;
import java.security.Key;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static io.jsonwebtoken.impl.security.AbstractJwkConverter.*;
public class DispatchingJwkConverter implements Converter<Key, Map<String,?>> {
private final Map<String, JwkConverter<?>> converters = new HashMap<>();
@SuppressWarnings("rawtypes")
public DispatchingJwkConverter() {
this(Collections.<JwkConverter<?>>of(
new SymmetricJwkConverter(),
new EcJwkConverter(),
new RsaJwkConverter()));
}
public DispatchingJwkConverter(List<JwkConverter<?>> converters) {
Assert.notEmpty(converters, "Converters cannot be null or empty.");
for (JwkConverter<?> converter : converters) {
this.converters.put(converter.getId(), converter);
}
}
private JwkConverter<?> getConverter(String kty) {
JwkConverter<?> converter = converters.get(kty);
if (converter == null) {
String msg = "Unrecognized JWK kty (key type) value: " + kty;
throw new UnsupportedKeyException(msg);
}
return converter;
}
@Override
public Map<String, ?> applyTo(Key key) {
Assert.notNull(key, "Key argument cannot be null.");
for (JwkConverter<?> converter : converters.values()) {
if (converter.supports(key)) {
@SuppressWarnings("unchecked") // converter indicates it supports the key type, so we can pass through
JwkConverter<Key> conv = (JwkConverter<Key>)converter;
return conv.applyTo(key);
}
}
String msg = "Unable to determine JWK converter for key of type " + key.getClass();
throw new UnsupportedKeyException(msg);
}
@Override
public Key applyFrom(Map<String, ?> jwk) {
String type = getRequiredString(jwk, DefaultJwk.TYPE);
JwkConverter<?> converter = getConverter(type);
return converter.applyFrom(jwk);
}
}

View File

@ -0,0 +1,79 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.UnsupportedKeyException;
import java.security.Key;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
class DispatchingJwkFactory implements JwkFactory<Key, Jwk<Key>> {
@SuppressWarnings({"unchecked", "rawtypes"})
private static Collection<FamilyJwkFactory<Key, ?>> createDefaultFactories() {
List families = new ArrayList<>(3);
families.add(new SecretJwkFactory());
families.add(new AsymmetricJwkFactory(EcPublicJwkFactory.DEFAULT_INSTANCE, new EcPrivateJwkFactory()));
families.add(new AsymmetricJwkFactory(RsaPublicJwkFactory.DEFAULT_INSTANCE, new RsaPrivateJwkFactory()));
return families;
}
private static final Collection<FamilyJwkFactory<Key, ?>> DEFAULT_FACTORIES = createDefaultFactories();
static final JwkFactory<Key, Jwk<Key>> DEFAULT_INSTANCE = new DispatchingJwkFactory();
private final Collection<FamilyJwkFactory<Key, ?>> factories;
DispatchingJwkFactory() {
this(DEFAULT_FACTORIES);
}
@SuppressWarnings("unchecked")
DispatchingJwkFactory(Collection<? extends FamilyJwkFactory<?, ?>> factories) {
Assert.notEmpty(factories, "FamilyJwkFactory collection cannot be null or empty.");
this.factories = new ArrayList<>(factories.size());
for (FamilyJwkFactory<?, ?> factory : factories) {
if (!Strings.hasText(factory.getId())) {
String msg = "FamilyJwkFactory instance of type " + factory.getClass().getName() + " does not " +
"have a required algorithm family id (factory.getFactoryId() cannot be null or empty).";
throw new IllegalArgumentException(msg);
}
this.factories.add((FamilyJwkFactory<Key, ?>) factory);
}
}
@Override
public Jwk<Key> createJwk(JwkContext<Key> ctx) {
final Key key = ctx.getKey();
final String kty = Strings.clean(ctx.getType());
if (key == null && kty == null) {
String msg = "Either a Key instance or a '" + AbstractJwk.TYPE + "' value is required to create a JWK.";
throw new IllegalArgumentException(msg);
}
for (FamilyJwkFactory<Key, ?> factory : this.factories) {
if (factory.supports(ctx)) {
String algFamilyId = Assert.hasText(factory.getId(), "factory id cannot be null or empty.");
if (kty == null) {
ctx.setType(algFamilyId); //ensure the kty is available for the rest of the creation process
}
return factory.createJwk(ctx);
}
}
// if nothing has been returned at this point, no factory supported the JwkContext, so that's an error:
String reason;
if (key != null) {
reason = "key of type " + key.getClass().getName();
} else {
reason = "kty value '" + kty + "'";
}
String msg = "Unable to create JWK for unrecognized " + reason + ": there is " +
"no known JWK Factory capable of creating JWKs for this key type.";
throw new UnsupportedKeyException(msg);
}
}

View File

@ -0,0 +1,82 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.EcPrivateJwk;
import io.jsonwebtoken.security.EcPublicJwk;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPrivateKeySpec;
class EcPrivateJwkFactory extends AbstractEcJwkFactory<ECPrivateKey, EcPrivateJwk> {
private static final String ECPUBKEY_ERR_MSG = "JwkContext publicKey must be an " + ECPublicKey.class.getName() + " instance.";
EcPrivateJwkFactory() {
super(ECPrivateKey.class);
}
@Override
protected boolean supportsKeyValues(JwkContext<?> ctx) {
return super.supportsKeyValues(ctx) && ctx.containsKey(DefaultEcPrivateJwk.D);
}
@Override
protected EcPrivateJwk createJwkFromKey(JwkContext<ECPrivateKey> ctx) {
ECPrivateKey key = ctx.getKey();
ECPublicKey ecPublicKey;
PublicKey publicKey = ctx.getPublicKey();
if (publicKey != null) {
ecPublicKey = Assert.isInstanceOf(ECPublicKey.class, publicKey, ECPUBKEY_ERR_MSG);
} else {
ecPublicKey = derivePublic(ctx);
}
// [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2)
// requires public values to be present in private JWKs, so add them:
JwkContext<ECPublicKey> pubCtx = new DefaultJwkContext<>();
pubCtx.setKey(ecPublicKey);
EcPublicJwk pubJwk = EcPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromKey(pubCtx);
ctx.putAll(pubJwk); // add public values to private key context
int fieldSize = key.getParams().getCurve().getField().getFieldSize();
String d = toOctetString(fieldSize, key.getS());
ctx.put(DefaultEcPrivateJwk.D, d);
return new DefaultEcPrivateJwk(ctx, pubJwk);
}
@Override
protected EcPrivateJwk createJwkFromValues(final JwkContext<ECPrivateKey> ctx) {
String curveId = getRequiredString(ctx, DefaultEcPublicJwk.CURVE_ID);
BigInteger d = getRequiredBigInt(ctx, DefaultEcPrivateJwk.D, true);
// We don't actually need the public x,y point coordinates for JVM lookup, but the
// [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2)
// requires them to be present and valid for the private key as well, so we assert that here:
JwkContext<ECPublicKey> pubCtx = new DefaultJwkContext<>(ctx, DefaultEcPrivateJwk.PRIVATE_NAMES);
EcPublicJwk pubJwk = EcPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromValues(pubCtx);
ECParameterSpec spec = getCurveByJwaId(curveId);
final ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(d, spec);
ECPrivateKey key = generateKey(ctx, new CheckedFunction<KeyFactory, ECPrivateKey>() {
@Override
public ECPrivateKey apply(KeyFactory kf) throws Exception {
return (ECPrivateKey) kf.generatePrivate(privateSpec);
}
});
ctx.setKey(key);
return new DefaultEcPrivateJwk(ctx, pubJwk);
}
}

View File

@ -0,0 +1,75 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.security.EcPublicJwk;
import io.jsonwebtoken.security.InvalidKeyException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
class EcPublicJwkFactory extends AbstractEcJwkFactory<ECPublicKey, EcPublicJwk> {
static final EcPublicJwkFactory DEFAULT_INSTANCE = new EcPublicJwkFactory();
EcPublicJwkFactory() {
super(ECPublicKey.class);
}
@Override
protected EcPublicJwk createJwkFromKey(JwkContext<ECPublicKey> ctx) {
ECPublicKey key = ctx.getKey();
ECParameterSpec spec = key.getParams();
EllipticCurve curve = spec.getCurve();
ECPoint point = key.getW();
String curveId = getJwaIdByCurve(curve);
ctx.put(DefaultEcPublicJwk.CURVE_ID, curveId);
int fieldSize = curve.getField().getFieldSize();
String x = toOctetString(fieldSize, point.getAffineX());
ctx.put(DefaultEcPublicJwk.X, x);
String y = toOctetString(fieldSize, point.getAffineY());
ctx.put(DefaultEcPublicJwk.Y, y);
return new DefaultEcPublicJwk(ctx);
}
@Override
protected EcPublicJwk createJwkFromValues(final JwkContext<ECPublicKey> ctx) {
String curveId = getRequiredString(ctx, DefaultEcPublicJwk.CURVE_ID);
BigInteger x = getRequiredBigInt(ctx, DefaultEcPublicJwk.X, false);
BigInteger y = getRequiredBigInt(ctx, DefaultEcPublicJwk.Y, false);
ECParameterSpec spec = getCurveByJwaId(curveId);
ECPoint point = new ECPoint(x, y);
if (!contains(spec.getCurve(), point)) {
String msg = "EC JWK x,y coordinates do not match a point on the '" + curveId + "' elliptic curve. This " +
"could be due simply to an incorrectly-created JWK or possibly an attempted Invalid Curve Attack " +
"(see https://safecurves.cr.yp.to/twist.html for more information). JWK: {" + ctx + "}.";
throw new InvalidKeyException(msg);
}
final ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, spec);
ECPublicKey key = generateKey(ctx, new CheckedFunction<KeyFactory, ECPublicKey>() {
@Override
public ECPublicKey apply(KeyFactory kf) throws Exception {
return (ECPublicKey) kf.generatePublic(pubSpec);
}
});
ctx.setKey(key);
return new DefaultEcPublicJwk(ctx);
}
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.Identifiable;
import io.jsonwebtoken.security.Jwk;
import java.security.Key;
public interface FamilyJwkFactory<K extends Key, J extends Jwk<K>> extends JwkFactory<K, J>, Identifiable {
boolean supports(JwkContext<?> context);
}

View File

@ -0,0 +1,16 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.ProtoJwkBuilder;
// Implementation bridge to concrete implementations so the API module doesn't need to know their
// internals. The API module just needs to call this class via reflection, and the internal Classes/Subclasses
// can change without requiring an API module change.
public final class JwkBuilders {
private JwkBuilders() {
}
public static ProtoJwkBuilder<?,?,?> builder() {
return new DefaultProtoJwkBuilder<>();
}
}

View File

@ -0,0 +1,86 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.Identifiable;
import java.net.URI;
import java.security.Key;
import java.security.Provider;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface JwkContext<K extends Key> extends Identifiable {
int size();
boolean isEmpty();
boolean containsKey(String key);
boolean containsValue(Object value);
Object get(String key);
Set<String> keySet();
Collection<Object> values();
Set<Map.Entry<String, Object>> entrySet();
Map<String,Object> getValues();
Object put(String name, Object value);
void putAll(Map<? extends String, ?> m);
void setId(String id);
String getType();
void setType(String type);
Set<String> getOperations();
void setOperations(Set<String> operations);
String getAlgorithm();
void setAlgorithm(String algorithm);
String getPublicKeyUse();
void setPublicKeyUse(String use);
URI getX509Url();
void setX509Url(URI url);
List<X509Certificate> getX509CertificateChain();
void setX509CertificateChain(List<X509Certificate> x5c);
byte[] getX509CertificateSha1Thumbprint();
void setX509CertificateSha1Thumbprint(byte[] x5t);
byte[] getX509CertificateSha256Thumbprint();
void setX509CertificateSha256Thumbprint(byte[] x5ts256);
K getKey();
JwkContext<K> setKey(K key);
PublicKey getPublicKey();
void setPublicKey(PublicKey publicKey);
Set<String> getPrivateMemberNames();
Provider getProvider();
void setProvider(Provider provider);
}

View File

@ -2,6 +2,7 @@ package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.Converter;
import io.jsonwebtoken.security.Identifiable;
import io.jsonwebtoken.security.Jwk;
import java.security.Key;
import java.util.Map;

View File

@ -0,0 +1,10 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.Jwk;
import java.security.Key;
public interface JwkFactory<K extends Key, J extends Jwk<K>> {
J createJwk(JwkContext<K> ctx);
}

View File

@ -1,5 +1,6 @@
package io.jsonwebtoken.impl.security;
//TODO: Make a non-impl concept?
public interface KeyUseStrategy {
//TODO: change argument to have more information?

View File

@ -1,40 +0,0 @@
package io.jsonwebtoken.impl.security;
import java.security.Key;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
public class RsaJwkConverter<K extends Key & RSAKey> extends AbstractJwkConverter<K> {
static final String TYPE_VALUE = "RSA";
static final String MODULUS = "n";
static final String EXPONENT = "e";
static String PRIVATE_EXPONENT = "d";
static String FIRST_PRIME = "p";
static String SECOND_PRIME = "q";
static String FIRST_CRT_EXPONENT = "dp";
static String SECOND_CRT_EXPONENT = "dq";
static String FIRST_CRT_COEFFICIENT = "qi";
static String OTHER_PRIMES_INFO = "oth";
public RsaJwkConverter() {
super("RSA");
}
@Override
public boolean supports(Key key) {
return key instanceof RSAPublicKey || key instanceof RSAPrivateKey;
}
@Override
public Map<String, ?> applyTo(K key) {
throw new UnsupportedOperationException("Not yet implemented.");
}
@Override
public K applyFrom(Map<String, ?> jwk) {
throw new UnsupportedOperationException("Not yet implemented.");
}
}

View File

@ -0,0 +1,240 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.impl.lang.Converter;
import io.jsonwebtoken.impl.lang.Converters;
import io.jsonwebtoken.lang.Arrays;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.MalformedKeyException;
import io.jsonwebtoken.security.RsaPrivateJwk;
import io.jsonwebtoken.security.RsaPublicJwk;
import io.jsonwebtoken.security.UnsupportedKeyException;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.RSAMultiPrimePrivateCrtKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.KeySpec;
import java.security.spec.RSAMultiPrimePrivateCrtKeySpec;
import java.security.spec.RSAOtherPrimeInfo;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory<RSAPrivateKey, RsaPrivateJwk> {
static final Converter<List<RSAOtherPrimeInfo>, Object> RSA_OTHER_PRIMES_CONVERTER =
Converters.forList(new RSAOtherPrimeInfoConverter());
private static final String PUBKEY_ERR_MSG = "JwkContext publicKey must be an " + RSAPublicKey.class.getName() + " instance.";
RsaPrivateJwkFactory() {
super(DefaultRsaPublicJwk.TYPE_VALUE, RSAPrivateKey.class);
}
@Override
protected boolean supportsKeyValues(JwkContext<?> ctx) {
return super.supportsKeyValues(ctx) && ctx.containsKey(DefaultRsaPrivateJwk.PRIVATE_EXPONENT);
}
private static BigInteger getPublicExponent(RSAPrivateKey key) {
if (key instanceof RSAPrivateCrtKey) {
return ((RSAPrivateCrtKey) key).getPublicExponent();
} else if (key instanceof RSAMultiPrimePrivateCrtKey) {
return ((RSAMultiPrimePrivateCrtKey) key).getPublicExponent();
}
String msg = "Unable to derive RSAPublicKey from RSAPrivateKey implementation [" +
key.getClass().getName() + "]. Supported keys implement the " +
RSAPrivateCrtKey.class.getName() + " or " + RSAMultiPrimePrivateCrtKey.class.getName() +
" interfaces. If the specified RSAPrivateKey cannot be one of these two, you must explicitly " +
"provide an RSAPublicKey in addition to the RSAPrivateKey, as the " +
"[JWA RFC, Section 6.3.2](https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2) " +
"requires public values to be present in private RSA JWKs.";
throw new UnsupportedKeyException(msg);
}
private RSAPublicKey derivePublic(final JwkContext<RSAPrivateKey> ctx) {
RSAPrivateKey key = ctx.getKey();
BigInteger modulus = key.getModulus();
BigInteger publicExponent = getPublicExponent(key);
final RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent);
return generateKey(ctx, RSAPublicKey.class, new CheckedFunction<KeyFactory, RSAPublicKey>() {
@Override
public RSAPublicKey apply(KeyFactory kf) {
try {
return (RSAPublicKey) kf.generatePublic(spec);
} catch (Exception e) {
String msg = "Unable to derive RSAPublicKey from RSAPrivateKey {" + ctx + "}.";
throw new UnsupportedKeyException(msg);
}
}
});
}
@Override
protected RsaPrivateJwk createJwkFromKey(JwkContext<RSAPrivateKey> ctx) {
RSAPrivateKey key = ctx.getKey();
RSAPublicKey rsaPublicKey;
PublicKey publicKey = ctx.getPublicKey();
if (publicKey != null) {
rsaPublicKey = Assert.isInstanceOf(RSAPublicKey.class, publicKey, PUBKEY_ERR_MSG);
} else {
rsaPublicKey = derivePublic(ctx);
}
// The [JWA Spec](https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.1)
// requires public values to be present in private JWKs, so add them:
JwkContext<RSAPublicKey> pubCtx = new DefaultJwkContext<>();
pubCtx.setKey(rsaPublicKey);
RsaPublicJwk pubJwk = RsaPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromKey(pubCtx);
ctx.putAll(pubJwk); // add public values to private key context
ctx.put(DefaultRsaPrivateJwk.PRIVATE_EXPONENT, encode(key.getPrivateExponent()));
if (key instanceof RSAPrivateCrtKey) {
RSAPrivateCrtKey ckey = (RSAPrivateCrtKey) key;
ctx.put(DefaultRsaPrivateJwk.FIRST_PRIME, encode(ckey.getPrimeP()));
ctx.put(DefaultRsaPrivateJwk.SECOND_PRIME, encode(ckey.getPrimeQ()));
ctx.put(DefaultRsaPrivateJwk.FIRST_CRT_EXPONENT, encode(ckey.getPrimeExponentP()));
ctx.put(DefaultRsaPrivateJwk.SECOND_CRT_EXPONENT, encode(ckey.getPrimeExponentQ()));
ctx.put(DefaultRsaPrivateJwk.FIRST_CRT_COEFFICIENT, encode(ckey.getCrtCoefficient()));
} else if (key instanceof RSAMultiPrimePrivateCrtKey) {
RSAMultiPrimePrivateCrtKey ckey = (RSAMultiPrimePrivateCrtKey) key;
ctx.put(DefaultRsaPrivateJwk.FIRST_PRIME, encode(ckey.getPrimeP()));
ctx.put(DefaultRsaPrivateJwk.SECOND_PRIME, encode(ckey.getPrimeQ()));
ctx.put(DefaultRsaPrivateJwk.FIRST_CRT_EXPONENT, encode(ckey.getPrimeExponentP()));
ctx.put(DefaultRsaPrivateJwk.SECOND_CRT_EXPONENT, encode(ckey.getPrimeExponentQ()));
ctx.put(DefaultRsaPrivateJwk.FIRST_CRT_COEFFICIENT, encode(ckey.getCrtCoefficient()));
List<RSAOtherPrimeInfo> infos = Arrays.asList(ckey.getOtherPrimeInfo());
if (!Collections.isEmpty(infos)) {
Object val = RSA_OTHER_PRIMES_CONVERTER.applyTo(infos);
ctx.put(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO, val);
}
}
return new DefaultRsaPrivateJwk(ctx, pubJwk);
}
@Override
protected RsaPrivateJwk createJwkFromValues(JwkContext<RSAPrivateKey> ctx) {
final BigInteger privateExponent = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.PRIVATE_EXPONENT, true);
//The [JWA Spec, Section 6.3.2](https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2) requires
//RSA Private Keys to also encode the public key values, so we assert that we can acquire it successfully:
JwkContext<RSAPublicKey> pubCtx = new DefaultJwkContext<>(ctx, DefaultRsaPrivateJwk.PRIVATE_NAMES);
RsaPublicJwk pubJwk = RsaPublicJwkFactory.DEFAULT_INSTANCE.createJwkFromValues(pubCtx);
RSAPublicKey pubKey = pubJwk.toKey();
final BigInteger modulus = pubKey.getModulus();
final BigInteger publicExponent = pubKey.getPublicExponent();
// JWA Section 6.3.2 also indicates that if any of the optional private names are present, then *all* of those
// optional values must be present (except 'oth', which is handled separately next). Quote:
//
// If the producer includes any of the other private key parameters, then all of the others MUST
// be present, with the exception of "oth", which MUST only be present when more than two prime
// factors were used
//
boolean containsOptional = false;
for (String optionalPrivateName : DefaultRsaPrivateJwk.OPTIONAL_PRIVATE_NAMES) {
if (ctx.containsKey(optionalPrivateName)) {
containsOptional = true;
break;
}
}
KeySpec spec;
if (containsOptional) { //if any one optional field exists, they are all required per JWA Section 6.3.2:
BigInteger firstPrime = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.FIRST_PRIME, true);
BigInteger secondPrime = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.SECOND_PRIME, true);
BigInteger firstCrtExponent = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.FIRST_CRT_EXPONENT, true);
BigInteger secondCrtExponent = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.SECOND_CRT_EXPONENT, true);
BigInteger firstCrtCoefficient = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.FIRST_CRT_COEFFICIENT, true);
// Other Primes Info is actually optional even if the above ones are required:
if (ctx.containsKey(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO)) {
Object value = ctx.get(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO);
List<RSAOtherPrimeInfo> otherPrimes = RSA_OTHER_PRIMES_CONVERTER.applyFrom(value);
RSAOtherPrimeInfo[] arr = new RSAOtherPrimeInfo[otherPrimes.size()];
otherPrimes.toArray(arr);
spec = new RSAMultiPrimePrivateCrtKeySpec(modulus, publicExponent, privateExponent, firstPrime,
secondPrime, firstCrtExponent, secondCrtExponent, firstCrtCoefficient, arr);
} else {
spec = new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, firstPrime, secondPrime,
firstCrtExponent, secondCrtExponent, firstCrtCoefficient);
}
} else {
spec = new RSAPrivateKeySpec(modulus, privateExponent);
}
final KeySpec keySpec = spec;
RSAPrivateKey key = generateKey(ctx, new CheckedFunction<KeyFactory, RSAPrivateKey>() {
@Override
public RSAPrivateKey apply(KeyFactory kf) throws Exception {
return (RSAPrivateKey) kf.generatePrivate(keySpec);
}
});
ctx.setKey(key);
return new DefaultRsaPrivateJwk(ctx, pubJwk);
}
static class RSAOtherPrimeInfoConverter implements Converter<RSAOtherPrimeInfo, Object> {
@Override
public Object applyTo(RSAOtherPrimeInfo info) {
Map<String, String> m = new LinkedHashMap<>(3);
m.put(DefaultRsaPrivateJwk.PRIME_FACTOR, encode(info.getPrime()));
m.put(DefaultRsaPrivateJwk.FACTOR_CRT_EXPONENT, encode(info.getExponent()));
m.put(DefaultRsaPrivateJwk.FACTOR_CRT_COEFFICIENT, encode(info.getCrtCoefficient()));
return m;
}
@Override
public RSAOtherPrimeInfo applyFrom(Object o) {
if (o == null) {
throw new MalformedKeyException("RSA JWK 'oth' Other Prime Info element cannot be null.");
}
if (!(o instanceof Map)) {
String msg = "RSA JWK 'oth' Other Prime Info list must contain map elements of name/value pairs. " +
"Element type found: " + o.getClass().getName();
throw new MalformedKeyException(msg);
}
Map<?, ?> m = (Map<?, ?>) o;
if (Collections.isEmpty(m)) {
throw new MalformedKeyException("RSA JWK 'oth' Other Prime Info element map cannot be empty.");
}
// Need to do add the values to a Context instance to satisfy the API contract of the getRequired* methods
// below. It's less than ideal, but it works:
JwkContext<?> ctx = new DefaultJwkContext<>();
for (Map.Entry<?, ?> entry : m.entrySet()) {
String name = String.valueOf(entry.getKey());
ctx.put(name, entry.getValue());
}
BigInteger prime = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.PRIME_FACTOR, true);
BigInteger primeExponent = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.FACTOR_CRT_EXPONENT, true);
BigInteger crtCoefficient = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.FACTOR_CRT_COEFFICIENT, true);
return new RSAOtherPrimeInfo(prime, primeExponent, crtCoefficient);
}
}
}

View File

@ -0,0 +1,45 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.security.RsaPublicJwk;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPublicKeySpec;
class RsaPublicJwkFactory extends AbstractFamilyJwkFactory<RSAPublicKey, RsaPublicJwk> {
static final RsaPublicJwkFactory DEFAULT_INSTANCE = new RsaPublicJwkFactory();
RsaPublicJwkFactory() {
super(DefaultRsaPublicJwk.TYPE_VALUE, RSAPublicKey.class);
}
@Override
protected RsaPublicJwk createJwkFromKey(JwkContext<RSAPublicKey> ctx) {
RSAPublicKey key = ctx.getKey();
ctx.put(DefaultRsaPublicJwk.MODULUS, encode(key.getModulus()));
ctx.put(DefaultRsaPublicJwk.PUBLIC_EXPONENT, encode(key.getPublicExponent()));
return new DefaultRsaPublicJwk(ctx);
}
@Override
protected RsaPublicJwk createJwkFromValues(JwkContext<RSAPublicKey> ctx) {
BigInteger modulus = getRequiredBigInt(ctx, DefaultRsaPublicJwk.MODULUS, false);
BigInteger publicExponent = getRequiredBigInt(ctx, DefaultRsaPublicJwk.PUBLIC_EXPONENT, false);
final RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent);
RSAPublicKey key = generateKey(ctx, new CheckedFunction<KeyFactory, RSAPublicKey>() {
@Override
public RSAPublicKey apply(KeyFactory keyFactory) throws Exception {
return (RSAPublicKey) keyFactory.generatePublic(spec);
}
});
ctx.setKey(key);
return new DefaultRsaPublicJwk(ctx);
}
}

View File

@ -11,13 +11,12 @@ import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Map;
public class SymmetricJwkConverter extends AbstractJwkConverter<SecretKey> {
public class SecretJwkConverter extends AbstractJwkConverter<SecretKey> {
static final String TYPE_VALUE = "oct";
static final String K = "k";
static final SecretJwkConverter DEFAULT_INSTANCE = new SecretJwkConverter();
public SymmetricJwkConverter() {
super(TYPE_VALUE);
public SecretJwkConverter() {
super(DefaultSecretJwk.TYPE_VALUE);
}
@Override
@ -43,14 +42,14 @@ public class SymmetricJwkConverter extends AbstractJwkConverter<SecretKey> {
assert k != null : "k value is mandatory.";
Map<String, String> m = newJwkMap();
m.put(K, k);
Map<String, Object> m = newJwkMap();
m.put(DefaultSecretJwk.K, k);
return m;
}
@Override
public SecretKey applyFrom(Map<String, ?> jwk) {
String encoded = getRequiredString(jwk, K);
String encoded = getRequiredString(jwk, DefaultSecretJwk.K);
byte[] bytes;
try {
bytes = Decoders.BASE64URL.decode(encoded);
@ -58,7 +57,7 @@ public class SymmetricJwkConverter extends AbstractJwkConverter<SecretKey> {
throw new IllegalArgumentException("JWK 'k' member does not have any encoded bytes. JWK: {" + jwk + "}");
}
} catch (Exception e) {
Map<String,?> msgJwk = sanitize(jwk, K);
Map<String,?> msgJwk = sanitize(jwk, DefaultSecretJwk.PRIVATE_NAMES);
String msg = "Unable to Base64Url-decode 'oct' JWK 'k' member value. JWK: {" + msgJwk + "}";
throw new MalformedKeyException(msg, e);
}

View File

@ -0,0 +1,61 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.lang.Arrays;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.MalformedKeyException;
import io.jsonwebtoken.security.SecretJwk;
import io.jsonwebtoken.security.UnsupportedKeyException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
class SecretJwkFactory extends AbstractFamilyJwkFactory<SecretKey, SecretJwk> {
SecretJwkFactory() {
super(DefaultSecretJwk.TYPE_VALUE, SecretKey.class);
}
@Override
protected SecretJwk createJwkFromKey(JwkContext<SecretKey> ctx) {
SecretKey key = Assert.notNull(ctx.getKey(), "JwkContext key cannot be null.");
String k;
try {
byte[] encoded = key.getEncoded();
if (Arrays.length(encoded) == 0) {
throw new IllegalArgumentException("SecretKey argument does not have any encoded bytes, or " +
"the key's backing JCA Provider is preventing key.getEncoded() from returning any bytes. In " +
"either case, it is not possible to represent the SecretKey instance as a JWK.");
}
k = Encoders.BASE64URL.encode(encoded);
} catch (Exception e) {
String msg = "Unable to encode SecretKey to JWK: " + e.getMessage();
throw new UnsupportedKeyException(msg, e);
}
assert k != null : "k value is mandatory.";
ctx.put(DefaultSecretJwk.K, k);
return new DefaultSecretJwk(ctx);
}
@Override
protected SecretJwk createJwkFromValues(JwkContext<SecretKey> ctx) {
String encoded = getRequiredString(ctx, DefaultSecretJwk.K);
byte[] bytes;
try {
bytes = Decoders.BASE64URL.decode(encoded);
if (Arrays.length(bytes) == 0) {
throw new IllegalArgumentException("JWK 'k' member does not have any encoded bytes. JWK: {" + ctx + "}");
}
} catch (Exception e) {
String msg = "Unable to Base64Url-decode " + DefaultSecretJwk.TYPE_VALUE +
" JWK 'k' member value. JWK: {" + ctx + "}";
throw new MalformedKeyException(msg, e);
}
SecretKey key = new SecretKeySpec(bytes, "NONE"); //TODO: do we need a JCA-specific ID here?
ctx.setKey(key);
return new DefaultSecretJwk(ctx);
}
}

View File

@ -1,40 +1,38 @@
package io.jsonwebtoken.impl.security
import io.jsonwebtoken.lang.Maps
import io.jsonwebtoken.security.Jwks
import io.jsonwebtoken.security.RsaPublicJwkBuilder
import io.jsonwebtoken.security.SignatureAlgorithms
import org.junit.Test
import java.security.cert.X509Certificate
import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
import static org.junit.Assert.*
class DefaultJwkBuilderTest {
private static DefaultJwkBuilder builder() {
return new DefaultJwkBuilder(Maps.of("typ", "okt").build())
}
class AbstractAsymmetricJwkBuilderTest {
private static final X509Certificate CERT = CertUtils.readTestCertificate(SignatureAlgorithms.RS256)
private static final RSAPublicKey PUB_KEY = (RSAPublicKey)CERT.getPublicKey();
private static RsaPublicJwkBuilder builder() {
return Jwks.builder().setKey(PUB_KEY)
}
@Test
void testUse() {
def val = UUID.randomUUID().toString()
assertEquals val, builder().setUse(val).build().getUse()
}
def jwk = builder().setPublicKeyUse(val).build()
assertEquals val, jwk.getPublicKeyUse()
assertEquals val, jwk.use
@Test
void testOperations() {
def a = UUID.randomUUID().toString()
def b = UUID.randomUUID().toString()
def set = [a, b] as Set<String>
assertEquals set, builder().setOperations(set).build().getOperations()
}
def privateKey = CertUtils.readTestPrivateKey(SignatureAlgorithms.RS256);
@Test
void testAlgorithm() {
def val = UUID.randomUUID().toString()
assertEquals val, builder().setAlgorithm(val).build().getAlgorithm()
}
@Test
void testId() {
def val = UUID.randomUUID().toString()
assertEquals val, builder().setId(val).build().getId()
jwk = builder().setPublicKeyUse(val).setPrivateKey((RSAPrivateKey)privateKey).build()
assertEquals val, jwk.getPublicKeyUse()
assertEquals val, jwk.use
}
@Test
@ -62,4 +60,6 @@ class DefaultJwkBuilderTest {
def val = UUID.randomUUID().toString()
assertEquals val, builder().setX509CertificateSha256Thumbprint(val).build().getX509CertificateSha256Thumbprint()
}
}

View File

@ -0,0 +1,113 @@
package io.jsonwebtoken.impl.security
import io.jsonwebtoken.security.EncryptionAlgorithms
import io.jsonwebtoken.security.Jwks
import io.jsonwebtoken.security.SecretJwk
import org.junit.Test
import javax.crypto.SecretKey
import java.security.Security
import static org.junit.Assert.*
class AbstractJwkBuilderTest {
private static final SecretKey SKEY = EncryptionAlgorithms.A256GCM.generateKey();
private static AbstractJwkBuilder<SecretKey, SecretJwk, AbstractJwkBuilder> builder() {
return (AbstractJwkBuilder)Jwks.builder().setKey(SKEY)
}
@Test
void testKeyType() {
def jwk = builder().build()
assertEquals 'oct', jwk.getType()
assertNotNull jwk.k // JWA id for raw key value
}
@Test
void testPut() {
def a = UUID.randomUUID()
def builder = builder()
builder.put('foo', a)
assertEquals a, builder.build().get('foo')
}
@Test
void testPutAll() {
def foo = UUID.randomUUID()
def bar = UUID.randomUUID().toString() //different type
def m = [foo: foo, bar: bar]
def jwk = builder().putAll(m).build()
assertEquals foo, jwk.foo
assertEquals bar, jwk.bar
}
@Test
void testAlgorithm() {
def alg = 'someAlgorithm'
def jwk = builder().setAlgorithm(alg).build()
assertEquals alg, jwk.getAlgorithm()
assertEquals alg, jwk.alg //test raw get via JWA member id
}
@Test
void testAlgorithmByPut() {
def alg = 'someAlgorithm'
def jwk = builder().put('alg', alg).build() //ensure direct put still is handled properly
assertEquals alg, jwk.getAlgorithm()
assertEquals alg, jwk.alg //test raw get via JWA member id
}
@Test
void testId() {
def kid = UUID.randomUUID().toString()
def jwk = builder().setId(kid).build()
assertEquals kid, jwk.getId()
assertEquals kid, jwk.kid //test raw get via JWA member id
}
@Test
void testIdByPut() {
def kid = UUID.randomUUID().toString()
def jwk = builder().put('kid', kid).build()
assertEquals kid, jwk.getId()
assertEquals kid, jwk.kid //test raw get via JWA member id
}
@Test
void testOperations() {
def a = UUID.randomUUID().toString()
def b = UUID.randomUUID().toString()
def set = [a, b] as Set<String>
def jwk = builder().setOperations(set).build()
assertEquals set, jwk.getOperations()
assertEquals set, jwk.key_ops
}
@Test
void testOperationsByPut() {
def a = UUID.randomUUID().toString()
def b = UUID.randomUUID().toString()
def set = [a, b] as Set<String>
def jwk = builder().put('key_ops', set).build()
assertEquals set, jwk.getOperations()
assertEquals set, jwk.key_ops
}
@Test //ensures that even if a raw single value is present it is represented as a Set per the JWA spec (string array)
void testOperationsByPutSingleValue() {
def a = UUID.randomUUID().toString()
def set = [a] as Set<String>
def jwk = builder().put('key_ops', a).build() // <-- put uses single raw value, not a set
assertEquals set, jwk.getOperations() // <-- still get a set
assertEquals set, jwk.key_ops // <-- still get a set
}
@Test
void testProvider() {
def provider = Security.getProvider("BC")
def jwk = builder().setProvider(provider).build()
assertEquals 'oct', jwk.getType()
}
}

View File

@ -13,9 +13,11 @@ import static org.junit.Assert.*
class DefaultJwkTest {
/*
private static final SecretKey TEST_KEY = SignatureAlgorithms.HS512.generateKey();
class TestJwk extends DefaultJwk {
class TestJwk extends AbstractJwk {
TestJwk(String type = "test", String use = null, Set<String> operations = null, String algorithm = null, String id = null, URI x509url = null, List<X509Certificate> certChain = null, byte[] x509Sha1Thumbprint = null, byte[] x509Sha256Thumbprint = null, Key key = TEST_KEY) {
super(type, use, operations, algorithm, id, x509url, certChain, x509Sha1Thumbprint, x509Sha256Thumbprint, TEST_KEY, null)
}
@ -30,17 +32,17 @@ class DefaultJwkTest {
void testUse() {
def jwk = new TestJwk()
assertEquals 'use', DefaultJwk.USE
assertNull jwk.get(DefaultJwk.USE)
assertEquals 'use', AbstractJwk.USE
assertNull jwk.get(AbstractJwk.USE)
assertNull jwk.getUse()
jwk = new TestJwk(use: ' ') //empty should remove
assertNull jwk.get(DefaultJwk.USE)
assertNull jwk.get(AbstractJwk.USE)
assertNull jwk.getUse()
String val = UUID.randomUUID().toString()
jwk = new TestJwk(use: val)
assertEquals val, jwk.get(DefaultJwk.USE)
assertEquals val, jwk.get(AbstractJwk.USE)
assertEquals val, jwk.getUse()
}
@ -49,20 +51,20 @@ class DefaultJwkTest {
def jwk = new TestJwk()
assertEquals 'key_ops', DefaultJwk.OPERATIONS
assertEquals 'key_ops', AbstractJwk.OPERATIONS
jwk.setOperations(null)
assertNull jwk.get(DefaultJwk.OPERATIONS)
assertNull jwk.get(AbstractJwk.OPERATIONS)
assertNull jwk.getOperations()
jwk.setOperations([] as Set<String>) //empty should remove
assertNull jwk.get(DefaultJwk.OPERATIONS)
assertNull jwk.get(AbstractJwk.OPERATIONS)
assertNull jwk.getOperations()
def set = ['a', 'b'] as Set<String>
jwk.setOperations(set)
assertEquals set, jwk.get(DefaultJwk.OPERATIONS)
assertEquals set, jwk.get(AbstractJwk.OPERATIONS)
assertEquals set, jwk.getOperations()
}
@ -70,19 +72,19 @@ class DefaultJwkTest {
void testAlgorithm() {
def jwk = new TestJwk()
assertEquals 'alg', DefaultJwk.ALGORITHM
assertEquals 'alg', AbstractJwk.ALGORITHM
jwk.setAlgorithm(null)
assertNull jwk.get(DefaultJwk.ALGORITHM)
assertNull jwk.get(AbstractJwk.ALGORITHM)
assertNull jwk.getAlgorithm()
jwk.setAlgorithm(' ') //empty should remove
assertNull jwk.get(DefaultJwk.ALGORITHM)
assertNull jwk.get(AbstractJwk.ALGORITHM)
assertNull jwk.getAlgorithm()
String val = UUID.randomUUID().toString()
jwk.setAlgorithm(val)
assertEquals val, jwk.get(DefaultJwk.ALGORITHM)
assertEquals val, jwk.get(AbstractJwk.ALGORITHM)
assertEquals val, jwk.getAlgorithm()
}
@ -90,19 +92,19 @@ class DefaultJwkTest {
void testId() {
def jwk = new TestJwk()
assertEquals 'kid', DefaultJwk.ID
assertEquals 'kid', AbstractJwk.ID
jwk.setId(null)
assertNull jwk.get(DefaultJwk.ID)
assertNull jwk.get(AbstractJwk.ID)
assertNull jwk.getId()
jwk.setId(' ') //empty should remove
assertNull jwk.get(DefaultJwk.ID)
assertNull jwk.get(AbstractJwk.ID)
assertNull jwk.getId()
String val = UUID.randomUUID().toString()
jwk.setId(val)
assertEquals val, jwk.get(DefaultJwk.ID)
assertEquals val, jwk.get(AbstractJwk.ID)
assertEquals val, jwk.getId()
}
@ -110,19 +112,19 @@ class DefaultJwkTest {
void testX509Sha1Thumbprint() {
def jwk = new TestJwk()
assertEquals 'x5t', DefaultJwk.X509_SHA1_THUMBPRINT
assertEquals 'x5t', AbstractJwk.X509_SHA1_THUMBPRINT
jwk.setX509CertificateSha1Thumbprint(null)
assertNull jwk.get(DefaultJwk.X509_SHA1_THUMBPRINT)
assertNull jwk.get(AbstractJwk.X509_SHA1_THUMBPRINT)
assertNull jwk.getX509CertificateSha1Thumbprint()
jwk.setX509CertificateSha1Thumbprint(' ') //empty should remove
assertNull jwk.get(DefaultJwk.X509_SHA1_THUMBPRINT)
assertNull jwk.get(AbstractJwk.X509_SHA1_THUMBPRINT)
assertNull jwk.getX509CertificateSha1Thumbprint()
String val = UUID.randomUUID().toString()
jwk.setX509CertificateSha1Thumbprint(val)
assertEquals val, jwk.get(DefaultJwk.X509_SHA1_THUMBPRINT)
assertEquals val, jwk.get(AbstractJwk.X509_SHA1_THUMBPRINT)
assertEquals val, jwk.getX509CertificateSha1Thumbprint()
}
@ -130,19 +132,19 @@ class DefaultJwkTest {
void testX509Sha256Thumbprint() {
def jwk = new TestJwk()
assertEquals 'x5t#S256', DefaultJwk.X509_SHA256_THUMBPRINT
assertEquals 'x5t#S256', AbstractJwk.X509_SHA256_THUMBPRINT
jwk.setX509CertificateSha1Thumbprint(null)
assertNull jwk.get(DefaultJwk.X509_SHA256_THUMBPRINT)
assertNull jwk.get(AbstractJwk.X509_SHA256_THUMBPRINT)
assertNull jwk.getX509CertificateSha256Thumbprint()
jwk.setX509CertificateSha256Thumbprint(' ') //empty should remove
assertNull jwk.get(DefaultJwk.X509_SHA256_THUMBPRINT)
assertNull jwk.get(AbstractJwk.X509_SHA256_THUMBPRINT)
assertNull jwk.getX509CertificateSha256Thumbprint()
String val = UUID.randomUUID().toString()
jwk.setX509CertificateSha256Thumbprint(val)
assertEquals val, jwk.get(DefaultJwk.X509_SHA256_THUMBPRINT)
assertEquals val, jwk.get(AbstractJwk.X509_SHA256_THUMBPRINT)
assertEquals val, jwk.getX509CertificateSha256Thumbprint()
}
@ -151,28 +153,28 @@ class DefaultJwkTest {
def jwk = new TestJwk()
assertEquals 'x5u', DefaultJwk.X509_URL
assertEquals 'x5u', AbstractJwk.X509_URL
jwk.setX509Url(null)
assertNull jwk.get(DefaultJwk.X509_URL)
assertNull jwk.get(AbstractJwk.X509_URL)
assertNull jwk.getX509Url()
String suri = 'https://whatever.com/cert'
def uri = new URI(suri)
jwk.put(DefaultJwk.X509_URL, uri)
assertEquals uri, jwk.get(DefaultJwk.X509_URL)
jwk.put(AbstractJwk.X509_URL, uri)
assertEquals uri, jwk.get(AbstractJwk.X509_URL)
assertEquals uri, jwk.getX509Url()
jwk.put(DefaultJwk.X509_URL, suri)
assertEquals suri, jwk.get(DefaultJwk.X509_URL) //string here
jwk.put(AbstractJwk.X509_URL, suri)
assertEquals suri, jwk.get(AbstractJwk.X509_URL) //string here
assertEquals uri, jwk.getX509Url() //conversion here
assertEquals uri, jwk.get(DefaultJwk.X509_URL) //ensure replaced with URI instance
assertEquals uri, jwk.get(AbstractJwk.X509_URL) //ensure replaced with URI instance
jwk.remove(DefaultJwk.X509_URL) //clear for next test
jwk.remove(AbstractJwk.X509_URL) //clear for next test
jwk.setX509Url(uri)
assertEquals uri, jwk.get(DefaultJwk.X509_URL)
assertEquals uri, jwk.get(AbstractJwk.X509_URL)
assertEquals uri, jwk.getX509Url()
}
@ -180,7 +182,7 @@ class DefaultJwkTest {
void testGetX509UrlWithInvalidUri() {
def jwk = new TestJwk()
def uri = '|not-a-uri|'
jwk.put(DefaultJwk.X509_URL, uri)
jwk.put(AbstractJwk.X509_URL, uri)
try {
jwk.getX509Url()
fail()
@ -224,20 +226,22 @@ class DefaultJwkTest {
def jwk = new TestJwk()
assertEquals 'x5c', DefaultJwk.X509_CERT_CHAIN
assertEquals 'x5c', AbstractJwk.X509_CERT_CHAIN
jwk.setX509CertificateChain(null)
assertNull jwk.get(DefaultJwk.X509_CERT_CHAIN)
assertNull jwk.get(AbstractJwk.X509_CERT_CHAIN)
assertNull jwk.getX509CertificateChain()
jwk.setX509CertificateChain([])
assertNull jwk.get(DefaultJwk.X509_CERT_CHAIN)
assertNull jwk.get(AbstractJwk.X509_CERT_CHAIN)
assertNull jwk.getX509CertificateChain()
String val = UUID.randomUUID().toString()
def chain = [val]
jwk.setX509CertificateChain(chain)
assertEquals chain, jwk.get(DefaultJwk.X509_CERT_CHAIN)
assertEquals chain, jwk.get(AbstractJwk.X509_CERT_CHAIN)
assertEquals chain, jwk.getX509CertificateChain()
}
*/
}

View File

@ -1,103 +0,0 @@
package io.jsonwebtoken.impl.security
import io.jsonwebtoken.security.InvalidKeyException
import io.jsonwebtoken.security.SignatureAlgorithms
import io.jsonwebtoken.security.UnsupportedKeyException
import org.junit.Ignore
import java.security.KeyPair
import java.security.interfaces.ECPrivateKey
import java.security.interfaces.ECPublicKey
import static org.junit.Assert.*
import io.jsonwebtoken.io.Encoders
import org.junit.Test
class DispatchingJwkConverterTest {
@Test
void testNullJwk() {
try {
new DispatchingJwkConverter().toKey(null)
fail()
} catch (InvalidKeyException expected) {
assertEquals 'JWK map cannot be null or empty.', expected.message
}
}
@Test
void testEmptyJwk() {
try {
new DispatchingJwkConverter().toKey([:])
fail()
} catch (InvalidKeyException expected) {
assertEquals 'JWK map cannot be null or empty.', expected.message
}
}
@Test
void testUnknownKeyType() {
def jwk = [
'kty': 'foo'
]
DispatchingJwkConverter converter = new DispatchingJwkConverter()
try {
converter.toKey(jwk)
fail()
} catch (UnsupportedKeyException e) {
assertEquals 'Unrecognized JWK kty (key type) value: foo', e.getMessage()
}
}
@Test
void testEcKeyPairToKey() {
def jwk = [
'kty': 'EC',
'crv': 'P-256',
"x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0",
"y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps",
"d":"0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo"
]
DispatchingJwkConverter converter = new DispatchingJwkConverter()
def key = converter.toKey(jwk)
assertTrue key instanceof ECPrivateKey
key = key as ECPrivateKey
String d = EcJwkConverter.toOctetString(key.params.curve.field.fieldSize, key.s)
assertEquals jwk.d, d
//remove the 'd' mapping to represent only a public key:
jwk.remove('d')
key = converter.toKey(jwk)
assertTrue key instanceof ECPublicKey
key = key as ECPublicKey
String x = EcJwkConverter.toOctetString(key.params.curve.field.fieldSize, key.w.affineX)
String y = EcJwkConverter.toOctetString(key.params.curve.field.fieldSize, key.w.affineY)
assertEquals jwk.x, x
assertEquals jwk.y, y
}
@Test
@Ignore //TODO re-enable
void testEcKeyPairToJwk() {
KeyPair pair = SignatureAlgorithms.ES256.generateKeyPair()
ECPublicKey pubKey = (ECPublicKey) pair.getPublic()
DispatchingJwkConverter converter = new DispatchingJwkConverter()
Map<String,String> jwk = converter.toJwk(pubKey)
assertNotNull jwk
assertEquals "EC", jwk.kty
assertEquals Encoders.BASE64URL.encode(pubKey.w.affineX.toByteArray()), jwk.x
assertEquals Encoders.BASE64URL.encode(pubKey.w.affineY.toByteArray()), jwk.y
assertNull jwk.d //public keys should not populate the private key 'd' parameter
}
}

View File

@ -0,0 +1,118 @@
package io.jsonwebtoken.impl.security
import io.jsonwebtoken.io.Encoders
import io.jsonwebtoken.security.EcPrivateJwk
import io.jsonwebtoken.security.EcPublicJwk
import io.jsonwebtoken.security.InvalidKeyException
import io.jsonwebtoken.security.SignatureAlgorithms
import io.jsonwebtoken.security.UnsupportedKeyException
import org.junit.Ignore
import org.junit.Test
import java.security.Key
import java.security.KeyPair
import java.security.interfaces.ECPrivateKey
import java.security.interfaces.ECPublicKey
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertNotNull
import static org.junit.Assert.assertNull
import static org.junit.Assert.assertTrue
import static org.junit.Assert.fail
class DispatchingJwkFactoryTest {
@Test
void testNullJwk() {
try {
new DispatchingJwkFactory().createJwk(null)
fail()
} catch (InvalidKeyException expected) {
assertEquals 'JWK map cannot be null or empty.', expected.message
}
}
@Test
void testEmptyJwk() {
try {
new DispatchingJwkFactory().createJwk(new DefaultJwkContext<Key>())
fail()
} catch (InvalidKeyException expected) {
assertEquals 'JWK map cannot be null or empty.', expected.message
}
}
@Test
void testUnknownKeyType() {
def ctx = new DefaultJwkContext();
ctx.put('kty', 'foo')
DispatchingJwkFactory factory = new DispatchingJwkFactory()
try {
factory.createJwk(ctx)
fail()
} catch (UnsupportedKeyException e) {
assertEquals 'Unrecognized JWK kty (key type) value: foo', e.getMessage()
}
}
@Test
void testEcKeyPairToKey() {
def m = [
'kty': 'EC',
'crv': 'P-256',
"x" : "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0",
"y" : "SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps",
"d" : "0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo"
]
def ctx = new DefaultJwkContext()
ctx.putAll(m)
DispatchingJwkFactory factory = new DispatchingJwkFactory()
def jwk = factory.createJwk(ctx) as EcPrivateJwk
assertTrue jwk instanceof EcPrivateJwk
def key = jwk.toKey()
assertTrue key instanceof ECPrivateKey
String x = AbstractEcJwkFactory.toOctetString(key.params.curve.field.fieldSize, jwk.toPublicJwk().toKey().w.affineX)
String y = AbstractEcJwkFactory.toOctetString(key.params.curve.field.fieldSize, jwk.toPublicJwk().toKey().w.affineY)
String d = AbstractEcJwkFactory.toOctetString(key.params.curve.field.fieldSize, key.s)
assertEquals jwk.d, d
//remove the 'd' mapping to represent only a public key:
m = m.remove(DefaultEcPrivateJwk.D)
ctx = new DefaultJwkContext()
ctx.putAll(m)
jwk = factory.createJwk(ctx) as EcPublicJwk
assertTrue jwk instanceof EcPublicJwk
key = jwk.toKey() as ECPublicKey
assertTrue key instanceof ECPublicKey
assertEquals jwk.x, x
assertEquals jwk.y, y
}
@Test
@Ignore
//TODO re-enable
void testEcKeyPairToJwk() {
KeyPair pair = SignatureAlgorithms.ES256.generateKeyPair()
ECPublicKey pubKey = (ECPublicKey) pair.getPublic()
def ctx = new DefaultJwkContext()
ctx.setKey(pubKey)
DispatchingJwkFactory factory = new DispatchingJwkFactory()
def jwk = factory.createJwk(ctx)
assertNotNull jwk
assertEquals "EC", jwk.kty
assertEquals Encoders.BASE64URL.encode(pubKey.w.affineX.toByteArray()), jwk.x
assertEquals Encoders.BASE64URL.encode(pubKey.w.affineY.toByteArray()), jwk.y
assertNull jwk.d //public keys should not populate the private key 'd' parameter
}
}

View File

@ -4,7 +4,6 @@ package io.jsonwebtoken.impl.security
import io.jsonwebtoken.io.Decoders
import io.jsonwebtoken.io.Encoders
import io.jsonwebtoken.security.EllipticCurveSignatureAlgorithm
import io.jsonwebtoken.security.Jwk
import io.jsonwebtoken.security.Jwks
import io.jsonwebtoken.security.SignatureAlgorithms
import org.junit.Test
@ -36,14 +35,14 @@ class JwksTest {
def key = name == 'use' ? EC_PAIR.public : SKEY
//test non-null value:
def builder = Jwks.builder(key)
def builder = Jwks.builder().setKey(key)
builder."set${cap}"(val)
def jwk = builder.build()
assertEquals val, jwk."get${cap}"()
assertEquals val, jwk."${id}"
//test null value:
builder = Jwks.builder(key)
builder = Jwks.builder().setKey(key)
try {
builder."set${cap}"(null)
fail("IAE should have been thrown")
@ -55,7 +54,7 @@ class JwksTest {
assertFalse jwk.containsKey(id)
//test empty string value
builder = Jwks.builder(key)
builder = Jwks.builder().setKey(key)
if (val instanceof String) {
try {
builder."set${cap}"(' ' as String)
@ -89,7 +88,7 @@ class JwksTest {
@Test
void testBuilderWithSecretKey() {
def jwk = Jwks.builder(SKEY).build()
def jwk = Jwks.builder().setKey(SKEY).build()
assertEquals 'oct', jwk.getType()
assertEquals 'oct', jwk.kty
assertNotNull jwk.k
@ -129,33 +128,26 @@ class JwksTest {
def pair = SignatureAlgorithms.ES256.generateKeyPair();
ECPublicKey ecPub = pair.getPublic() as ECPublicKey
ECPrivateKey ecPriv = pair.getPrivate() as ECPrivateKey
pair = SignatureAlgorithms.RS256.generateKeyPair()
RSAPublicKey rsaPub = pair.getPublic() as RSAPublicKey
RSAPrivateKey rsaPriv = pair.getPrivate() as RSAPrivateKey
SecretKey secretKey = SignatureAlgorithms.HS256.generateKey()
Jwk<ECPublicKey,?> ecPubJwk = Jwks.builder(ecPub).setPublicKeyUse("foo").build()
def ecPubJwk = Jwks.builder().setKey(ecPub).setPublicKeyUse("sig").build()
assertEquals ecPub, ecPubJwk.toKey()
def rsaPrivJwk = Jwks.builder().setKey(rsaPub).setPublicKeyUse("foo").setPrivateKey(rsaPriv).build();
Jwks.builder(rsaPub).setPublicKeyUse("foo").setPrivateKey(rsaPriv).set
Jwk<ECPrivateKey,?> ecPrivJwk = Jwks.builder(ecPriv).build()
def ecPrivJwk = Jwks.builder().setKey(ecPriv).build()
def pubJwk = ecPrivJwk.toPublicJwk()
assertEquals ecPubJwk, pubJwk
assertEquals ecPub, ecPrivJwk.toPublicKey()
Jwks.builder(rsaPriv).build().to
Jwk<RSAPublicKey,?> rsaPubJwk = Jwks.builder(rsaPub).build()
Jwk<RSAPrivateKey,?> rsaPrivJwk = Jwks.builder(rsaPriv).setPublicKey(rsaPub).build()
def rsaPubJwk = Jwks.builder().setKey(rsaPub).build()
rsaPrivJwk = Jwks.builder().setKey(rsaPriv).setPublicKey(rsaPub).build()
assertEquals rsaPubJwk, rsaPrivJwk.toPublicJwk()
}
@Test
@ -163,8 +155,8 @@ class JwksTest {
def algs = [SignatureAlgorithms.HS256, SignatureAlgorithms.HS384, SignatureAlgorithms.HS512]
for (def alg : algs) {
SecretKey key = alg.generateKey();
def jwk = Jwks.builder(key).build()
def result = Jwks.forValues(jwk)
def jwk = Jwks.builder().setKey(key).build()
def result = Jwks.builder().putAll(jwk).build()
assertArrayEquals key.encoded, result.toKey().encoded
}
}
@ -180,14 +172,13 @@ class JwksTest {
ECPublicKey pubKey = (ECPublicKey) pair.getPublic();
ECPrivateKey privKey = (ECPrivateKey) pair.getPrivate();
def jwk = Jwks.builder(pubKey).build()
def result = Jwks.forValues(jwk);
def jwk = Jwks.builder().setKey(pubKey).build()
def result = Jwks.builder().putAll(jwk).build()
assertEquals pubKey, result.toKey()
jwk = Jwks.builder(privKey).build()
result = Jwks.forValues(jwk)
jwk = Jwks.builder().setKey(privKey).build()
result = Jwks.builder().putAll(jwk).build()
assertEquals privKey, result.toKey()
}
}
}

View File

@ -8,6 +8,7 @@ import io.jsonwebtoken.security.SignatureAlgorithms
import org.junit.Test
import java.nio.charset.StandardCharsets
import java.security.Key
import java.security.interfaces.RSAPrivateKey
/**
@ -48,8 +49,8 @@ class RsaesPkcsv15Aes128CbcHmacSha256Test {
"ngs4iLEFk6eALoUS4vIWEwcL4txw9LsWH_zKI-hwoReoP77cOdSL4AVcra" +
"Hawlkpyd2TWjE5evgbhWtOxnZee3cXJBkAi64Ik6jZxbvk-RR3pEhnCs",
"dq" : "o_8V14SezckO6CNLKs_btPdFiO9_kC1DsuUTd2LAfIIVeMZ7jn1Gus_Ff" +
"7 B7IVx3p5KuBGOVF8L-qifLb6nQnLysgHDh132NDioZkhH7mI7hPG-PYE_" +
"odApKdnqECHWw0J-F0JWnUd6D2B_1TvF9mXA2Qx-iGYn8OVV1Bsmp6qU ",
"7B7IVx3p5KuBGOVF8L-qifLb6nQnLysgHDh132NDioZkhH7mI7hPG-PYE_" +
"odApKdnqECHWw0J-F0JWnUd6D2B_1TvF9mXA2Qx-iGYn8OVV1Bsmp6qU",
"qi" : "eNho5yRBEBxhGBtQRww9QirZsB66TrfFReG_CcteI1aCneT0ELGhYlRlC" +
"tUkTRclIfuEPmNsNDPbLoLqqCVznFbvdB7x-Tl-m0l_eFTj2KiqwGqE9PZ" +
"B9nNTwMVvH3VRRSLWACvPnSiwP8N5Usy-WRXS-V7TbpxIhvepTfE0NNo"
@ -58,7 +59,9 @@ class RsaesPkcsv15Aes128CbcHmacSha256Test {
@Test
void test() {
Jwk<RSAPrivateKey,?> jwk = Jwks.forValues(kek);
def jwk = Jwks.builder().putAll(kek).build();
println "JWK: " + jwk.toString()
final Map<String, Object> claims = Collections.unmodifiableMap(Maps.of('my-claim', 'my-map').build());
final String subject = "admin";