mirror of https://github.com/jwtk/jjwt.git
Sanity checkpoint so I don't lose work.
This commit is contained in:
parent
3f4e40ad27
commit
5819aa2f4b
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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>.
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
@ -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> {
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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> {
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package io.jsonwebtoken.impl.lang;
|
||||
|
||||
public interface CheckedFunction<T, R> {
|
||||
R apply(T t) throws Exception;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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<>();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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?
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Reference in New Issue