mirror of https://github.com/jwtk/jjwt.git
password hashing / iteration estimate algorithm / impl checkpoint
This commit is contained in:
parent
aa9af6859e
commit
23ef0333a3
|
@ -1,6 +1,5 @@
|
||||||
package io.jsonwebtoken;
|
package io.jsonwebtoken;
|
||||||
|
|
||||||
import io.jsonwebtoken.security.EncryptedKeyAlgorithm;
|
|
||||||
import io.jsonwebtoken.security.KeyAlgorithm;
|
import io.jsonwebtoken.security.KeyAlgorithm;
|
||||||
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
|
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
|
||||||
|
|
||||||
|
@ -17,6 +16,4 @@ public interface JweBuilder extends JwtBuilder<JweBuilder> {
|
||||||
JweBuilder withKey(SecretKey key);
|
JweBuilder withKey(SecretKey key);
|
||||||
|
|
||||||
<K extends Key> JweBuilder withKeyFrom(K key, KeyAlgorithm<K, ?> alg);
|
<K extends Key> JweBuilder withKeyFrom(K key, KeyAlgorithm<K, ?> alg);
|
||||||
|
|
||||||
JweBuilder withKeyFrom(char[] password, int iterations, EncryptedKeyAlgorithm<SecretKey,SecretKey> alg);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ public interface JwtBuilder<T extends JwtBuilder<T>> extends ClaimsMutator<T> {
|
||||||
* @param header the header to set (and potentially replace any existing header).
|
* @param header the header to set (and potentially replace any existing header).
|
||||||
* @return the builder for method chaining.
|
* @return the builder for method chaining.
|
||||||
*/
|
*/
|
||||||
T setHeader(Map<String, Object> header);
|
T setHeader(Map<String, ?> header);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the specified name/value pairs to the header. If a header does not yet exist at the time this method
|
* Applies the specified name/value pairs to the header. If a header does not yet exist at the time this method
|
||||||
|
@ -83,7 +83,7 @@ public interface JwtBuilder<T extends JwtBuilder<T>> extends ClaimsMutator<T> {
|
||||||
* @param params the header name/value pairs to append to the header.
|
* @param params the header name/value pairs to append to the header.
|
||||||
* @return the builder for method chaining.
|
* @return the builder for method chaining.
|
||||||
*/
|
*/
|
||||||
T setHeaderParams(Map<String, Object> params);
|
T setHeaderParams(Map<String, ?> params);
|
||||||
|
|
||||||
//sets the specified header parameter, overwriting any previous value under the same name.
|
//sets the specified header parameter, overwriting any previous value under the same name.
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ public interface JwtBuilder<T extends JwtBuilder<T>> extends ClaimsMutator<T> {
|
||||||
* @return the builder for method chaining.
|
* @return the builder for method chaining.
|
||||||
* @since 0.8
|
* @since 0.8
|
||||||
*/
|
*/
|
||||||
T addClaims(Map<String, Object> claims);
|
T addClaims(Map<String, ?> claims);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the JWT Claims <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.1">
|
* Sets the JWT Claims <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-4.1.1">
|
||||||
|
|
|
@ -293,7 +293,7 @@ public interface JwtParserBuilder {
|
||||||
* .parseClaimsJws(compact);
|
* .parseClaimsJws(compact);
|
||||||
* </pre>
|
* </pre>
|
||||||
* <p>
|
* <p>
|
||||||
* <p>A {@code KeyResolver} is invoked once during parsing before performing decryption or signature verification.</p>
|
* <p>A Key {@code Locator} is invoked once during parsing before performing decryption or signature verification.</p>
|
||||||
*
|
*
|
||||||
* @param keyLocator the locator used to retrieve decryption or signature verification keys.
|
* @param keyLocator the locator used to retrieve decryption or signature verification keys.
|
||||||
* @return the parser builder for method chaining.
|
* @return the parser builder for method chaining.
|
||||||
|
|
|
@ -3,5 +3,5 @@ package io.jsonwebtoken.security;
|
||||||
/**
|
/**
|
||||||
* @since JJWT_RELEASE_VERSION
|
* @since JJWT_RELEASE_VERSION
|
||||||
*/
|
*/
|
||||||
public interface AeadResult extends PayloadSupplier<byte[]>, AuthenticationTagSource, InitializationVectorSource {
|
public interface AeadResult extends PayloadSupplier<byte[]>, DigestSupplier, InitializationVectorSupplier {
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package io.jsonwebtoken.security;
|
||||||
/**
|
/**
|
||||||
* @since JJWT_RELEASE_VERSION
|
* @since JJWT_RELEASE_VERSION
|
||||||
*/
|
*/
|
||||||
public interface AssociatedDataSource {
|
public interface AssociatedDataSupplier {
|
||||||
|
|
||||||
byte[] getAssociatedData();
|
byte[] getAssociatedData();
|
||||||
|
|
|
@ -18,8 +18,8 @@ package io.jsonwebtoken.security;
|
||||||
/**
|
/**
|
||||||
* @since JJWT_RELEASE_VERSION
|
* @since JJWT_RELEASE_VERSION
|
||||||
*/
|
*/
|
||||||
public interface AuthenticationTagSource {
|
public interface DigestSupplier {
|
||||||
|
|
||||||
byte[] getAuthenticationTag();
|
byte[] getDigest();
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package io.jsonwebtoken.security;
|
||||||
|
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.interfaces.ECKey;
|
||||||
|
|
||||||
|
public interface EcKeyAlgorithm<E extends ECKey & PublicKey, D extends ECKey & PrivateKey> extends KeyAlgorithm<E, D> {
|
||||||
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
package io.jsonwebtoken.security;
|
|
||||||
|
|
||||||
import java.security.Key;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link KeyAlgorithm} that produces an encrypted key value. {@code EncryptedKeyAlgorithm}s will be supplied
|
|
||||||
* a secure-randomly-generated Content Encryption Key in the request's {@link CryptoRequest#getPayload() getData()} method.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* <b>A {@code KeyAlgorithm} that does not produce an encrypted key value (or produces an empty key byte array) should
|
|
||||||
* not implement this interface, and instead implement the {@code KeyAlgorithm} parent interface directly.</p>
|
|
||||||
*
|
|
||||||
* @since JJWT_RELEASE_VERSION
|
|
||||||
*/
|
|
||||||
public interface EncryptedKeyAlgorithm<E extends Key, D extends Key> extends KeyAlgorithm<E, D> {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package io.jsonwebtoken.security;
|
|
||||||
|
|
||||||
import io.jsonwebtoken.JweHeader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @since JJWT_RELEASE_VERSION
|
|
||||||
*/
|
|
||||||
public interface EncryptionAlgorithmLocator {
|
|
||||||
|
|
||||||
SymmetricAeadAlgorithm getEncryptionAlgorithm(JweHeader jweHeader);
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
package io.jsonwebtoken.security;
|
|
||||||
|
|
||||||
import io.jsonwebtoken.Identifiable;
|
|
||||||
|
|
||||||
public interface HashAlgorithm extends Identifiable {
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
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");
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@ package io.jsonwebtoken.security;
|
||||||
/**
|
/**
|
||||||
* @since JJWT_RELEASE_VERSION
|
* @since JJWT_RELEASE_VERSION
|
||||||
*/
|
*/
|
||||||
public interface InitializationVectorSource {
|
public interface InitializationVectorSupplier {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the secure-random initialization vector used during encryption that must be presented in order
|
* Returns the secure-random initialization vector used during encryption that must be presented in order
|
|
@ -6,21 +6,15 @@ import javax.crypto.SecretKey;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <code><a href="https://tools.ietf.org/html/rfc7516#section-2">Key Management Algorithm</a></code> is an algorithm that
|
* A {@code KeyAlgorithm} produces the {@link SecretKey} used to encrypt or decrypt a JWE. The {@code KeyAlgorithm}
|
||||||
* produces a {@link SecretKey} used to encrypt or decrypt a JWE. The Key Management Algorithm used for a particular
|
* used for a particular JWE is {@link #getId() identified} in the JWE's
|
||||||
* JWE is {@link #getId() identified} in the
|
* <a href="https://tools.ietf.org/html/rfc7516#section-4.1.1">{@code alg} header</a>.
|
||||||
* <a href="https://tools.ietf.org/html/rfc7516#section-4.1.1">JWE's {@code alg} header</a>.
|
* <p/>
|
||||||
* <h4>Key Management Mode</h4>
|
* <p>The {@code KeyAlgorithm} interface is JJWT's idiomatic approach to the JWE specification's
|
||||||
* The JWE specification indicates that all {@code Key Management Algorithm}s utilize what is called a
|
* <code><a href="https://tools.ietf.org/html/rfc7516#section-2">{@code Key Management Mode}</a></code> concept.</p>
|
||||||
* {@code Key Management Mode} to indicate if the encryption key will be supplied to a JWE recipient within the
|
|
||||||
* JWE as an encrypted value. <b>This interface does not indicate which {@code Key Management Mode} is used:</b>
|
|
||||||
* the {@link #getEncryptionKey(KeyRequest) key result} may contain either an empty or populated encrypted key via
|
|
||||||
* {@link KeyResult#getPayload() result.getPayload()}.
|
|
||||||
* <p>Therefore, <b>algorithms that produce encrypted keys <em>MUST</em> implement the
|
|
||||||
* {@link EncryptedKeyAlgorithm} interface instead of this one.</b></p>
|
|
||||||
*
|
*
|
||||||
* @since JJWT_RELEASE_VERSION
|
* @since JJWT_RELEASE_VERSION
|
||||||
* @see EncryptedKeyAlgorithm
|
* @see <a href="https://tools.ietf.org/html/rfc7516#section-2">RFC 7561, Section 2: JWE Key (Management) Algorithms</a>
|
||||||
*/
|
*/
|
||||||
public interface KeyAlgorithm<E extends Key, D extends Key> extends Identifiable {
|
public interface KeyAlgorithm<E extends Key, D extends Key> extends Identifiable {
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@ import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.lang.Classes;
|
import io.jsonwebtoken.lang.Classes;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
|
||||||
import java.security.interfaces.RSAPublicKey;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,8 +17,9 @@ public final class KeyAlgorithms {
|
||||||
|
|
||||||
private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.KeyAlgorithmsBridge";
|
private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.KeyAlgorithmsBridge";
|
||||||
private static final Class<?>[] ID_ARG_TYPES = new Class[]{String.class};
|
private static final Class<?>[] ID_ARG_TYPES = new Class[]{String.class};
|
||||||
|
private static final Class<?>[] ESTIMATE_ITERATIONS_ARG_TYPES = new Class[]{KeyAlgorithm.class, long.class};
|
||||||
|
|
||||||
public static Collection<SymmetricAeadAlgorithm> values() {
|
public static Collection<KeyAlgorithm<?, ?>> values() {
|
||||||
return Classes.invokeStatic(BRIDGE_CLASSNAME, "values", null, (Object[]) null);
|
return Classes.invokeStatic(BRIDGE_CLASSNAME, "values", null, (Object[]) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,16 +48,23 @@ public final class KeyAlgorithms {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final KeyAlgorithm<SecretKey, SecretKey> DIRECT = forId0("dir");
|
public static final KeyAlgorithm<SecretKey, SecretKey> DIRECT = forId0("dir");
|
||||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128KW = forId0("A128KW");
|
public static final KeyAlgorithm<SecretKey, SecretKey> A128KW = forId0("A128KW");
|
||||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192KW = forId0("A192KW");
|
public static final KeyAlgorithm<SecretKey, SecretKey> A192KW = forId0("A192KW");
|
||||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256KW = forId0("A256KW");
|
public static final KeyAlgorithm<SecretKey, SecretKey> A256KW = forId0("A256KW");
|
||||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128GCMKW = forId0("A128GCMKW");
|
public static final KeyAlgorithm<SecretKey, SecretKey> A128GCMKW = forId0("A128GCMKW");
|
||||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192GCMKW = forId0("A192GCMKW");
|
public static final KeyAlgorithm<SecretKey, SecretKey> A192GCMKW = forId0("A192GCMKW");
|
||||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256GCMKW = forId0("A256GCMKW");
|
public static final KeyAlgorithm<SecretKey, SecretKey> A256GCMKW = forId0("A256GCMKW");
|
||||||
public static final EncryptedKeyAlgorithm<RSAPublicKey, RSAPrivateKey> RSA1_5 = forId0("RSA1_5");
|
@SuppressWarnings("rawtypes")
|
||||||
public static final EncryptedKeyAlgorithm<RSAPublicKey, RSAPrivateKey> RSA_OAEP = forId0("RSA-OAEP");
|
public static final RsaKeyAlgorithm RSA1_5 = forId0("RSA1_5");
|
||||||
public static final EncryptedKeyAlgorithm<RSAPublicKey, RSAPrivateKey> RSA_OAEP_256 = forId0("RSA-OAEP-256");
|
@SuppressWarnings("rawtypes")
|
||||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> PBES2_HS256_A128KW = forId0("PBES2-HS256+A128KW");
|
public static final RsaKeyAlgorithm RSA_OAEP = forId0("RSA-OAEP");
|
||||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> PBES2_HS384_A192KW = forId0("PBES2-HS384+A192KW");
|
@SuppressWarnings("rawtypes")
|
||||||
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> PBES2_HS512_A256KW = forId0("PBES2-HS512+A256KW");
|
public static final RsaKeyAlgorithm RSA_OAEP_256 = forId0("RSA-OAEP-256");
|
||||||
|
public static final KeyAlgorithm<PbeKey, SecretKey> PBES2_HS256_A128KW = forId0("PBES2-HS256+A128KW");
|
||||||
|
public static final KeyAlgorithm<PbeKey, SecretKey> PBES2_HS384_A192KW = forId0("PBES2-HS384+A192KW");
|
||||||
|
public static final KeyAlgorithm<PbeKey, SecretKey> PBES2_HS512_A256KW = forId0("PBES2-HS512+A256KW");
|
||||||
|
|
||||||
|
public static int estimateIterations(KeyAlgorithm<PbeKey, SecretKey> alg, long desiredMillis) {
|
||||||
|
return Classes.invokeStatic(BRIDGE_CLASSNAME, "estimateIterations", ESTIMATE_ITERATIONS_ARG_TYPES, alg, desiredMillis);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,5 +9,7 @@ import java.security.Key;
|
||||||
*/
|
*/
|
||||||
public interface KeyRequest<T, K extends Key> extends CryptoRequest<T, K> {
|
public interface KeyRequest<T, K extends Key> extends CryptoRequest<T, K> {
|
||||||
|
|
||||||
|
SymmetricAeadAlgorithm getEncryptionAlgorithm();
|
||||||
|
|
||||||
JweHeader getHeader();
|
JweHeader getHeader();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
package io.jsonwebtoken.security;
|
|
||||||
|
|
||||||
import io.jsonwebtoken.Header;
|
|
||||||
|
|
||||||
import java.security.Key;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @since JJWT_RELEASE_VERSION
|
|
||||||
*/
|
|
||||||
public interface KeyResolver {
|
|
||||||
|
|
||||||
Key resolveKey(Header<?> header);
|
|
||||||
}
|
|
|
@ -1,12 +1,9 @@
|
||||||
package io.jsonwebtoken.security;
|
package io.jsonwebtoken.security;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since JJWT_RELEASE_VERSION
|
* @since JJWT_RELEASE_VERSION
|
||||||
*/
|
*/
|
||||||
public interface KeyResult extends PayloadSupplier<byte[]>, KeySupplier<SecretKey> {
|
public interface KeyResult extends PayloadSupplier<byte[]>, KeySupplier<SecretKey> {
|
||||||
|
|
||||||
Map<String,?> getHeaderParams();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,10 @@
|
||||||
package io.jsonwebtoken.security;
|
package io.jsonwebtoken.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.lang.Classes;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.interfaces.PBEKey;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
|
||||||
|
@ -28,6 +30,8 @@ import java.security.KeyPair;
|
||||||
*/
|
*/
|
||||||
public final class Keys {
|
public final class Keys {
|
||||||
|
|
||||||
|
private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.KeysBridge";
|
||||||
|
|
||||||
//prevent instantiation
|
//prevent instantiation
|
||||||
private Keys() {
|
private Keys() {
|
||||||
}
|
}
|
||||||
|
@ -115,7 +119,7 @@ public final class Keys {
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static SecretKey secretKeyFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException {
|
public static SecretKey secretKeyFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException {
|
||||||
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
|
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
|
||||||
SignatureAlgorithm<?,?> salg = SignatureAlgorithms.forId(alg.name());
|
SignatureAlgorithm<?, ?> salg = SignatureAlgorithms.forId(alg.name());
|
||||||
if (!(salg instanceof SecretKeySignatureAlgorithm)) {
|
if (!(salg instanceof SecretKeySignatureAlgorithm)) {
|
||||||
String msg = "The " + alg.name() + " algorithm does not support shared secret keys.";
|
String msg = "The " + alg.name() + " algorithm does not support shared secret keys.";
|
||||||
throw new IllegalArgumentException(msg);
|
throw new IllegalArgumentException(msg);
|
||||||
|
@ -210,12 +214,20 @@ public final class Keys {
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static KeyPair keyPairFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException {
|
public static KeyPair keyPairFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException {
|
||||||
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
|
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
|
||||||
SignatureAlgorithm<?,?> salg = SignatureAlgorithms.forId(alg.name());
|
SignatureAlgorithm<?, ?> salg = SignatureAlgorithms.forId(alg.name());
|
||||||
if (!(salg instanceof AsymmetricKeySignatureAlgorithm)) {
|
if (!(salg instanceof AsymmetricKeySignatureAlgorithm)) {
|
||||||
String msg = "The " + alg.name() + " algorithm does not support Key Pairs.";
|
String msg = "The " + alg.name() + " algorithm does not support Key Pairs.";
|
||||||
throw new IllegalArgumentException(msg);
|
throw new IllegalArgumentException(msg);
|
||||||
}
|
}
|
||||||
AsymmetricKeySignatureAlgorithm<?,?> asalg = ((AsymmetricKeySignatureAlgorithm<?,?>) salg);
|
AsymmetricKeySignatureAlgorithm<?, ?> asalg = ((AsymmetricKeySignatureAlgorithm<?, ?>) salg);
|
||||||
return asalg.generateKeyPair();
|
return asalg.generateKeyPair();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PbeKey toPbeKey(PBEKey key) {
|
||||||
|
return forPbe().forKey(key).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PbeKeyBuilder<PbeKey> forPbe() {
|
||||||
|
return Classes.invokeStatic(BRIDGE_CLASSNAME, "forPbe", null, (Object[]) null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,9 @@ public abstract class LocatorAdapter<H extends Header<H>, R> implements Locator<
|
||||||
return locate((JwsHeader) header);
|
return locate((JwsHeader) header);
|
||||||
} else if (header instanceof JweHeader) {
|
} else if (header instanceof JweHeader) {
|
||||||
return locate((JweHeader) header);
|
return locate((JweHeader) header);
|
||||||
|
} else {
|
||||||
|
return doLocate(header);
|
||||||
}
|
}
|
||||||
String msg = "Unrecognized header type: " + header.getClass().getName();
|
|
||||||
throw new IllegalStateException(msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected R locate(JweHeader header) {
|
protected R locate(JweHeader header) {
|
||||||
|
@ -27,4 +27,8 @@ public abstract class LocatorAdapter<H extends Header<H>, R> implements Locator<
|
||||||
protected R locate(JwsHeader header) {
|
protected R locate(JwsHeader header) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected R doLocate(Header<?> header) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package io.jsonwebtoken.security;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
|
public interface PbeKey extends SecretKey {
|
||||||
|
|
||||||
|
char[] getPassword();
|
||||||
|
|
||||||
|
int getWorkFactor();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package io.jsonwebtoken.security;
|
||||||
|
|
||||||
|
import javax.crypto.interfaces.PBEKey;
|
||||||
|
|
||||||
|
public interface PbeKeyBuilder<K extends PbeKey> {
|
||||||
|
|
||||||
|
PbeKeyBuilder<K> forKey(PBEKey jcaKey);
|
||||||
|
|
||||||
|
PbeKeyBuilder<K> setPassword(String password);
|
||||||
|
|
||||||
|
PbeKeyBuilder<K> setPassword(char[] password);
|
||||||
|
|
||||||
|
PbeKeyBuilder<K> setWorkFactor(int workFactor);
|
||||||
|
|
||||||
|
K build();
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package io.jsonwebtoken.security;
|
||||||
|
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.interfaces.RSAKey;
|
||||||
|
|
||||||
|
public interface RsaKeyAlgorithm<EK extends RSAKey & PublicKey, DK extends RSAKey & PrivateKey> extends KeyAlgorithm<EK, DK> {
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import io.jsonwebtoken.Identifiable;
|
||||||
*/
|
*/
|
||||||
public interface SymmetricAeadAlgorithm extends Identifiable, SecretKeyGenerator {
|
public interface SymmetricAeadAlgorithm extends Identifiable, SecretKeyGenerator {
|
||||||
|
|
||||||
AeadResult encrypt(SymmetricAeadRequest request) throws CryptoException, KeyException;
|
AeadResult encrypt(SymmetricAeadRequest request) throws SecurityException;
|
||||||
|
|
||||||
PayloadSupplier<byte[]> decrypt(SymmetricAeadDecryptionRequest request) throws CryptoException, KeyException;
|
PayloadSupplier<byte[]> decrypt(SymmetricAeadDecryptionRequest request) throws SecurityException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,5 @@ package io.jsonwebtoken.security;
|
||||||
/**
|
/**
|
||||||
* @since JJWT_RELEASE_VERSION
|
* @since JJWT_RELEASE_VERSION
|
||||||
*/
|
*/
|
||||||
public interface SymmetricAeadDecryptionRequest extends SymmetricAeadRequest, InitializationVectorSource, AuthenticationTagSource {
|
public interface SymmetricAeadDecryptionRequest extends SymmetricAeadRequest, InitializationVectorSupplier, DigestSupplier {
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,5 @@ import javax.crypto.SecretKey;
|
||||||
/**
|
/**
|
||||||
* @since JJWT_RELEASE_VERSION
|
* @since JJWT_RELEASE_VERSION
|
||||||
*/
|
*/
|
||||||
public interface SymmetricAeadRequest extends CryptoRequest<byte[], SecretKey>, AssociatedDataSource {
|
public interface SymmetricAeadRequest extends CryptoRequest<byte[], SecretKey>, AssociatedDataSupplier {
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,5 @@ import java.security.Key;
|
||||||
/**
|
/**
|
||||||
* @since JJWT_RELEASE_VERSION
|
* @since JJWT_RELEASE_VERSION
|
||||||
*/
|
*/
|
||||||
public interface VerifySignatureRequest<K extends Key> extends SignatureRequest<K> {
|
public interface VerifySignatureRequest<K extends Key> extends SignatureRequest<K>, DigestSupplier {
|
||||||
|
|
||||||
byte[] getSignature();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import io.jsonwebtoken.impl.lang.Function;
|
||||||
import io.jsonwebtoken.impl.lang.PropagatingExceptionFunction;
|
import io.jsonwebtoken.impl.lang.PropagatingExceptionFunction;
|
||||||
import io.jsonwebtoken.impl.lang.Services;
|
import io.jsonwebtoken.impl.lang.Services;
|
||||||
import io.jsonwebtoken.impl.security.DefaultKeyRequest;
|
import io.jsonwebtoken.impl.security.DefaultKeyRequest;
|
||||||
import io.jsonwebtoken.impl.security.DefaultPBEKey;
|
|
||||||
import io.jsonwebtoken.impl.security.DefaultSymmetricAeadRequest;
|
import io.jsonwebtoken.impl.security.DefaultSymmetricAeadRequest;
|
||||||
import io.jsonwebtoken.io.SerializationException;
|
import io.jsonwebtoken.io.SerializationException;
|
||||||
import io.jsonwebtoken.io.Serializer;
|
import io.jsonwebtoken.io.Serializer;
|
||||||
|
@ -16,25 +15,24 @@ import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.lang.Collections;
|
import io.jsonwebtoken.lang.Collections;
|
||||||
import io.jsonwebtoken.lang.Strings;
|
import io.jsonwebtoken.lang.Strings;
|
||||||
import io.jsonwebtoken.security.AeadResult;
|
import io.jsonwebtoken.security.AeadResult;
|
||||||
import io.jsonwebtoken.security.EncryptedKeyAlgorithm;
|
|
||||||
import io.jsonwebtoken.security.KeyAlgorithm;
|
import io.jsonwebtoken.security.KeyAlgorithm;
|
||||||
import io.jsonwebtoken.security.KeyAlgorithms;
|
import io.jsonwebtoken.security.KeyAlgorithms;
|
||||||
import io.jsonwebtoken.security.KeyRequest;
|
import io.jsonwebtoken.security.KeyRequest;
|
||||||
import io.jsonwebtoken.security.KeyResult;
|
import io.jsonwebtoken.security.KeyResult;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import io.jsonwebtoken.security.PbeKey;
|
||||||
import io.jsonwebtoken.security.SecurityException;
|
import io.jsonwebtoken.security.SecurityException;
|
||||||
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
|
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
|
||||||
import io.jsonwebtoken.security.SymmetricAeadRequest;
|
import io.jsonwebtoken.security.SymmetricAeadRequest;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.interfaces.PBEKey;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class DefaultJweBuilder extends DefaultJwtBuilder<JweBuilder> implements JweBuilder {
|
public class DefaultJweBuilder extends DefaultJwtBuilder<JweBuilder> implements JweBuilder {
|
||||||
|
|
||||||
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 SymmetricAeadAlgorithm enc; // MUST be Symmetric AEAD per https://tools.ietf.org/html/rfc7516#section-4.1.2
|
||||||
private Function<SymmetricAeadRequest, AeadResult> encFunction;
|
private Function<SymmetricAeadRequest, AeadResult> encFunction;
|
||||||
|
|
||||||
|
@ -82,6 +80,12 @@ public class DefaultJweBuilder extends DefaultJwtBuilder<JweBuilder> implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JweBuilder withKey(SecretKey key) {
|
public JweBuilder withKey(SecretKey key) {
|
||||||
|
if (key instanceof PBEKey) {
|
||||||
|
key = Keys.toPbeKey((PBEKey) key);
|
||||||
|
}
|
||||||
|
if (key instanceof PbeKey) {
|
||||||
|
return withKeyFrom((PbeKey) key, KeyAlgorithms.PBES2_HS512_A256KW);
|
||||||
|
}
|
||||||
return withKeyFrom(key, KeyAlgorithms.DIRECT);
|
return withKeyFrom(key, KeyAlgorithms.DIRECT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,11 +108,6 @@ public class DefaultJweBuilder extends DefaultJwtBuilder<JweBuilder> implements
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public JweBuilder withKeyFrom(char[] password, int iterations, EncryptedKeyAlgorithm<SecretKey, SecretKey> alg) {
|
|
||||||
return withKeyFrom(new DefaultPBEKey(password, iterations, alg.getId()), alg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String compact() {
|
public String compact() {
|
||||||
|
|
||||||
|
@ -146,15 +145,13 @@ public class DefaultJweBuilder extends DefaultJwtBuilder<JweBuilder> implements
|
||||||
jweHeader.setCompressionAlgorithm(compressionCodec.getAlgorithmName());
|
jweHeader.setCompressionAlgorithm(compressionCodec.getAlgorithmName());
|
||||||
}
|
}
|
||||||
|
|
||||||
SecretKey cek = alg instanceof EncryptedKeyAlgorithm ? enc.generateKey() : EMPTY_SECRET_KEY; //for algorithms that don't need one
|
KeyRequest<SecretKey, Key> keyRequest = new DefaultKeyRequest<>(this.provider, this.secureRandom, null, this.key, jweHeader, enc);
|
||||||
KeyRequest<SecretKey, Key> keyRequest = new DefaultKeyRequest<>(this.provider, this.secureRandom, cek, this.key, jweHeader);
|
|
||||||
KeyResult keyResult = algFunction.apply(keyRequest);
|
KeyResult keyResult = algFunction.apply(keyRequest);
|
||||||
|
|
||||||
Assert.state(keyResult != null, "KeyAlgorithm must return a KeyResult.");
|
Assert.state(keyResult != null, "KeyAlgorithm must return a KeyResult.");
|
||||||
cek = Assert.notNull(keyResult.getKey(), "KeyResult must return a content encryption key.");
|
SecretKey cek = Assert.notNull(keyResult.getKey(), "KeyResult must return a content encryption key.");
|
||||||
byte[] encryptedCek = Assert.notNull(keyResult.getPayload(), "KeyResult must return an encrypted key byte array, even if empty.");
|
byte[] encryptedCek = Assert.notNull(keyResult.getPayload(), "KeyResult must return an encrypted key byte array, even if empty.");
|
||||||
|
|
||||||
jweHeader.putAll(keyResult.getHeaderParams());
|
|
||||||
jweHeader.setAlgorithm(alg.getId());
|
jweHeader.setAlgorithm(alg.getId());
|
||||||
jweHeader.setEncryptionAlgorithm(enc.getId());
|
jweHeader.setEncryptionAlgorithm(enc.getId());
|
||||||
|
|
||||||
|
@ -167,7 +164,7 @@ public class DefaultJweBuilder extends DefaultJwtBuilder<JweBuilder> implements
|
||||||
|
|
||||||
byte[] iv = Assert.notEmpty(encResult.getInitializationVector(), "Encryption result must have a non-empty initialization vector.");
|
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()).");
|
byte[] ciphertext = Assert.notEmpty(encResult.getPayload(), "Encryption result must have non-empty ciphertext (result.getData()).");
|
||||||
byte[] tag = Assert.notEmpty(encResult.getAuthenticationTag(), "Encryption result must have a non-empty authentication tag.");
|
byte[] tag = Assert.notEmpty(encResult.getDigest(), "Encryption result must have a non-empty authentication tag.");
|
||||||
|
|
||||||
String base64UrlEncodedEncryptedCek = base64UrlEncoder.encode(encryptedCek);
|
String base64UrlEncodedEncryptedCek = base64UrlEncoder.encode(encryptedCek);
|
||||||
String base64UrlEncodedIv = base64UrlEncoder.encode(iv);
|
String base64UrlEncodedIv = base64UrlEncoder.encode(iv);
|
||||||
|
|
|
@ -122,13 +122,13 @@ public class DefaultJwtBuilder<T extends JwtBuilder<T>> implements JwtBuilder<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T setHeader(Map<String, Object> header) {
|
public T setHeader(Map<String, ?> header) {
|
||||||
this.header = new DefaultHeader<>(header);
|
this.header = new DefaultHeader<>(header);
|
||||||
return (T)this;
|
return (T)this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T setHeaderParams(Map<String, Object> params) {
|
public T setHeaderParams(Map<String, ?> params) {
|
||||||
if (!Collections.isEmpty(params)) {
|
if (!Collections.isEmpty(params)) {
|
||||||
Header<?> header = ensureHeader();
|
Header<?> header = ensureHeader();
|
||||||
header.putAll(params);
|
header.putAll(params);
|
||||||
|
@ -237,7 +237,7 @@ public class DefaultJwtBuilder<T extends JwtBuilder<T>> implements JwtBuilder<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T addClaims(Map<String, Object> claims) {
|
public T addClaims(Map<String, ?> claims) {
|
||||||
ensureClaims().putAll(claims);
|
ensureClaims().putAll(claims);
|
||||||
return (T)this;
|
return (T)this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -455,7 +455,7 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
throw new UnsupportedJwtException(msg);
|
throw new UnsupportedJwtException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyRequest<byte[], ?> request = new DefaultKeyRequest<>(this.provider, null, cekBytes, key, jweHeader);
|
KeyRequest<byte[], ?> request = new DefaultKeyRequest<>(this.provider, null, cekBytes, key, jweHeader, encAlg);
|
||||||
final SecretKey cek = keyAlg.getDecryptionKey(request);
|
final SecretKey cek = keyAlg.getDecryptionKey(request);
|
||||||
|
|
||||||
SymmetricAeadDecryptionRequest decryptRequest =
|
SymmetricAeadDecryptionRequest decryptRequest =
|
||||||
|
|
|
@ -216,7 +216,7 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JwtParserBuilder addEncryptionAlgorithms(Collection<SymmetricAeadAlgorithm> encAlgs) {
|
public JwtParserBuilder addEncryptionAlgorithms(Collection<SymmetricAeadAlgorithm> encAlgs) {
|
||||||
Assert.notEmpty(encAlgs, "Additional EncryptionAlgorithm collection cannot be null or empty.");
|
Assert.notEmpty(encAlgs, "Additional SymmetricAeadAlgorithm collection cannot be null or empty.");
|
||||||
this.extraEncryptionAlgorithms.addAll(encAlgs);
|
this.extraEncryptionAlgorithms.addAll(encAlgs);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ public final class Bytes {
|
||||||
return ints;
|
return ints;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] plus(byte[]... arrays) {
|
public static byte[] concat(byte[]... arrays) {
|
||||||
int len = 0;
|
int len = 0;
|
||||||
int count = Arrays.length(arrays);
|
int count = Arrays.length(arrays);
|
||||||
for(int i = 0; i < count; i++) {
|
for(int i = 0; i < count; i++) {
|
||||||
|
@ -90,6 +90,14 @@ public final class Bytes {
|
||||||
return bytes == null ? 0 : bytes.length * (long)Byte.SIZE;
|
return bytes == null ? 0 : bytes.length * (long)Byte.SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String bitsMsg(long bitLength) {
|
||||||
|
return bitLength + " bits (" + bitLength / Byte.SIZE + " bytes)";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String bytesMsg(int byteArrayLength) {
|
||||||
|
return bitsMsg((long)byteArrayLength * Byte.SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
public static void increment(byte[] a) {
|
public static void increment(byte[] a) {
|
||||||
for (int i = a.length - 1; i >= 0; --i) {
|
for (int i = a.length - 1; i >= 0; --i) {
|
||||||
if (++a[i] != 0) {
|
if (++a[i] != 0) {
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
package io.jsonwebtoken.impl.lang;
|
|
||||||
|
|
||||||
public interface Supplier<T> {
|
|
||||||
|
|
||||||
T get();
|
|
||||||
}
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package io.jsonwebtoken.impl.lang;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
public interface ValueGetter {
|
||||||
|
|
||||||
|
String getRequiredString(String key);
|
||||||
|
|
||||||
|
int getRequiredInteger(String key);
|
||||||
|
|
||||||
|
int getRequiredPositiveInteger(String key);
|
||||||
|
|
||||||
|
byte[] getRequiredBytes(String key);
|
||||||
|
|
||||||
|
byte[] getRequiredBytes(String key, int requiredByteLength);
|
||||||
|
|
||||||
|
BigInteger getRequiredBigInt(String key, boolean sensitive);
|
||||||
|
}
|
|
@ -96,8 +96,9 @@ abstract class AbstractEcJwkFactory<K extends Key & ECKey, J extends Jwk<K>> ext
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns {@code true} if a given elliptic curve contains the specified {@code point}, {@code false} otherwise.
|
* Returns {@code true} if a given elliptic {@code curve} contains the specified {@code point}, {@code false}
|
||||||
* Assumes elliptic curves over finite fields adhering to the reduced (a.k.a short or narrow) Weierstrass form:
|
* otherwise. Assumes elliptic curves over finite fields adhering to the reduced (a.k.a short or narrow)
|
||||||
|
* Weierstrass form:
|
||||||
* <p>
|
* <p>
|
||||||
* <code>y<sup>2</sup> = x<sup>3</sup> + ax + b</code>
|
* <code>y<sup>2</sup> = x<sup>3</sup> + ax + b</code>
|
||||||
* </p>
|
* </p>
|
||||||
|
@ -125,24 +126,6 @@ abstract class AbstractEcJwkFactory<K extends Key & ECKey, J extends Jwk<K>> ext
|
||||||
return lhs.equals(rhs);
|
return lhs.equals(rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ECPublicKey derivePublic(final JwkContext<ECPrivateKey> ctx) {
|
|
||||||
final ECPrivateKey key = ctx.getKey();
|
|
||||||
final ECParameterSpec params = key.getParams();
|
|
||||||
final ECPoint w = multiply(params.getGenerator(), key.getS(), params);
|
|
||||||
final ECPublicKeySpec spec = new ECPublicKeySpec(w, params);
|
|
||||||
return generateKey(ctx, ECPublicKey.class, new CheckedFunction<KeyFactory, ECPublicKey>() {
|
|
||||||
@Override
|
|
||||||
public ECPublicKey apply(KeyFactory kf) {
|
|
||||||
try {
|
|
||||||
return (ECPublicKey) kf.generatePublic(spec);
|
|
||||||
} catch (Exception e) {
|
|
||||||
String msg = "Unable to derive ECPublicKey from ECPrivateKey {" + ctx + "}.";
|
|
||||||
throw new UnsupportedKeyException(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multiply a point {@code p} by scalar {@code s} on the curve identified by {@code spec}.
|
* Multiply a point {@code p} by scalar {@code s} on the curve identified by {@code spec}.
|
||||||
*
|
*
|
||||||
|
@ -217,4 +200,22 @@ abstract class AbstractEcJwkFactory<K extends Key & ECKey, J extends Jwk<K>> ext
|
||||||
AbstractEcJwkFactory(Class<K> keyType) {
|
AbstractEcJwkFactory(Class<K> keyType) {
|
||||||
super(DefaultEcPublicJwk.TYPE_VALUE, keyType);
|
super(DefaultEcPublicJwk.TYPE_VALUE, keyType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ECPublicKey derivePublic(final JwkContext<ECPrivateKey> ctx) {
|
||||||
|
final ECPrivateKey key = ctx.getKey();
|
||||||
|
final ECParameterSpec params = key.getParams();
|
||||||
|
final ECPoint w = multiply(params.getGenerator(), key.getS(), params);
|
||||||
|
final ECPublicKeySpec spec = new ECPublicKeySpec(w, params);
|
||||||
|
return generateKey(ctx, ECPublicKey.class, new CheckedFunction<KeyFactory, ECPublicKey>() {
|
||||||
|
@Override
|
||||||
|
public ECPublicKey apply(KeyFactory kf) {
|
||||||
|
try {
|
||||||
|
return (ECPublicKey) kf.generatePublic(spec);
|
||||||
|
} catch (Exception e) {
|
||||||
|
String msg = "Unable to derive ECPublicKey from ECPrivateKey {" + ctx + "}.";
|
||||||
|
throw new UnsupportedKeyException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
import io.jsonwebtoken.io.Decoders;
|
|
||||||
import io.jsonwebtoken.io.Encoders;
|
import io.jsonwebtoken.io.Encoders;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.lang.Strings;
|
|
||||||
import io.jsonwebtoken.security.InvalidKeyException;
|
import io.jsonwebtoken.security.InvalidKeyException;
|
||||||
import io.jsonwebtoken.security.Jwk;
|
import io.jsonwebtoken.security.Jwk;
|
||||||
import io.jsonwebtoken.security.MalformedKeyException;
|
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
@ -17,35 +14,6 @@ import java.security.spec.InvalidKeySpecException;
|
||||||
|
|
||||||
abstract class AbstractFamilyJwkFactory<K extends Key, J extends Jwk<K>> implements FamilyJwkFactory<K, J> {
|
abstract class AbstractFamilyJwkFactory<K extends Key, J extends Jwk<K>> implements FamilyJwkFactory<K, J> {
|
||||||
|
|
||||||
static void malformed(String msg) {
|
|
||||||
throw new MalformedKeyException(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getRequiredString(JwkContext<?> ctx, String name) {
|
|
||||||
Assert.notNull(ctx, "JWK map cannot be null or empty.");
|
|
||||||
Object value = ctx.get(name);
|
|
||||||
if (value == null) {
|
|
||||||
malformed("JWK is missing required case-sensitive '" + name + "' member.");
|
|
||||||
}
|
|
||||||
String s = String.valueOf(value);
|
|
||||||
if (!Strings.hasText(s)) {
|
|
||||||
malformed("JWK '" + name + "' member cannot be null or empty.");
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
static BigInteger getRequiredBigInt(JwkContext<?> ctx, String name, boolean sensitive) {
|
|
||||||
String s = getRequiredString(ctx, name);
|
|
||||||
try {
|
|
||||||
byte[] bytes = Decoders.BASE64URL.decode(s);
|
|
||||||
return new BigInteger(1, bytes);
|
|
||||||
} catch (Exception e) {
|
|
||||||
String val = sensitive ? AbstractJwk.REDACTED_VALUE : s;
|
|
||||||
String msg = "Unable to decode JWK member '" + name + "' to BigInteger from value: " + val;
|
|
||||||
throw new MalformedKeyException(msg, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from Apache Commons Codec 1.14:
|
// 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
|
// 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) {
|
static byte[] toUnsignedBytes(BigInteger bigInt) {
|
||||||
|
@ -109,9 +77,9 @@ abstract class AbstractFamilyJwkFactory<K extends Key, J extends Jwk<K>> impleme
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <T extends Key> T generateKey(final JwkContext<?> ctx, final Class<T> type, final CheckedFunction<KeyFactory, T> fn) {
|
protected <T extends Key> T generateKey(final JwkContext<?> ctx, final Class<T> type, final CheckedFunction<KeyFactory, T> fn) {
|
||||||
return new JcaTemplate(getId(), ctx.getProvider()).execute(KeyFactory.class, new InstanceCallback<KeyFactory, T>() {
|
return new JcaTemplate(getId(), ctx.getProvider()).execute(KeyFactory.class, new CheckedFunction<KeyFactory, T>() {
|
||||||
@Override
|
@Override
|
||||||
public T doWithInstance(KeyFactory instance) throws Exception {
|
public T apply(KeyFactory instance) throws Exception {
|
||||||
try {
|
try {
|
||||||
return fn.apply(instance);
|
return fn.apply(instance);
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||||
|
|
|
@ -62,10 +62,7 @@ abstract class AbstractJwk<K extends Key> implements Jwk<K> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsKey(Object key) {
|
public boolean containsKey(Object key) {
|
||||||
if (key instanceof String) {
|
return this.context.containsKey(key);
|
||||||
return this.context.containsKey((String) key);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -75,10 +72,7 @@ abstract class AbstractJwk<K extends Key> implements Jwk<K> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object get(Object key) {
|
public Object get(Object key) {
|
||||||
if (key instanceof String) {
|
return this.context.get(key);
|
||||||
return this.context.get((String) key);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -132,9 +126,8 @@ abstract class AbstractJwk<K extends Key> implements Jwk<K> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof AbstractJwk) {
|
if (obj instanceof Map) {
|
||||||
AbstractJwk<?> other = (AbstractJwk<?>) obj;
|
return this.context.equals(obj);
|
||||||
return this.context.equals(other.context);
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.security.SecurityRequest;
|
||||||
|
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
abstract class AbstractSecurityRequest implements SecurityRequest {
|
||||||
|
|
||||||
|
private final Provider provider;
|
||||||
|
private final SecureRandom secureRandom;
|
||||||
|
|
||||||
|
public AbstractSecurityRequest(Provider provider, SecureRandom secureRandom) {
|
||||||
|
this.provider = provider;
|
||||||
|
this.secureRandom = secureRandom;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Provider getProvider() {
|
||||||
|
return this.provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecureRandom getSecureRandom() {
|
||||||
|
return this.secureRandom;
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ abstract class AbstractSignatureAlgorithm<SK extends Key, VK extends Key> extend
|
||||||
public boolean verify(VerifySignatureRequest<VK> request) throws SecurityException {
|
public boolean verify(VerifySignatureRequest<VK> request) throws SecurityException {
|
||||||
final VK key = Assert.notNull(request.getKey(), "Request key cannot be null.");
|
final VK key = Assert.notNull(request.getKey(), "Request key cannot be null.");
|
||||||
Assert.notEmpty(request.getPayload(), "Request payload cannot be null or empty.");
|
Assert.notEmpty(request.getPayload(), "Request payload cannot be null or empty.");
|
||||||
Assert.notEmpty(request.getSignature(), "Request signature byte array cannot be null or empty.");
|
Assert.notEmpty(request.getDigest(), "Request signature byte array cannot be null or empty.");
|
||||||
try {
|
try {
|
||||||
validateKey(key, false);
|
validateKey(key, false);
|
||||||
return doVerify(request);
|
return doVerify(request);
|
||||||
|
@ -59,7 +59,7 @@ abstract class AbstractSignatureAlgorithm<SK extends Key, VK extends Key> extend
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean doVerify(VerifySignatureRequest<VK> request) throws Exception {
|
protected boolean doVerify(VerifySignatureRequest<VK> request) throws Exception {
|
||||||
byte[] providedSignature = request.getSignature();
|
byte[] providedSignature = request.getDigest();
|
||||||
Assert.notEmpty(providedSignature, "Request signature byte array cannot be null or empty.");
|
Assert.notEmpty(providedSignature, "Request signature byte array cannot be null or empty.");
|
||||||
@SuppressWarnings("unchecked") byte[] computedSignature = sign((SignatureRequest<SK>)request);
|
@SuppressWarnings("unchecked") byte[] computedSignature = sign((SignatureRequest<SK>)request);
|
||||||
return MessageDigest.isEqual(providedSignature, computedSignature);
|
return MessageDigest.isEqual(providedSignature, computedSignature);
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.lang.Bytes;
|
||||||
import io.jsonwebtoken.lang.Arrays;
|
import io.jsonwebtoken.lang.Arrays;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.AssociatedDataSource;
|
import io.jsonwebtoken.security.AssociatedDataSupplier;
|
||||||
import io.jsonwebtoken.security.CryptoRequest;
|
import io.jsonwebtoken.security.InitializationVectorSupplier;
|
||||||
import io.jsonwebtoken.security.InitializationVectorSource;
|
import io.jsonwebtoken.security.KeySupplier;
|
||||||
import io.jsonwebtoken.security.SecurityRequest;
|
|
||||||
import io.jsonwebtoken.security.SecretKeyGenerator;
|
import io.jsonwebtoken.security.SecretKeyGenerator;
|
||||||
|
import io.jsonwebtoken.security.SecurityRequest;
|
||||||
import io.jsonwebtoken.security.WeakKeyException;
|
import io.jsonwebtoken.security.WeakKeyException;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
@ -29,41 +30,41 @@ abstract class AesAlgorithm extends CryptoAlgorithm implements SecretKeyGenerato
|
||||||
"requests that do not include initialization vectors. AES ciphertext without an IV is weak and " +
|
"requests that do not include initialization vectors. AES ciphertext without an IV is weak and " +
|
||||||
"susceptible to attack.";
|
"susceptible to attack.";
|
||||||
|
|
||||||
protected final int keyLength;
|
protected final int keyBitLength;
|
||||||
protected final int ivLength;
|
protected final int ivBitLength;
|
||||||
protected final int tagLength;
|
protected final int tagBitLength;
|
||||||
protected final boolean gcm;
|
protected final boolean gcm;
|
||||||
|
|
||||||
AesAlgorithm(String id, String jcaTransformation, int keyLength) {
|
AesAlgorithm(String id, String jcaTransformation, int keyBitLength) {
|
||||||
super(id, jcaTransformation);
|
super(id, jcaTransformation);
|
||||||
Assert.isTrue(keyLength == 128 || keyLength == 192 || keyLength == 256, "Invalid AES key length: it must equal 128, 192, or 256.");
|
Assert.isTrue(keyBitLength == 128 || keyBitLength == 192 || keyBitLength == 256, "Invalid AES key length: it must equal 128, 192, or 256.");
|
||||||
this.keyLength = keyLength;
|
this.keyBitLength = keyBitLength;
|
||||||
this.gcm = jcaTransformation.startsWith("AES/GCM");
|
this.gcm = jcaTransformation.startsWith("AES/GCM");
|
||||||
this.ivLength = jcaTransformation.equals("AESWrap") ? 0 : (this.gcm ? GCM_IV_SIZE : BLOCK_SIZE);
|
this.ivBitLength = 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 :
|
// https://tools.ietf.org/html/rfc7518#section-5.2.3 through https://tools.ietf.org/html/rfc7518#section-5.3 :
|
||||||
this.tagLength = this.gcm ? BLOCK_SIZE : this.keyLength;
|
this.tagBitLength = this.gcm ? BLOCK_SIZE : this.keyBitLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKey generateKey() {
|
public SecretKey generateKey() {
|
||||||
return new JcaTemplate(KEY_ALG_NAME, null).generateSecretKey(this.keyLength);
|
return new JcaTemplate(KEY_ALG_NAME, null).generateSecretKey(this.keyBitLength);
|
||||||
//TODO: assert generated key length?
|
//TODO: assert generated key length?
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SecretKey assertKey(CryptoRequest<?,SecretKey> request) {
|
protected SecretKey assertKey(KeySupplier<? extends SecretKey> request) {
|
||||||
SecretKey key = Assert.notNull(request.getKey(), "Request key cannot be null.");
|
SecretKey key = Assert.notNull(request.getKey(), "Request key cannot be null.");
|
||||||
validateLengthIfPossible(key);
|
validateLengthIfPossible(key);
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateLengthIfPossible(SecretKey key) {
|
private void validateLengthIfPossible(SecretKey key) {
|
||||||
validateLength(key, this.keyLength, false);
|
validateLength(key, this.keyBitLength, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static String lengthMsg(String id, String type, int requiredLengthInBits, int actualLengthInBits) {
|
protected static String lengthMsg(String id, String type, int requiredLengthInBits, int actualLengthInBits) {
|
||||||
return "The '" + id + "' algorithm requires " + type + " with a length of " + requiredLengthInBits +
|
return "The '" + id + "' algorithm requires " + type + " with a length of " +
|
||||||
" bits (" + (requiredLengthInBits / Byte.SIZE) + " bytes). The provided key has a length of " +
|
Bytes.bitsMsg(requiredLengthInBits) + ". The provided key has a length of " +
|
||||||
actualLengthInBits + " bits (" + actualLengthInBits / Byte.SIZE + " bytes).";
|
Bytes.bitsMsg(actualLengthInBits) + ".";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected byte[] validateLength(SecretKey key, int requiredBitLength, boolean propagate) {
|
protected byte[] validateLength(SecretKey key, int requiredBitLength, boolean propagate) {
|
||||||
|
@ -88,8 +89,8 @@ abstract class AesAlgorithm extends CryptoAlgorithm implements SecretKeyGenerato
|
||||||
|
|
||||||
byte[] assertIvLength(final byte[] iv) {
|
byte[] assertIvLength(final byte[] iv) {
|
||||||
int length = length(iv);
|
int length = length(iv);
|
||||||
if ((this.ivLength / Byte.SIZE) != length) {
|
if ((this.ivBitLength / Byte.SIZE) != length) {
|
||||||
String msg = lengthMsg(getId(), "initialization vectors", this.ivLength, length * Byte.SIZE);
|
String msg = lengthMsg(getId(), "initialization vectors", this.ivBitLength, length * Byte.SIZE);
|
||||||
throw new IllegalArgumentException(msg);
|
throw new IllegalArgumentException(msg);
|
||||||
}
|
}
|
||||||
return iv;
|
return iv;
|
||||||
|
@ -97,14 +98,14 @@ abstract class AesAlgorithm extends CryptoAlgorithm implements SecretKeyGenerato
|
||||||
|
|
||||||
byte[] assertTag(byte[] tag) {
|
byte[] assertTag(byte[] tag) {
|
||||||
int len = Arrays.length(tag) * Byte.SIZE;
|
int len = Arrays.length(tag) * Byte.SIZE;
|
||||||
if (this.tagLength != len) {
|
if (this.tagBitLength != len) {
|
||||||
String msg = lengthMsg(getId(), "authentication tags", this.tagLength, len);
|
String msg = lengthMsg(getId(), "authentication tags", this.tagBitLength, len);
|
||||||
throw new IllegalArgumentException(msg);
|
throw new IllegalArgumentException(msg);
|
||||||
}
|
}
|
||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] assertDecryptionIv(InitializationVectorSource src) throws IllegalArgumentException {
|
byte[] assertDecryptionIv(InitializationVectorSupplier src) throws IllegalArgumentException {
|
||||||
byte[] iv = src.getInitializationVector();
|
byte[] iv = src.getInitializationVector();
|
||||||
Assert.notEmpty(iv, DECRYPT_NO_IV);
|
Assert.notEmpty(iv, DECRYPT_NO_IV);
|
||||||
return assertIvLength(iv);
|
return assertIvLength(iv);
|
||||||
|
@ -112,10 +113,10 @@ abstract class AesAlgorithm extends CryptoAlgorithm implements SecretKeyGenerato
|
||||||
|
|
||||||
protected byte[] ensureInitializationVector(SecurityRequest request) {
|
protected byte[] ensureInitializationVector(SecurityRequest request) {
|
||||||
byte[] iv = null;
|
byte[] iv = null;
|
||||||
if (request instanceof InitializationVectorSource) {
|
if (request instanceof InitializationVectorSupplier) {
|
||||||
iv = Arrays.clean(((InitializationVectorSource) request).getInitializationVector());
|
iv = Arrays.clean(((InitializationVectorSupplier) request).getInitializationVector());
|
||||||
}
|
}
|
||||||
int ivByteLength = this.ivLength / Byte.SIZE;
|
int ivByteLength = this.ivBitLength / Byte.SIZE;
|
||||||
if (iv == null || iv.length == 0) {
|
if (iv == null || iv.length == 0) {
|
||||||
iv = new byte[ivByteLength];
|
iv = new byte[ivByteLength];
|
||||||
SecureRandom random = ensureSecureRandom(request);
|
SecureRandom random = ensureSecureRandom(request);
|
||||||
|
@ -135,16 +136,9 @@ abstract class AesAlgorithm extends CryptoAlgorithm implements SecretKeyGenerato
|
||||||
|
|
||||||
protected byte[] getAAD(SecurityRequest request) {
|
protected byte[] getAAD(SecurityRequest request) {
|
||||||
byte[] aad = null;
|
byte[] aad = null;
|
||||||
if (request instanceof AssociatedDataSource) {
|
if (request instanceof AssociatedDataSupplier) {
|
||||||
aad = Arrays.clean(((AssociatedDataSource) request).getAssociatedData());
|
aad = Arrays.clean(((AssociatedDataSupplier) request).getAssociatedData());
|
||||||
}
|
}
|
||||||
return aad;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,26 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.JweHeader;
|
import io.jsonwebtoken.JweHeader;
|
||||||
import io.jsonwebtoken.MalformedJwtException;
|
import io.jsonwebtoken.impl.lang.Bytes;
|
||||||
import io.jsonwebtoken.io.Decoders;
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
|
import io.jsonwebtoken.impl.lang.ValueGetter;
|
||||||
import io.jsonwebtoken.io.Encoders;
|
import io.jsonwebtoken.io.Encoders;
|
||||||
import io.jsonwebtoken.lang.Arrays;
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.lang.Maps;
|
import io.jsonwebtoken.security.KeyAlgorithm;
|
||||||
import io.jsonwebtoken.security.EncryptedKeyAlgorithm;
|
|
||||||
import io.jsonwebtoken.security.KeyRequest;
|
import io.jsonwebtoken.security.KeyRequest;
|
||||||
import io.jsonwebtoken.security.KeyResult;
|
import io.jsonwebtoken.security.KeyResult;
|
||||||
import io.jsonwebtoken.security.SecurityException;
|
import io.jsonwebtoken.security.SecurityException;
|
||||||
|
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.spec.AlgorithmParameterSpec;
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since JJWT_RELEASE_VERSION
|
* @since JJWT_RELEASE_VERSION
|
||||||
*/
|
*/
|
||||||
public class AesGcmKeyAlgorithm extends AesAlgorithm implements EncryptedKeyAlgorithm<SecretKey, SecretKey> {
|
public class AesGcmKeyAlgorithm extends AesAlgorithm implements KeyAlgorithm<SecretKey, SecretKey> {
|
||||||
|
|
||||||
public static final String TRANSFORMATION = "AES/GCM/NoPadding";
|
public static final String TRANSFORMATION = "AES/GCM/NoPadding";
|
||||||
|
|
||||||
|
@ -34,19 +33,20 @@ public class AesGcmKeyAlgorithm extends AesAlgorithm implements EncryptedKeyAlgo
|
||||||
|
|
||||||
Assert.notNull(request, "request cannot be null.");
|
Assert.notNull(request, "request cannot be null.");
|
||||||
final SecretKey kek = assertKey(request);
|
final SecretKey kek = assertKey(request);
|
||||||
final SecretKey cek = Assert.notNull(request.getPayload(), "Request content encryption key (request.getPayload()) cannot be null.");
|
SymmetricAeadAlgorithm enc = Assert.notNull(request.getEncryptionAlgorithm(), "Request encryptionAlgorithm cannot be null.");
|
||||||
|
final SecretKey cek = Assert.notNull(enc.generateKey(), "Request encryption algorithm cannot generate a null key.");
|
||||||
final byte[] iv = ensureInitializationVector(request);
|
final byte[] iv = ensureInitializationVector(request);
|
||||||
final AlgorithmParameterSpec ivSpec = getIvSpec(iv);
|
final AlgorithmParameterSpec ivSpec = getIvSpec(iv);
|
||||||
|
|
||||||
byte[] taggedCiphertext = execute(request, Cipher.class, new InstanceCallback<Cipher, byte[]>() {
|
byte[] taggedCiphertext = execute(request, Cipher.class, new CheckedFunction<Cipher, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
public byte[] doWithInstance(Cipher cipher) throws Exception {
|
public byte[] apply(Cipher cipher) throws Exception {
|
||||||
cipher.init(Cipher.WRAP_MODE, kek, ivSpec);
|
cipher.init(Cipher.WRAP_MODE, kek, ivSpec);
|
||||||
return cipher.wrap(cek);
|
return cipher.wrap(cek);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
int tagByteLength = this.tagLength / Byte.SIZE;
|
int tagByteLength = this.tagBitLength / Byte.SIZE;
|
||||||
// When using GCM mode, the JDK appends the authentication tag to the ciphertext, so let's extract it:
|
// When using GCM mode, the JDK appends the authentication tag to the ciphertext, so let's extract it:
|
||||||
int ciphertextLength = taggedCiphertext.length - tagByteLength;
|
int ciphertextLength = taggedCiphertext.length - tagByteLength;
|
||||||
byte[] ciphertext = new byte[ciphertextLength];
|
byte[] ciphertext = new byte[ciphertextLength];
|
||||||
|
@ -56,9 +56,10 @@ public class AesGcmKeyAlgorithm extends AesAlgorithm implements EncryptedKeyAlgo
|
||||||
|
|
||||||
String encodedIv = Encoders.BASE64URL.encode(iv);
|
String encodedIv = Encoders.BASE64URL.encode(iv);
|
||||||
String encodedTag = Encoders.BASE64URL.encode(tag);
|
String encodedTag = Encoders.BASE64URL.encode(tag);
|
||||||
Map<String,String> extraParams = Maps.of("iv", encodedIv).and("tag", encodedTag).build();
|
request.getHeader().put("iv", encodedIv);
|
||||||
|
request.getHeader().put("tag", encodedTag);
|
||||||
|
|
||||||
return new DefaultKeyResult(ciphertext, cek, extraParams);
|
return new DefaultKeyResult(cek, ciphertext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -67,53 +68,22 @@ public class AesGcmKeyAlgorithm extends AesAlgorithm implements EncryptedKeyAlgo
|
||||||
final SecretKey kek = assertKey(request);
|
final SecretKey kek = assertKey(request);
|
||||||
final byte[] cekBytes = Assert.notEmpty(request.getPayload(), "Decryption request payload (ciphertext) cannot be null or empty.");
|
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 JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null.");
|
||||||
final byte[] tag = getHeaderByteArray(header, "tag", this.tagLength / Byte.SIZE);
|
final ValueGetter getter = new DefaultValueGetter(header);
|
||||||
final byte[] iv = getHeaderByteArray(header, "iv", this.ivLength / Byte.SIZE);
|
final byte[] tag = getter.getRequiredBytes("tag", this.tagBitLength / Byte.SIZE);
|
||||||
|
final byte[] iv = getter.getRequiredBytes("iv", this.ivBitLength / Byte.SIZE);
|
||||||
final AlgorithmParameterSpec ivSpec = getIvSpec(iv);
|
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:
|
//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);
|
final byte[] taggedCiphertext = Bytes.concat(cekBytes, tag);
|
||||||
|
|
||||||
return execute(request, Cipher.class, new InstanceCallback<Cipher, SecretKey>() {
|
return execute(request, Cipher.class, new CheckedFunction<Cipher, SecretKey>() {
|
||||||
@Override
|
@Override
|
||||||
public SecretKey doWithInstance(Cipher cipher) throws Exception {
|
public SecretKey apply(Cipher cipher) throws Exception {
|
||||||
cipher.init(Cipher.UNWRAP_MODE, kek, ivSpec);
|
cipher.init(Cipher.UNWRAP_MODE, kek, ivSpec);
|
||||||
Key key = cipher.unwrap(taggedCiphertext, KEY_ALG_NAME, Cipher.SECRET_KEY);
|
Key key = cipher.unwrap(taggedCiphertext, KEY_ALG_NAME, Cipher.SECRET_KEY);
|
||||||
Assert.state(key instanceof SecretKey, "cipher.unwrap must produce a SecretKey instance.");
|
Assert.state(key instanceof SecretKey, "cipher.unwrap must produce a SecretKey instance.");
|
||||||
return (SecretKey)key;
|
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.";
|
|
||||||
throw new MalformedJwtException(msg);
|
|
||||||
}
|
|
||||||
if (!(value instanceof String)) {
|
|
||||||
String msg = "The " + getId() + " Key Management Algorithm requires the JweHeader '" + name + "' value to be a Base64URL-encoded String. Actual type: " + value.getClass().getName();
|
|
||||||
throw new MalformedJwtException(msg);
|
|
||||||
}
|
|
||||||
String encoded = (String)value;
|
|
||||||
|
|
||||||
byte[] decoded;
|
|
||||||
try {
|
|
||||||
decoded = Decoders.BASE64URL.decode(encoded);
|
|
||||||
} catch (Exception e) {
|
|
||||||
String msg = "JweHeader '" + name + "' value '" + encoded +
|
|
||||||
"' does not appear to be a valid Base64URL String: " + e.getMessage();
|
|
||||||
throw new MalformedJwtException(msg, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
int len = Arrays.length(decoded);
|
|
||||||
if (len != requiredByteLength) {
|
|
||||||
String msg = "The '" + getId() + "' key management algorithm requires the JweHeader '" + name +
|
|
||||||
"' 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.EncryptedKeyAlgorithm;
|
import io.jsonwebtoken.security.KeyAlgorithm;
|
||||||
import io.jsonwebtoken.security.KeyRequest;
|
import io.jsonwebtoken.security.KeyRequest;
|
||||||
import io.jsonwebtoken.security.KeyResult;
|
import io.jsonwebtoken.security.KeyResult;
|
||||||
import io.jsonwebtoken.security.SecurityException;
|
import io.jsonwebtoken.security.SecurityException;
|
||||||
|
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
@ -13,7 +15,7 @@ import java.security.Key;
|
||||||
/**
|
/**
|
||||||
* @since JJWT_RELEASE_VERSION
|
* @since JJWT_RELEASE_VERSION
|
||||||
*/
|
*/
|
||||||
public class AesWrapKeyAlgorithm extends AesAlgorithm implements EncryptedKeyAlgorithm<SecretKey, SecretKey> {
|
public class AesWrapKeyAlgorithm extends AesAlgorithm implements KeyAlgorithm<SecretKey, SecretKey> {
|
||||||
|
|
||||||
private static final String TRANSFORMATION = "AESWrap";
|
private static final String TRANSFORMATION = "AESWrap";
|
||||||
|
|
||||||
|
@ -25,17 +27,19 @@ public class AesWrapKeyAlgorithm extends AesAlgorithm implements EncryptedKeyAlg
|
||||||
public KeyResult getEncryptionKey(KeyRequest<SecretKey, SecretKey> request) throws SecurityException {
|
public KeyResult getEncryptionKey(KeyRequest<SecretKey, SecretKey> request) throws SecurityException {
|
||||||
Assert.notNull(request, "request cannot be null.");
|
Assert.notNull(request, "request cannot be null.");
|
||||||
final SecretKey kek = assertKey(request);
|
final SecretKey kek = assertKey(request);
|
||||||
final SecretKey cek = Assert.notNull(request.getPayload(), "Request content encryption key (request.getPayload()) cannot be null.");
|
SymmetricAeadAlgorithm enc = Assert.notNull(request.getEncryptionAlgorithm(), "Request encryptionAlgorithm cannot be null.");
|
||||||
|
final SecretKey cek = enc.generateKey();
|
||||||
|
Assert.notNull(cek, "Request encryption algorithm cannot generate a null key.");
|
||||||
|
|
||||||
byte[] ciphertext = execute(request, Cipher.class, new InstanceCallback<Cipher, byte[]>() {
|
byte[] ciphertext = execute(request, Cipher.class, new CheckedFunction<Cipher, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
public byte[] doWithInstance(Cipher cipher) throws Exception {
|
public byte[] apply(Cipher cipher) throws Exception {
|
||||||
cipher.init(Cipher.WRAP_MODE, kek);
|
cipher.init(Cipher.WRAP_MODE, kek);
|
||||||
return cipher.wrap(cek);
|
return cipher.wrap(cek);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return new DefaultKeyResult(ciphertext, cek);
|
return new DefaultKeyResult(cek, ciphertext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -44,9 +48,9 @@ public class AesWrapKeyAlgorithm extends AesAlgorithm implements EncryptedKeyAlg
|
||||||
final SecretKey kek = assertKey(request);
|
final SecretKey kek = assertKey(request);
|
||||||
final byte[] cekBytes = Assert.notEmpty(request.getPayload(), "Request encrypted key (request.getPayload()) cannot be null or empty.");
|
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>() {
|
return execute(request, Cipher.class, new CheckedFunction<Cipher, SecretKey>() {
|
||||||
@Override
|
@Override
|
||||||
public SecretKey doWithInstance(Cipher cipher) throws Exception {
|
public SecretKey apply(Cipher cipher) throws Exception {
|
||||||
cipher.init(Cipher.UNWRAP_MODE, kek);
|
cipher.init(Cipher.UNWRAP_MODE, kek);
|
||||||
Key key = cipher.unwrap(cekBytes, KEY_ALG_NAME, Cipher.SECRET_KEY);
|
Key key = cipher.unwrap(cekBytes, KEY_ALG_NAME, Cipher.SECRET_KEY);
|
||||||
Assert.state(key instanceof SecretKey, "Cipher unwrap must return a SecretKey instance.");
|
Assert.state(key instanceof SecretKey, "Cipher unwrap must return a SecretKey instance.");
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
import io.jsonwebtoken.lang.Arrays;
|
import io.jsonwebtoken.lang.Arrays;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.CryptoException;
|
import io.jsonwebtoken.security.CryptoException;
|
||||||
|
@ -29,9 +30,9 @@ final class ConcatKDF extends CryptoAlgorithm {
|
||||||
|
|
||||||
ConcatKDF(String jcaName) {
|
ConcatKDF(String jcaName) {
|
||||||
super("ConcatKDF", jcaName);
|
super("ConcatKDF", jcaName);
|
||||||
int hashByteLength = new JcaTemplate(jcaName, null).execute(MessageDigest.class, new InstanceCallback<MessageDigest, Integer>() {
|
int hashByteLength = execute(MessageDigest.class, new CheckedFunction<MessageDigest, Integer>() {
|
||||||
@Override
|
@Override
|
||||||
public Integer doWithInstance(MessageDigest instance) {
|
public Integer apply(MessageDigest instance) {
|
||||||
return instance.getDigestLength();
|
return instance.getDigestLength();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -74,9 +75,8 @@ final class ConcatKDF extends CryptoAlgorithm {
|
||||||
}
|
}
|
||||||
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf, Section 5.8.1.1, Input requirement #2:
|
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf, Section 5.8.1.1, Input requirement #2:
|
||||||
if (derivedKeyBitLength > MAX_DERIVED_KEY_BIT_LENGTH) {
|
if (derivedKeyBitLength > MAX_DERIVED_KEY_BIT_LENGTH) {
|
||||||
String msg = "derivedKeyBitLength for " + getJcaName() + " derived keys may not exceed " +
|
String msg = "derivedKeyBitLength for " + getJcaName() + "-derived keys may not exceed " +
|
||||||
MAX_DERIVED_KEY_BIT_LENGTH + " bits (" + MAX_DERIVED_KEY_BIT_LENGTH / Byte.SIZE + " bytes). " +
|
bitsMsg(MAX_DERIVED_KEY_BIT_LENGTH) + ". Specified size: " + bitsMsg(derivedKeyBitLength) + ".";
|
||||||
"Specified size: " + derivedKeyBitLength + " bits (" + derivedKeyBitLength / Byte.SIZE + " bytes).";
|
|
||||||
throw new IllegalArgumentException(msg);
|
throw new IllegalArgumentException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,11 +94,11 @@ final class ConcatKDF extends CryptoAlgorithm {
|
||||||
long inputBitLength = bitLength(counter) + bitLength(Z) + bitLength(OtherInfo);
|
long inputBitLength = bitLength(counter) + bitLength(Z) + bitLength(OtherInfo);
|
||||||
assert inputBitLength <= MAX_HASH_INPUT_BIT_LENGTH : "Hash input is too large.";
|
assert inputBitLength <= MAX_HASH_INPUT_BIT_LENGTH : "Hash input is too large.";
|
||||||
|
|
||||||
byte[] derivedKeyBytes = new JcaTemplate(getJcaName(), null).execute(MessageDigest.class, new InstanceCallback<MessageDigest, byte[]>() {
|
byte[] derivedKeyBytes = new JcaTemplate(getJcaName(), null).execute(MessageDigest.class, new CheckedFunction<MessageDigest, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
public byte[] doWithInstance(MessageDigest md) throws Exception {
|
public byte[] apply(MessageDigest md) throws Exception {
|
||||||
|
|
||||||
final ByteArrayOutputStream stream = new ByteArrayOutputStream((int)derivedKeyByteLength);
|
final ByteArrayOutputStream stream = new ByteArrayOutputStream((int) derivedKeyByteLength);
|
||||||
long kLastIndex = reps - 1;
|
long kLastIndex = reps - 1;
|
||||||
|
|
||||||
// Section 5.8.1.1, Process step #5:
|
// Section 5.8.1.1, Process step #5:
|
||||||
|
@ -118,7 +118,7 @@ final class ConcatKDF extends CryptoAlgorithm {
|
||||||
// Section 5.8.1.1, Process step #6:
|
// Section 5.8.1.1, Process step #6:
|
||||||
if (i == kLastIndex && repsd != (double) reps) { //repsd calculation above didn't result in a whole number:
|
if (i == kLastIndex && repsd != (double) reps) { //repsd calculation above didn't result in a whole number:
|
||||||
long leftmostBitLength = derivedKeyBitLength % hashBitLength;
|
long leftmostBitLength = derivedKeyBitLength % hashBitLength;
|
||||||
int leftmostByteLength = (int)(leftmostBitLength / Byte.SIZE);
|
int leftmostByteLength = (int) (leftmostBitLength / Byte.SIZE);
|
||||||
byte[] kLast = new byte[leftmostByteLength];
|
byte[] kLast = new byte[leftmostByteLength];
|
||||||
System.arraycopy(Ki, 0, kLast, 0, kLast.length);
|
System.arraycopy(Ki, 0, kLast, 0, kLast.length);
|
||||||
Ki = kLast;
|
Ki = kLast;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.Identifiable;
|
import io.jsonwebtoken.Identifiable;
|
||||||
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.SecurityRequest;
|
import io.jsonwebtoken.security.SecurityRequest;
|
||||||
|
|
||||||
|
@ -30,17 +31,20 @@ abstract class CryptoAlgorithm implements Identifiable {
|
||||||
}
|
}
|
||||||
|
|
||||||
SecureRandom ensureSecureRandom(SecurityRequest request) {
|
SecureRandom ensureSecureRandom(SecurityRequest request) {
|
||||||
Assert.notNull(request, "request cannot be null.");
|
SecureRandom random = request != null ? request.getSecureRandom() : null;
|
||||||
SecureRandom random = request.getSecureRandom();
|
|
||||||
return random != null ? random : Randoms.secureRandom();
|
return random != null ? random : Randoms.secureRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <I, T> T execute(SecurityRequest request, Class<I> clazz, InstanceCallback<I, T> callback) {
|
protected <T, R> R execute(Class<T> clazz, CheckedFunction<T, R> fn) {
|
||||||
|
return new JcaTemplate(getJcaName(), null).execute(clazz, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <I, T> T execute(SecurityRequest request, Class<I> clazz, CheckedFunction<I, T> fn) {
|
||||||
Assert.notNull(request, "request cannot be null.");
|
Assert.notNull(request, "request cannot be null.");
|
||||||
Provider provider = request.getProvider();
|
Provider provider = request.getProvider();
|
||||||
SecureRandom random = ensureSecureRandom(request);
|
SecureRandom random = ensureSecureRandom(request);
|
||||||
JcaTemplate template = new JcaTemplate(getJcaName(), provider, random);
|
JcaTemplate template = new JcaTemplate(getJcaName(), provider, random);
|
||||||
return template.execute(clazz, callback);
|
return template.execute(clazz, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.SymmetricAeadDecryptionRequest;
|
|
||||||
import io.jsonwebtoken.security.AeadResult;
|
import io.jsonwebtoken.security.AeadResult;
|
||||||
|
import io.jsonwebtoken.security.SymmetricAeadDecryptionRequest;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
|
@ -19,7 +19,7 @@ public class DefaultAeadResult extends DefaultSymmetricAeadRequest implements Ae
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getAuthenticationTag() {
|
public byte[] getDigest() {
|
||||||
return this.TAG;
|
return this.TAG;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ public class DefaultCryptoRequest<T, K extends Key> extends DefaultPayloadSuppli
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
private final K key;
|
private final K key;
|
||||||
|
|
||||||
public DefaultCryptoRequest(Provider provider, SecureRandom secureRandom, T data, K key) {
|
public DefaultCryptoRequest(Provider provider, SecureRandom secureRandom, T payload, K key) {
|
||||||
super(data);
|
super(payload);
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.secureRandom = secureRandom;
|
this.secureRandom = secureRandom;
|
||||||
this.key = Assert.notNull(key, "key cannot be null.");
|
this.key = Assert.notNull(key, "key cannot be null.");
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.JwtException;
|
import io.jsonwebtoken.JwtException;
|
||||||
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.EllipticCurveSignatureAlgorithm;
|
import io.jsonwebtoken.security.EllipticCurveSignatureAlgorithm;
|
||||||
import io.jsonwebtoken.security.InvalidKeyException;
|
import io.jsonwebtoken.security.InvalidKeyException;
|
||||||
|
@ -55,9 +56,9 @@ public class DefaultEllipticCurveSignatureAlgorithm<SK extends ECKey & PrivateKe
|
||||||
public KeyPair generateKeyPair() {
|
public KeyPair generateKeyPair() {
|
||||||
final ECGenParameterSpec spec = new ECGenParameterSpec(this.curveName);
|
final ECGenParameterSpec spec = new ECGenParameterSpec(this.curveName);
|
||||||
JcaTemplate template = new JcaTemplate("EC", null);
|
JcaTemplate template = new JcaTemplate("EC", null);
|
||||||
return template.execute(KeyPairGenerator.class, new InstanceCallback<KeyPairGenerator, KeyPair>() {
|
return template.execute(KeyPairGenerator.class, new CheckedFunction<KeyPairGenerator, KeyPair>() {
|
||||||
@Override
|
@Override
|
||||||
public KeyPair doWithInstance(KeyPairGenerator generator) throws Exception {
|
public KeyPair apply(KeyPairGenerator generator) throws Exception {
|
||||||
generator.initialize(spec, Randoms.secureRandom());
|
generator.initialize(spec, Randoms.secureRandom());
|
||||||
return generator.generateKeyPair();
|
return generator.generateKeyPair();
|
||||||
}
|
}
|
||||||
|
@ -104,9 +105,9 @@ public class DefaultEllipticCurveSignatureAlgorithm<SK extends ECKey & PrivateKe
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected byte[] doSign(final SignatureRequest<SK> request) {
|
protected byte[] doSign(final SignatureRequest<SK> request) {
|
||||||
return execute(request, Signature.class, new InstanceCallback<Signature, byte[]>() {
|
return execute(request, Signature.class, new CheckedFunction<Signature, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
public byte[] doWithInstance(Signature sig) throws Exception {
|
public byte[] apply(Signature sig) throws Exception {
|
||||||
sig.initSign(request.getKey());
|
sig.initSign(request.getKey());
|
||||||
sig.update(request.getPayload());
|
sig.update(request.getPayload());
|
||||||
byte[] signature = sig.sign();
|
byte[] signature = sig.sign();
|
||||||
|
@ -117,10 +118,10 @@ public class DefaultEllipticCurveSignatureAlgorithm<SK extends ECKey & PrivateKe
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doVerify(final VerifySignatureRequest<VK> request) {
|
protected boolean doVerify(final VerifySignatureRequest<VK> request) {
|
||||||
return execute(request, Signature.class, new InstanceCallback<Signature, Boolean>() {
|
return execute(request, Signature.class, new CheckedFunction<Signature, Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public Boolean doWithInstance(Signature sig) throws Exception {
|
public Boolean apply(Signature sig) throws Exception {
|
||||||
byte[] signature = request.getSignature();
|
byte[] signature = request.getDigest();
|
||||||
/*
|
/*
|
||||||
* If the expected size is not valid for JOSE, fall back to ASN.1 DER signature.
|
* If the expected size is not valid for JOSE, fall back to ASN.1 DER signature.
|
||||||
* This fallback is for backwards compatibility ONLY (to support tokens generated by previous versions of jjwt)
|
* This fallback is for backwards compatibility ONLY (to support tokens generated by previous versions of jjwt)
|
||||||
|
|
|
@ -1,123 +0,0 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
|
||||||
|
|
||||||
import io.jsonwebtoken.impl.lang.Services;
|
|
||||||
import io.jsonwebtoken.io.Decoder;
|
|
||||||
import io.jsonwebtoken.io.Decoders;
|
|
||||||
import io.jsonwebtoken.io.Deserializer;
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
|
||||||
import io.jsonwebtoken.security.EncryptionAlgorithms;
|
|
||||||
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @since JJWT_RELEASE_VERSION
|
|
||||||
*/
|
|
||||||
public class DefaultJweFactory {
|
|
||||||
|
|
||||||
/*private final Decoder<String, byte[]> base64UrlDecoder;
|
|
||||||
|
|
||||||
private final Deserializer<Map<String, ?>> deserializer;
|
|
||||||
|
|
||||||
private final SymmetricAeadAlgorithm encryptionAlgorithm;
|
|
||||||
|
|
||||||
private static Deserializer<Map<String, ?>> loadDeserializer() {
|
|
||||||
Deserializer deserializer = Services.loadFirst(Deserializer.class);
|
|
||||||
//noinspection unchecked
|
|
||||||
return deserializer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultJweFactory() {
|
|
||||||
//this(Decoders.BASE64URL, loadDeserializer(), EncryptionAlgorithms.A256GCM);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultJweFactory(Decoder<String, byte[]> base64UrlDecoder,
|
|
||||||
Deserializer<Map<String, Object>> deserializer,
|
|
||||||
EncryptionAlgorithm encryptionAlgorithm) {
|
|
||||||
this.base64UrlDecoder = Assert.notNull(base64UrlDecoder, "Base64Url TextCodec cannot be null.");
|
|
||||||
this.deserializer = Assert.notNull(deserializer, "Deserializer cannot be null.");
|
|
||||||
this.encryptionAlgorithm = Assert.notNull(encryptionAlgorithm, "EncryptionAlgorithm cannot be null.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
public Jwe createJwe(String base64UrlProtectedHeader, String base64UrlEncryptedKey, String base64UrlIv,
|
|
||||||
String base64UrlCiphertext, String base64UrlAuthenticationTag) {
|
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
// https://tools.ietf.org/html/rfc7516#section-5.2 #2
|
|
||||||
// ====================================================================
|
|
||||||
|
|
||||||
final byte[] headerBytes = base64UrlDecode(base64UrlProtectedHeader, "Protected Header");
|
|
||||||
|
|
||||||
// encrypted key can be null with Direct Key or Direct Key Agreement
|
|
||||||
// https://tools.ietf.org/html/rfc7516#section-5.2
|
|
||||||
// so we use a 'null safe' variant:
|
|
||||||
final byte[] encryptedKeyBytes = nullSafeBase64UrlDecode(base64UrlEncryptedKey, "Encrypted Key");
|
|
||||||
|
|
||||||
final byte[] iv = base64UrlDecode(base64UrlIv, "Initialization Vector");
|
|
||||||
|
|
||||||
final byte[] ciphertext = base64UrlDecode(base64UrlCiphertext, "Ciphertext");
|
|
||||||
|
|
||||||
final byte[] authcTag = base64UrlDecode(base64UrlAuthenticationTag, "Authentication Tag");
|
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
// https://tools.ietf.org/html/rfc7516#section-5.2 #3
|
|
||||||
// ====================================================================
|
|
||||||
|
|
||||||
Map<String, Object> protectedHeader;
|
|
||||||
try {
|
|
||||||
protectedHeader = parseJson(headerBytes);
|
|
||||||
} catch (Exception e) {
|
|
||||||
String msg = "JWE Protected Header must be a valid JSON object.";
|
|
||||||
throw new IllegalArgumentException(msg, e);
|
|
||||||
}
|
|
||||||
Assert.notEmpty(protectedHeader, "JWE Protected Header cannot be a null or empty JSON object.");
|
|
||||||
|
|
||||||
DefaultJweHeader header = new DefaultJweHeader(protectedHeader);
|
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
// https://tools.ietf.org/html/rfc7516#section-5.2 #4
|
|
||||||
// ====================================================================
|
|
||||||
|
|
||||||
// we currently don't support JSON serialization (just compact), so we can skip #4
|
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
// https://tools.ietf.org/html/rfc7516#section-5.2 #11 and #12
|
|
||||||
// ====================================================================
|
|
||||||
|
|
||||||
|
|
||||||
throw new UnsupportedOperationException("Not yet finished.");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected byte[] nullSafeBase64UrlDecode(String base64UrlEncoded, String jweName) {
|
|
||||||
if (base64UrlEncoded == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return base64UrlDecode(base64UrlEncoded, jweName);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected byte[] base64UrlDecode(String base64UrlEncoded, String jweName) {
|
|
||||||
|
|
||||||
if (base64UrlEncoded == null) {
|
|
||||||
String msg = "Invalid compact JWE: base64url JWE " + jweName + " is missing.";
|
|
||||||
throw new IllegalArgumentException(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return base64UrlDecoder.decode(base64UrlEncoded);
|
|
||||||
} catch (Exception e) {
|
|
||||||
String msg = "Invalid compact JWE: JWE " + jweName +
|
|
||||||
" fragment is invalid and cannot be Base64Url-decoded: " + base64UrlEncoded;
|
|
||||||
throw new IllegalArgumentException(msg, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected Map<String, Object> parseJson(byte[] json) {
|
|
||||||
return deserializer.deserialize(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
}
|
|
|
@ -66,10 +66,10 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
SETTERS = java.util.Collections.unmodifiableMap(s);
|
SETTERS = java.util.Collections.unmodifiableMap(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Map<String, Object> values;
|
private final Map<String, Object> values; // canonical values formatted per RFC requirements
|
||||||
private final Map<String, Object> canonicalValues;
|
private final Map<String, Object> idiomaticValues; // the values map with any string/encoded values converted to Java type-safe values where possible
|
||||||
private final Map<String, Object> redactedValues;
|
private final Map<String, Object> redactedValues; // the values map with any sensitive/secret values redacted. Used in the toString implementation.
|
||||||
private final Set<String> privateMemberNames;
|
private final Set<String> privateMemberNames; // names of values that should be redacted for toString output
|
||||||
private K key;
|
private K key;
|
||||||
private PublicKey publicKey;
|
private PublicKey publicKey;
|
||||||
private Provider provider;
|
private Provider provider;
|
||||||
|
@ -84,7 +84,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
public DefaultJwkContext(Set<String> privateMemberNames) {
|
public DefaultJwkContext(Set<String> privateMemberNames) {
|
||||||
this.privateMemberNames = Assert.notEmpty(privateMemberNames, "privateMemberNames cannot be null or empty.");
|
this.privateMemberNames = Assert.notEmpty(privateMemberNames, "privateMemberNames cannot be null or empty.");
|
||||||
this.values = new LinkedHashMap<>();
|
this.values = new LinkedHashMap<>();
|
||||||
this.canonicalValues = new LinkedHashMap<>();
|
this.idiomaticValues = new LinkedHashMap<>();
|
||||||
this.redactedValues = new LinkedHashMap<>();
|
this.redactedValues = new LinkedHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
DefaultJwkContext<?> src = (DefaultJwkContext<?>) other;
|
DefaultJwkContext<?> src = (DefaultJwkContext<?>) other;
|
||||||
this.provider = other.getProvider();
|
this.provider = other.getProvider();
|
||||||
this.values = new LinkedHashMap<>(src.values);
|
this.values = new LinkedHashMap<>(src.values);
|
||||||
this.canonicalValues = new LinkedHashMap<>(src.canonicalValues);
|
this.idiomaticValues = new LinkedHashMap<>(src.idiomaticValues);
|
||||||
this.redactedValues = new LinkedHashMap<>(src.redactedValues);
|
this.redactedValues = new LinkedHashMap<>(src.redactedValues);
|
||||||
if (removePrivate) {
|
if (removePrivate) {
|
||||||
for (String name : this.privateMemberNames) {
|
for (String name : this.privateMemberNames) {
|
||||||
|
@ -126,7 +126,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
} else {
|
} else {
|
||||||
Object redactedValue = this.privateMemberNames.contains(name) ? AbstractJwk.REDACTED_VALUE : value;
|
Object redactedValue = this.privateMemberNames.contains(name) ? AbstractJwk.REDACTED_VALUE : value;
|
||||||
this.redactedValues.put(name, redactedValue);
|
this.redactedValues.put(name, redactedValue);
|
||||||
this.canonicalValues.put(name, value);
|
this.idiomaticValues.put(name, value);
|
||||||
return this.values.put(name, value);
|
return this.values.put(name, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,13 +139,15 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
} else if (Objects.isArray(value) && !value.getClass().getComponentType().isPrimitive()) {
|
} else if (Objects.isArray(value) && !value.getClass().getComponentType().isPrimitive()) {
|
||||||
value = Collections.arrayToList(value);
|
value = Collections.arrayToList(value);
|
||||||
}
|
}
|
||||||
return doPut(name, value);
|
return idiomaticPut(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object doPut(String name, Object value) {
|
// ensures that if a property name matches an RFC-specified name, that value can be represented
|
||||||
|
// as an idiomatic type-safe Java value in addition to the canonical RFC/encoded value.
|
||||||
|
private Object idiomaticPut(String name, Object value) {
|
||||||
assert name != null; //asserted by caller.
|
assert name != null; //asserted by caller.
|
||||||
Canonicalizer<?> fn = SETTERS.get(name);
|
Canonicalizer<?> fn = SETTERS.get(name);
|
||||||
if (fn != null) { //Setting a JWA-standard property - let's ensure we can represent it canonically:
|
if (fn != null) { //Setting a JWA-standard property - let's ensure we can represent it idiomatically:
|
||||||
return fn.apply(this, value);
|
return fn.apply(this, value);
|
||||||
} else { //non-standard/custom property:
|
} else { //non-standard/custom property:
|
||||||
return nullSafePut(name, value);
|
return nullSafePut(name, value);
|
||||||
|
@ -153,17 +155,17 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JwkContext<K> putAll(Map<? extends String, ?> m) {
|
public void putAll(Map<? extends String, ?> m) {
|
||||||
Assert.notEmpty(m, "JWK values cannot be null or empty.");
|
Assert.notEmpty(m, "JWK values cannot be null or empty.");
|
||||||
for (Map.Entry<? extends String, ?> entry : m.entrySet()) {
|
for (Map.Entry<? extends String, ?> entry : m.entrySet()) {
|
||||||
put(entry.getKey(), entry.getValue());
|
put(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object remove(String key) {
|
@Override
|
||||||
|
public Object remove(Object key) {
|
||||||
this.redactedValues.remove(key);
|
this.redactedValues.remove(key);
|
||||||
this.canonicalValues.remove(key);
|
this.idiomaticValues.remove(key);
|
||||||
return this.values.remove(key);
|
return this.values.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +180,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsKey(String key) {
|
public boolean containsKey(Object key) {
|
||||||
return this.values.containsKey(key);
|
return this.values.containsKey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,10 +190,15 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object get(String key) {
|
public Object get(Object key) {
|
||||||
return this.values.get(key);
|
return this.values.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
throw new UnsupportedOperationException("Cannot clear JwkContext objects.");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> keySet() {
|
public Set<String> keySet() {
|
||||||
return this.values.keySet();
|
return this.values.keySet();
|
||||||
|
@ -207,14 +214,9 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
return this.values.entrySet();
|
return this.values.entrySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getValues() {
|
|
||||||
return this.values;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAlgorithm() {
|
public String getAlgorithm() {
|
||||||
return (String) this.canonicalValues.get(AbstractJwk.ALGORITHM);
|
return (String) this.values.get(AbstractJwk.ALGORITHM);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -225,7 +227,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return (String) this.canonicalValues.get(AbstractJwk.ID);
|
return (String) this.values.get(AbstractJwk.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -237,7 +239,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
@Override
|
@Override
|
||||||
public Set<String> getOperations() {
|
public Set<String> getOperations() {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (Set<String>) this.canonicalValues.get(AbstractJwk.OPERATIONS);
|
return (Set<String>) this.idiomaticValues.get(AbstractJwk.OPERATIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -248,7 +250,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return (String) this.canonicalValues.get(AbstractJwk.TYPE);
|
return (String) this.values.get(AbstractJwk.TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -259,7 +261,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPublicKeyUse() {
|
public String getPublicKeyUse() {
|
||||||
return (String) this.canonicalValues.get(AbstractAsymmetricJwk.PUBLIC_KEY_USE);
|
return (String) this.values.get(AbstractAsymmetricJwk.PUBLIC_KEY_USE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -271,7 +273,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
@Override
|
@Override
|
||||||
public List<X509Certificate> getX509CertificateChain() {
|
public List<X509Certificate> getX509CertificateChain() {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (List<X509Certificate>) this.canonicalValues.get(AbstractAsymmetricJwk.X509_CERT_CHAIN);
|
return (List<X509Certificate>) this.idiomaticValues.get(AbstractAsymmetricJwk.X509_CERT_CHAIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -282,7 +284,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getX509CertificateSha1Thumbprint() {
|
public byte[] getX509CertificateSha1Thumbprint() {
|
||||||
return (byte[]) this.canonicalValues.get(AbstractAsymmetricJwk.X509_SHA1_THUMBPRINT);
|
return (byte[]) this.idiomaticValues.get(AbstractAsymmetricJwk.X509_SHA1_THUMBPRINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -293,7 +295,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getX509CertificateSha256Thumbprint() {
|
public byte[] getX509CertificateSha256Thumbprint() {
|
||||||
return (byte[]) this.canonicalValues.get(AbstractAsymmetricJwk.X509_SHA256_THUMBPRINT);
|
return (byte[]) this.idiomaticValues.get(AbstractAsymmetricJwk.X509_SHA256_THUMBPRINT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -304,7 +306,7 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI getX509Url() {
|
public URI getX509Url() {
|
||||||
return (URI) this.canonicalValues.get(AbstractAsymmetricJwk.X509_URL);
|
return (URI) this.idiomaticValues.get(AbstractAsymmetricJwk.X509_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -353,18 +355,13 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int hash = 7;
|
return this.values.hashCode();
|
||||||
hash = hash * 31 + Objects.nullSafeHashCode(this.key);
|
|
||||||
hash = hash * 31 + Objects.nullSafeHashCode(this.values);
|
|
||||||
return hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof DefaultJwkContext) {
|
if (obj instanceof Map) {
|
||||||
DefaultJwkContext<?> c = (DefaultJwkContext<?>) obj;
|
return this.values.equals(obj);
|
||||||
return Objects.nullSafeEquals(this.key, c.key) &&
|
|
||||||
Objects.nullSafeEquals(this.values, c.values);
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -403,23 +400,23 @@ public class DefaultJwkContext<K extends Key> implements JwkContext<K> {
|
||||||
public T apply(DefaultJwkContext<?> ctx, Object rawValue) {
|
public T apply(DefaultJwkContext<?> ctx, Object rawValue) {
|
||||||
|
|
||||||
if (JwtMap.isReduceableToNull(rawValue)) {
|
if (JwtMap.isReduceableToNull(rawValue)) {
|
||||||
//noinspection unchecked
|
ctx.remove(id);
|
||||||
return (T) ctx.remove(id);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
T canonicalValue;
|
T idiomaticValue; // preferred Java format
|
||||||
Object encodedValue;
|
Object canonicalValue; //as required by the RFC
|
||||||
try {
|
try {
|
||||||
canonicalValue = converter.applyFrom(rawValue);
|
idiomaticValue = converter.applyFrom(rawValue);
|
||||||
encodedValue = converter.applyTo(canonicalValue);
|
canonicalValue = converter.applyTo(idiomaticValue);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String msg = "Invalid JWK " + title + "('" + id + "') value [" + rawValue + "]: " + e.getMessage();
|
String msg = "Invalid JWK '" + id + "' (" + title + ") value [" + rawValue + "]: " + e.getMessage();
|
||||||
throw new MalformedKeyException(msg, e);
|
throw new MalformedKeyException(msg, e);
|
||||||
}
|
}
|
||||||
ctx.nullSafePut(id, encodedValue);
|
ctx.nullSafePut(id, canonicalValue);
|
||||||
ctx.canonicalValues.put(id, canonicalValue);
|
ctx.idiomaticValues.put(id, idiomaticValue);
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (T) encodedValue;
|
return (T) canonicalValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.jsonwebtoken.impl.security;
|
||||||
import io.jsonwebtoken.JweHeader;
|
import io.jsonwebtoken.JweHeader;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.KeyRequest;
|
import io.jsonwebtoken.security.KeyRequest;
|
||||||
|
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
|
||||||
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
|
@ -11,14 +12,29 @@ import java.security.SecureRandom;
|
||||||
public class DefaultKeyRequest<T, K extends Key> extends DefaultCryptoRequest<T, K> implements KeyRequest<T, K> {
|
public class DefaultKeyRequest<T, K extends Key> extends DefaultCryptoRequest<T, K> implements KeyRequest<T, K> {
|
||||||
|
|
||||||
private final JweHeader header;
|
private final JweHeader header;
|
||||||
|
private final SymmetricAeadAlgorithm encryptionAlgorithm;
|
||||||
|
|
||||||
public DefaultKeyRequest(Provider provider, SecureRandom secureRandom, T data, K key, JweHeader header) {
|
public DefaultKeyRequest(Provider provider, SecureRandom secureRandom, T payload, K key, JweHeader header, SymmetricAeadAlgorithm encryptionAlgorithm) {
|
||||||
super(provider, secureRandom, data, key);
|
super(provider, secureRandom, payload, key);
|
||||||
this.header = Assert.notNull(header, "JweHeader cannot be null.");
|
this.header = Assert.notNull(header, "JweHeader cannot be null.");
|
||||||
|
this.encryptionAlgorithm = Assert.notNull(encryptionAlgorithm, "SymmetricAeadAlgorithm argument cannot be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected T assertValidPayload(T payload) throws IllegalArgumentException {
|
||||||
|
if (payload != null) {
|
||||||
|
return super.assertValidPayload(payload);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JweHeader getHeader() {
|
public JweHeader getHeader() {
|
||||||
return this.header;
|
return this.header;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SymmetricAeadAlgorithm getEncryptionAlgorithm() {
|
||||||
|
return this.encryptionAlgorithm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +1,23 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.lang.Bytes;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.KeyResult;
|
import io.jsonwebtoken.security.KeyResult;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class DefaultKeyResult implements KeyResult {
|
public class DefaultKeyResult implements KeyResult {
|
||||||
|
|
||||||
private static final byte[] EMPTY_BYTES = new byte[0];
|
|
||||||
|
|
||||||
private final byte[] payload;
|
private final byte[] payload;
|
||||||
private final SecretKey key;
|
private final SecretKey key;
|
||||||
private final Map<String, ?> headerParams;
|
|
||||||
|
|
||||||
public DefaultKeyResult(SecretKey key) {
|
public DefaultKeyResult(SecretKey key) {
|
||||||
this(EMPTY_BYTES, key);
|
this(key, Bytes.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultKeyResult(byte[] encryptedKey, SecretKey key) {
|
public DefaultKeyResult(SecretKey key, byte[] encryptedKey) {
|
||||||
this(encryptedKey, key, Collections.<String, Object>emptyMap());
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultKeyResult(byte[] encryptedKey, SecretKey key, Map<String, ?> headerParams) {
|
|
||||||
this.payload = Assert.notNull(encryptedKey, "encryptedKey cannot be null (but can be empty).");
|
this.payload = Assert.notNull(encryptedKey, "encryptedKey cannot be null (but can be empty).");
|
||||||
this.key = Assert.notNull(key, "Key argument cannot be null.");
|
this.key = Assert.notNull(key, "Key argument cannot be null.");
|
||||||
Assert.notNull(headerParams, "headerParams cannot be null.");
|
|
||||||
this.headerParams = Collections.unmodifiableMap(new LinkedHashMap<>(headerParams));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -40,9 +29,4 @@ public class DefaultKeyResult implements KeyResult {
|
||||||
public SecretKey getKey() {
|
public SecretKey getKey() {
|
||||||
return this.key;
|
return this.key;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, ?> getHeaderParams() {
|
|
||||||
return this.headerParams;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
|
||||||
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
|
||||||
import io.jsonwebtoken.lang.Objects;
|
|
||||||
|
|
||||||
import javax.crypto.interfaces.PBEKey;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.CharBuffer;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public final class DefaultPBEKey implements PBEKey {
|
|
||||||
|
|
||||||
private static final String RAW_FORMAT = "RAW";
|
|
||||||
|
|
||||||
private volatile boolean destroyed = false;
|
|
||||||
private final char[] chars;
|
|
||||||
private final byte[] bytes;
|
|
||||||
private final int iterations;
|
|
||||||
private final String algorithm;
|
|
||||||
|
|
||||||
private static byte[] toBytes(char[] chars) {
|
|
||||||
ByteBuffer buf = StandardCharsets.UTF_8.encode(CharBuffer.wrap(chars));
|
|
||||||
byte[] bytes = new byte[buf.limit()];
|
|
||||||
buf.get(bytes);
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultPBEKey(char[] password, int iterations, String algorithm) {
|
|
||||||
boolean empty = Objects.isEmpty(password);
|
|
||||||
this.chars = empty ? new char[0] : password.clone();
|
|
||||||
this.bytes = empty ? new byte[0] : toBytes(this.chars);
|
|
||||||
if (iterations <= 0) {
|
|
||||||
String msg = "Iterations must be an integer greater than zero. Value: " + iterations;
|
|
||||||
throw new IllegalArgumentException(msg);
|
|
||||||
}
|
|
||||||
this.iterations = iterations;
|
|
||||||
this.algorithm = Assert.hasText(algorithm, "Algorithm string cannot be null or empty.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertActive() {
|
|
||||||
if (destroyed) {
|
|
||||||
String msg = "PBKey has been destroyed. Password characters or bytes may not be obtained.";
|
|
||||||
throw new IllegalStateException(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char[] getPassword() {
|
|
||||||
assertActive();
|
|
||||||
return this.chars.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getSalt() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIterationCount() {
|
|
||||||
return this.iterations;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAlgorithm() {
|
|
||||||
return this.algorithm;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getFormat() {
|
|
||||||
return RAW_FORMAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getEncoded() {
|
|
||||||
assertActive();
|
|
||||||
return this.bytes.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {
|
|
||||||
if (destroyed) return;
|
|
||||||
java.util.Arrays.fill(bytes, (byte) 0);
|
|
||||||
java.util.Arrays.fill(chars, '\u0000');
|
|
||||||
this.destroyed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDestroyed() {
|
|
||||||
return destroyed;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -10,11 +10,18 @@ class DefaultPayloadSupplier<T> implements PayloadSupplier<T> {
|
||||||
private final T payload;
|
private final T payload;
|
||||||
|
|
||||||
DefaultPayloadSupplier(T payload) {
|
DefaultPayloadSupplier(T payload) {
|
||||||
this.payload = Assert.notNull(payload, "payload cannot be null.");
|
this.payload = assertValidPayload(payload);
|
||||||
Assert.isTrue(payload instanceof byte[] || payload instanceof Key, "Payload argument must be either a byte array or a java.security.Key.");
|
}
|
||||||
if (payload instanceof byte[] && ((byte[]) payload).length == 0) {
|
|
||||||
throw new IllegalArgumentException("Payload byte array cannot be empty.");
|
protected T assertValidPayload(T payload) throws IllegalArgumentException {
|
||||||
|
Assert.notNull(payload, "payload cannot be null.");
|
||||||
|
if (payload instanceof byte[]) {
|
||||||
|
Assert.notEmpty((byte[])payload, "payload byte array cannot be empty.");
|
||||||
|
} else if (!(payload instanceof Key)) {
|
||||||
|
String msg = "payload must be either a byte array or a java.security.Key instance.";
|
||||||
|
throw new IllegalArgumentException(msg);
|
||||||
}
|
}
|
||||||
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.lang.Objects;
|
||||||
|
import io.jsonwebtoken.security.PbeKey;
|
||||||
|
|
||||||
|
public class DefaultPbeKey implements PbeKey {
|
||||||
|
|
||||||
|
private static final String RAW_FORMAT = "RAW";
|
||||||
|
private static final String NONE_ALGORITHM = "NONE";
|
||||||
|
|
||||||
|
private volatile boolean destroyed = false;
|
||||||
|
private final char[] chars;
|
||||||
|
//private final byte[] bytes;
|
||||||
|
private final int workFactor;
|
||||||
|
|
||||||
|
// private static byte[] toBytes(char[] chars) {
|
||||||
|
// ByteBuffer buf = StandardCharsets.UTF_8.encode(CharBuffer.wrap(chars));
|
||||||
|
// byte[] bytes = new byte[buf.limit()];
|
||||||
|
// buf.get(bytes);
|
||||||
|
// return bytes;
|
||||||
|
// }
|
||||||
|
|
||||||
|
public DefaultPbeKey(char[] password, int workFactor) {
|
||||||
|
boolean empty = Objects.isEmpty(password);
|
||||||
|
this.chars = empty ? new char[0] : password.clone();
|
||||||
|
//this.bytes = empty ? new byte[0] : toBytes(this.chars);
|
||||||
|
if (workFactor < 0) {
|
||||||
|
String msg = "workFactor cannot be negative. Value: " + workFactor;
|
||||||
|
throw new IllegalArgumentException(msg);
|
||||||
|
}
|
||||||
|
this.workFactor = workFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertActive() {
|
||||||
|
if (destroyed) {
|
||||||
|
String msg = "PBKey has been destroyed. Password characters or bytes may not be obtained.";
|
||||||
|
throw new IllegalStateException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] getPassword() {
|
||||||
|
assertActive();
|
||||||
|
return this.chars.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWorkFactor() {
|
||||||
|
return this.workFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlgorithm() {
|
||||||
|
return NONE_ALGORITHM;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFormat() {
|
||||||
|
return RAW_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getEncoded() {
|
||||||
|
throw new UnsupportedOperationException("getEncoded is not supported for PbeKey instances.");
|
||||||
|
//assertActive();
|
||||||
|
//return this.bytes.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
// if (bytes != null) {
|
||||||
|
// java.util.Arrays.fill(bytes, (byte) 0);
|
||||||
|
// }
|
||||||
|
if (chars != null) {
|
||||||
|
java.util.Arrays.fill(chars, '\u0000');
|
||||||
|
}
|
||||||
|
this.destroyed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDestroyed() {
|
||||||
|
return destroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.nullSafeHashCode(this.chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj instanceof DefaultPbeKey) {
|
||||||
|
DefaultPbeKey other = (DefaultPbeKey) obj;
|
||||||
|
return this.workFactor == other.workFactor &&
|
||||||
|
Objects.nullSafeEquals(this.chars, other.chars);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "password=<redacted>, workFactor=" + this.workFactor;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.security.PbeKey;
|
||||||
|
import io.jsonwebtoken.security.PbeKeyBuilder;
|
||||||
|
|
||||||
|
import javax.crypto.interfaces.PBEKey;
|
||||||
|
import javax.security.auth.Destroyable;
|
||||||
|
|
||||||
|
//
|
||||||
|
// MAINTAINER NOTE:
|
||||||
|
//
|
||||||
|
// If editing/modifying this class, DO NOT attempt to call jcaKey.getPassword(): doing so creates a clone of that
|
||||||
|
// character array. There is no need to create copies of sensitive data (that we would be responsible for cleaning up)
|
||||||
|
// since the JcaPbeKey implementation will just delegate to the jcaKey as needed.
|
||||||
|
//
|
||||||
|
public class DefaultPbeKeyBuilder<K extends PbeKey> implements PbeKeyBuilder<K>, Destroyable {
|
||||||
|
|
||||||
|
private char[] password;
|
||||||
|
private int workFactor;
|
||||||
|
private PBEKey jcaKey;
|
||||||
|
private volatile boolean destroyed;
|
||||||
|
|
||||||
|
private static char[] assertPassword(char[] password) {
|
||||||
|
Assert.notEmpty(password, "Password cannot be null or empty.");
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int assertWorkFactor(int workFactor) {
|
||||||
|
if (workFactor < 0) {
|
||||||
|
String msg = "workFactor cannot be negative.";
|
||||||
|
throw new IllegalArgumentException(msg);
|
||||||
|
}
|
||||||
|
return workFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PbeKeyBuilder<K> forKey(PBEKey jcaKey) {
|
||||||
|
this.jcaKey = Assert.notNull(jcaKey, "PBEKey cannot be null.");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PbeKeyBuilder<K> setPassword(String password) {
|
||||||
|
return setPassword(Assert.notNull(password, "password cannot be null.").toCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultPbeKeyBuilder<K> setPassword(char[] password) {
|
||||||
|
this.password = password;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultPbeKeyBuilder<K> setWorkFactor(int workFactor) {
|
||||||
|
this.workFactor = workFactor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
if (this.password != null) {
|
||||||
|
destroyed = true;
|
||||||
|
java.util.Arrays.fill(this.password, '\u0000');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDestroyed() {
|
||||||
|
return destroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertActive() {
|
||||||
|
if (destroyed) {
|
||||||
|
String msg = "This PbeKeyBuilder has been destroyed in order to clean/zero-out internal password " +
|
||||||
|
"arrays for safety. Please use a new builder for each PbeKey instance you need to create.";
|
||||||
|
throw new IllegalStateException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public K build() {
|
||||||
|
try {
|
||||||
|
if (this.jcaKey != null) {
|
||||||
|
return (K) new JcaPbeKey(this.jcaKey);
|
||||||
|
}
|
||||||
|
assertActive();
|
||||||
|
assertPassword(this.password);
|
||||||
|
assertWorkFactor(this.workFactor);
|
||||||
|
return (K) new DefaultPbeKey(this.password, this.workFactor);
|
||||||
|
} finally {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.EncryptedKeyAlgorithm;
|
|
||||||
import io.jsonwebtoken.security.KeyRequest;
|
import io.jsonwebtoken.security.KeyRequest;
|
||||||
import io.jsonwebtoken.security.KeyResult;
|
import io.jsonwebtoken.security.KeyResult;
|
||||||
|
import io.jsonwebtoken.security.RsaKeyAlgorithm;
|
||||||
import io.jsonwebtoken.security.SecurityException;
|
import io.jsonwebtoken.security.SecurityException;
|
||||||
|
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
@ -14,8 +16,8 @@ import java.security.PublicKey;
|
||||||
import java.security.interfaces.RSAKey;
|
import java.security.interfaces.RSAKey;
|
||||||
import java.security.spec.AlgorithmParameterSpec;
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
|
|
||||||
public class DefaultRsaKeyAlgorithm<EK extends RSAKey & PublicKey, DK extends RSAKey & PrivateKey> extends CryptoAlgorithm
|
public class DefaultRsaKeyAlgorithm<E extends RSAKey & PublicKey, D extends RSAKey & PrivateKey> extends CryptoAlgorithm
|
||||||
implements EncryptedKeyAlgorithm<EK, DK> {
|
implements RsaKeyAlgorithm<E, D> {
|
||||||
|
|
||||||
private final AlgorithmParameterSpec SPEC; //can be null
|
private final AlgorithmParameterSpec SPEC; //can be null
|
||||||
|
|
||||||
|
@ -29,14 +31,15 @@ public class DefaultRsaKeyAlgorithm<EK extends RSAKey & PublicKey, DK extends RS
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyResult getEncryptionKey(final KeyRequest<SecretKey, EK> request) throws SecurityException {
|
public KeyResult getEncryptionKey(final KeyRequest<SecretKey, E> request) throws SecurityException {
|
||||||
Assert.notNull(request, "Request cannot be null.");
|
Assert.notNull(request, "Request cannot be null.");
|
||||||
final EK kek = Assert.notNull(request.getKey(), "Request key encryption key cannot be null.");
|
final E kek = Assert.notNull(request.getKey(), "Request key encryption key cannot be null.");
|
||||||
final SecretKey cek = Assert.notNull(request.getPayload(), "Request content encryption key (request.getPayload() cannot be null.");
|
SymmetricAeadAlgorithm enc = Assert.notNull(request.getEncryptionAlgorithm(), "Request encryptionAlgorithm cannot be null.");
|
||||||
|
final SecretKey cek = Assert.notNull(enc.generateKey(), "Request encryption algorithm cannot generate a null key.");
|
||||||
|
|
||||||
byte[] ciphertext = execute(request, Cipher.class, new InstanceCallback<Cipher, byte[]>() {
|
byte[] ciphertext = execute(request, Cipher.class, new CheckedFunction<Cipher, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
public byte[] doWithInstance(Cipher cipher) throws Exception {
|
public byte[] apply(Cipher cipher) throws Exception {
|
||||||
if (SPEC == null) {
|
if (SPEC == null) {
|
||||||
cipher.init(Cipher.WRAP_MODE, kek, ensureSecureRandom(request));
|
cipher.init(Cipher.WRAP_MODE, kek, ensureSecureRandom(request));
|
||||||
} else {
|
} else {
|
||||||
|
@ -46,18 +49,18 @@ public class DefaultRsaKeyAlgorithm<EK extends RSAKey & PublicKey, DK extends RS
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return new DefaultKeyResult(ciphertext, cek);
|
return new DefaultKeyResult(cek, ciphertext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKey getDecryptionKey(KeyRequest<byte[], DK> request) throws SecurityException {
|
public SecretKey getDecryptionKey(KeyRequest<byte[], D> request) throws SecurityException {
|
||||||
Assert.notNull(request, "request cannot be null.");
|
Assert.notNull(request, "request cannot be null.");
|
||||||
final DK kek = Assert.notNull(request.getKey(), "Request key decryption key cannot be null.");
|
final D kek = Assert.notNull(request.getKey(), "Request key decryption key cannot be null.");
|
||||||
final byte[] cekBytes = Assert.notEmpty(request.getPayload(), "Request encrypted key (request.getPayload()) cannot be null or empty.");
|
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>() {
|
return execute(request, Cipher.class, new CheckedFunction<Cipher, SecretKey>() {
|
||||||
@Override
|
@Override
|
||||||
public SecretKey doWithInstance(Cipher cipher) throws Exception {
|
public SecretKey apply(Cipher cipher) throws Exception {
|
||||||
if (SPEC == null) {
|
if (SPEC == null) {
|
||||||
cipher.init(Cipher.UNWRAP_MODE, kek);
|
cipher.init(Cipher.UNWRAP_MODE, kek);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
import io.jsonwebtoken.lang.RuntimeEnvironment;
|
import io.jsonwebtoken.lang.RuntimeEnvironment;
|
||||||
import io.jsonwebtoken.security.InvalidKeyException;
|
import io.jsonwebtoken.security.InvalidKeyException;
|
||||||
import io.jsonwebtoken.security.RsaSignatureAlgorithm;
|
import io.jsonwebtoken.security.RsaSignatureAlgorithm;
|
||||||
|
@ -102,10 +103,10 @@ public class DefaultRsaSignatureAlgorithm<SK extends RSAKey & PrivateKey, VK ext
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected byte[] doSign(final SignatureRequest<SK> request) throws Exception {
|
protected byte[] doSign(final SignatureRequest<SK> request) {
|
||||||
return execute(request, Signature.class, new InstanceCallback<Signature, byte[]>() {
|
return execute(request, Signature.class, new CheckedFunction<Signature, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
public byte[] doWithInstance(Signature sig) throws Exception {
|
public byte[] apply(Signature sig) throws Exception {
|
||||||
if (algorithmParameterSpec != null) {
|
if (algorithmParameterSpec != null) {
|
||||||
sig.setParameter(algorithmParameterSpec);
|
sig.setParameter(algorithmParameterSpec);
|
||||||
}
|
}
|
||||||
|
@ -122,15 +123,15 @@ public class DefaultRsaSignatureAlgorithm<SK extends RSAKey & PrivateKey, VK ext
|
||||||
if (key instanceof PrivateKey) { //legacy support only
|
if (key instanceof PrivateKey) { //legacy support only
|
||||||
return super.doVerify(request);
|
return super.doVerify(request);
|
||||||
}
|
}
|
||||||
return execute(request, Signature.class, new InstanceCallback<Signature, Boolean>() {
|
return execute(request, Signature.class, new CheckedFunction<Signature, Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public Boolean doWithInstance(Signature sig) throws Exception {
|
public Boolean apply(Signature sig) throws Exception {
|
||||||
if (algorithmParameterSpec != null) {
|
if (algorithmParameterSpec != null) {
|
||||||
sig.setParameter(algorithmParameterSpec);
|
sig.setParameter(algorithmParameterSpec);
|
||||||
}
|
}
|
||||||
sig.initVerify(request.getKey());
|
sig.initVerify(request.getKey());
|
||||||
sig.update(request.getPayload());
|
sig.update(request.getPayload());
|
||||||
return sig.verify(request.getSignature());
|
return sig.verify(request.getDigest());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.security.SecurityRequest;
|
||||||
|
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
public class DefaultSecurityRequest implements SecurityRequest {
|
||||||
|
|
||||||
|
private final Provider provider;
|
||||||
|
private final SecureRandom secureRandom;
|
||||||
|
|
||||||
|
public DefaultSecurityRequest(Provider provider, SecureRandom secureRandom) {
|
||||||
|
this.provider = provider;
|
||||||
|
this.secureRandom = secureRandom;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Provider getProvider() {
|
||||||
|
return this.provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecureRandom getSecureRandom() {
|
||||||
|
return this.secureRandom;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.security.InitializationVectorSource;
|
import io.jsonwebtoken.security.InitializationVectorSupplier;
|
||||||
import io.jsonwebtoken.security.SymmetricAeadRequest;
|
import io.jsonwebtoken.security.SymmetricAeadRequest;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
public class DefaultSymmetricAeadRequest extends DefaultCryptoRequest<byte[], SecretKey> implements SymmetricAeadRequest, InitializationVectorSource {
|
public class DefaultSymmetricAeadRequest extends DefaultCryptoRequest<byte[], SecretKey> implements SymmetricAeadRequest, InitializationVectorSupplier {
|
||||||
|
|
||||||
private final byte[] IV;
|
private final byte[] IV;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Header;
|
||||||
|
import io.jsonwebtoken.JweHeader;
|
||||||
|
import io.jsonwebtoken.JwsHeader;
|
||||||
|
import io.jsonwebtoken.JwtException;
|
||||||
|
import io.jsonwebtoken.MalformedJwtException;
|
||||||
|
import io.jsonwebtoken.impl.lang.Bytes;
|
||||||
|
import io.jsonwebtoken.impl.lang.ValueGetter;
|
||||||
|
import io.jsonwebtoken.io.Decoders;
|
||||||
|
import io.jsonwebtoken.lang.Arrays;
|
||||||
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.lang.Strings;
|
||||||
|
import io.jsonwebtoken.security.Jwk;
|
||||||
|
import io.jsonwebtoken.security.MalformedKeyException;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows use af shared assertions across codebase, regardless of inheritance hierarchy.
|
||||||
|
*/
|
||||||
|
public class DefaultValueGetter implements ValueGetter {
|
||||||
|
|
||||||
|
private final Map<String, ?> values;
|
||||||
|
|
||||||
|
public DefaultValueGetter(Map<String, ?> values) {
|
||||||
|
this.values = Assert.notEmpty(values, "Values cannot be null or empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String name() {
|
||||||
|
if (values instanceof JweHeader) {
|
||||||
|
return "JWE header";
|
||||||
|
} else if (values instanceof JwsHeader) {
|
||||||
|
return "JWS header";
|
||||||
|
} else if (values instanceof Header) {
|
||||||
|
return "JWT header";
|
||||||
|
} else if (values instanceof Jwk || values instanceof JwkContext) {
|
||||||
|
Object value = values.get(AbstractJwk.TYPE);
|
||||||
|
if (DefaultSecretJwk.TYPE_VALUE.equals(value)) {
|
||||||
|
value = "Secret";
|
||||||
|
}
|
||||||
|
return value instanceof String ? value + " JWK" : "JWK";
|
||||||
|
} else {
|
||||||
|
return "Map";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JwtException malformed(String msg) {
|
||||||
|
if (values instanceof JwkContext || values instanceof Jwk) {
|
||||||
|
return new MalformedKeyException(msg);
|
||||||
|
} else {
|
||||||
|
return new MalformedJwtException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object getRequiredValue(String key) {
|
||||||
|
Object value = this.values.get(key);
|
||||||
|
if (value == null) {
|
||||||
|
String msg = name() + " is missing required '" + key + "' value.";
|
||||||
|
throw malformed(msg);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRequiredString(String key) {
|
||||||
|
Object value = getRequiredValue(key);
|
||||||
|
if (!(value instanceof String)) {
|
||||||
|
String msg = name() + " '" + key + "' value must be a String. Actual type: " + value.getClass().getName();
|
||||||
|
throw malformed(msg);
|
||||||
|
}
|
||||||
|
String sval = Strings.clean((String) value);
|
||||||
|
if (!Strings.hasText(sval)) {
|
||||||
|
String msg = name() + " '" + key + "' string value cannot be null or empty.";
|
||||||
|
throw malformed(msg);
|
||||||
|
}
|
||||||
|
return (String) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRequiredInteger(String key) {
|
||||||
|
Object value = getRequiredValue(key);
|
||||||
|
if (!(value instanceof Integer)) {
|
||||||
|
String msg = name() + " '" + key + "' value must be an Integer. Actual type: " + value.getClass().getName();
|
||||||
|
throw malformed(msg);
|
||||||
|
}
|
||||||
|
return (Integer) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRequiredPositiveInteger(String key) {
|
||||||
|
int value = getRequiredInteger(key);
|
||||||
|
if (value <= 0) {
|
||||||
|
String msg = name() + " '" + key + "' value must be a positive Integer. Value: " + value;
|
||||||
|
throw malformed(msg);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRequiredBytes(String key) {
|
||||||
|
|
||||||
|
String encoded = getRequiredString(key);
|
||||||
|
|
||||||
|
byte[] decoded;
|
||||||
|
try {
|
||||||
|
decoded = Decoders.BASE64URL.decode(encoded);
|
||||||
|
} catch (Exception e) {
|
||||||
|
String msg = name() + " '" + key + "' value is not a valid Base64URL String: " + e.getMessage();
|
||||||
|
throw malformed(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Arrays.length(decoded) == 0) {
|
||||||
|
String msg = name() + " '" + key + "' decoded byte array cannot be empty.";
|
||||||
|
throw malformed(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getRequiredBytes(String key, int requiredByteLength) {
|
||||||
|
byte[] decoded = getRequiredBytes(key);
|
||||||
|
int len = Arrays.length(decoded);
|
||||||
|
if (len != requiredByteLength) {
|
||||||
|
String msg = name() + " '" + key + "' decoded byte array must be " + Bytes.bytesMsg(requiredByteLength) +
|
||||||
|
" long. Actual length: " + Bytes.bytesMsg(len) + ".";
|
||||||
|
throw malformed(msg);
|
||||||
|
}
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigInteger getRequiredBigInt(String key, boolean sensitive) {
|
||||||
|
String s = getRequiredString(key);
|
||||||
|
try {
|
||||||
|
byte[] bytes = Decoders.BASE64URL.decode(s);
|
||||||
|
return new BigInteger(1, bytes);
|
||||||
|
} catch (Exception e) {
|
||||||
|
String msg = "Unable to decode " + name() + " '" + key + "' value";
|
||||||
|
if (!sensitive) {
|
||||||
|
msg += " '" + s + "'";
|
||||||
|
}
|
||||||
|
msg += " to BigInteger: " + e.getMessage();
|
||||||
|
throw malformed(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ public class DefaultVerifySignatureRequest<K extends Key> extends DefaultSignatu
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getSignature() {
|
public byte[] getDigest() {
|
||||||
return this.signature;
|
return this.signature;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
|
import io.jsonwebtoken.impl.lang.ValueGetter;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.EcPrivateJwk;
|
import io.jsonwebtoken.security.EcPrivateJwk;
|
||||||
import io.jsonwebtoken.security.EcPublicJwk;
|
import io.jsonwebtoken.security.EcPublicJwk;
|
||||||
|
@ -55,8 +56,9 @@ class EcPrivateJwkFactory extends AbstractEcJwkFactory<ECPrivateKey, EcPrivateJw
|
||||||
@Override
|
@Override
|
||||||
protected EcPrivateJwk createJwkFromValues(final JwkContext<ECPrivateKey> ctx) {
|
protected EcPrivateJwk createJwkFromValues(final JwkContext<ECPrivateKey> ctx) {
|
||||||
|
|
||||||
String curveId = getRequiredString(ctx, DefaultEcPublicJwk.CURVE_ID);
|
ValueGetter getter = new DefaultValueGetter(ctx);
|
||||||
BigInteger d = getRequiredBigInt(ctx, DefaultEcPrivateJwk.D, true);
|
String curveId = getter.getRequiredString(DefaultEcPublicJwk.CURVE_ID);
|
||||||
|
BigInteger d = getter.getRequiredBigInt(DefaultEcPrivateJwk.D, true);
|
||||||
|
|
||||||
// We don't actually need the public x,y point coordinates for JVM lookup, but the
|
// We don't actually need the public x,y point coordinates for JVM lookup, but the
|
||||||
// [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2)
|
// [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
|
import io.jsonwebtoken.impl.lang.ValueGetter;
|
||||||
import io.jsonwebtoken.security.EcPublicJwk;
|
import io.jsonwebtoken.security.EcPublicJwk;
|
||||||
import io.jsonwebtoken.security.InvalidKeyException;
|
import io.jsonwebtoken.security.InvalidKeyException;
|
||||||
|
|
||||||
|
@ -45,9 +46,10 @@ class EcPublicJwkFactory extends AbstractEcJwkFactory<ECPublicKey, EcPublicJwk>
|
||||||
@Override
|
@Override
|
||||||
protected EcPublicJwk createJwkFromValues(final JwkContext<ECPublicKey> ctx) {
|
protected EcPublicJwk createJwkFromValues(final JwkContext<ECPublicKey> ctx) {
|
||||||
|
|
||||||
String curveId = getRequiredString(ctx, DefaultEcPublicJwk.CURVE_ID);
|
ValueGetter getter = new DefaultValueGetter(ctx);
|
||||||
BigInteger x = getRequiredBigInt(ctx, DefaultEcPublicJwk.X, false);
|
String curveId = getter.getRequiredString(DefaultEcPublicJwk.CURVE_ID);
|
||||||
BigInteger y = getRequiredBigInt(ctx, DefaultEcPublicJwk.Y, false);
|
BigInteger x = getter.getRequiredBigInt(DefaultEcPublicJwk.X, false);
|
||||||
|
BigInteger y = getter.getRequiredBigInt(DefaultEcPublicJwk.Y, false);
|
||||||
|
|
||||||
ECParameterSpec spec = getCurveByJwaId(curveId);
|
ECParameterSpec spec = getCurveByJwaId(curveId);
|
||||||
ECPoint point = new ECPoint(x, y);
|
ECPoint point = new ECPoint(x, y);
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
|
||||||
|
|
||||||
import io.jsonwebtoken.Identifiable;
|
|
||||||
import io.jsonwebtoken.security.CryptoException;
|
|
||||||
import io.jsonwebtoken.security.CryptoRequest;
|
|
||||||
import io.jsonwebtoken.security.KeyException;
|
|
||||||
import io.jsonwebtoken.security.PayloadSupplier;
|
|
||||||
|
|
||||||
import java.security.Key;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @since JJWT_RELEASE_VERSION
|
|
||||||
*/
|
|
||||||
public interface EncryptionAlgorithm<T,
|
|
||||||
EK extends Key, DK extends Key,
|
|
||||||
EReq extends CryptoRequest<T, EK>, ERes extends PayloadSupplier<byte[]>,
|
|
||||||
DReq extends CryptoRequest<byte[], DK>, DRes extends PayloadSupplier<T>> extends Identifiable {
|
|
||||||
|
|
||||||
ERes encrypt(EReq request) throws CryptoException, KeyException;
|
|
||||||
|
|
||||||
DRes decrypt(DReq request) throws CryptoException, KeyException;
|
|
||||||
}
|
|
|
@ -40,7 +40,7 @@ public class EncryptionAlgorithmsBridge {
|
||||||
public static SymmetricAeadAlgorithm forId(String id) {
|
public static SymmetricAeadAlgorithm forId(String id) {
|
||||||
SymmetricAeadAlgorithm alg = findById(id);
|
SymmetricAeadAlgorithm alg = findById(id);
|
||||||
if (alg == null) {
|
if (alg == null) {
|
||||||
String msg = "Unrecognized JWA EncryptionAlgorithm identifier: " + id;
|
String msg = "Unrecognized JWA SymmetricAeadAlgorithm identifier: " + id;
|
||||||
throw new UnsupportedJwtException(msg);
|
throw new UnsupportedJwtException(msg);
|
||||||
}
|
}
|
||||||
return alg;
|
return alg;
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.lang.Bytes;
|
||||||
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
import io.jsonwebtoken.lang.Arrays;
|
import io.jsonwebtoken.lang.Arrays;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.lang.RuntimeEnvironment;
|
import io.jsonwebtoken.lang.RuntimeEnvironment;
|
||||||
|
import io.jsonwebtoken.security.AeadResult;
|
||||||
import io.jsonwebtoken.security.CryptoException;
|
import io.jsonwebtoken.security.CryptoException;
|
||||||
import io.jsonwebtoken.security.KeyException;
|
import io.jsonwebtoken.security.KeyException;
|
||||||
import io.jsonwebtoken.security.PayloadSupplier;
|
import io.jsonwebtoken.security.PayloadSupplier;
|
||||||
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
|
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
|
||||||
import io.jsonwebtoken.security.SymmetricAeadDecryptionRequest;
|
import io.jsonwebtoken.security.SymmetricAeadDecryptionRequest;
|
||||||
import io.jsonwebtoken.security.AeadResult;
|
|
||||||
import io.jsonwebtoken.security.SymmetricAeadRequest;
|
import io.jsonwebtoken.security.SymmetricAeadRequest;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
@ -42,9 +44,9 @@ public class GcmAesAeadAlgorithm extends AesAlgorithm implements SymmetricAeadAl
|
||||||
final byte[] iv = ensureInitializationVector(req);
|
final byte[] iv = ensureInitializationVector(req);
|
||||||
final AlgorithmParameterSpec ivSpec = getIvSpec(iv);
|
final AlgorithmParameterSpec ivSpec = getIvSpec(iv);
|
||||||
|
|
||||||
byte[] taggedCiphertext = execute(req, Cipher.class, new InstanceCallback<Cipher, byte[]>() {
|
byte[] taggedCiphertext = execute(req, Cipher.class, new CheckedFunction<Cipher, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
public byte[] doWithInstance(Cipher cipher) throws Exception {
|
public byte[] apply(Cipher cipher) throws Exception {
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
||||||
if (Arrays.length(aad) > 0) {
|
if (Arrays.length(aad) > 0) {
|
||||||
cipher.updateAAD(aad);
|
cipher.updateAAD(aad);
|
||||||
|
@ -61,7 +63,7 @@ public class GcmAesAeadAlgorithm extends AesAlgorithm implements SymmetricAeadAl
|
||||||
byte[] tag = new byte[BLOCK_BYTE_SIZE];
|
byte[] tag = new byte[BLOCK_BYTE_SIZE];
|
||||||
System.arraycopy(taggedCiphertext, ciphertextLength, tag, 0, BLOCK_BYTE_SIZE);
|
System.arraycopy(taggedCiphertext, ciphertextLength, tag, 0, BLOCK_BYTE_SIZE);
|
||||||
|
|
||||||
return new DefaultAeadResult(req.getProvider(), req.getSecureRandom(),ciphertext, key, aad, tag, iv);
|
return new DefaultAeadResult(req.getProvider(), req.getSecureRandom(), ciphertext, key, aad, tag, iv);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -71,16 +73,16 @@ public class GcmAesAeadAlgorithm extends AesAlgorithm implements SymmetricAeadAl
|
||||||
final SecretKey key = assertKey(req);
|
final SecretKey key = assertKey(req);
|
||||||
final byte[] ciphertext = Assert.notEmpty(req.getPayload(), "Decryption request payload (ciphertext) cannot be null or empty.");
|
final byte[] ciphertext = Assert.notEmpty(req.getPayload(), "Decryption request payload (ciphertext) cannot be null or empty.");
|
||||||
final byte[] aad = getAAD(req);
|
final byte[] aad = getAAD(req);
|
||||||
final byte[] tag = Assert.notEmpty(req.getAuthenticationTag(), "Decryption request authentication tag cannot be null or empty.");
|
final byte[] tag = Assert.notEmpty(req.getDigest(), "Decryption request authentication tag cannot be null or empty.");
|
||||||
final byte[] iv = assertDecryptionIv(req);
|
final byte[] iv = assertDecryptionIv(req);
|
||||||
final AlgorithmParameterSpec ivSpec = getIvSpec(iv);
|
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:
|
//for tagged GCM, the JCA spec requires that the tag be appended to the end of the ciphertext byte array:
|
||||||
final byte[] taggedCiphertext = plus(ciphertext, tag);
|
final byte[] taggedCiphertext = Bytes.concat(ciphertext, tag);
|
||||||
|
|
||||||
byte[] plaintext = execute(req, Cipher.class, new InstanceCallback<Cipher, byte[]>() {
|
byte[] plaintext = execute(req, Cipher.class, new CheckedFunction<Cipher, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
public byte[] doWithInstance(Cipher cipher) throws Exception {
|
public byte[] apply(Cipher cipher) throws Exception {
|
||||||
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
|
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
|
||||||
if (Arrays.length(aad) > 0) {
|
if (Arrays.length(aad) > 0) {
|
||||||
cipher.updateAAD(aad);
|
cipher.updateAAD(aad);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.AeadResult;
|
import io.jsonwebtoken.security.AeadResult;
|
||||||
import io.jsonwebtoken.security.CryptoRequest;
|
import io.jsonwebtoken.security.CryptoRequest;
|
||||||
|
@ -46,12 +47,12 @@ public class HmacAesAeadAlgorithm extends AesAlgorithm implements SymmetricAeadA
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SecretKey generateKey() {
|
public SecretKey generateKey() {
|
||||||
return new JcaTemplate("AES", null).generateSecretKey(this.keyLength * 2);
|
return new JcaTemplate("AES", null).generateSecretKey(this.keyBitLength * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] assertKeyBytes(CryptoRequest<?, SecretKey> request) {
|
byte[] assertKeyBytes(CryptoRequest<?, SecretKey> request) {
|
||||||
SecretKey key = Assert.notNull(request.getKey(), "Request key cannot be null.");
|
SecretKey key = Assert.notNull(request.getKey(), "Request key cannot be null.");
|
||||||
return validateLength(key, this.keyLength * 2, true);
|
return validateLength(key, this.keyBitLength * 2, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -70,9 +71,9 @@ public class HmacAesAeadAlgorithm extends AesAlgorithm implements SymmetricAeadA
|
||||||
final byte[] iv = ensureInitializationVector(req);
|
final byte[] iv = ensureInitializationVector(req);
|
||||||
final AlgorithmParameterSpec ivSpec = getIvSpec(iv);
|
final AlgorithmParameterSpec ivSpec = getIvSpec(iv);
|
||||||
|
|
||||||
final byte[] ciphertext = execute(req, Cipher.class, new InstanceCallback<Cipher, byte[]>() {
|
final byte[] ciphertext = execute(req, Cipher.class, new CheckedFunction<Cipher, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
public byte[] doWithInstance(Cipher cipher) throws Exception {
|
public byte[] apply(Cipher cipher) throws Exception {
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, ivSpec);
|
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, ivSpec);
|
||||||
return cipher.doFinal(plaintext);
|
return cipher.doFinal(plaintext);
|
||||||
}
|
}
|
||||||
|
@ -135,7 +136,7 @@ public class HmacAesAeadAlgorithm extends AesAlgorithm implements SymmetricAeadA
|
||||||
|
|
||||||
final byte[] ciphertext = Assert.notEmpty(req.getPayload(), "Decryption request payload (ciphertext) cannot be null or empty.");
|
final byte[] ciphertext = Assert.notEmpty(req.getPayload(), "Decryption request payload (ciphertext) cannot be null or empty.");
|
||||||
final byte[] aad = getAAD(req);
|
final byte[] aad = getAAD(req);
|
||||||
final byte[] tag = assertTag(req.getAuthenticationTag());
|
final byte[] tag = assertTag(req.getDigest());
|
||||||
final byte[] iv = assertDecryptionIv(req);
|
final byte[] iv = assertDecryptionIv(req);
|
||||||
final AlgorithmParameterSpec ivSpec = getIvSpec(iv);
|
final AlgorithmParameterSpec ivSpec = getIvSpec(iv);
|
||||||
|
|
||||||
|
@ -147,9 +148,9 @@ public class HmacAesAeadAlgorithm extends AesAlgorithm implements SymmetricAeadA
|
||||||
throw new SignatureException(msg);
|
throw new SignatureException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] plaintext = execute(req, Cipher.class, new InstanceCallback<Cipher, byte[]>() {
|
byte[] plaintext = execute(req, Cipher.class, new CheckedFunction<Cipher, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
public byte[] doWithInstance(Cipher cipher) throws Exception {
|
public byte[] apply(Cipher cipher) throws Exception {
|
||||||
cipher.init(Cipher.DECRYPT_MODE, decryptionKey, ivSpec);
|
cipher.init(Cipher.DECRYPT_MODE, decryptionKey, ivSpec);
|
||||||
return cipher.doFinal(ciphertext);
|
return cipher.doFinal(ciphertext);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
|
||||||
|
|
||||||
public interface InstanceCallback<I,O> {
|
|
||||||
|
|
||||||
O doWithInstance(I instance) throws Exception;
|
|
||||||
}
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.security.PbeKey;
|
||||||
|
|
||||||
|
import javax.crypto.interfaces.PBEKey;
|
||||||
|
import javax.security.auth.DestroyFailedException;
|
||||||
|
|
||||||
|
public class JcaPbeKey implements PbeKey {
|
||||||
|
|
||||||
|
private final PBEKey jcaKey;
|
||||||
|
|
||||||
|
public JcaPbeKey(PBEKey jcaKey) {
|
||||||
|
this.jcaKey = Assert.notNull(jcaKey, "PBEKey cannot be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char[] getPassword() {
|
||||||
|
return this.jcaKey.getPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWorkFactor() {
|
||||||
|
return this.jcaKey.getIterationCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlgorithm() {
|
||||||
|
return this.jcaKey.getAlgorithm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFormat() {
|
||||||
|
return this.jcaKey.getFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getEncoded() {
|
||||||
|
return this.jcaKey.getEncoded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() throws DestroyFailedException {
|
||||||
|
this.jcaKey.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDestroyed() {
|
||||||
|
return this.jcaKey.isDestroyed();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.lang.Classes;
|
import io.jsonwebtoken.lang.Classes;
|
||||||
import io.jsonwebtoken.security.CryptoException;
|
import io.jsonwebtoken.security.CryptoException;
|
||||||
|
@ -32,34 +33,34 @@ public class JcaTemplate {
|
||||||
this.secureRandom = Assert.notNull(secureRandom, "SecureRandom cannot be null.");
|
this.secureRandom = Assert.notNull(secureRandom, "SecureRandom cannot be null.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public <I, T> T execute(Class<I> clazz, InstanceCallback<I, T> callback) throws CryptoException {
|
public <T, R> R execute(Class<T> clazz, CheckedFunction<T, R> fn) throws CryptoException {
|
||||||
return execute(new JcaInstanceSupplier<>(clazz, this.jcaName, this.provider), callback);
|
return execute(new JcaInstanceSupplier<>(clazz, this.jcaName, this.provider), fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SecretKey generateSecretKey(final int keyLength) {
|
public SecretKey generateSecretKey(final int keyBitLength) {
|
||||||
return execute(KeyGenerator.class, new InstanceCallback<KeyGenerator, SecretKey>() {
|
return execute(KeyGenerator.class, new CheckedFunction<KeyGenerator, SecretKey>() {
|
||||||
@Override
|
@Override
|
||||||
public SecretKey doWithInstance(KeyGenerator generator) {
|
public SecretKey apply(KeyGenerator generator) {
|
||||||
generator.init(keyLength, secureRandom);
|
generator.init(keyBitLength, secureRandom);
|
||||||
return generator.generateKey();
|
return generator.generateKey();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyPair generateKeyPair(final int keyLength) {
|
public KeyPair generateKeyPair(final int keyBitLength) {
|
||||||
return execute(KeyPairGenerator.class, new InstanceCallback<KeyPairGenerator, KeyPair>() {
|
return execute(KeyPairGenerator.class, new CheckedFunction<KeyPairGenerator, KeyPair>() {
|
||||||
@Override
|
@Override
|
||||||
public KeyPair doWithInstance(KeyPairGenerator generator) {
|
public KeyPair apply(KeyPairGenerator generator) {
|
||||||
generator.initialize(keyLength, secureRandom);
|
generator.initialize(keyBitLength, secureRandom);
|
||||||
return generator.generateKeyPair();
|
return generator.generateKeyPair();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private <I, T> T execute(JcaInstanceSupplier<I> supplier, InstanceCallback<I, T> callback) throws CryptoException {
|
private <T, R> R execute(JcaInstanceSupplier<T> supplier, CheckedFunction<T, R> callback) throws CryptoException {
|
||||||
try {
|
try {
|
||||||
I instance = supplier.getInstance();
|
T instance = supplier.getInstance();
|
||||||
return callback.doWithInstance(instance);
|
return callback.apply(instance);
|
||||||
} catch (SecurityException se) {
|
} catch (SecurityException se) {
|
||||||
throw se; //propagate
|
throw se; //propagate
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
|
@ -7,34 +7,11 @@ import java.security.Key;
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public interface JwkContext<K extends Key> extends Identifiable {
|
public interface JwkContext<K extends Key> extends Identifiable, Map<String,Object> {
|
||||||
|
|
||||||
int size();
|
|
||||||
|
|
||||||
boolean isEmpty();
|
|
||||||
|
|
||||||
boolean containsKey(String key);
|
|
||||||
|
|
||||||
boolean containsValue(Object value);
|
|
||||||
|
|
||||||
Object get(String key);
|
|
||||||
|
|
||||||
Set<String> keySet();
|
|
||||||
|
|
||||||
Collection<Object> values();
|
|
||||||
|
|
||||||
Set<Map.Entry<String, Object>> entrySet();
|
|
||||||
|
|
||||||
Map<String,Object> getValues();
|
|
||||||
|
|
||||||
Object put(String name, Object value);
|
|
||||||
|
|
||||||
JwkContext<K> putAll(Map<? extends String, ?> m);
|
|
||||||
|
|
||||||
JwkContext<K> setId(String id);
|
JwkContext<K> setId(String id);
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,29 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.UnsupportedJwtException;
|
import io.jsonwebtoken.UnsupportedJwtException;
|
||||||
|
import io.jsonwebtoken.impl.DefaultJweHeader;
|
||||||
import io.jsonwebtoken.impl.IdRegistry;
|
import io.jsonwebtoken.impl.IdRegistry;
|
||||||
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
import io.jsonwebtoken.impl.lang.Registry;
|
import io.jsonwebtoken.impl.lang.Registry;
|
||||||
import io.jsonwebtoken.lang.Collections;
|
import io.jsonwebtoken.lang.Collections;
|
||||||
|
import io.jsonwebtoken.security.EncryptionAlgorithms;
|
||||||
import io.jsonwebtoken.security.KeyAlgorithm;
|
import io.jsonwebtoken.security.KeyAlgorithm;
|
||||||
|
import io.jsonwebtoken.security.KeyRequest;
|
||||||
|
import io.jsonwebtoken.security.KeyResult;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import io.jsonwebtoken.security.PbeKey;
|
||||||
|
import io.jsonwebtoken.security.SecurityException;
|
||||||
|
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.SecretKeyFactory;
|
||||||
import javax.crypto.spec.OAEPParameterSpec;
|
import javax.crypto.spec.OAEPParameterSpec;
|
||||||
import javax.crypto.spec.PSource;
|
import javax.crypto.spec.PSource;
|
||||||
import java.security.spec.AlgorithmParameterSpec;
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
import java.security.spec.MGF1ParameterSpec;
|
import java.security.spec.MGF1ParameterSpec;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.KeyAlgorithms implementation
|
@SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.KeyAlgorithms implementation
|
||||||
public final class KeyAlgorithmsBridge {
|
public final class KeyAlgorithmsBridge {
|
||||||
|
@ -65,4 +78,153 @@ public final class KeyAlgorithmsBridge {
|
||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static KeyAlgorithm<PbeKey, SecretKey> lean(final Pbes2HsAkwAlgorithm alg) {
|
||||||
|
|
||||||
|
// ensure we use the same key factory over and over so that time spent acquiring one is not repeated:
|
||||||
|
JcaTemplate template = new JcaTemplate(alg.getJcaName(), null, Randoms.secureRandom());
|
||||||
|
final SecretKeyFactory factory = template.execute(SecretKeyFactory.class, new CheckedFunction<SecretKeyFactory, SecretKeyFactory>() {
|
||||||
|
@Override
|
||||||
|
public SecretKeyFactory apply(SecretKeyFactory secretKeyFactory) {
|
||||||
|
return secretKeyFactory;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// pre-compute the salt so we don't spend time doing that on each iteration. Doesn't need to be random for a
|
||||||
|
// computation-only test:
|
||||||
|
final byte[] rfcSalt = alg.toRfcSalt(alg.generateInputSalt(null));
|
||||||
|
|
||||||
|
// ensure that the bare minimum steps are performed to hash, ensuring our time sampling pertains only to
|
||||||
|
// hashing and not ancillary steps needed to setup the hashing/derivation
|
||||||
|
return new KeyAlgorithm<PbeKey, SecretKey>() {
|
||||||
|
@Override
|
||||||
|
public KeyResult getEncryptionKey(KeyRequest<SecretKey, PbeKey> request) throws SecurityException {
|
||||||
|
int iterations = request.getKey().getWorkFactor();
|
||||||
|
char[] password = request.getKey().getPassword();
|
||||||
|
try {
|
||||||
|
alg.deriveKey(factory, password, rfcSalt, iterations);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new SecurityException("Unable to derive key", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretKey getDecryptionKey(KeyRequest<byte[], SecretKey> request) throws SecurityException {
|
||||||
|
throw new UnsupportedOperationException("Not intended to be called.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return alg.getId();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int estimateIterations(KeyAlgorithm<PbeKey, SecretKey> alg, long desiredMillis) {
|
||||||
|
|
||||||
|
// The number of computational samples that land in our 'sweet spot' timing range matching desiredMillis.
|
||||||
|
// These samples will be averaged and the final average will be the return value of this method
|
||||||
|
// representing the number of iterations that should be taken for any given PBE hashing attempt to get
|
||||||
|
// reasonably close to desiredMillis:
|
||||||
|
final int NUM_SAMPLES = 30;
|
||||||
|
final int SKIP = 3;
|
||||||
|
|
||||||
|
// This is used by `alg` to generate an encryption key during the PBE attempt. While technically the time to
|
||||||
|
// generate this key during the alg call is not part of the hashing time and shouldn't be counted towards
|
||||||
|
// desiredMillis, in practice, this is so fast (about ~ 3 milliseconds total aggregated across all
|
||||||
|
// NUM_SAMPLES on a developer laptop), it is in practice negligible, so we won't need to adjust our
|
||||||
|
// timing logic below to account for this.
|
||||||
|
SymmetricAeadAlgorithm encAlg = EncryptionAlgorithms.A128GCM;
|
||||||
|
|
||||||
|
// Strip away all things that cause time during computation except for the actual hashing algorithm:
|
||||||
|
if (alg instanceof Pbes2HsAkwAlgorithm) {
|
||||||
|
alg = lean((Pbes2HsAkwAlgorithm) alg); //strip out everything except for the computation we care about
|
||||||
|
}
|
||||||
|
|
||||||
|
int workFactor = 1000; // same as iterations for PBKDF2. Different concept for Bcrypt/Scrypt
|
||||||
|
int minWorkFactor = workFactor;
|
||||||
|
List<Point> points = new ArrayList<>(NUM_SAMPLES);
|
||||||
|
for (int i = 0; points.size() < NUM_SAMPLES; i++) {
|
||||||
|
|
||||||
|
PbeKey pbeKey = Keys.forPbe().setPassword("12345678").setWorkFactor(workFactor).build();
|
||||||
|
KeyRequest<SecretKey, PbeKey> request = new DefaultKeyRequest<>(null, null, null, pbeKey, new DefaultJweHeader(), encAlg);
|
||||||
|
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
alg.getEncryptionKey(request); // <-- Computation occurs here. Don't need the result, just need to exec
|
||||||
|
long end = System.currentTimeMillis();
|
||||||
|
long duration = end - start;
|
||||||
|
|
||||||
|
// Exclude the first SKIP number of attempts from the average due to initial JIT optimization/slowness.
|
||||||
|
// After a few attempts, the JVM should be relatively optimized and the subsequent
|
||||||
|
// PBE hashing times are the ones we want to include in our analysis
|
||||||
|
boolean warmedUp = i >= SKIP;
|
||||||
|
|
||||||
|
// how close we were on this hashing attempt to reach our desiredMillis target:
|
||||||
|
// A number under 1 means we weren't slow enough, a number greater than 1 means we were too slow:
|
||||||
|
double durationPercentAchieved = (double) duration / (double) desiredMillis;
|
||||||
|
|
||||||
|
// we only want to collect timing samples if :
|
||||||
|
// 1. we're warmed up (to account for JIT optimization)
|
||||||
|
// 2. The attempt time at least met (>=) the desiredMillis target
|
||||||
|
boolean collectSample = warmedUp && duration >= desiredMillis;
|
||||||
|
if (collectSample) {
|
||||||
|
// For each attempt, the x axis is the workFactor, and the y axis is how long it took to compute:
|
||||||
|
points.add(new Point(workFactor, duration));
|
||||||
|
//System.out.println("Collected point: workFactor=" + workFactor + ", duration=" + duration + " ms, %achieved=" + durationPercentAchieved);
|
||||||
|
} else {
|
||||||
|
minWorkFactor = Math.max(minWorkFactor, workFactor);
|
||||||
|
//System.out.println(" Excluding sample: workFactor=" + workFactor + ", duration=" + duration + " ms, %achieved=" + durationPercentAchieved);
|
||||||
|
}
|
||||||
|
|
||||||
|
// amount to increase or decrease the workFactor for the next hashing iteration. We increase if
|
||||||
|
// we haven't met the desired millisecond time, and decrease if we're over it a little too much, always
|
||||||
|
// trying to stay in that desired timing sweet spot
|
||||||
|
double percentAdjust = workFactor * 0.0075; // 3/4ths of a percent
|
||||||
|
if (durationPercentAchieved < 1d) {
|
||||||
|
// Under target. Let's increase by the amount that should get right at (or near) 100%:
|
||||||
|
double ratio = desiredMillis / (double) duration;
|
||||||
|
if (ratio > 1) {
|
||||||
|
double result = workFactor * ratio;
|
||||||
|
workFactor = (int) result;
|
||||||
|
} else {
|
||||||
|
double difference = workFactor * (1 - durationPercentAchieved);
|
||||||
|
workFactor += Math.max(percentAdjust, difference);
|
||||||
|
}
|
||||||
|
} else if (durationPercentAchieved > 1.01d) {
|
||||||
|
// Over target. Let's decrease gently to get closer.
|
||||||
|
double difference = workFactor * (durationPercentAchieved - 1.01);
|
||||||
|
difference = Math.min(percentAdjust, difference);
|
||||||
|
// math.max here because the min allowed is 1000 per the JWA RFC, so we never want to go below that.
|
||||||
|
workFactor = (int) Math.max(1000, workFactor - difference);
|
||||||
|
} else {
|
||||||
|
// we're at our target (desiredMillis); let's increase by a teeny bit to see where we get
|
||||||
|
// (and the JVM might optimize with the same inputs, so we want to prevent that here)
|
||||||
|
workFactor += 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've collected all of our samples, now let's find the workFactor average number
|
||||||
|
// That average is the best estimate for ensuring PBE hashes for the specified algorithm meet the
|
||||||
|
// desiredMillis target on the current JVM/CPU platform:
|
||||||
|
double sumX = 0;
|
||||||
|
for (Point p : points) {
|
||||||
|
sumX += p.x;
|
||||||
|
}
|
||||||
|
double average = sumX / points.size();
|
||||||
|
//ensure our average is at least as much as the smallest work factor that got us closest to desiredMillis:
|
||||||
|
return (int) Math.max(average, minWorkFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Point {
|
||||||
|
long x;
|
||||||
|
long y;
|
||||||
|
double lnY;
|
||||||
|
|
||||||
|
public Point(long x, long y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.lnY = Math.log((double) y);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.security.KeySupplier;
|
||||||
|
import io.jsonwebtoken.security.SecurityRequest;
|
||||||
|
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.Provider;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
public class KeyedRequest<K extends Key> extends DefaultSecurityRequest implements SecurityRequest, KeySupplier<K> {
|
||||||
|
|
||||||
|
private final K key;
|
||||||
|
|
||||||
|
public KeyedRequest(Provider provider, SecureRandom secureRandom, K key) {
|
||||||
|
super(provider, secureRandom);
|
||||||
|
this.key = Assert.notNull(key, "Key cannot be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public K getKey() {
|
||||||
|
return this.key;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.security.PbeKey;
|
||||||
|
import io.jsonwebtoken.security.PbeKeyBuilder;
|
||||||
|
|
||||||
|
@SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.Keys implementation
|
||||||
|
public class KeysBridge {
|
||||||
|
|
||||||
|
// prevent instantiation
|
||||||
|
private KeysBridge() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PbeKeyBuilder<PbeKey> forPbe() {
|
||||||
|
return new DefaultPbeKeyBuilder<>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
import io.jsonwebtoken.lang.Arrays;
|
import io.jsonwebtoken.lang.Arrays;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.lang.Collections;
|
import io.jsonwebtoken.lang.Collections;
|
||||||
|
@ -134,9 +135,9 @@ public class MacSignatureAlgorithm extends AbstractSignatureAlgorithm<SecretKey,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] doSign(final SignatureRequest<SecretKey> request) throws Exception {
|
public byte[] doSign(final SignatureRequest<SecretKey> request) throws Exception {
|
||||||
return execute(request, Mac.class, new InstanceCallback<Mac, byte[]>() {
|
return execute(request, Mac.class, new CheckedFunction<Mac, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
public byte[] doWithInstance(Mac mac) throws Exception {
|
public byte[] apply(Mac mac) throws Exception {
|
||||||
mac.init(request.getKey());
|
mac.init(request.getKey());
|
||||||
return mac.doFinal(request.getPayload());
|
return mac.doFinal(request.getPayload());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.JweHeader;
|
import io.jsonwebtoken.JweHeader;
|
||||||
import io.jsonwebtoken.MalformedJwtException;
|
|
||||||
import io.jsonwebtoken.impl.lang.Bytes;
|
import io.jsonwebtoken.impl.lang.Bytes;
|
||||||
import io.jsonwebtoken.io.Decoders;
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
|
import io.jsonwebtoken.impl.lang.ValueGetter;
|
||||||
import io.jsonwebtoken.io.Encoders;
|
import io.jsonwebtoken.io.Encoders;
|
||||||
import io.jsonwebtoken.lang.Arrays;
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.EncryptedKeyAlgorithm;
|
|
||||||
import io.jsonwebtoken.security.KeyAlgorithm;
|
import io.jsonwebtoken.security.KeyAlgorithm;
|
||||||
import io.jsonwebtoken.security.KeyRequest;
|
import io.jsonwebtoken.security.KeyRequest;
|
||||||
import io.jsonwebtoken.security.KeyResult;
|
import io.jsonwebtoken.security.KeyResult;
|
||||||
|
import io.jsonwebtoken.security.PbeKey;
|
||||||
import io.jsonwebtoken.security.SecurityException;
|
import io.jsonwebtoken.security.SecurityException;
|
||||||
|
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
|
||||||
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.SecretKeyFactory;
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
@ -21,10 +21,11 @@ import java.nio.ByteBuffer;
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public class Pbes2HsAkwAlgorithm extends CryptoAlgorithm implements EncryptedKeyAlgorithm<SecretKey, SecretKey> {
|
public class Pbes2HsAkwAlgorithm extends CryptoAlgorithm implements KeyAlgorithm<PbeKey, SecretKey> {
|
||||||
|
|
||||||
private static final String SALT_HEADER_NAME = "p2s";
|
private static final String SALT_HEADER_NAME = "p2s";
|
||||||
private static final String ITERATION_HEADER_NAME = "p2c"; // iteration count
|
private static final String ITERATION_HEADER_NAME = "p2c"; // iteration count
|
||||||
|
private static final int MIN_RECOMMENDED_ITERATIONS = 1000; // https://datatracker.ietf.org/doc/html/rfc7518#section-4.8.1.2
|
||||||
|
|
||||||
private final int HASH_BYTE_LENGTH;
|
private final int HASH_BYTE_LENGTH;
|
||||||
private final int DERIVED_KEY_BIT_LENGTH;
|
private final int DERIVED_KEY_BIT_LENGTH;
|
||||||
|
@ -50,6 +51,16 @@ public class Pbes2HsAkwAlgorithm extends CryptoAlgorithm implements EncryptedKey
|
||||||
return "PBES2-HS" + hashBitLength + "+" + wrapAlg.getId();
|
return "PBES2-HS" + hashBitLength + "+" + wrapAlg.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int assertIterations(int iterations) {
|
||||||
|
if (iterations < MIN_RECOMMENDED_ITERATIONS) {
|
||||||
|
String msg = "[JWA RFC 7518, Section 4.8.1.2](https://datatracker.ietf.org/doc/html/rfc7518#section-4.8.1.2) " +
|
||||||
|
"recommends password-based-encryption iterations be greater than or equal to " +
|
||||||
|
MIN_RECOMMENDED_ITERATIONS + ". Provided: " + iterations;
|
||||||
|
throw new IllegalArgumentException(msg);
|
||||||
|
}
|
||||||
|
return iterations;
|
||||||
|
}
|
||||||
|
|
||||||
public Pbes2HsAkwAlgorithm(int keyBitLength) {
|
public Pbes2HsAkwAlgorithm(int keyBitLength) {
|
||||||
this(hashBitLength(keyBitLength), new AesWrapKeyAlgorithm(keyBitLength));
|
this(hashBitLength(keyBitLength), new AesWrapKeyAlgorithm(keyBitLength));
|
||||||
}
|
}
|
||||||
|
@ -70,63 +81,63 @@ public class Pbes2HsAkwAlgorithm extends CryptoAlgorithm implements EncryptedKey
|
||||||
this.SALT_PREFIX = toRfcSaltPrefix(getId().getBytes(StandardCharsets.UTF_8));
|
this.SALT_PREFIX = toRfcSaltPrefix(getId().getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
private SecretKey deriveKey(final KeyRequest<?, ?> request, final PBEKey pbeKey, final byte[] salt, final int iterations) {
|
// protected visibility for testing
|
||||||
return execute(request, SecretKeyFactory.class, new InstanceCallback<SecretKeyFactory, SecretKey>() {
|
protected SecretKey deriveKey(SecretKeyFactory factory, final char[] password, final byte[] rfcSalt, int iterations) throws Exception {
|
||||||
@Override
|
PBEKeySpec spec = null;
|
||||||
public SecretKey doWithInstance(SecretKeyFactory factory) throws Exception {
|
try {
|
||||||
PBEKeySpec spec = null;
|
spec = new PBEKeySpec(password, rfcSalt, iterations, DERIVED_KEY_BIT_LENGTH);
|
||||||
try {
|
return factory.generateSecret(spec);
|
||||||
spec = new PBEKeySpec(pbeKey.getPassword(), salt, iterations, DERIVED_KEY_BIT_LENGTH);
|
} finally {
|
||||||
return factory.generateSecret(spec);
|
if (spec != null) {
|
||||||
} finally {
|
spec.clearPassword();
|
||||||
if (spec != null) {
|
|
||||||
spec.clearPassword();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected byte[] generateInputSalt(KeyRequest<SecretKey, SecretKey> request) {
|
private SecretKey deriveKey(final KeyRequest<?, ?> request, final char[] password, final byte[] salt, final int iterations) {
|
||||||
|
try {
|
||||||
|
return execute(request, SecretKeyFactory.class, new CheckedFunction<SecretKeyFactory, SecretKey>() {
|
||||||
|
@Override
|
||||||
|
public SecretKey apply(SecretKeyFactory factory) throws Exception {
|
||||||
|
return deriveKey(factory, password, salt, iterations);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
if (password != null) {
|
||||||
|
java.util.Arrays.fill(password, '\u0000');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte[] generateInputSalt(KeyRequest<?, ?> request) {
|
||||||
byte[] inputSalt = new byte[this.HASH_BYTE_LENGTH];
|
byte[] inputSalt = new byte[this.HASH_BYTE_LENGTH];
|
||||||
ensureSecureRandom(request).nextBytes(inputSalt);
|
ensureSecureRandom(request).nextBytes(inputSalt);
|
||||||
return inputSalt;
|
return inputSalt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// protected visibility for testing
|
||||||
|
protected byte[] toRfcSalt(byte[] inputSalt) {
|
||||||
|
return Bytes.concat(this.SALT_PREFIX, inputSalt);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyResult getEncryptionKey(KeyRequest<SecretKey, SecretKey> request) throws SecurityException {
|
public KeyResult getEncryptionKey(KeyRequest<SecretKey, PbeKey> request) throws SecurityException {
|
||||||
|
|
||||||
Assert.notNull(request, "request cannot be null.");
|
Assert.notNull(request, "request cannot be null.");
|
||||||
final SecretKey cek = Assert.notNull(request.getPayload(), "request.getPayload() (content encryption key) cannot be null.");
|
SymmetricAeadAlgorithm enc = Assert.notNull(request.getEncryptionAlgorithm(), "Request encryptionAlgorithm cannot be null.");
|
||||||
|
final SecretKey cek = Assert.notNull(enc.generateKey(), "Request encryption algorithm cannot generate a null key.");
|
||||||
SecretKey reqKey = request.getKey();
|
final PbeKey pbeKey = Assert.notNull(request.getKey(), "request.getKey() cannot be null.");
|
||||||
Assert.notNull(reqKey, "request.getKey() cannot be null.");
|
|
||||||
if (!(reqKey instanceof PBEKey)) {
|
|
||||||
String msg = "request.getKey() must be a " + PBEKey.class.getName() + " instance. Type found: " +
|
|
||||||
reqKey.getClass().getName();
|
|
||||||
throw new IllegalArgumentException(msg);
|
|
||||||
}
|
|
||||||
final PBEKey pbeKey = (PBEKey) reqKey;
|
|
||||||
// we explicitly do not attempt to validate pbeKey.getPassword() at this point because that call will create
|
|
||||||
// a clone of the char array, and we'd have to guarantee cleanup of that clone if any failure/exception occurs.
|
|
||||||
// Instead, we access the password in only one place - in the execute* method call below - and guarantee
|
|
||||||
// cleanup there in a try/finally block
|
|
||||||
|
|
||||||
final int iterations = pbeKey.getIterationCount();
|
|
||||||
if (iterations < 1000) {
|
|
||||||
String msg = "Password-based encryption password iterations must be >= 1000. Found: " + iterations;
|
|
||||||
throw new IllegalArgumentException(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
final int iterations = assertIterations(pbeKey.getWorkFactor());
|
||||||
byte[] inputSalt = generateInputSalt(request);
|
byte[] inputSalt = generateInputSalt(request);
|
||||||
final byte[] rfcSalt = Bytes.plus(this.SALT_PREFIX, inputSalt);
|
final byte[] rfcSalt = toRfcSalt(inputSalt);
|
||||||
final String p2s = Encoders.BASE64URL.encode(inputSalt);
|
final String p2s = Encoders.BASE64URL.encode(inputSalt);
|
||||||
|
char[] password = pbeKey.getPassword(); // will be safely cleaned/zeroed in deriveKey next:
|
||||||
final SecretKey derivedKek = deriveKey(request, pbeKey, rfcSalt, iterations);
|
final SecretKey derivedKek = deriveKey(request, password, rfcSalt, iterations);
|
||||||
|
|
||||||
// now encrypt (wrap) the CEK with the PBE-derived key:
|
// now encrypt (wrap) the CEK with the PBE-derived key:
|
||||||
DefaultKeyRequest<SecretKey, SecretKey> wrapReq = new DefaultKeyRequest<>(request.getProvider(),
|
DefaultKeyRequest<SecretKey, SecretKey> wrapReq = new DefaultKeyRequest<>(request.getProvider(),
|
||||||
request.getSecureRandom(), cek, derivedKek, request.getHeader());
|
request.getSecureRandom(), cek, derivedKek, request.getHeader(), request.getEncryptionAlgorithm());
|
||||||
KeyResult result = wrapAlg.getEncryptionKey(wrapReq);
|
KeyResult result = wrapAlg.getEncryptionKey(wrapReq);
|
||||||
|
|
||||||
request.getHeader().put(SALT_HEADER_NAME, p2s);
|
request.getHeader().put(SALT_HEADER_NAME, p2s);
|
||||||
|
@ -138,30 +149,25 @@ public class Pbes2HsAkwAlgorithm extends CryptoAlgorithm implements EncryptedKey
|
||||||
private static char[] toChars(byte[] bytes) {
|
private static char[] toChars(byte[] bytes) {
|
||||||
ByteBuffer buf = ByteBuffer.wrap(bytes);
|
ByteBuffer buf = ByteBuffer.wrap(bytes);
|
||||||
CharBuffer cbuf = StandardCharsets.UTF_8.decode(buf);
|
CharBuffer cbuf = StandardCharsets.UTF_8.decode(buf);
|
||||||
char[] chars = new char[cbuf.limit()];
|
char[] chars = cbuf.compact().array();
|
||||||
cbuf.get(chars);
|
|
||||||
return chars;
|
return chars;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PBEKey toPBEKey(SecretKey key, int iterations) {
|
private char[] toPasswordChars(SecretKey key) {
|
||||||
if (key instanceof PBEKey) {
|
if (key instanceof PBEKey) {
|
||||||
return (PBEKey) key;
|
return ((PBEKey) key).getPassword();
|
||||||
}
|
}
|
||||||
|
if (key instanceof PbeKey) {
|
||||||
|
return ((PbeKey) key).getPassword();
|
||||||
|
}
|
||||||
|
// convert bytes to UTF-8 characters:
|
||||||
byte[] keyBytes = null;
|
byte[] keyBytes = null;
|
||||||
char[] keyChars = null;
|
|
||||||
try {
|
try {
|
||||||
keyBytes = key.getEncoded();
|
keyBytes = key.getEncoded();
|
||||||
keyChars = toChars(keyBytes);
|
return toChars(keyBytes);
|
||||||
return new DefaultPBEKey(keyChars, iterations, getId());
|
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
if (keyBytes != null) {
|
||||||
if (keyChars != null) {
|
java.util.Arrays.fill(keyBytes, (byte) 0);
|
||||||
java.util.Arrays.fill(keyChars, '\u0000');
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (keyBytes != null) {
|
|
||||||
java.util.Arrays.fill(keyBytes, (byte) 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,48 +176,18 @@ public class Pbes2HsAkwAlgorithm extends CryptoAlgorithm implements EncryptedKey
|
||||||
public SecretKey getDecryptionKey(KeyRequest<byte[], SecretKey> request) throws SecurityException {
|
public SecretKey getDecryptionKey(KeyRequest<byte[], SecretKey> request) throws SecurityException {
|
||||||
|
|
||||||
JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null.");
|
JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null.");
|
||||||
String name = SALT_HEADER_NAME;
|
final SecretKey key = Assert.notNull(request.getKey(), "Request Key cannot be null.");
|
||||||
|
|
||||||
Object value = header.get(name);
|
ValueGetter getter = new DefaultValueGetter(header);
|
||||||
if (value == null) {
|
final byte[] inputSalt = getter.getRequiredBytes(SALT_HEADER_NAME);
|
||||||
String msg = "The " + getId() + " Key Management Algorithm requires a JweHeader '" + name + "' value.";
|
final byte[] rfcSalt = Bytes.concat(SALT_PREFIX, inputSalt);
|
||||||
throw new MalformedJwtException(msg);
|
final int iterations = getter.getRequiredPositiveInteger(ITERATION_HEADER_NAME);
|
||||||
}
|
final char[] password = toPasswordChars(key); // will be safely cleaned/zeroed in deriveKey next:
|
||||||
if (!(value instanceof String)) {
|
|
||||||
String msg = "The " + getId() + " Key Management Algorithm requires the JweHeader '" + name + "' value to be a Base64URL-encoded String. Actual type: " + value.getClass().getName();
|
|
||||||
throw new MalformedJwtException(msg);
|
|
||||||
}
|
|
||||||
String encoded = (String) value;
|
|
||||||
|
|
||||||
final byte[] inputSalt = Decoders.BASE64URL.decode(encoded);
|
final SecretKey derivedKek = deriveKey(request, password, rfcSalt, iterations);
|
||||||
if (Arrays.length(inputSalt) == 0) {
|
|
||||||
String msg = "The " + getId() + " Key Management Algorithm does not support empty JweHeader '" + name + "' values.";
|
|
||||||
throw new MalformedJwtException(msg);
|
|
||||||
}
|
|
||||||
final byte[] rfcSalt = Bytes.plus(SALT_PREFIX, inputSalt);
|
|
||||||
|
|
||||||
name = ITERATION_HEADER_NAME;
|
|
||||||
value = header.get(name);
|
|
||||||
if (value == null) {
|
|
||||||
String msg = "The " + getId() + " Key Management Algorithm requires a JweHeader '" + name + "' value.";
|
|
||||||
throw new MalformedJwtException(msg);
|
|
||||||
}
|
|
||||||
if (!(value instanceof Integer)) {
|
|
||||||
String msg = "The " + getId() + " Key Management Algorithm requires the JweHeader '" + name + "' value to be an integer. Actual type: " + value.getClass().getName();
|
|
||||||
throw new MalformedJwtException(msg);
|
|
||||||
}
|
|
||||||
final int iterations = (Integer) value;
|
|
||||||
if (iterations <= 0) {
|
|
||||||
String msg = "The " + getId() + " Key Management Algorithm requires the JweHeader '" + name + "' value to be a positive integer. Actual value: " + iterations;
|
|
||||||
throw new MalformedJwtException(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
PBEKey pbeKey = toPBEKey(request.getKey(), iterations);
|
|
||||||
|
|
||||||
final SecretKey derivedKek = deriveKey(request, pbeKey, rfcSalt, iterations);
|
|
||||||
|
|
||||||
KeyRequest<byte[], SecretKey> unwrapReq = new DefaultKeyRequest<>(request.getProvider(),
|
KeyRequest<byte[], SecretKey> unwrapReq = new DefaultKeyRequest<>(request.getProvider(),
|
||||||
request.getSecureRandom(), request.getPayload(), derivedKek, header);
|
request.getSecureRandom(), request.getPayload(), derivedKek, header, request.getEncryptionAlgorithm());
|
||||||
|
|
||||||
return wrapAlg.getDecryptionKey(unwrapReq);
|
return wrapAlg.getDecryptionKey(unwrapReq);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.jsonwebtoken.impl.security;
|
||||||
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
import io.jsonwebtoken.impl.lang.Converter;
|
import io.jsonwebtoken.impl.lang.Converter;
|
||||||
import io.jsonwebtoken.impl.lang.Converters;
|
import io.jsonwebtoken.impl.lang.Converters;
|
||||||
|
import io.jsonwebtoken.impl.lang.ValueGetter;
|
||||||
import io.jsonwebtoken.lang.Arrays;
|
import io.jsonwebtoken.lang.Arrays;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.lang.Collections;
|
import io.jsonwebtoken.lang.Collections;
|
||||||
|
@ -127,7 +128,8 @@ class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory<RSAPrivateKey, RsaPr
|
||||||
@Override
|
@Override
|
||||||
protected RsaPrivateJwk createJwkFromValues(JwkContext<RSAPrivateKey> ctx) {
|
protected RsaPrivateJwk createJwkFromValues(JwkContext<RSAPrivateKey> ctx) {
|
||||||
|
|
||||||
final BigInteger privateExponent = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.PRIVATE_EXPONENT, true);
|
final ValueGetter getter = new DefaultValueGetter(ctx);
|
||||||
|
final BigInteger privateExponent = getter.getRequiredBigInt(DefaultRsaPrivateJwk.PRIVATE_EXPONENT, true);
|
||||||
|
|
||||||
//The [JWA Spec, Section 6.3.2](https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2) requires
|
//The [JWA Spec, Section 6.3.2](https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.2) requires
|
||||||
//RSA Private Keys to also encode the public key values, so we assert that we can acquire it successfully:
|
//RSA Private Keys to also encode the public key values, so we assert that we can acquire it successfully:
|
||||||
|
@ -155,11 +157,11 @@ class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory<RSAPrivateKey, RsaPr
|
||||||
KeySpec spec;
|
KeySpec spec;
|
||||||
|
|
||||||
if (containsOptional) { //if any one optional field exists, they are all required per JWA Section 6.3.2:
|
if (containsOptional) { //if any one optional field exists, they are all required per JWA Section 6.3.2:
|
||||||
BigInteger firstPrime = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.FIRST_PRIME, true);
|
BigInteger firstPrime = getter.getRequiredBigInt(DefaultRsaPrivateJwk.FIRST_PRIME, true);
|
||||||
BigInteger secondPrime = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.SECOND_PRIME, true);
|
BigInteger secondPrime = getter.getRequiredBigInt(DefaultRsaPrivateJwk.SECOND_PRIME, true);
|
||||||
BigInteger firstCrtExponent = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.FIRST_CRT_EXPONENT, true);
|
BigInteger firstCrtExponent = getter.getRequiredBigInt(DefaultRsaPrivateJwk.FIRST_CRT_EXPONENT, true);
|
||||||
BigInteger secondCrtExponent = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.SECOND_CRT_EXPONENT, true);
|
BigInteger secondCrtExponent = getter.getRequiredBigInt(DefaultRsaPrivateJwk.SECOND_CRT_EXPONENT, true);
|
||||||
BigInteger firstCrtCoefficient = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.FIRST_CRT_COEFFICIENT, true);
|
BigInteger firstCrtCoefficient = getter.getRequiredBigInt(DefaultRsaPrivateJwk.FIRST_CRT_COEFFICIENT, true);
|
||||||
|
|
||||||
// Other Primes Info is actually optional even if the above ones are required:
|
// Other Primes Info is actually optional even if the above ones are required:
|
||||||
if (ctx.containsKey(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO)) {
|
if (ctx.containsKey(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO)) {
|
||||||
|
@ -226,9 +228,10 @@ class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory<RSAPrivateKey, RsaPr
|
||||||
ctx.put(name, entry.getValue());
|
ctx.put(name, entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
BigInteger prime = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.PRIME_FACTOR, true);
|
final ValueGetter getter = new DefaultValueGetter(ctx);
|
||||||
BigInteger primeExponent = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.FACTOR_CRT_EXPONENT, true);
|
BigInteger prime = getter.getRequiredBigInt(DefaultRsaPrivateJwk.PRIME_FACTOR, true);
|
||||||
BigInteger crtCoefficient = getRequiredBigInt(ctx, DefaultRsaPrivateJwk.FACTOR_CRT_COEFFICIENT, true);
|
BigInteger primeExponent = getter.getRequiredBigInt(DefaultRsaPrivateJwk.FACTOR_CRT_EXPONENT, true);
|
||||||
|
BigInteger crtCoefficient = getter.getRequiredBigInt(DefaultRsaPrivateJwk.FACTOR_CRT_COEFFICIENT, true);
|
||||||
|
|
||||||
return new RSAOtherPrimeInfo(prime, primeExponent, crtCoefficient);
|
return new RSAOtherPrimeInfo(prime, primeExponent, crtCoefficient);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
import io.jsonwebtoken.impl.lang.CheckedFunction;
|
||||||
|
import io.jsonwebtoken.impl.lang.ValueGetter;
|
||||||
import io.jsonwebtoken.security.RsaPublicJwk;
|
import io.jsonwebtoken.security.RsaPublicJwk;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
@ -26,9 +27,9 @@ class RsaPublicJwkFactory extends AbstractFamilyJwkFactory<RSAPublicKey, RsaPubl
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RsaPublicJwk createJwkFromValues(JwkContext<RSAPublicKey> ctx) {
|
protected RsaPublicJwk createJwkFromValues(JwkContext<RSAPublicKey> ctx) {
|
||||||
|
ValueGetter getter = new DefaultValueGetter(ctx);
|
||||||
BigInteger modulus = getRequiredBigInt(ctx, DefaultRsaPublicJwk.MODULUS, false);
|
BigInteger modulus = getter.getRequiredBigInt(DefaultRsaPublicJwk.MODULUS, false);
|
||||||
BigInteger publicExponent = getRequiredBigInt(ctx, DefaultRsaPublicJwk.PUBLIC_EXPONENT, false);
|
BigInteger publicExponent = getter.getRequiredBigInt(DefaultRsaPublicJwk.PUBLIC_EXPONENT, false);
|
||||||
final RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent);
|
final RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent);
|
||||||
|
|
||||||
RSAPublicKey key = generateKey(ctx, new CheckedFunction<KeyFactory, RSAPublicKey>() {
|
RSAPublicKey key = generateKey(ctx, new CheckedFunction<KeyFactory, RSAPublicKey>() {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
package io.jsonwebtoken.impl.security;
|
package io.jsonwebtoken.impl.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.io.Decoders;
|
import io.jsonwebtoken.impl.lang.ValueGetter;
|
||||||
import io.jsonwebtoken.io.Encoders;
|
import io.jsonwebtoken.io.Encoders;
|
||||||
import io.jsonwebtoken.lang.Arrays;
|
import io.jsonwebtoken.lang.Arrays;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.security.MalformedKeyException;
|
|
||||||
import io.jsonwebtoken.security.SecretJwk;
|
import io.jsonwebtoken.security.SecretJwk;
|
||||||
import io.jsonwebtoken.security.UnsupportedKeyException;
|
import io.jsonwebtoken.security.UnsupportedKeyException;
|
||||||
|
|
||||||
|
@ -58,18 +57,8 @@ class SecretJwkFactory extends AbstractFamilyJwkFactory<SecretKey, SecretJwk> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected SecretJwk createJwkFromValues(JwkContext<SecretKey> ctx) {
|
protected SecretJwk createJwkFromValues(JwkContext<SecretKey> ctx) {
|
||||||
String encoded = getRequiredString(ctx, DefaultSecretJwk.K);
|
ValueGetter getter = new DefaultValueGetter(ctx);
|
||||||
byte[] bytes;
|
byte[] bytes = getter.getRequiredBytes(DefaultSecretJwk.K);
|
||||||
try {
|
|
||||||
bytes = Decoders.BASE64URL.decode(encoded);
|
|
||||||
if (Arrays.length(bytes) == 0) {
|
|
||||||
throw new IllegalArgumentException("JWK 'k' member does not have any encoded bytes. JWK: {" + ctx + "}");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
String msg = "Unable to Base64Url-decode " + DefaultSecretJwk.TYPE_VALUE +
|
|
||||||
" JWK 'k' member value. JWK: {" + ctx + "}";
|
|
||||||
throw new MalformedKeyException(msg, e);
|
|
||||||
}
|
|
||||||
SecretKey key = new SecretKeySpec(bytes, "NONE"); //TODO: do we need a JCA-specific ID here?
|
SecretKey key = new SecretKeySpec(bytes, "NONE"); //TODO: do we need a JCA-specific ID here?
|
||||||
ctx.setKey(key);
|
ctx.setKey(key);
|
||||||
return new DefaultSecretJwk(ctx);
|
return new DefaultSecretJwk(ctx);
|
||||||
|
|
|
@ -2,14 +2,10 @@ package io.jsonwebtoken.impl
|
||||||
|
|
||||||
import io.jsonwebtoken.Jwts
|
import io.jsonwebtoken.Jwts
|
||||||
import io.jsonwebtoken.security.EncryptionAlgorithms
|
import io.jsonwebtoken.security.EncryptionAlgorithms
|
||||||
import io.jsonwebtoken.security.KeyAlgorithm
|
|
||||||
import io.jsonwebtoken.security.KeyAlgorithms
|
|
||||||
import io.jsonwebtoken.security.SymmetricAeadAlgorithm
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import java.security.Key
|
import static org.junit.Assert.assertEquals
|
||||||
|
import static org.junit.Assert.fail
|
||||||
import static org.junit.Assert.*
|
|
||||||
|
|
||||||
class DefaultJweBuilderTest {
|
class DefaultJweBuilderTest {
|
||||||
|
|
||||||
|
@ -51,7 +47,7 @@ class DefaultJweBuilderTest {
|
||||||
try {
|
try {
|
||||||
builder().setIssuer("me").withKey(key).compact()
|
builder().setIssuer("me").withKey(key).compact()
|
||||||
} catch (IllegalStateException ise) {
|
} catch (IllegalStateException ise) {
|
||||||
assertEquals 'EncryptionAlgorithm is required.', ise.message
|
assertEquals 'Encryption algorithm is required.', ise.message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,24 +2,24 @@ package io.jsonwebtoken.impl.security
|
||||||
|
|
||||||
import io.jsonwebtoken.MalformedJwtException
|
import io.jsonwebtoken.MalformedJwtException
|
||||||
import io.jsonwebtoken.impl.DefaultJweHeader
|
import io.jsonwebtoken.impl.DefaultJweHeader
|
||||||
|
import io.jsonwebtoken.impl.lang.Bytes
|
||||||
|
import io.jsonwebtoken.impl.lang.CheckedFunction
|
||||||
import io.jsonwebtoken.io.Encoders
|
import io.jsonwebtoken.io.Encoders
|
||||||
import io.jsonwebtoken.lang.Arrays
|
import io.jsonwebtoken.lang.Arrays
|
||||||
|
|
||||||
import javax.crypto.SecretKey
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
|
|
||||||
import static org.junit.Assert.*
|
|
||||||
|
|
||||||
import io.jsonwebtoken.security.EncryptionAlgorithms
|
import io.jsonwebtoken.security.EncryptionAlgorithms
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.SecretKey
|
||||||
import javax.crypto.spec.GCMParameterSpec
|
import javax.crypto.spec.GCMParameterSpec
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
class AesGcmKeyAlgorithmTest {
|
class AesGcmKeyAlgorithmTest {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This tests asserts that our EncyrptionAlgorithm implementation and the JCA 'AES/GCM/NoPadding' wrap algorithm
|
* This tests asserts that our SymmetricAeadAlgorithm implementation and the JCA 'AES/GCM/NoPadding' wrap algorithm
|
||||||
* produce the exact same values. This should be the case when the transformation is identical, even though
|
* produce the exact same values. This should be the case when the transformation is identical, even though
|
||||||
* one uses Cipher.WRAP_MODE and the other uses a raw plaintext byte array.
|
* one uses Cipher.WRAP_MODE and the other uses a raw plaintext byte array.
|
||||||
*/
|
*/
|
||||||
|
@ -35,9 +35,9 @@ class AesGcmKeyAlgorithmTest {
|
||||||
def cek = alg.generateKey();
|
def cek = alg.generateKey();
|
||||||
|
|
||||||
JcaTemplate template = new JcaTemplate("AES/GCM/NoPadding", null)
|
JcaTemplate template = new JcaTemplate("AES/GCM/NoPadding", null)
|
||||||
byte[] jcaResult = template.execute(Cipher.class, new InstanceCallback<Cipher, byte[]>() {
|
byte[] jcaResult = template.execute(Cipher.class, new CheckedFunction<Cipher, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
byte[] doWithInstance(Cipher cipher) throws Exception {
|
byte[] apply(Cipher cipher) throws Exception {
|
||||||
cipher.init(Cipher.WRAP_MODE, kek, new GCMParameterSpec(128, iv))
|
cipher.init(Cipher.WRAP_MODE, kek, new GCMParameterSpec(128, iv))
|
||||||
return cipher.wrap(cek)
|
return cipher.wrap(cek)
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ class AesGcmKeyAlgorithmTest {
|
||||||
def encRequest = new DefaultSymmetricAeadRequest(null, null, cek.getEncoded(), kek, null, iv)
|
def encRequest = new DefaultSymmetricAeadRequest(null, null, cek.getEncoded(), kek, null, iv)
|
||||||
def encResult = EncryptionAlgorithms.A256GCM.encrypt(encRequest)
|
def encResult = EncryptionAlgorithms.A256GCM.encrypt(encRequest)
|
||||||
|
|
||||||
assertArrayEquals resultA.authenticationTag, encResult.authenticationTag
|
assertArrayEquals resultA.digest, encResult.digest
|
||||||
assertArrayEquals resultA.initializationVector, encResult.initializationVector
|
assertArrayEquals resultA.initializationVector, encResult.initializationVector
|
||||||
assertArrayEquals resultA.payload, encResult.payload
|
assertArrayEquals resultA.payload, encResult.payload
|
||||||
}
|
}
|
||||||
|
@ -70,16 +70,21 @@ class AesGcmKeyAlgorithmTest {
|
||||||
def header = new DefaultJweHeader()
|
def header = new DefaultJweHeader()
|
||||||
def kek = template.generateSecretKey(keyLength)
|
def kek = template.generateSecretKey(keyLength)
|
||||||
def cek = template.generateSecretKey(keyLength)
|
def cek = template.generateSecretKey(keyLength)
|
||||||
|
def enc = new GcmAesAeadAlgorithm(keyLength) {
|
||||||
|
@Override
|
||||||
|
SecretKey generateKey() {
|
||||||
|
return cek;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def ereq = new DefaultKeyRequest(null, null, cek, kek, header)
|
def ereq = new DefaultKeyRequest(null, null, cek, kek, header, enc)
|
||||||
|
|
||||||
def result = alg.getEncryptionKey(ereq)
|
def result = alg.getEncryptionKey(ereq)
|
||||||
header.putAll(result.getHeaderParams())
|
|
||||||
|
|
||||||
byte[] encryptedKeyBytes = result.getPayload()
|
byte[] encryptedKeyBytes = result.getPayload()
|
||||||
assertFalse "encryptedKey must be populated", Arrays.length(encryptedKeyBytes) == 0
|
assertFalse "encryptedKey must be populated", Arrays.length(encryptedKeyBytes) == 0
|
||||||
|
|
||||||
def dcek = alg.getDecryptionKey(new DefaultKeyRequest<byte[], SecretKey>(null, null, encryptedKeyBytes, kek, header))
|
def dcek = alg.getDecryptionKey(new DefaultKeyRequest<byte[], SecretKey>(null, null, encryptedKeyBytes, kek, header, enc))
|
||||||
|
|
||||||
//Assert the decrypted key matches the original cek
|
//Assert the decrypted key matches the original cek
|
||||||
assertEquals cek.algorithm, dcek.algorithm
|
assertEquals cek.algorithm, dcek.algorithm
|
||||||
|
@ -100,16 +105,21 @@ class AesGcmKeyAlgorithmTest {
|
||||||
def header = new DefaultJweHeader()
|
def header = new DefaultJweHeader()
|
||||||
def kek = template.generateSecretKey(keyLength)
|
def kek = template.generateSecretKey(keyLength)
|
||||||
def cek = template.generateSecretKey(keyLength)
|
def cek = template.generateSecretKey(keyLength)
|
||||||
def ereq = new DefaultKeyRequest(null, null, cek, kek, header)
|
def enc = new GcmAesAeadAlgorithm(keyLength) {
|
||||||
|
@Override
|
||||||
|
SecretKey generateKey() {
|
||||||
|
return cek
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def ereq = new DefaultKeyRequest(null, null, cek, kek, header, enc)
|
||||||
def result = alg.getEncryptionKey(ereq)
|
def result = alg.getEncryptionKey(ereq)
|
||||||
header.putAll(result.getHeaderParams())
|
|
||||||
|
|
||||||
header.put(headerName, value) //null value will remove it
|
header.put(headerName, value) //null value will remove it
|
||||||
|
|
||||||
byte[] encryptedKeyBytes = result.getPayload()
|
byte[] encryptedKeyBytes = result.getPayload()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
alg.getDecryptionKey(new DefaultKeyRequest<byte[], SecretKey>(null, null, encryptedKeyBytes, kek, header))
|
alg.getDecryptionKey(new DefaultKeyRequest<byte[], SecretKey>(null, null, encryptedKeyBytes, kek, header, enc))
|
||||||
fail()
|
fail()
|
||||||
} catch (MalformedJwtException iae) {
|
} catch (MalformedJwtException iae) {
|
||||||
assertEquals exmsg, iae.getMessage()
|
assertEquals exmsg, iae.getMessage()
|
||||||
|
@ -117,17 +127,16 @@ class AesGcmKeyAlgorithmTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
String missing(String name) {
|
String missing(String name) {
|
||||||
return "The A128GCMKW Key Management Algorithm requires a JweHeader '${name}' value." as String
|
return "JWE header is missing required '${name}' value." as String
|
||||||
}
|
}
|
||||||
|
|
||||||
String type(String name) {
|
String type(String name) {
|
||||||
return "The A128GCMKW Key Management Algorithm requires the JweHeader '${name}' value to be a Base64URL-encoded String. Actual type: java.lang.Integer" as String
|
return "JWE header '${name}' value must be a String. Actual type: java.lang.Integer" as String
|
||||||
}
|
}
|
||||||
String base64Url(String name) {
|
String base64Url(String name) {
|
||||||
return "JweHeader '${name}' value 'T#ZW@#' does not appear to be a valid Base64URL String: Illegal base64url character: '#'"
|
return "JWE header '${name}' value is not a valid Base64URL String: Illegal base64url character: '#'"
|
||||||
}
|
}
|
||||||
String length(String name, int requiredLen) {
|
String length(String name, int requiredBitLength) {
|
||||||
return "The 'A128GCMKW' key management algorithm requires the JweHeader '${name}' value to be ${requiredLen * Byte.SIZE} bits (${requiredLen} bytes) in length. Actual length: 16 bits (2 bytes)."
|
return "JWE header '${name}' decoded byte array must be ${Bytes.bitsMsg(requiredBitLength)} long. Actual length: ${Bytes.bitsMsg(16)}."
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -151,7 +160,7 @@ class AesGcmKeyAlgorithmTest {
|
||||||
@Test
|
@Test
|
||||||
void testIncorrectLengths() {
|
void testIncorrectLengths() {
|
||||||
def value = Encoders.BASE64URL.encode("hi".getBytes(StandardCharsets.US_ASCII))
|
def value = Encoders.BASE64URL.encode("hi".getBytes(StandardCharsets.US_ASCII))
|
||||||
testDecryptionHeader('iv', value, length('iv', 12))
|
testDecryptionHeader('iv', value, length('iv', 96))
|
||||||
testDecryptionHeader('tag', value, length('tag', 16))
|
testDecryptionHeader('tag', value, length('tag', 128))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
package io.jsonwebtoken.impl.security
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @since JJWT_RELEASE_VERSION
|
|
||||||
*/
|
|
||||||
class DefaultJweFactoryTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDefaultCtor() {
|
|
||||||
new DefaultJweFactory()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,7 +21,7 @@ class DirectKeyAlgorithmTest {
|
||||||
void testGetEncryptionKey() {
|
void testGetEncryptionKey() {
|
||||||
def alg = new DirectKeyAlgorithm()
|
def alg = new DirectKeyAlgorithm()
|
||||||
def key = new SecretKeySpec(new byte[1], "AES")
|
def key = new SecretKeySpec(new byte[1], "AES")
|
||||||
def request = new DefaultKeyRequest(null, null, key, key, new DefaultJweHeader())
|
def request = new DefaultKeyRequest(null, null, key, key, new DefaultJweHeader(), null)
|
||||||
def result = alg.getEncryptionKey(request)
|
def result = alg.getEncryptionKey(request)
|
||||||
assertSame key, result.getKey()
|
assertSame key, result.getKey()
|
||||||
assertEquals 0, Arrays.length(result.getPayload()) //must not have an encrypted key
|
assertEquals 0, Arrays.length(result.getPayload()) //must not have an encrypted key
|
||||||
|
@ -35,7 +35,7 @@ class DirectKeyAlgorithmTest {
|
||||||
@Test(expected = IllegalArgumentException)
|
@Test(expected = IllegalArgumentException)
|
||||||
void testGetEncryptionKeyWithNullRequestKey() {
|
void testGetEncryptionKeyWithNullRequestKey() {
|
||||||
def key = new SecretKeySpec(new byte[1], "AES")
|
def key = new SecretKeySpec(new byte[1], "AES")
|
||||||
def request = new DefaultKeyRequest(null, null, key, key, new DefaultJweHeader()) {
|
def request = new DefaultKeyRequest(null, null, key, key, new DefaultJweHeader(), null) {
|
||||||
@Override
|
@Override
|
||||||
Key getKey() {
|
Key getKey() {
|
||||||
return null
|
return null
|
||||||
|
@ -48,7 +48,7 @@ class DirectKeyAlgorithmTest {
|
||||||
void testGetDecryptionKey() {
|
void testGetDecryptionKey() {
|
||||||
def alg = new DirectKeyAlgorithm()
|
def alg = new DirectKeyAlgorithm()
|
||||||
def key = new SecretKeySpec(new byte[1], "AES")
|
def key = new SecretKeySpec(new byte[1], "AES")
|
||||||
def request = new DefaultKeyRequest(null, null, key, key, new DefaultJweHeader())
|
def request = new DefaultKeyRequest(null, null, key, key, new DefaultJweHeader(), null)
|
||||||
def result = alg.getDecryptionKey(request)
|
def result = alg.getDecryptionKey(request)
|
||||||
assertSame key, result
|
assertSame key, result
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ class DirectKeyAlgorithmTest {
|
||||||
@Test(expected = IllegalArgumentException)
|
@Test(expected = IllegalArgumentException)
|
||||||
void testGetDecryptionKeyWithNullRequestKey() {
|
void testGetDecryptionKeyWithNullRequestKey() {
|
||||||
def key = new SecretKeySpec(new byte[1], "AES")
|
def key = new SecretKeySpec(new byte[1], "AES")
|
||||||
def request = new DefaultKeyRequest(null, null, key, key, new DefaultJweHeader()) {
|
def request = new DefaultKeyRequest(null, null, key, key, new DefaultJweHeader(), null) {
|
||||||
@Override
|
@Override
|
||||||
Key getKey() {
|
Key getKey() {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -51,7 +51,7 @@ class GcmAesAeadAlgorithmTest {
|
||||||
def result = alg.encrypt(req)
|
def result = alg.encrypt(req)
|
||||||
|
|
||||||
byte[] ciphertext = result.getPayload()
|
byte[] ciphertext = result.getPayload()
|
||||||
byte[] tag = result.getAuthenticationTag()
|
byte[] tag = result.getDigest()
|
||||||
byte[] iv = result.getInitializationVector()
|
byte[] iv = result.getInitializationVector()
|
||||||
|
|
||||||
assertArrayEquals E, ciphertext
|
assertArrayEquals E, ciphertext
|
||||||
|
|
|
@ -33,7 +33,7 @@ class HmacAesAeadAlgorithmTest {
|
||||||
def req = new DefaultSymmetricAeadRequest(null, null, plaintext, key, null)
|
def req = new DefaultSymmetricAeadRequest(null, null, plaintext, key, null)
|
||||||
def result = alg.encrypt(req);
|
def result = alg.encrypt(req);
|
||||||
|
|
||||||
def realTag = result.getAuthenticationTag();
|
def realTag = result.getDigest();
|
||||||
|
|
||||||
//fake it:
|
//fake it:
|
||||||
def fakeTag = new byte[realTag.length]
|
def fakeTag = new byte[realTag.length]
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package io.jsonwebtoken.impl.security
|
package io.jsonwebtoken.impl.security
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.lang.CheckedFunction
|
||||||
import io.jsonwebtoken.security.CryptoException
|
import io.jsonwebtoken.security.CryptoException
|
||||||
import io.jsonwebtoken.security.SignatureException
|
import io.jsonwebtoken.security.SignatureException
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -18,9 +19,9 @@ class JcaTemplateTest {
|
||||||
void testNewCipherWithExplicitProvider() {
|
void testNewCipherWithExplicitProvider() {
|
||||||
Provider provider = Security.getProvider('SunJCE')
|
Provider provider = Security.getProvider('SunJCE')
|
||||||
def template = new JcaTemplate('AES/CBC/PKCS5Padding', provider)
|
def template = new JcaTemplate('AES/CBC/PKCS5Padding', provider)
|
||||||
template.execute(Cipher.class, new InstanceCallback<Cipher, byte[]>() {
|
template.execute(Cipher.class, new CheckedFunction<Cipher, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
byte[] doWithInstance(Cipher cipher) throws Exception {
|
byte[] apply(Cipher cipher) throws Exception {
|
||||||
assertNotNull cipher
|
assertNotNull cipher
|
||||||
assertSame provider, cipher.provider
|
assertSame provider, cipher.provider
|
||||||
return new byte[0]
|
return new byte[0]
|
||||||
|
@ -97,9 +98,9 @@ class JcaTemplateTest {
|
||||||
def ex = new Exception("testing")
|
def ex = new Exception("testing")
|
||||||
def template = new JcaTemplate('AES/CBC/PKCS5Padding', null)
|
def template = new JcaTemplate('AES/CBC/PKCS5Padding', null)
|
||||||
try {
|
try {
|
||||||
template.execute(Cipher.class, new InstanceCallback<Cipher,byte[]>() {
|
template.execute(Cipher.class, new CheckedFunction<Cipher, byte[]>() {
|
||||||
@Override
|
@Override
|
||||||
byte[] doWithInstance(Cipher cipher) throws Exception {
|
byte[] apply(Cipher cipher) throws Exception {
|
||||||
throw ex
|
throw ex
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package io.jsonwebtoken.impl.security
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.DefaultJweHeader
|
||||||
|
import io.jsonwebtoken.security.EncryptionAlgorithms
|
||||||
|
import io.jsonwebtoken.security.KeyAlgorithms
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
|
class Pbes2HsAkwAlgorithmTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void test() {
|
||||||
|
|
||||||
|
def alg = KeyAlgorithms.PBES2_HS256_A128KW
|
||||||
|
|
||||||
|
int desiredMillis = 200
|
||||||
|
int iterations = KeyAlgorithms.estimateIterations(alg, desiredMillis)
|
||||||
|
println "Estimated iterations: $iterations"
|
||||||
|
|
||||||
|
int tries = 30
|
||||||
|
int skip = 6
|
||||||
|
//double scale = 0.5035246727
|
||||||
|
|
||||||
|
def payload = 'hello world'.getBytes(StandardCharsets.UTF_8)
|
||||||
|
def key = Keys.forPbe().setPassword('hellowor').setWorkFactor(iterations).build()
|
||||||
|
def req = new DefaultKeyRequest(null, null, null, key, new DefaultJweHeader(), EncryptionAlgorithms.A128GCM)
|
||||||
|
int sum = 0;
|
||||||
|
for(int i = 0; i < tries; i++) {
|
||||||
|
long start = System.currentTimeMillis()
|
||||||
|
alg.getEncryptionKey(req)
|
||||||
|
long end = System.currentTimeMillis()
|
||||||
|
long duration = end - start;
|
||||||
|
if (i >= skip) {
|
||||||
|
sum+= duration
|
||||||
|
}
|
||||||
|
println "Try $i: ${alg.id} took $duration millis"
|
||||||
|
}
|
||||||
|
long avg = Math.round(sum / (tries - skip))
|
||||||
|
println "Average duration: $avg"
|
||||||
|
println "scale factor: ${desiredMillis / avg}"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package io.jsonwebtoken.impl.security
|
package io.jsonwebtoken.impl.security
|
||||||
|
|
||||||
|
|
||||||
import io.jsonwebtoken.Jwe
|
import io.jsonwebtoken.Jwe
|
||||||
import io.jsonwebtoken.JweHeader
|
import io.jsonwebtoken.JweHeader
|
||||||
import io.jsonwebtoken.Jwts
|
import io.jsonwebtoken.Jwts
|
||||||
|
@ -8,6 +7,8 @@ import io.jsonwebtoken.io.Encoders
|
||||||
import io.jsonwebtoken.io.SerializationException
|
import io.jsonwebtoken.io.SerializationException
|
||||||
import io.jsonwebtoken.io.Serializer
|
import io.jsonwebtoken.io.Serializer
|
||||||
import io.jsonwebtoken.security.KeyRequest
|
import io.jsonwebtoken.security.KeyRequest
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
|
import io.jsonwebtoken.security.PbeKey
|
||||||
import io.jsonwebtoken.security.SecurityRequest
|
import io.jsonwebtoken.security.SecurityRequest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
@ -303,11 +304,13 @@ class RFC7517AppendixCTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PbeKey pbeKey = Keys.forPbe().setPassword(RFC_SHARED_PASSPHRASE).setWorkFactor(RFC_P2C).build()
|
||||||
|
|
||||||
String compact = Jwts.jweBuilder()
|
String compact = Jwts.jweBuilder()
|
||||||
.setPayload(RFC_JWK_JSON)
|
.setPayload(RFC_JWK_JSON)
|
||||||
.setHeaderParam('cty', 'jwk+json')
|
.setHeaderParam('cty', 'jwk+json')
|
||||||
.encryptWith(encAlg)
|
.encryptWith(encAlg)
|
||||||
.withKeyFrom(RFC_SHARED_PASSPHRASE.toCharArray(), RFC_P2C, keyAlg)
|
.withKeyFrom(pbeKey, keyAlg)
|
||||||
.serializeToJsonWith(serializer) //ensure JJWT created the header as expected with an assertion serializer
|
.serializeToJsonWith(serializer) //ensure JJWT created the header as expected with an assertion serializer
|
||||||
.compact();
|
.compact();
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ class RFC7518AppendixB1Test {
|
||||||
def result = alg.encrypt(request);
|
def result = alg.encrypt(request);
|
||||||
|
|
||||||
byte[] ciphertext = result.getPayload()
|
byte[] ciphertext = result.getPayload()
|
||||||
byte[] tag = result.getAuthenticationTag()
|
byte[] tag = result.getDigest()
|
||||||
byte[] iv = result.getInitializationVector()
|
byte[] iv = result.getInitializationVector()
|
||||||
|
|
||||||
assertArrayEquals E, ciphertext
|
assertArrayEquals E, ciphertext
|
||||||
|
|
|
@ -74,7 +74,7 @@ class RFC7518AppendixB2Test {
|
||||||
AeadResult result = alg.encrypt(req)
|
AeadResult result = alg.encrypt(req)
|
||||||
|
|
||||||
byte[] resultCiphertext = result.getPayload()
|
byte[] resultCiphertext = result.getPayload()
|
||||||
byte[] resultTag = result.getAuthenticationTag()
|
byte[] resultTag = result.getDigest()
|
||||||
byte[] resultIv = result.getInitializationVector()
|
byte[] resultIv = result.getInitializationVector()
|
||||||
|
|
||||||
assertArrayEquals E, resultCiphertext
|
assertArrayEquals E, resultCiphertext
|
||||||
|
|
|
@ -74,7 +74,7 @@ class RFC7518AppendixB3Test {
|
||||||
AeadResult result = alg.encrypt(req)
|
AeadResult result = alg.encrypt(req)
|
||||||
|
|
||||||
byte[] resultCiphertext = result.getPayload()
|
byte[] resultCiphertext = result.getPayload()
|
||||||
byte[] resultTag = result.getAuthenticationTag();
|
byte[] resultTag = result.getDigest();
|
||||||
byte[] resultIv = result.getInitializationVector();
|
byte[] resultIv = result.getInitializationVector();
|
||||||
|
|
||||||
assertArrayEquals E, resultCiphertext
|
assertArrayEquals E, resultCiphertext
|
||||||
|
|
|
@ -49,7 +49,7 @@ class EncryptionAlgorithmsTest {
|
||||||
|
|
||||||
def result = alg.encrypt(request)
|
def result = alg.encrypt(request)
|
||||||
|
|
||||||
byte[] tag = result.getAuthenticationTag() //there is always a tag, even if there is no AAD
|
byte[] tag = result.getDigest() //there is always a tag, even if there is no AAD
|
||||||
assertNotNull tag
|
assertNotNull tag
|
||||||
|
|
||||||
byte[] ciphertext = result.getPayload()
|
byte[] ciphertext = result.getPayload()
|
||||||
|
@ -87,7 +87,7 @@ class EncryptionAlgorithmsTest {
|
||||||
assertEquals(ciphertext.length, PLAINTEXT_BYTES.length)
|
assertEquals(ciphertext.length, PLAINTEXT_BYTES.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
def dreq = new DefaultAeadResult(null, null, result.getPayload(), key, AAD_BYTES, result.getAuthenticationTag(), result.getInitializationVector());
|
def dreq = new DefaultAeadResult(null, null, result.getPayload(), key, AAD_BYTES, result.getDigest(), result.getInitializationVector());
|
||||||
byte[] decryptedPlaintextBytes = alg.decrypt(dreq).getPayload()
|
byte[] decryptedPlaintextBytes = alg.decrypt(dreq).getPayload()
|
||||||
assertArrayEquals(PLAINTEXT_BYTES, decryptedPlaintextBytes)
|
assertArrayEquals(PLAINTEXT_BYTES, decryptedPlaintextBytes)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue