mirror of https://github.com/jwtk/jjwt.git
implementation checkpoint so I don't lose a ton of work. Won't build, but need to backup.
This commit is contained in:
parent
bd01e84406
commit
3f4e40ad27
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface AeadResult extends PayloadSupplier<byte[]>, AuthenticationTagSource, InitializationVectorSource {
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface CurveId {
|
||||
|
||||
String toString();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface EcJwkBuilderFactory {
|
||||
|
||||
PublicEcJwkBuilder publicKey();
|
||||
|
||||
PrivateEcJwkBuilder privateKey();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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>> {
|
||||
}
|
|
@ -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>> {
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
|
||||
public interface EcPublicJwk<V> extends PublicJwk<V, ECPublicKey> {
|
||||
}
|
|
@ -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>> {
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
public interface HashAlgorithm extends Identifiable {
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface JwkBuilderFactory {
|
||||
|
||||
EcJwkBuilderFactory ellipticCurve();
|
||||
|
||||
SymmetricJwkBuilder symmetric();
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface JwkRsaPrimeInfoBuilder extends JwkRsaPrimeInfoMutator<JwkRsaPrimeInfoBuilder> {
|
||||
|
||||
JwkRsaPrimeInfo build();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface PrivateEcJwk extends EcJwk<PrivateEcJwk>, PrivateEcJwkMutator<PrivateEcJwk> {
|
||||
|
||||
String getD();
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface PrivateEcJwkBuilder extends EcJwkBuilder<PrivateEcJwk, PrivateEcJwkBuilder> {
|
||||
|
||||
PrivateEcJwkBuilder setD(String d);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface PublicEcJwk extends EcJwk<PublicEcJwk> {
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface PublicEcJwkBuilder extends EcJwkBuilder<PublicEcJwk, PublicEcJwkBuilder> {
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
public interface PublicJwk<V, K extends PublicKey> extends AsymmetricJwk<V, K> {
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface PublicRsaJwk extends RsaJwk {
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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>> {
|
||||
}
|
|
@ -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>> {
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
|
||||
public interface RsaPublicJwk<V> extends PublicJwk<V, RSAPublicKey> {
|
||||
}
|
|
@ -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>> {
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
public interface SecretJwk<V> extends Jwk<V, SecretKey> {
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
public interface SecretJwkBuilder extends JwkBuilder<SecretKey, SecretJwk<?>, SecretJwkBuilder> {
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface SecretKeySignatureAlgorithm extends SignatureAlgorithm<SecretKey, SecretKey>, SecretKeyGenerator {
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface SymmetricAeadEncryptionResult extends PayloadSupplier<byte[]>, AuthenticationTagSource, InitializationVectorSource {
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface SymmetricJwk extends Jwk<SymmetricJwk>, SymmetricJwkMutator<SymmetricJwk> {
|
||||
|
||||
String getK();
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface SymmetricJwkBuilder extends JwkBuilder<SymmetricJwk, SymmetricJwkBuilder>, SymmetricJwkMutator<SymmetricJwkBuilder> {
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
package io.jsonwebtoken.security;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
/**
|
||||
* @since JJWT_RELEASE_VERSION
|
||||
*/
|
||||
public interface SymmetricKeySignatureAlgorithm extends SignatureAlgorithm<SecretKey, SecretKey>, SymmetricKeyAlgorithm {
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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()).");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.jsonwebtoken.impl.lang;
|
||||
|
||||
public interface BiFunction<T, U, R> {
|
||||
|
||||
R apply(T t, U u);
|
||||
}
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package io.jsonwebtoken.impl.lang;
|
||||
|
||||
public interface Converter<A,B> {
|
||||
|
||||
B applyTo(A a);
|
||||
|
||||
A applyFrom(B b);
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.jsonwebtoken.impl.lang;
|
||||
|
||||
public interface Supplier<T> {
|
||||
|
||||
T get();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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.");
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue