implementation checkpoint so I don't lose a ton of work. Won't build, but need to backup.

This commit is contained in:
Les Hazlewood 2021-07-28 01:46:32 -07:00
parent bd01e84406
commit 3f4e40ad27
152 changed files with 2496 additions and 2705 deletions

View File

@ -241,10 +241,11 @@ public final class Assert {
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the collection is <code>null</code> or has no elements
*/
public static void notEmpty(Collection collection, String message) {
public static <T extends Collection<?>> T notEmpty(T collection, String message) {
if (Collections.isEmpty(collection)) {
throw new IllegalArgumentException(message);
}
return collection;
}
/**

View File

@ -0,0 +1,7 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface AeadResult extends PayloadSupplier<byte[]>, AuthenticationTagSource, InitializationVectorSource {
}

View File

@ -0,0 +1,19 @@
package io.jsonwebtoken.security;
import java.net.URI;
import java.security.Key;
import java.security.cert.X509Certificate;
import java.util.List;
public interface AsymmetricJwk<V, K extends Key> extends Jwk<V, K> {
String getUse();
URI getX509Url();
List<X509Certificate> getX509CertificateChain();
byte[] getX509CertificateSha1Thumbprint();
byte[] getX509CertificateSha256Thumbprint();
}

View File

@ -0,0 +1,20 @@
package io.jsonwebtoken.security;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.List;
public interface AsymmetricJwkMutator<T extends AsymmetricJwkMutator<T>> {
T setPublicKeyUse(String use);
T setX509CertificateChain(List<X509Certificate> chain);
T setX509Url(URI uri);
T withX509KeyUse(boolean enable);
T withX509Sha1Thumbprint(boolean enable);
T withX509Sha256Thumbprint(boolean enable);
}

View File

@ -5,12 +5,12 @@ import java.security.KeyPair;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface AsymmetricKeyAlgorithm {
public interface AsymmetricKeyGenerator {
/**
* Generates a new secure-random key pair with a key length suitable for this Algorithm.
* Generates a new secure-random key pair with a key length suitable for the associated Algorithm.
*
* @return a new secure-random key pair with a key length suitable for this Algorithm.
* @return a new secure-random key pair with a key length suitable for the associated Algorithm.
*/
KeyPair generateKeyPair();
}

View File

@ -6,5 +6,5 @@ import java.security.PublicKey;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface AsymmetricKeySignatureAlgorithm<SK extends PrivateKey, VK extends PublicKey> extends SignatureAlgorithm<SK, VK>, AsymmetricKeyAlgorithm {
public interface AsymmetricKeySignatureAlgorithm<SK extends PrivateKey, VK extends PublicKey> extends SignatureAlgorithm<SK, VK>, AsymmetricKeyGenerator {
}

View File

@ -1,9 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface CurveId {
String toString();
}

View File

@ -1,44 +0,0 @@
package io.jsonwebtoken.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Maps;
import io.jsonwebtoken.lang.Strings;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* @since JJWT_RELEASE_VERSION
*/
public class CurveIds {
public static final CurveId P256 = new DefaultCurveId("P-256");
public static final CurveId P384 = new DefaultCurveId("P-384");
public static final CurveId P521 = new DefaultCurveId("P-521"); // yes, this is supposed to be 521 and not 512
private static final Map<String, CurveId> STANDARD_IDS = Collections.unmodifiableMap(Maps
.of(P256.toString(), P256)
.and(P384.toString(), P384)
.and(P521.toString(), P521)
.build());
private static final Set<CurveId> STANDARD_IDS_SET =
Collections.unmodifiableSet(new LinkedHashSet<>(STANDARD_IDS.values()));
public static Set<CurveId> values() {
return STANDARD_IDS_SET;
}
public static boolean isStandard(CurveId curveId) {
return curveId != null && STANDARD_IDS.containsKey(curveId.toString());
}
public static CurveId forValue(String value) {
value = Strings.clean(value);
Assert.hasText(value, "value argument cannot be null or empty.");
CurveId std = STANDARD_IDS.get(value);
return std != null ? std : new DefaultCurveId(value);
}
}

View File

@ -1,19 +0,0 @@
package io.jsonwebtoken.security;
import io.jsonwebtoken.JweHeader;
import java.security.Key;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface DecryptionKeyResolver {
/**
* Returns the decryption key that should be used to decrypt a corresponding JWE's Ciphertext (payload).
*
* @param header the JWE header to inspect to determine which decryption key should be used
* @return the decryption key that should be used to decrypt a corresponding JWE's Ciphertext (payload).
*/
Key resolveDecryptionKey(JweHeader header);
}

View File

@ -1,33 +0,0 @@
package io.jsonwebtoken.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
/**
* @since JJWT_RELEASE_VERSION
*/
final class DefaultCurveId implements CurveId {
private final String id;
DefaultCurveId(String id) {
id = Strings.clean(id);
Assert.hasText(id, "id argument cannot be null or empty.");
this.id = id;
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj == this || (obj instanceof CurveId && obj.toString().equals(this.id));
}
@Override
public String toString() {
return id;
}
}

View File

@ -1,13 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface EcJwk<T extends EcJwk<T>> extends Jwk<T>, EcJwkMutator<T> {
CurveId getCurveId();
String getX();
String getY();
}

View File

@ -1,7 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface EcJwkBuilder<K extends EcJwk<K>, T extends EcJwkBuilder<K,T>> extends JwkBuilder<K, T>, EcJwkMutator<T> {
}

View File

@ -1,11 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface EcJwkBuilderFactory {
PublicEcJwkBuilder publicKey();
PrivateEcJwkBuilder privateKey();
}

View File

@ -1,13 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface EcJwkMutator<T extends EcJwkMutator<T>> extends JwkMutator<T> {
T setCurveId(CurveId curveId);
T setX(String x);
T setY(String y);
}

View File

@ -0,0 +1,7 @@
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>> {
}

View File

@ -0,0 +1,7 @@
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>> {
}

View File

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

View File

@ -0,0 +1,7 @@
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>> {
}

View File

@ -0,0 +1,4 @@
package io.jsonwebtoken.security;
public interface HashAlgorithm extends Identifiable {
}

View File

@ -0,0 +1,20 @@
package io.jsonwebtoken.security;
public final class HashAlgorithms {
//prevent instantiation
private HashAlgorithms() {}
private static HashAlgorithm forJcaName(final String jcaName) {
//TODO: IMPLEMENT ME
return new HashAlgorithm() {
@Override
public String getId() {
return jcaName;
}
};
}
public static final HashAlgorithm MD5 = forJcaName("MD5");
public static final HashAlgorithm SHA_1 = forJcaName("SHA-1");
public static final HashAlgorithm SHA_256 = forJcaName("SHA-256");
}

View File

@ -1,30 +1,21 @@
package io.jsonwebtoken.security;
import java.net.URI;
import java.util.List;
import java.security.Key;
import java.util.Map;
import java.util.Set;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface Jwk<T extends Jwk<T>> extends Map<String, Object>, JwkMutator<T>, Identifiable {
public interface Jwk<V, K extends Key> extends Identifiable, Map<String,V> {
String getType();
String getUse();
Set<String> getOperations();
String getAlgorithm();
String getId();
URI getX509Url();
List<String> getX509CertficateChain();
String getX509CertificateSha1Thumbprint();
String getX509CertificateSha256Thumbprint();
K toKey();
}

View File

@ -1,10 +1,34 @@
package io.jsonwebtoken.security;
import java.security.Key;
import java.security.Provider;
import java.util.Map;
import java.util.Set;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface JwkBuilder<K extends Jwk<K>, T extends JwkBuilder<K,T>> extends JwkMutator<T> {
public interface JwkBuilder<K extends Key, J extends Jwk<?, K>, T extends JwkBuilder<K, J, T>> {
K build();
T put(String name, Object value);
T putAll(Map<String,?> values);
T setAlgorithm(String alg);
T setId(String id);
T setOperations(Set<String> ops);
/**
* Sets the JCA Provider to use during key operations, or {@code null} if the
* JCA subsystem preferred provider should be used.
*
* @param provider the JCA Provider to use during key operations, or {@code null} if the
* JCA subsystem preferred provider should be used.
* @return the builder for method chaining.
*/
T setProvider(Provider provider);
J build();
}

View File

@ -1,12 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface JwkBuilderFactory {
EcJwkBuilderFactory ellipticCurve();
SymmetricJwkBuilder symmetric();
}

View File

@ -1,27 +0,0 @@
package io.jsonwebtoken.security;
import java.net.URI;
import java.util.List;
import java.util.Set;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface JwkMutator<T extends JwkMutator<T>> {
T setUse(String use);
T setOperations(Set<String> ops);
T setAlgorithm(String alg);
T setId(String id);
T setX509Url(URI uri);
T setX509CertificateChain(List<String> chain);
T setX509CertificateSha1Thumbprint(String thumbprint);
T setX509CertificateSha256Thumbprint(String thumbprint);
}

View File

@ -1,16 +0,0 @@
package io.jsonwebtoken.security;
import java.util.Map;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface JwkRsaPrimeInfo extends Map<String,Object>, JwkRsaPrimeInfoMutator<JwkRsaPrimeInfo> {
String getPrime();
String getCrtExponent();
String getCrtCoefficient();
}

View File

@ -1,9 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface JwkRsaPrimeInfoBuilder extends JwkRsaPrimeInfoMutator<JwkRsaPrimeInfoBuilder> {
JwkRsaPrimeInfo build();
}

View File

@ -1,13 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface JwkRsaPrimeInfoMutator<T extends JwkRsaPrimeInfoMutator> {
T setPrime(String r);
T setCrtExponent(String d);
T setCrtCoefficient(String t);
}

View File

@ -2,13 +2,44 @@ 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 {
public static <T extends JwkBuilderFactory> T builder() {
return Classes.newInstance("io.jsonwebtoken.impl.security.DefaultJwkBuilderFactory");
private static final String BUILDER_CLASS_NAME = "io.jsonwebtoken.impl.security.DefaultJwkBuilder";
private static final Class<?>[] KEY_ARGS = new Class[]{Key.class};
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();
}
}

View File

@ -26,7 +26,7 @@ public final class KeyAlgorithms {
return Classes.newInstance(fqcn, AESWRAP_ARGS, keyLength);
}
public static final KeyAlgorithm<SecretKey, SecretKey> DIRECT = Classes.newInstance("io.jsonwebtoken.impl.security.DefaultDirectKeyAlgorithm");
public static final KeyAlgorithm<SecretKey, SecretKey> DIRECT = Classes.newInstance("io.jsonwebtoken.impl.security.DirectKeyAlgorithm");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128KW = aeswrap(AESWRAP, 128);
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192KW = aeswrap(AESWRAP, 192);
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256KW = aeswrap(AESWRAP, 256);

View File

@ -109,18 +109,18 @@ public final class Keys {
* @return a new {@link SecretKey} instance suitable for use with the specified {@link SignatureAlgorithm}.
* @throws IllegalArgumentException for any input value other than {@link io.jsonwebtoken.SignatureAlgorithm#HS256},
* {@link io.jsonwebtoken.SignatureAlgorithm#HS384}, or {@link io.jsonwebtoken.SignatureAlgorithm#HS512}
* @deprecated since JJWT_RELEASE_VERSION. Use your preferred {@link SymmetricKeySignatureAlgorithm} instance's
* {@link SymmetricKeySignatureAlgorithm#generateKey() generateKey()} method directly.
* @deprecated since JJWT_RELEASE_VERSION. Use your preferred {@link SecretKeySignatureAlgorithm} instance's
* {@link SecretKeySignatureAlgorithm#generateKey() generateKey()} method directly.
*/
@Deprecated
public static SecretKey secretKeyFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
SignatureAlgorithm<?,?> salg = SignatureAlgorithms.forName(alg.name());
if (!(salg instanceof SymmetricKeySignatureAlgorithm)) {
if (!(salg instanceof SecretKeySignatureAlgorithm)) {
String msg = "The " + alg.name() + " algorithm does not support shared secret keys.";
throw new IllegalArgumentException(msg);
}
return ((SymmetricKeySignatureAlgorithm) salg).generateKey();
return ((SecretKeySignatureAlgorithm) salg).generateKey();
}
/**

View File

@ -1,9 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PrivateEcJwk extends EcJwk<PrivateEcJwk>, PrivateEcJwkMutator<PrivateEcJwk> {
String getD();
}

View File

@ -1,9 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PrivateEcJwkBuilder extends EcJwkBuilder<PrivateEcJwk, PrivateEcJwkBuilder> {
PrivateEcJwkBuilder setD(String d);
}

View File

@ -1,9 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PrivateEcJwkMutator<T extends PrivateEcJwkMutator<T>> extends EcJwkMutator<T> {
T setD(String d);
}

View File

@ -0,0 +1,13 @@
package io.jsonwebtoken.security;
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> {
M toPublicJwk();
KeyPair toKeyPair();
}

View File

@ -0,0 +1,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> {
T setPublicKey(L publicKey);
}

View File

@ -1,23 +0,0 @@
package io.jsonwebtoken.security;
import java.util.List;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PrivateRsaJwk extends RsaJwk<PrivateRsaJwk>, PrivateRsaJwkMutator<PrivateRsaJwk> {
String getD();
String getP();
String getQ();
String getDP();
String getDQ();
String getQI();
List<JwkRsaPrimeInfo> getOtherPrimesInfo();
}

View File

@ -1,23 +0,0 @@
package io.jsonwebtoken.security;
import java.util.List;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PrivateRsaJwkMutator<T extends PrivateRsaJwkMutator<T>> extends RsaJwkMutator<T> {
T setD(String d);
T setP(String p);
T setQ(String q);
T setDP(String dp);
T setDQ(String dq);
T setQI(String qi);
T setOtherPrimesInfo(List<JwkRsaPrimeInfo> infos);
}

View File

@ -1,7 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PublicEcJwk extends EcJwk<PublicEcJwk> {
}

View File

@ -1,7 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PublicEcJwkBuilder extends EcJwkBuilder<PublicEcJwk, PublicEcJwkBuilder> {
}

View File

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

View File

@ -0,0 +1,9 @@
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> {
P setPrivateKey(L privateKey);
}

View File

@ -1,7 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface PublicRsaJwk extends RsaJwk {
}

View File

@ -1,11 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface RsaJwk<T extends RsaJwk<T>> extends Jwk<T>, RsaJwkMutator<T> {
String getModulus();
String getExponent();
}

View File

@ -1,11 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface RsaJwkMutator<T extends RsaJwkMutator<T>> extends JwkMutator<T> {
T setModulus(String n);
T setExponent(String e);
}

View File

@ -0,0 +1,7 @@
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>> {
}

View File

@ -0,0 +1,7 @@
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>> {
}

View File

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

View File

@ -0,0 +1,8 @@
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>> {
}

View File

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

View File

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

View File

@ -5,12 +5,12 @@ import javax.crypto.SecretKey;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface SymmetricKeyAlgorithm {
public interface SecretKeyGenerator {
/**
* Creates and returns a new secure-random key with a length sufficient to be used by this Algorithm.
* Creates and returns a new secure-random key with a length sufficient to be used by the associated Algorithm.
*
* @return a new secure-random key with a length sufficient to be used by this Algorithm.
* @return a new secure-random key with a length sufficient to be used by the associated Algorithm.
*/
SecretKey generateKey();
}

View File

@ -0,0 +1,9 @@
package io.jsonwebtoken.security;
import javax.crypto.SecretKey;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface SecretKeySignatureAlgorithm extends SignatureAlgorithm<SecretKey, SecretKey>, SecretKeyGenerator {
}

View File

@ -23,8 +23,8 @@ public final class SignatureAlgorithms {
private SignatureAlgorithms() {
}
static final String HMAC = "io.jsonwebtoken.impl.security.MacSignatureAlgorithm";
static final Class<?>[] HMAC_ARGS = new Class[]{String.class, String.class, int.class};
private static final String HMAC = "io.jsonwebtoken.impl.security.MacSignatureAlgorithm";
private static final Class<?>[] HMAC_ARGS = new Class[]{String.class, String.class, int.class};
private static final String RSA = "io.jsonwebtoken.impl.security.DefaultRsaSignatureAlgorithm";
private static final Class<?>[] RSA_ARGS = new Class[]{String.class, String.class, int.class};
@ -33,7 +33,7 @@ public final class SignatureAlgorithms {
private static final String EC = "io.jsonwebtoken.impl.security.DefaultEllipticCurveSignatureAlgorithm";
private static final Class<?>[] EC_ARGS = new Class[]{String.class, String.class, String.class, int.class, int.class};
private static SymmetricKeySignatureAlgorithm hmacSha(int minKeyLength) {
private static SecretKeySignatureAlgorithm hmacSha(int minKeyLength) {
return Classes.newInstance(HMAC, HMAC_ARGS, "HS" + minKeyLength, "HmacSHA" + minKeyLength, minKeyLength);
}
@ -51,9 +51,9 @@ public final class SignatureAlgorithms {
}
public static final SignatureAlgorithm<Key, Key> NONE = Classes.newInstance("io.jsonwebtoken.impl.security.NoneSignatureAlgorithm");
public static final SymmetricKeySignatureAlgorithm HS256 = hmacSha(256);
public static final SymmetricKeySignatureAlgorithm HS384 = hmacSha(384);
public static final SymmetricKeySignatureAlgorithm HS512 = hmacSha(512);
public static final SecretKeySignatureAlgorithm HS256 = hmacSha(256);
public static final SecretKeySignatureAlgorithm HS384 = hmacSha(384);
public static final SecretKeySignatureAlgorithm HS512 = hmacSha(512);
public static final RsaSignatureAlgorithm RS256 = rsa(256, 2048);
public static final RsaSignatureAlgorithm RS384 = rsa(384, 3072);
public static final RsaSignatureAlgorithm RS512 = rsa(512, 4096);

View File

@ -3,9 +3,9 @@ package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface SymmetricAeadAlgorithm extends Identifiable, SymmetricKeyAlgorithm {
public interface SymmetricAeadAlgorithm extends Identifiable, SecretKeyGenerator {
SymmetricAeadEncryptionResult encrypt(SymmetricAeadRequest request) throws CryptoException, KeyException;
AeadResult encrypt(SymmetricAeadRequest request) throws CryptoException, KeyException;
PayloadSupplier<byte[]> decrypt(SymmetricAeadDecryptionRequest request) throws CryptoException, KeyException;
}

View File

@ -1,7 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface SymmetricAeadEncryptionResult extends PayloadSupplier<byte[]>, AuthenticationTagSource, InitializationVectorSource {
}

View File

@ -1,9 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface SymmetricJwk extends Jwk<SymmetricJwk>, SymmetricJwkMutator<SymmetricJwk> {
String getK();
}

View File

@ -1,7 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface SymmetricJwkBuilder extends JwkBuilder<SymmetricJwk, SymmetricJwkBuilder>, SymmetricJwkMutator<SymmetricJwkBuilder> {
}

View File

@ -1,9 +0,0 @@
package io.jsonwebtoken.security;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface SymmetricJwkMutator<T extends SymmetricJwkMutator<T>> extends JwkMutator<T> {
T setK(String k);
}

View File

@ -1,9 +0,0 @@
package io.jsonwebtoken.security;
import javax.crypto.SecretKey;
/**
* @since JJWT_RELEASE_VERSION
*/
public interface SymmetricKeySignatureAlgorithm extends SignatureAlgorithm<SecretKey, SecretKey>, SymmetricKeyAlgorithm {
}

View File

@ -1,24 +0,0 @@
package io.jsonwebtoken.security
import org.junit.Test
import static org.junit.Assert.*
class CurveIdsTest {
@Test(expected=IllegalArgumentException)
void testNullId() {
CurveIds.forValue(null)
}
@Test(expected=IllegalArgumentException)
void testEmptyId() {
CurveIds.forValue(' ')
}
@Test
void testNonStandardId() {
CurveId id = CurveIds.forValue("NonStandard")
assertNotNull id
assertEquals 'NonStandard', id.toString()
}
}

View File

@ -112,7 +112,7 @@ class KeysTest {
if (name.startsWith('H')) {
def key = createMock(SecretKey)
def salg = createMock(SymmetricKeySignatureAlgorithm)
def salg = createMock(SecretKeySignatureAlgorithm)
expect(SignatureAlgorithms.forName(eq(name))).andReturn(salg)
expect(salg.generateKey()).andReturn(key)
@ -148,7 +148,7 @@ class KeysTest {
String name = alg.name()
if (name.equals('NONE') || name.startsWith('H')) {
def salg = name == 'NONE' ? createMock(io.jsonwebtoken.security.SignatureAlgorithm) : createMock(SymmetricKeySignatureAlgorithm)
def salg = name == 'NONE' ? createMock(io.jsonwebtoken.security.SignatureAlgorithm) : createMock(SecretKeySignatureAlgorithm)
expect(SignatureAlgorithms.forName(eq(name))).andReturn(salg)
replay SignatureAlgorithms, salg
try {

View File

@ -21,7 +21,7 @@ import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithms;
import io.jsonwebtoken.security.KeyResult;
import io.jsonwebtoken.security.SymmetricAeadEncryptionResult;
import io.jsonwebtoken.security.AeadResult;
import io.jsonwebtoken.security.SymmetricAeadRequest;
import javax.crypto.SecretKey;
@ -35,7 +35,7 @@ public class DefaultJweBuilder extends DefaultJwtBuilder<JweBuilder> implements
private static final SecretKey EMPTY_SECRET_KEY = new SecretKeySpec("NONE".getBytes(StandardCharsets.UTF_8), "NONE");
private SymmetricAeadAlgorithm enc; // MUST be Symmetric AEAD per https://tools.ietf.org/html/rfc7516#section-4.1.2
private Function<SymmetricAeadRequest,SymmetricAeadEncryptionResult> encFunction;
private Function<SymmetricAeadRequest, AeadResult> encFunction;
private KeyAlgorithm<Key,?> alg;
private Function<KeyRequest<SecretKey,Key>,KeyResult> algFunction;
@ -70,9 +70,9 @@ public class DefaultJweBuilder extends DefaultJwtBuilder<JweBuilder> implements
this.enc = Assert.notNull(enc, "EncryptionAlgorithm cannot be null.");
Assert.hasText(enc.getId(), "EncryptionAlgorithm id cannot be null or empty.");
String encMsg = enc.getId() + " encryption failed.";
this.encFunction = wrap(encMsg, new Function<SymmetricAeadRequest, SymmetricAeadEncryptionResult>() {
this.encFunction = wrap(encMsg, new Function<SymmetricAeadRequest, AeadResult>() {
@Override
public SymmetricAeadEncryptionResult apply(SymmetricAeadRequest request) {
public AeadResult apply(SymmetricAeadRequest request) {
return enc.encrypt(request);
}
});
@ -155,7 +155,7 @@ public class DefaultJweBuilder extends DefaultJwtBuilder<JweBuilder> implements
byte[] headerBytes = this.headerSerializer.apply(jweHeader);
SymmetricAeadRequest encRequest = new DefaultSymmetricAeadRequest(provider, secureRandom, plaintext, cek, headerBytes);
SymmetricAeadEncryptionResult encResult = encFunction.apply(encRequest);
AeadResult encResult = encFunction.apply(encRequest);
byte[] iv = Assert.notEmpty(encResult.getInitializationVector(), "Encryption result must have a non-empty initialization vector.");
byte[] ciphertext = Assert.notEmpty(encResult.getPayload(), "Encryption result must have non-empty ciphertext (result.getData()).");

View File

@ -41,7 +41,7 @@ import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
import io.jsonwebtoken.impl.lang.LegacyServices;
import io.jsonwebtoken.impl.security.DefaultKeyRequest;
import io.jsonwebtoken.impl.security.DefaultSymmetricAeadResult;
import io.jsonwebtoken.impl.security.DefaultAeadResult;
import io.jsonwebtoken.impl.security.DefaultVerifySignatureRequest;
import io.jsonwebtoken.impl.security.DelegatingSigningKeyResolver;
import io.jsonwebtoken.impl.security.StaticKeyResolver;
@ -327,7 +327,7 @@ public class DefaultJwtParser implements JwtParser {
TokenizedJwe tokenizedJwe = (TokenizedJwe)tokenized;
JweHeader jweHeader = (JweHeader)header;
byte[] cekBytes = new byte[1]; //ignored unless using an encrypted key algorithm
byte[] cekBytes = new byte[0]; //ignored unless using an encrypted key algorithm
String base64Url = tokenizedJwe.getEncryptedKey();
if (Strings.hasText(base64Url)) {
cekBytes = base64UrlDecode(base64Url, "JWE encrypted key");
@ -373,7 +373,7 @@ public class DefaultJwtParser implements JwtParser {
final SecretKey cek = keyAlg.getDecryptionKey(request);
SymmetricAeadDecryptionRequest decryptRequest =
new DefaultSymmetricAeadResult(this.provider, null, bytes, cek, headerBytes, tag, iv);
new DefaultAeadResult(this.provider, null, bytes, cek, headerBytes, tag, iv);
PayloadSupplier<byte[]> result = encAlg.decrypt(decryptRequest);
bytes = result.getPayload();
}

View File

@ -104,11 +104,11 @@ public class JwtMap implements Map<String, Object> {
return toDate(v, name);
}
protected static boolean isReduceableToNull(Object v) {
public static boolean isReduceableToNull(Object v) {
return v == null ||
(v instanceof String && !Strings.hasText((String)v)) ||
(v instanceof Collection && Collections.isEmpty((Collection) v)) ||
(v instanceof Map && Collections.isEmpty((Map)v)) ||
(v instanceof Collection && Collections.isEmpty((Collection<?>) v)) ||
(v instanceof Map && Collections.isEmpty((Map<?,?>)v)) ||
(v.getClass().isArray() && Array.getLength(v) == 0);
}

View File

@ -0,0 +1,32 @@
package io.jsonwebtoken.impl.io;
import io.jsonwebtoken.impl.lang.Converter;
import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.lang.Assert;
public class CodecConverter<A, B> implements Converter<A, B> {
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);
private final Encoder<A, B> encoder;
private final Decoder<B, A> decoder;
public CodecConverter(Encoder<A, B> encoder, Decoder<B, A> decoder) {
this.encoder = Assert.notNull(encoder, "Encoder cannot be null.");
this.decoder = Assert.notNull(decoder, "Decoder cannot be null.");
}
@Override
public B applyTo(A a) {
return this.encoder.encode(a);
}
@Override
public A applyFrom(B b) {
return this.decoder.decode(b);
}
}

View File

@ -0,0 +1,10 @@
package io.jsonwebtoken.impl.io;
import io.jsonwebtoken.io.Decoders;
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);
}

View File

@ -0,0 +1,6 @@
package io.jsonwebtoken.impl.lang;
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}

View File

@ -0,0 +1,89 @@
package io.jsonwebtoken.impl.lang;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
class CollectionConverter<T, C extends Collection<T>> implements Converter<C, Object> {
private final Converter<T, Object> elementConverter;
private final Function<Integer, C> fn;
public static <T> CollectionConverter<T, List<T>> forList(Converter<T,Object> elementConverter) {
return new CollectionConverter<>(elementConverter, new CreateListFunction<T>());
}
public static <T> CollectionConverter<T, Set<T>> forSet(Converter<T, Object> elementConverter) {
return new CollectionConverter<>(elementConverter, new CreateSetFunction<T>());
}
public CollectionConverter(Converter<T, Object> elementConverter, Function<Integer, C> fn) {
this.elementConverter = Assert.notNull(elementConverter, "Element converter cannot be null.");
this.fn = Assert.notNull(fn, "Collection function cannot be null.");
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public Object applyTo(C ts) {
if (Collections.isEmpty(ts)) {
return ts;
}
Collection c = fn.apply(ts.size());
for (T element : ts) {
Object encoded = elementConverter.applyTo(element);
c.add(encoded);
}
return c;
}
private C toElementList(Collection<?> c) {
Assert.notEmpty(c, "Collection cannot be null or empty.");
C result = fn.apply(c.size());
for (Object o : c) {
T element = elementConverter.applyFrom(o);
result.add(element);
}
return result;
}
@Override
public C applyFrom(Object value) {
if (value == null) {
return null;
}
Collection<?> c;
if (value.getClass().isArray() && !value.getClass().getComponentType().isArray()) {
c = Collections.arrayToList(value);
} else if (value instanceof Collection) {
c = (Collection<?>) value;
} else {
c = java.util.Collections.singletonList(value);
}
C result;
if (Collections.isEmpty(c)) {
result = fn.apply(0);
} else {
result = toElementList(c);
}
return result;
}
private static class CreateListFunction<A> implements Function<Integer, List<A>> {
@Override
public List<A> apply(Integer size) {
return size > 0 ? new ArrayList<A>(size) : new ArrayList<A>();
}
}
private static class CreateSetFunction<T> implements Function<Integer, Set<T>> {
@Override
public Set<T> apply(Integer size) {
return size > 0 ? new LinkedHashSet<T>(size) : new LinkedHashSet<T>();
}
}
}

View File

@ -0,0 +1,8 @@
package io.jsonwebtoken.impl.lang;
public interface Converter<A,B> {
B applyTo(A a);
A applyFrom(B b);
}

View File

@ -0,0 +1,35 @@
package io.jsonwebtoken.impl.lang;
import java.util.List;
import java.util.Set;
public final class Converters {
//prevent instantiation
private Converters() {
}
public static <T> Converter<T, Object> none(Class<T> clazz) {
return new NoConverter<>(clazz);
}
public static <T> Converter<Set<T>, Object> forSet(Converter<T, Object> elementConverter) {
return CollectionConverter.forSet(elementConverter);
}
public static <T> Converter<Set<T>, Object> forSetOf(Class<T> clazz) {
return forSet(none(clazz));
}
public static <T> Converter<List<T>, Object> forList(Converter<T, Object> elementConverter) {
return CollectionConverter.forList(elementConverter);
}
public static <T> Converter<T, Object> forEncoded(Class<T> elementType, Converter<T, String> elementConverter) {
return new EncodedObjectConverter<T>(elementType, elementConverter);
}
public static <T> Converter<List<T>, Object> forEncodedList(Class<T> elementType, Converter<T, String> elementConverter) {
return forList(forEncoded(elementType, elementConverter));
}
}

View File

@ -0,0 +1,33 @@
package io.jsonwebtoken.impl.lang;
import io.jsonwebtoken.lang.Assert;
public class EncodedObjectConverter<T> implements Converter<T, Object> {
private final Class<T> type;
private final Converter<T, String> converter;
public EncodedObjectConverter(Class<T> type, Converter<T, String> converter) {
this.type = Assert.notNull(type, "Value type cannot be null.");
this.converter = Assert.notNull(converter, "Value converter cannot be null.");
}
@Override
public Object applyTo(T t) {
return converter.applyTo(t);
}
@Override
public T applyFrom(Object value) {
Assert.notNull(value, "Value argument cannot be null.");
if (type.isAssignableFrom(value.getClass())) {
return type.cast(value);
} else if (value instanceof String) {
return converter.applyFrom((String) value);
} else {
String msg = "Values must be either String or " + type.getName() +
" instances. Value type found: " + value.getClass().getName() + ". Value: " + value;
throw new IllegalArgumentException(msg);
}
}
}

View File

@ -0,0 +1,28 @@
package io.jsonwebtoken.impl.lang;
class NoConverter<T> implements Converter<T, Object> {
private final Class<T> type;
public NoConverter(Class<T> type) {
this.type = type;
}
@Override
public Object applyTo(T t) {
return t;
}
@Override
public T applyFrom(Object o) {
if (o == null) {
return null;
}
Class<?> clazz = o.getClass();
if (!type.isAssignableFrom(clazz)) {
String msg = "Unsupported value type: " + clazz.getName();
throw new IllegalArgumentException(msg);
}
return type.cast(o);
}
}

View File

@ -0,0 +1,22 @@
package io.jsonwebtoken.impl.lang;
import io.jsonwebtoken.lang.Assert;
public class NullSafeConverter<A, B> implements Converter<A, B> {
private final Converter<A, B> converter;
public NullSafeConverter(Converter<A, B> converter) {
this.converter = Assert.notNull(converter, "Delegate converter cannot be null.");
}
@Override
public B applyTo(A a) {
return a == null ? null : converter.applyTo(a);
}
@Override
public A applyFrom(B b) {
return b == null ? null : converter.applyFrom(b);
}
}

View File

@ -0,0 +1,6 @@
package io.jsonwebtoken.impl.lang;
public interface Supplier<T> {
T get();
}

View File

@ -0,0 +1,21 @@
package io.jsonwebtoken.impl.lang;
import java.net.URI;
public class UriStringConverter implements Converter<URI, String> {
@Override
public String applyTo(URI uri) {
return uri.toString();
}
@Override
public URI applyFrom(String s) {
try {
return URI.create(s);
} catch (Exception e) {
String msg = "Unable to convert String value '" + s + "' to URI instance: " + e.getMessage();
throw new IllegalArgumentException(msg, e);
}
}
}

View File

@ -0,0 +1,54 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.AsymmetricJwk;
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> {
private final String use;
private final URI x509Url;
private final List<X509Certificate> certChain;
private final byte[] x509Sha1Thumbprint;
private final byte[] x509Sha256Thumbprint;
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;
}
@Override
public String getUse() {
return this.use;
}
@Override
public URI getX509Url() {
return this.x509Url;
}
@Override
public List<X509Certificate> getX509CertificateChain() {
return this.certChain;
}
@Override
public byte[] getX509CertificateSha1Thumbprint() {
return this.x509Sha1Thumbprint;
}
@Override
public byte[] getX509CertificateSha256Thumbprint() {
return this.x509Sha256Thumbprint;
}
}

View File

@ -1,68 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.CurveId;
import io.jsonwebtoken.security.CurveIds;
import io.jsonwebtoken.security.EcJwk;
import io.jsonwebtoken.security.MalformedKeyException;
/**
* @since JJWT_RELEASE_VERSION
*/
@SuppressWarnings("unchecked")
class AbstractEcJwk<T extends EcJwk<T>> extends AbstractJwk<T> implements EcJwk<T> {
static final String TYPE_VALUE = "EC";
static final String CURVE_ID = "crv";
static final String X = "x";
static final String Y = "y";
AbstractEcJwk() {
super(TYPE_VALUE);
}
@Override
public CurveId getCurveId() {
Object val = get(CURVE_ID);
if (val == null) {
return null;
}
if (val instanceof CurveId) {
return (CurveId) val;
}
if (val instanceof String) {
CurveId id = CurveIds.forValue((String) val);
setCurveId(id); //replace string with type safe value
return id;
}
throw new MalformedKeyException("EC JWK 'crv' value must be an CurveId or a String. Value has type: " +
val.getClass().getName());
}
@Override
public T setCurveId(CurveId curveId) {
return setRequiredValue(CURVE_ID, curveId, "curve id");
}
@Override
public String getX() {
return getString(X);
}
@Override
public T setX(String x) {
return setRequiredValue(X, x, "x coordinate");
}
@Override
public String getY() {
return getString(Y);
}
@Override
public T setY(String y) {
y = Strings.clean(y);
setValue(Y, y);
return (T) this;
}
}

View File

@ -1,31 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.CurveId;
import io.jsonwebtoken.security.EcJwk;
import io.jsonwebtoken.security.EcJwkBuilder;
@SuppressWarnings("unchecked")
abstract class AbstractEcJwkBuilder<K extends EcJwk<K>, T extends EcJwkBuilder<K, T>> extends AbstractJwkBuilder<K, T> implements EcJwkBuilder<K, T> {
AbstractEcJwkBuilder(JwkValidator<K> validator) {
super(validator);
}
@Override
public T setCurveId(CurveId curveId) {
this.jwk.setCurveId(curveId);
return (T) this;
}
@Override
public T setX(String x) {
this.jwk.setX(x);
return (T) this;
}
@Override
public T setY(String y) {
this.jwk.setY(y);
return (T) this;
}
}

View File

@ -1,39 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.CurveId;
import io.jsonwebtoken.security.CurveIds;
import io.jsonwebtoken.security.EcJwk;
import io.jsonwebtoken.security.KeyException;
abstract class AbstractEcJwkValidator<T extends EcJwk> extends AbstractJwkValidator<T> {
AbstractEcJwkValidator() {
super(AbstractEcJwk.TYPE_VALUE);
}
@Override
final void validateJwk(T jwk) throws KeyException {
CurveId curveId = jwk.getCurveId();
if (curveId == null) { // https://tools.ietf.org/html/rfc7518#section-6.2.1
malformed("EC JWK curve id ('crv' property) must be specified.");
}
String x = jwk.getX();
if (!Strings.hasText(x)) { // https://tools.ietf.org/html/rfc7518#section-6.2.1
malformed("EC JWK x coordinate ('x' property) must be specified.");
}
// https://tools.ietf.org/html/rfc7518#section-6.2.1 (last sentence):
if (CurveIds.isStandard(curveId) && !Strings.hasText(jwk.getY())) {
malformed(curveId + " EC JWK y coordinate ('y' property) must be specified.");
}
//TODO: RFC length validation for x and y values
validateEcJwk(jwk);
}
abstract void validateEcJwk(T jwk);
}

View File

@ -1,52 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.CryptoException;
import io.jsonwebtoken.security.PayloadSupplier;
import io.jsonwebtoken.security.CryptoRequest;
import io.jsonwebtoken.security.KeyException;
import io.jsonwebtoken.security.SecurityException;
import java.security.Key;
abstract class AbstractEncryptionAlgorithm<T, E extends Key, D extends Key,
EReq extends CryptoRequest<T, E>, ERes extends PayloadSupplier<byte[]>,
DReq extends CryptoRequest<byte[], D>, DRes extends PayloadSupplier<T>>
extends CryptoAlgorithm implements EncryptionAlgorithm<T, E, D, EReq, ERes, DReq, DRes> {
AbstractEncryptionAlgorithm(String id, String transformationString) {
super(id, transformationString);
}
@Override
public ERes encrypt(EReq req) throws CryptoException {
try {
Assert.notNull(req, "Encryption request cannot be null.");
return doEncrypt(req);
} catch (SecurityException se) {
throw se; //propagate
} catch (Exception e) {
String msg = "Unable to perform " + getId() + " encryption: " + e.getMessage();
throw new CryptoException(msg, e);
}
}
protected abstract ERes doEncrypt(EReq req) throws Exception;
@Override
public DRes decrypt(DReq req) throws CryptoException, KeyException {
try {
Assert.notNull(req, "Decryption request cannot be null.");
byte[] bytes = doDecrypt(req);
//noinspection unchecked
return (DRes) new DefaultPayloadSupplier<>(bytes);
} catch (SecurityException se) {
throw se; //propagate
} catch (Exception e) {
String msg = "Unable to perform " + getId() + " decryption: " + e.getMessage();
throw new CryptoException(msg, e);
}
}
protected abstract byte[] doDecrypt(DReq req) throws Exception;
}

View File

@ -1,191 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.JwtMap;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.MalformedKeyException;
import java.lang.reflect.Array;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@SuppressWarnings("unchecked")
abstract class AbstractJwk<T extends Jwk<T>> extends JwtMap implements Jwk<T> {
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";
AbstractJwk(String type) {
type = Strings.clean(type);
Assert.notNull(type, "JWK type cannot be null or empty.");
put(TYPE, type);
}
T setRequiredValue(String key, Object value, String name) {
boolean reduceable = value != null && isReduceableToNull(value);
if (reduceable) {
value = null;
}
if (value == null) {
String msg = getType() + " JWK " + name + " ('" + key + "' property) cannot be null";
if (reduceable) {
msg += " or empty";
}
msg += ".";
throw new IllegalArgumentException(msg);
}
setValue(key, value);
return (T) this;
}
protected List<String> getList(String name) {
Object value = get(name);
if (value == null) {
return null;
}
List<String> list = new ArrayList<>();
if (value instanceof Collection) {
Collection c = (Collection)value;
for (Object o : c) {
list.add(o == null ? null : String.valueOf(o));
}
} else if (value.getClass().isArray()) {
int length = Array.getLength(value);
for (int i = 0; i < length; i ++) {
Object o = Array.get(value, i);
list.add(o == null ? null : String.valueOf(o));
}
}
return list;
}
@Override
public String getType() {
return getString(TYPE);
}
@Override
public String getUse() {
return getString(USE);
}
@Override
public T setUse(String use) {
setValue(USE, Strings.clean(use));
return (T)this;
}
@Override
public Set<String> getOperations() {
Object val = get(OPERATIONS);
if (val instanceof Set) {
return (Set)val;
}
List<String> list = getList(OPERATIONS);
return val == null ? null : new LinkedHashSet<>(list);
}
@Override
public T setOperations(Set<String> ops) {
Set<String> operations = Collections.isEmpty(ops) ? null : new LinkedHashSet<>(ops);
setValue(OPERATIONS, operations);
return (T)this;
}
@Override
public String getAlgorithm() {
return getString(ALGORITHM);
}
@Override
public T setAlgorithm(String alg) {
setValue(ALGORITHM, Strings.clean(alg));
return (T)this;
}
@Override
public String getId() {
return getString(ID);
}
@Override
public T setId(String id) {
setValue(ID, Strings.clean(id));
return (T)this;
}
@Override
public URI getX509Url() {
Object val = get(X509_URL);
if (val == null) {
return null;
}
if (val instanceof URI) {
return (URI)val;
}
String sval = String.valueOf(val);
URI uri;
try {
uri = new URI(sval);
setValue(X509_URL, uri); //replace with constructed instance
} catch (URISyntaxException e) {
String msg = getType() + " JWK x5u value cannot be converted to a URI instance: " + sval;
throw new MalformedKeyException(msg, e);
}
return uri;
}
@Override
public T setX509Url(URI url) {
setValue(X509_URL, url);
return (T)this;
}
@Override
public List<String> getX509CertficateChain() {
return getList(X509_CERT_CHAIN);
}
@Override
public T setX509CertificateChain(List<String> chain) {
chain = Collections.isEmpty(chain) ? null : new ArrayList<>(new LinkedHashSet<>(chain)); //guarantee no duplicate elements
setValue(X509_CERT_CHAIN, chain);
return (T)this;
}
@Override
public String getX509CertificateSha1Thumbprint() {
return getString(X509_SHA1_THUMBPRINT);
}
@Override
public T setX509CertificateSha1Thumbprint(String thumbprint) {
setValue(X509_SHA1_THUMBPRINT, Strings.clean(thumbprint));
return (T)this;
}
@Override
public String getX509CertificateSha256Thumbprint() {
return getString(X509_SHA256_THUMBPRINT);
}
@Override
public T setX509CertificateSha256Thumbprint(String thumbprint) {
setValue(X509_SHA256_THUMBPRINT, Strings.clean(thumbprint));
return (T)this;
}
}

View File

@ -1,79 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.JwkBuilder;
import java.net.URI;
import java.util.List;
import java.util.Set;
@SuppressWarnings("unchecked")
abstract class AbstractJwkBuilder<K extends Jwk<K>, T extends JwkBuilder<K, T>> implements JwkBuilder<K, T> {
protected final K jwk;
private final JwkValidator<K> validator;
AbstractJwkBuilder(JwkValidator<K> validator) {
Assert.notNull(validator, "validator cannot be null.");
this.validator = validator;
this.jwk = newJwk();
Assert.notNull(this.jwk, "newJwk implementation cannot return a null instance.");
}
abstract K newJwk();
public final K build() {
validator.validate(this.jwk);
return jwk;
}
@Override
public T setUse(String use) {
this.jwk.setUse(use);
return (T)this;
}
@Override
public T setOperations(Set<String> ops) {
this.jwk.setOperations(ops);
return (T)this;
}
@Override
public T setAlgorithm(String alg) {
this.jwk.setAlgorithm(alg);
return (T)this;
}
@Override
public T setId(String id) {
this.jwk.setId(id);
return (T)this;
}
@Override
public T setX509Url(URI url) {
this.jwk.setX509Url(url);
return (T)this;
}
@Override
public T setX509CertificateChain(List<String> chain) {
this.jwk.setX509CertificateChain(chain);
return (T)this;
}
@Override
public T setX509CertificateSha1Thumbprint(String thumbprint) {
this.jwk.setX509CertificateSha1Thumbprint(thumbprint);
return (T)this;
}
@Override
public T setX509CertificateSha256Thumbprint(String thumbprint) {
this.jwk.setX509CertificateSha256Thumbprint(thumbprint);
return (T)this;
}
}

View File

@ -1,31 +1,39 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.KeyException;
import io.jsonwebtoken.security.MalformedKeyException;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
abstract class AbstractJwkConverter implements JwkConverter {
private static Map<String, ?> assertNotEmpty(Map<String, ?> m) {
if (m == null || m.isEmpty()) {
throw new InvalidKeyException("JWK map cannot be null or empty.");
}
return m;
}
abstract class AbstractJwkConverter<K extends Key> implements JwkConverter<K> {
static void malformed(String msg) {
throw new MalformedKeyException(msg);
}
protected static Map<String,?> sanitize(Map<String,?> jwk, String sensitiveKey) {
//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;
}
return msgJwk;
}
static String getRequiredString(Map<String, ?> m, String name) {
assertNotEmpty(m);
Assert.notEmpty(m, "JWK map cannot be null or empty.");
Object value = m.get(name);
if (value == null) {
malformed("JWK is missing required case-sensitive '" + name + "' member.");
@ -37,13 +45,14 @@ abstract class AbstractJwkConverter implements JwkConverter {
return s;
}
static BigInteger getRequiredBigInt(Map<String, ?> m, String name) {
static BigInteger getRequiredBigInt(Map<String, ?> m, String name, boolean sensitive) {
String s = getRequiredString(m, name);
try {
byte[] bytes = Decoders.BASE64URL.decode(s);
return new BigInteger(bytes);
return new BigInteger(1, bytes);
} catch (Exception e) {
String msg = "Unable to decode JWK member '" + name + "' to integer from value: " + s;
String val = sensitive ? "<redacted>" : s;
String msg = "Unable to decode JWK member '" + name + "' to BigInteger from value: " + val;
throw new MalformedKeyException(msg, e);
}
}
@ -51,12 +60,12 @@ abstract class AbstractJwkConverter implements JwkConverter {
// 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) {
int bitlen = bigInt.bitLength();
final int bitlen = bigInt.bitLength();
// round bitlen
bitlen = ((bitlen + 7) >> 3) << 3;
final int roundedBitlen = ((bitlen + 7) >> 3) << 3;
final byte[] bigBytes = bigInt.toByteArray();
if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) {
if (((bitlen % 8) != 0) && (((bitlen / 8) + 1) == (roundedBitlen / 8))) {
return bigBytes;
}
// set up params for copying everything but sign bit
@ -64,16 +73,32 @@ abstract class AbstractJwkConverter implements JwkConverter {
int len = bigBytes.length;
// if bigInt is exactly byte-aligned, just skip signbit in copy
if ((bigInt.bitLength() % 8) == 0) {
if ((bitlen % 8) == 0) {
startSrc = 1;
len--;
}
final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec
final byte[] resizedBytes = new byte[bitlen / 8];
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;
}
private final String keyType;
AbstractJwkConverter(String keyType) {
Assert.hasText(keyType, "keyType argument cannot be null or empty.");
this.keyType = keyType;
}
@Override
public String getId() {
return this.keyType;
}
KeyFactory getKeyFactory() {
return getKeyFactory(getId());
}
KeyFactory getKeyFactory(String alg) {
try {
return KeyFactory.getInstance(alg);
@ -83,4 +108,10 @@ abstract class AbstractJwkConverter implements JwkConverter {
}
}
Map<String,String> newJwkMap() {
Map<String,String> m = new HashMap<>();
m.put("kty", getId());
return m;
}
}

View File

@ -1,40 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.KeyException;
import io.jsonwebtoken.security.MalformedKeyException;
abstract class AbstractJwkValidator<T extends Jwk> implements JwkValidator<T> {
private final String TYPE_VALUE;
AbstractJwkValidator(String kty) {
kty = Strings.clean(kty);
Assert.notNull(kty);
this.TYPE_VALUE = kty;
}
static void malformed(String msg) throws MalformedKeyException {
throw new MalformedKeyException(msg);
}
@Override
public final void validate(T jwk) throws KeyException {
String type = jwk.getType();
if (!Strings.hasText(type)) {
malformed("JWKs must have a key type ('kty') property value.");
}
if (!TYPE_VALUE.equals(type)) {
malformed("JWK does not have expected key type ('kty') value of '" +
TYPE_VALUE + "'. Value found: " + type);
}
validateJwk(jwk);
}
abstract void validateJwk(T jwk) throws KeyException;
}

View File

@ -1,35 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.RsaJwk;
@SuppressWarnings("unchecked")
public class AbstractRsaJwk<T extends RsaJwk<T>> extends AbstractJwk<T> implements RsaJwk<T> {
static final String TYPE_VALUE = "RSA";
static final String MODULUS = "n";
static final String EXPONENT = "e";
AbstractRsaJwk() {
super(TYPE_VALUE);
}
@Override
public String getModulus() {
return getString(MODULUS);
}
@Override
public T setModulus(String value) {
return setRequiredValue(MODULUS, value, "modulus");
}
@Override
public String getExponent() {
return getString(EXPONENT);
}
@Override
public T setExponent(String value) {
return setRequiredValue(EXPONENT, value, "exponent");
}
}

View File

@ -1,16 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.KeyException;
import io.jsonwebtoken.security.RsaJwk;
public class AbstractRsaJwkValidator<T extends RsaJwk> extends AbstractJwkValidator<T> {
AbstractRsaJwkValidator() {
super(AbstractRsaJwk.TYPE_VALUE);
}
@Override
void validateJwk(T jwk) throws KeyException {
}
}

View File

@ -1,33 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import java.security.KeyFactory;
import java.util.HashMap;
import java.util.Map;
abstract class AbstractTypedJwkConverter extends AbstractJwkConverter implements TypedJwkConverter {
private final String keyType;
AbstractTypedJwkConverter(String keyType) {
Assert.hasText(keyType, "keyType argument cannot be null or empty.");
this.keyType = keyType;
}
@Override
public String getKeyType() {
return this.keyType;
}
KeyFactory getKeyFactory() {
return getKeyFactory(getKeyType());
}
Map<String,String> newJwkMap() {
Map<String,String> m = new HashMap<>();
m.put("kty", getKeyType());
return m;
}
}

View File

@ -1,116 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Arrays;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.PayloadSupplier;
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
import io.jsonwebtoken.security.AssociatedDataSource;
import io.jsonwebtoken.security.CryptoException;
import io.jsonwebtoken.security.CryptoRequest;
import io.jsonwebtoken.security.InitializationVectorSource;
import io.jsonwebtoken.security.SymmetricAeadDecryptionRequest;
import io.jsonwebtoken.security.SymmetricAeadEncryptionResult;
import io.jsonwebtoken.security.SymmetricAeadRequest;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.SecureRandom;
import static io.jsonwebtoken.lang.Arrays.*;
/**
* @since JJWT_RELEASE_VERSION
*/
abstract class AesAeadAlgorithm
extends AbstractEncryptionAlgorithm<byte[], SecretKey, SecretKey, SymmetricAeadRequest, SymmetricAeadEncryptionResult, SymmetricAeadDecryptionRequest, PayloadSupplier<byte[]>>
implements SymmetricAeadAlgorithm {
protected static final int AES_BLOCK_SIZE_BYTES = 16;
protected static final int AES_BLOCK_SIZE_BITS = AES_BLOCK_SIZE_BYTES * Byte.SIZE;
public static final String INVALID_GENERATED_IV_LENGTH =
"generatedIvLengthInBits must be a positive number <= " + AES_BLOCK_SIZE_BITS;
protected static final String DECRYPT_NO_IV = "This EncryptionAlgorithm implementation rejects decryption " +
"requests that do not include initialization vectors. AES ciphertext without an IV is weak and should " +
"never be used.";
private final int generatedIvByteLength;
private final int requiredKeyByteLength;
private final int requiredKeyBitLength;
public AesAeadAlgorithm(String id, String transformationString, int generatedIvLengthInBits, int requiredKeyLengthInBits) {
super(id, transformationString);
Assert.isTrue(generatedIvLengthInBits > 0 && generatedIvLengthInBits <= AES_BLOCK_SIZE_BITS, INVALID_GENERATED_IV_LENGTH);
Assert.isTrue((generatedIvLengthInBits % Byte.SIZE) == 0, "generatedIvLengthInBits must be evenly divisible by 8.");
this.generatedIvByteLength = generatedIvLengthInBits / Byte.SIZE;
Assert.isTrue(requiredKeyLengthInBits > 0, "requiredKeyLengthInBits must be greater than zero.");
Assert.isTrue((requiredKeyLengthInBits % Byte.SIZE) == 0, "requiredKeyLengthInBits must be evenly divisible by 8.");
this.requiredKeyBitLength = requiredKeyLengthInBits;
this.requiredKeyByteLength = requiredKeyLengthInBits / Byte.SIZE;
}
public int getRequiredKeyByteLength() {
return this.requiredKeyByteLength;
}
@Override
public SecretKey generateKey() {
return new JcaTemplate("AES", null).generateSecretKey(requiredKeyBitLength);
//TODO: assert generated key length?
}
byte[] ensureInitializationVector(CryptoRequest<?,?> request) {
byte[] iv = null;
if (request instanceof InitializationVectorSource) {
iv = Arrays.clean(((InitializationVectorSource)request).getInitializationVector());
}
if (Arrays.length(iv) == 0) {
iv = new byte[this.generatedIvByteLength];
SecureRandom random = ensureSecureRandom(request);
random.nextBytes(iv);
}
return iv;
}
SecretKey assertKey(CryptoRequest<?, SecretKey> request) {
SecretKey key = request.getKey();
return assertKeyLength(key);
}
SecretKey assertKeyLength(SecretKey key) {
int length = length(key.getEncoded());
if (length != requiredKeyByteLength) {
throw new CryptoException("The " + getId() + " algorithm requires that keys have a key length of " +
"(preferably secure-random) " + requiredKeyBitLength + " bits (" +
requiredKeyByteLength + " bytes). The provided key has a length of " + length * Byte.SIZE
+ " bits (" + length + " bytes).");
}
return key;
}
byte[] assertIvLength(final byte[] iv) {
int length = length(iv);
if (length != generatedIvByteLength) {
String msg = "The " + getId() + " algorithm requires initialization vectors with a " +
"length of " + generatedIvByteLength * Byte.SIZE + " bits (" + generatedIvByteLength + " bytes). " +
"The provided initialization vector has a length of " + length * Byte.SIZE + " bits (" +
length + " bytes).";
throw new CryptoException(msg);
}
return iv;
}
byte[] assertDecryptionIv(InitializationVectorSource src) throws IllegalArgumentException {
byte[] iv = src.getInitializationVector();
Assert.notEmpty(iv, DECRYPT_NO_IV);
return assertIvLength(iv);
}
byte[] getAAD(AssociatedDataSource src) {
byte[] aad = src.getAssociatedData();
return io.jsonwebtoken.lang.Arrays.clean(aad);
}
}

View File

@ -0,0 +1,150 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Arrays;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.AssociatedDataSource;
import io.jsonwebtoken.security.CryptoRequest;
import io.jsonwebtoken.security.InitializationVectorSource;
import io.jsonwebtoken.security.SecurityRequest;
import io.jsonwebtoken.security.SecretKeyGenerator;
import io.jsonwebtoken.security.WeakKeyException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import static io.jsonwebtoken.lang.Arrays.*;
abstract class AesAlgorithm extends CryptoAlgorithm implements SecretKeyGenerator {
protected static final String KEY_ALG_NAME = "AES";
protected static final int BLOCK_SIZE = 128;
protected static final int BLOCK_BYTE_SIZE = BLOCK_SIZE / Byte.SIZE;
protected static final int GCM_IV_SIZE = 96; // https://tools.ietf.org/html/rfc7518#section-5.3
protected static final int GCM_IV_BYTE_SIZE = GCM_IV_SIZE / Byte.SIZE;
protected static final String DECRYPT_NO_IV = "This algorithm implementation rejects decryption " +
"requests that do not include initialization vectors. AES ciphertext without an IV is weak and " +
"susceptible to attack.";
protected final int keyLength;
protected final int ivLength;
protected final int tagLength;
protected final boolean gcm;
AesAlgorithm(String id, String jcaTransformation, int keyLength) {
super(id, jcaTransformation);
Assert.isTrue(keyLength == 128 || keyLength == 192 || keyLength == 256, "Invalid AES key length: it must equal 128, 192, or 256.");
this.keyLength = keyLength;
this.gcm = jcaTransformation.startsWith("AES/GCM");
this.ivLength = jcaTransformation.equals("AESWrap") ? 0 : (this.gcm ? GCM_IV_SIZE : BLOCK_SIZE);
// https://tools.ietf.org/html/rfc7518#section-5.2.3 through ttps://tools.ietf.org/html/rfc7518#section-5.3 :
this.tagLength = this.gcm ? BLOCK_SIZE : this.keyLength;
}
@Override
public SecretKey generateKey() {
return new JcaTemplate(KEY_ALG_NAME, null).generateSecretKey(this.keyLength);
//TODO: assert generated key length?
}
protected SecretKey assertKey(CryptoRequest<?,SecretKey> request) {
SecretKey key = Assert.notNull(request.getKey(), "Request key cannot be null.");
validateLengthIfPossible(key);
return key;
}
private void validateLengthIfPossible(SecretKey key) {
validateLength(key, this.keyLength, false);
}
protected static String lengthMsg(String id, String type, int requiredLengthInBits, int actualLengthInBits) {
return "The '" + id + "' algorithm requires " + type + " with a length of " + requiredLengthInBits +
" bits (" + (requiredLengthInBits / Byte.SIZE) + " bytes). The provided key has a length of " +
actualLengthInBits + " bits (" + actualLengthInBits / Byte.SIZE + " bytes).";
}
protected byte[] validateLength(SecretKey key, int requiredBitLength, boolean propagate) {
byte[] keyBytes = null;
try {
keyBytes = key.getEncoded();
} catch (RuntimeException re) {
if (propagate) {
throw re;
}
//can't get the bytes to validate, e.g. hardware security module or later Android, so just return:
return keyBytes;
}
int keyBitLength = keyBytes.length * Byte.SIZE;
if (keyBitLength < requiredBitLength) {
throw new WeakKeyException(lengthMsg(getId(), "keys", requiredBitLength, keyBitLength));
}
return keyBytes;
}
byte[] assertIvLength(final byte[] iv) {
int length = length(iv);
if ((this.ivLength / Byte.SIZE) != length) {
String msg = lengthMsg(getId(), "initialization vectors", this.ivLength, length * Byte.SIZE);
throw new IllegalArgumentException(msg);
}
return iv;
}
byte[] assertTag(byte[] tag) {
int len = Arrays.length(tag) * Byte.SIZE;
if (this.tagLength != len) {
String msg = lengthMsg(getId(), "authentication tags", this.tagLength, len);
throw new IllegalArgumentException(msg);
}
return tag;
}
byte[] assertDecryptionIv(InitializationVectorSource src) throws IllegalArgumentException {
byte[] iv = src.getInitializationVector();
Assert.notEmpty(iv, DECRYPT_NO_IV);
return assertIvLength(iv);
}
protected byte[] ensureInitializationVector(SecurityRequest request) {
byte[] iv = null;
if (request instanceof InitializationVectorSource) {
iv = Arrays.clean(((InitializationVectorSource) request).getInitializationVector());
}
int ivByteLength = this.ivLength / Byte.SIZE;
if (iv == null || iv.length == 0) {
iv = new byte[ivByteLength];
SecureRandom random = ensureSecureRandom(request);
random.nextBytes(iv);
} else {
assertIvLength(iv);
}
return iv;
}
protected AlgorithmParameterSpec getIvSpec(byte[] iv) {
if (Arrays.length(iv) == 0) {
return null;
}
return this.gcm ? new GCMParameterSpec(BLOCK_SIZE, iv) : new IvParameterSpec(iv);
}
protected byte[] getAAD(SecurityRequest request) {
byte[] aad = null;
if (request instanceof AssociatedDataSource) {
aad = Arrays.clean(((AssociatedDataSource) request).getAssociatedData());
}
return aad;
}
protected byte[] plus(byte[] a, byte[] b) {
byte[] c = new byte[length(a) + length(b)];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
}

View File

@ -14,48 +14,45 @@ import io.jsonwebtoken.security.SecurityException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.security.Key;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Map;
/**
* @since JJWT_RELEASE_VERSION
*/
public class AesGcmKeyAlgorithm extends CryptoAlgorithm implements EncryptedKeyAlgorithm<SecretKey, SecretKey> {
public class AesGcmKeyAlgorithm extends AesAlgorithm implements EncryptedKeyAlgorithm<SecretKey, SecretKey> {
public static final String TRANSFORMATION = "AES/GCM/NoPadding";
public AesGcmKeyAlgorithm(int keyLen) {
super("A" + keyLen + "GCMKW", TRANSFORMATION);
Assert.isTrue(keyLen == 128 || keyLen == 192 || keyLen == 256, "Invalid AES key length: it must equal 128, 192, or 256.");
super("A" + keyLen + "GCMKW", TRANSFORMATION, keyLen);
}
@Override
public KeyResult getEncryptionKey(KeyRequest<SecretKey, SecretKey> request) throws SecurityException {
public KeyResult getEncryptionKey(final KeyRequest<SecretKey, SecretKey> request) throws SecurityException {
Assert.notNull(request, "request cannot be null.");
final SecretKey kek = Assert.notNull(request.getKey(), "Request key encryption key (request.getKey()) cannot be null.");
final SecretKey kek = assertKey(request);
final SecretKey cek = Assert.notNull(request.getPayload(), "Request content encryption key (request.getPayload()) cannot be null.");
final byte[] iv = ensureInitializationVector(request);
final AlgorithmParameterSpec ivSpec = getIvSpec(iv);
final byte[] iv = new byte[12]; // GCM IV is 96 bits (12 bytes)
SecureRandom random = ensureSecureRandom(request);
random.nextBytes(iv);
byte[] jcaResult = execute(request, Cipher.class, new InstanceCallback<Cipher, byte[]>() {
byte[] taggedCiphertext = execute(request, Cipher.class, new InstanceCallback<Cipher, byte[]>() {
@Override
public byte[] doWithInstance(Cipher cipher) throws Exception {
cipher.init(Cipher.WRAP_MODE, kek, new GCMParameterSpec(128, iv));
cipher.init(Cipher.WRAP_MODE, kek, ivSpec);
return cipher.wrap(cek);
}
});
//The JCA concatenates the ciphertext and the tag - split them:
int ciphertextLength = jcaResult.length - 16; //16 == AES block size in bytes (128 bits)
int tagByteLength = this.tagLength / Byte.SIZE;
// When using GCM mode, the JDK appends the authentication tag to the ciphertext, so let's extract it:
int ciphertextLength = taggedCiphertext.length - tagByteLength;
byte[] ciphertext = new byte[ciphertextLength];
System.arraycopy(jcaResult, 0, ciphertext, 0, ciphertextLength);
byte[] tag = new byte[16];
System.arraycopy(jcaResult, ciphertextLength, tag, 0, 16);
System.arraycopy(taggedCiphertext, 0, ciphertext, 0, ciphertextLength);
byte[] tag = new byte[tagByteLength];
System.arraycopy(taggedCiphertext, ciphertextLength, tag, 0, tagByteLength);
String encodedIv = Encoders.BASE64URL.encode(iv);
String encodedTag = Encoders.BASE64URL.encode(tag);
@ -64,7 +61,31 @@ public class AesGcmKeyAlgorithm extends CryptoAlgorithm implements EncryptedKeyA
return new DefaultKeyResult(ciphertext, cek, extraParams);
}
private byte[] getHeaderByteArray(JweHeader header, String name, int requiredLength) {
@Override
public SecretKey getDecryptionKey(KeyRequest<byte[], SecretKey> request) throws SecurityException {
Assert.notNull(request, "request cannot be null.");
final SecretKey kek = assertKey(request);
final byte[] cekBytes = Assert.notEmpty(request.getPayload(), "Decryption request payload (ciphertext) cannot be null or empty.");
final JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null.");
final byte[] tag = getHeaderByteArray(header, "tag", this.tagLength / Byte.SIZE);
final byte[] iv = getHeaderByteArray(header, "iv", this.ivLength / Byte.SIZE);
final AlgorithmParameterSpec ivSpec = getIvSpec(iv);
//for tagged GCM, the JCA spec requires that the tag be appended to the end of the ciphertext byte array:
final byte[] taggedCiphertext = plus(cekBytes, tag);
return execute(request, Cipher.class, new InstanceCallback<Cipher, SecretKey>() {
@Override
public SecretKey doWithInstance(Cipher cipher) throws Exception {
cipher.init(Cipher.UNWRAP_MODE, kek, ivSpec);
Key key = cipher.unwrap(taggedCiphertext, KEY_ALG_NAME, Cipher.SECRET_KEY);
Assert.state(key instanceof SecretKey, "cipher.unwrap must produce a SecretKey instance.");
return (SecretKey)key;
}
});
}
private byte[] getHeaderByteArray(JweHeader header, String name, int requiredByteLength) {
Object value = header.get(name);
if (value == null) {
String msg = "The " + getId() + " Key Management Algorithm requires a JweHeader '" + name + "' value.";
@ -86,42 +107,13 @@ public class AesGcmKeyAlgorithm extends CryptoAlgorithm implements EncryptedKeyA
}
int len = Arrays.length(decoded);
if (len != requiredLength) {
if (len != requiredByteLength) {
String msg = "The '" + getId() + "' key management algorithm requires the JweHeader '" + name +
"' value to be " + (requiredLength * Byte.SIZE) + " bits (" + requiredLength +
"' value to be " + (requiredByteLength * Byte.SIZE) + " bits (" + requiredByteLength +
" bytes) in length. Actual length: " + (len * Byte.SIZE) + " bits (" + len + " bytes).";
throw new MalformedJwtException(msg);
}
return decoded;
}
@Override
public SecretKey getDecryptionKey(KeyRequest<byte[], SecretKey> request) throws SecurityException {
Assert.notNull(request, "request cannot be null.");
final SecretKey kek = Assert.notNull(request.getKey(), "Request key decryption key (request.getKey()) cannot be null.");
final byte[] cekBytes = Assert.notNull(request.getPayload(), "Request encrypted key (request.getPayload()) cannot be null.");
Assert.isTrue(cekBytes.length > 0, "Request encrypted key (request.getPayload()) cannot be empty.");
final JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null.");
final byte[] iv = getHeaderByteArray(header, "iv", 12);
final byte[] tag = getHeaderByteArray(header, "tag", 16);
// JCA api expects the ciphertext to have the format: encrypted_bytes + authentication_tag
// so we need to reconstitute that format before passing in to the cipher:
final byte[] ciphertext = new byte[cekBytes.length + tag.length];
System.arraycopy(cekBytes, 0, ciphertext, 0, cekBytes.length);
System.arraycopy(tag, 0, ciphertext, cekBytes.length, tag.length);
return execute(request, Cipher.class, new InstanceCallback<Cipher, SecretKey>() {
@Override
public SecretKey doWithInstance(Cipher cipher) throws Exception {
cipher.init(Cipher.UNWRAP_MODE, kek, new GCMParameterSpec(128, iv));
Key key = cipher.unwrap(ciphertext, "AES", Cipher.SECRET_KEY);
Assert.state(key instanceof SecretKey, "cipher.unwrap must produce a SecretKey instance.");
return (SecretKey)key;
}
});
}
}

View File

@ -13,25 +13,21 @@ import java.security.Key;
/**
* @since JJWT_RELEASE_VERSION
*/
public class AesWrapKeyAlgorithm extends CryptoAlgorithm implements EncryptedKeyAlgorithm<SecretKey, SecretKey> {
public class AesWrapKeyAlgorithm extends AesAlgorithm implements EncryptedKeyAlgorithm<SecretKey, SecretKey> {
private static final String KEY_ALG_NAME = "AES";
private static final String TRANSFORMATION = "AESWrap";
private final int keyLen;
public AesWrapKeyAlgorithm(int keyLen) {
super("A" + keyLen + "KW", TRANSFORMATION);
Assert.isTrue(keyLen == 128 || keyLen == 192 || keyLen == 256, "Invalid AES key length - it must equal 128, 192, or 256.");
this.keyLen = keyLen;
super("A" + keyLen + "KW", TRANSFORMATION, keyLen);
}
@Override
public KeyResult getEncryptionKey(KeyRequest<SecretKey, SecretKey> request) throws SecurityException {
Assert.notNull(request, "request cannot be null.");
final SecretKey kek = Assert.notNull(request.getKey(), "Request encryption key (request.getKey()) cannot be null.");
final SecretKey kek = assertKey(request);
final SecretKey cek = Assert.notNull(request.getPayload(), "Request content encryption key (request.getPayload()) cannot be null.");
byte[] encryptedCekBytes = execute(request, Cipher.class, new InstanceCallback<Cipher, byte[]>() {
byte[] ciphertext = execute(request, Cipher.class, new InstanceCallback<Cipher, byte[]>() {
@Override
public byte[] doWithInstance(Cipher cipher) throws Exception {
cipher.init(Cipher.WRAP_MODE, kek);
@ -39,14 +35,14 @@ public class AesWrapKeyAlgorithm extends CryptoAlgorithm implements EncryptedKey
}
});
return new DefaultKeyResult(encryptedCekBytes, cek);
return new DefaultKeyResult(ciphertext, cek);
}
@Override
public SecretKey getDecryptionKey(KeyRequest<byte[], SecretKey> request) throws SecurityException {
Assert.notNull(request, "request cannot be null.");
final SecretKey kek = Assert.notNull(request.getKey(), "Request decryption key (request.getKey()) cannot be null.");
final byte[] cekBytes = Assert.notNull(request.getPayload(), "Request encrypted key (request.getPayload()) cannot be null.");
final SecretKey kek = assertKey(request);
final byte[] cekBytes = Assert.notEmpty(request.getPayload(), "Request encrypted key (request.getPayload()) cannot be null or empty.");
return execute(request, Cipher.class, new InstanceCallback<Cipher, SecretKey>() {
@Override
@ -54,7 +50,7 @@ public class AesWrapKeyAlgorithm extends CryptoAlgorithm implements EncryptedKey
cipher.init(Cipher.UNWRAP_MODE, kek);
Key key = cipher.unwrap(cekBytes, KEY_ALG_NAME, Cipher.SECRET_KEY);
Assert.state(key instanceof SecretKey, "Cipher unwrap must return a SecretKey instance.");
return (SecretKey)key;
return (SecretKey) key;
}
});
}

View File

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

View File

@ -2,17 +2,17 @@ package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.SymmetricAeadDecryptionRequest;
import io.jsonwebtoken.security.SymmetricAeadEncryptionResult;
import io.jsonwebtoken.security.AeadResult;
import javax.crypto.SecretKey;
import java.security.Provider;
import java.security.SecureRandom;
public class DefaultSymmetricAeadResult extends DefaultSymmetricAeadRequest implements SymmetricAeadEncryptionResult, SymmetricAeadDecryptionRequest {
public class DefaultAeadResult extends DefaultSymmetricAeadRequest implements AeadResult, SymmetricAeadDecryptionRequest {
private final byte[] TAG;
public DefaultSymmetricAeadResult(Provider provider, SecureRandom secureRandom, byte[] data, SecretKey key, byte[] aad, byte[] tag, byte[] iv) {
public DefaultAeadResult(Provider provider, SecureRandom secureRandom, byte[] data, SecretKey key, byte[] aad, byte[] tag, byte[] iv) {
super(provider, secureRandom, data, key, aad, iv);
Assert.notEmpty(iv, "initialization vector cannot be null or empty.");
this.TAG = Assert.notEmpty(tag, "authentication tag cannot be null or empty.");

View File

@ -1,18 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.EcJwkBuilderFactory;
import io.jsonwebtoken.security.PrivateEcJwkBuilder;
import io.jsonwebtoken.security.PublicEcJwkBuilder;
final class DefaultEcJwkBuilderFactory implements EcJwkBuilderFactory {
@Override
public PublicEcJwkBuilder publicKey() {
return new DefaultPublicEcJwkBuilder();
}
@Override
public PrivateEcJwkBuilder privateKey() {
return new DefaultPrivateEcJwkBuilder();
}
}

View File

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

View File

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

View File

@ -1,18 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.EcJwkBuilderFactory;
import io.jsonwebtoken.security.JwkBuilderFactory;
import io.jsonwebtoken.security.SymmetricJwkBuilder;
public final class DefaultJwkBuilderFactory implements JwkBuilderFactory {
@Override
public EcJwkBuilderFactory ellipticCurve() {
return new DefaultEcJwkBuilderFactory();
}
@Override
public SymmetricJwkBuilder symmetric() {
return new DefaultSymmetricJwkBuilder();
}
}

View File

@ -1,58 +0,0 @@
package io.jsonwebtoken.impl.security;
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;
public class DefaultJwkConverter extends AbstractJwkConverter {
private final Map<String, TypedJwkConverter> converters = new HashMap<>();
public DefaultJwkConverter() {
this(Collections.<TypedJwkConverter>of(
new SymmetricJwkConverter(),
new EcJwkConverter(),
new RsaJwkConverter()));
}
public DefaultJwkConverter(List<TypedJwkConverter> converters) {
Assert.notEmpty(converters, "Converters cannot be null or empty.");
for(TypedJwkConverter converter : converters) {
this.converters.put(converter.getKeyType(), 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 Key toKey(Map<String, ?> jwk) {
String type = getRequiredString(jwk, "kty");
JwkConverter converter = getConverter(type);
return converter.toKey(jwk);
}
@Override
public Map<String, String> toJwk(Key key) {
Assert.notNull(key, "Key argument cannot be null.");
for(TypedJwkConverter converter : converters.values()) {
if (converter.supports(key)) {
return converter.toJwk(key);
}
}
String msg = "Unable to determine JWK converter for key of type " + key.getClass();
throw new UnsupportedKeyException(msg);
}
}

View File

@ -0,0 +1,31 @@
package io.jsonwebtoken.impl.security;
public class DefaultKeyUseStrategy implements KeyUseStrategy {
static final KeyUseStrategy INSTANCE = new DefaultKeyUseStrategy();
// values from https://datatracker.ietf.org/doc/html/rfc7517#section-4.2
private static final String SIGNATURE = "sig";
private static final String ENCRYPTION = "enc";
@Override
public String toJwkValue(KeyUsage usage) {
// states 2, 3, 4
if (usage.isKeyEncipherment() || usage.isDataEncipherment() || usage.isKeyAgreement()) {
return ENCRYPTION;
}
// states 0, 1, 5, 6
if (usage.isDigitalSignature() || usage.isNonRepudiation() || usage.isKeyCertSign() || usage.isCRLSign()) {
return SIGNATURE;
}
// We don't need to check for encipherOnly (7) and decipherOnly (8) because per
// [RFC 5280, Section 4.2.1.3](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3),
// those two states are only relevant when keyAgreement (4) is true, and that is covered in the first
// conditional above
return null; //can't infer
}
}

View File

@ -1,18 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.PrivateEcJwk;
class DefaultPrivateEcJwk extends AbstractEcJwk<PrivateEcJwk> implements PrivateEcJwk {
static final String D = "d";
@Override
public String getD() {
return getString(D);
}
@Override
public PrivateEcJwk setD(String d) {
return setRequiredValue(D, d, "private key");
}
}

Some files were not shown because too many files have changed in this diff Show More