diff --git a/api/src/main/java/io/jsonwebtoken/Jwts.java b/api/src/main/java/io/jsonwebtoken/Jwts.java index eed9bca1b..5d87653be 100644 --- a/api/src/main/java/io/jsonwebtoken/Jwts.java +++ b/api/src/main/java/io/jsonwebtoken/Jwts.java @@ -1068,7 +1068,7 @@ public static JwtBuilder builder() { /** * Returns a new {@link JwtParserBuilder} instance that can be configured to create an immutable/thread-safe {@link JwtParser}. * - * @return a new {@link JwtParser} instance that can be configured create an immutable/thread-safe {@link JwtParser}. + * @return a new {@link JwtParserBuilder} instance that can be configured create an immutable/thread-safe {@link JwtParser}. */ public static JwtParserBuilder parser() { return JWT_PARSER_BUILDER_SUPPLIER.get(); diff --git a/api/src/main/java/io/jsonwebtoken/security/AeadAlgorithm.java b/api/src/main/java/io/jsonwebtoken/security/AeadAlgorithm.java index 7e82d85f9..721f53a2a 100644 --- a/api/src/main/java/io/jsonwebtoken/security/AeadAlgorithm.java +++ b/api/src/main/java/io/jsonwebtoken/security/AeadAlgorithm.java @@ -78,6 +78,12 @@ public interface AeadAlgorithm extends Identifiable, KeyLengthSupplier, KeyBuild */ void encrypt(AeadRequest req, AeadResult res) throws SecurityException; + default AeadResult encrypt(AeadRequest req, OutputStream out) throws SecurityException { + AeadResult result = AeadResult.with(out); + encrypt(req, result); + return result; + } + /** * Decrypts ciphertext and authenticates any {@link DecryptAeadRequest#getAssociatedData() associated data}, * writing the decrypted plaintext to the provided {@code out}put stream. diff --git a/api/src/main/java/io/jsonwebtoken/security/AeadRequest.java b/api/src/main/java/io/jsonwebtoken/security/AeadRequest.java index 828786982..5dc1aa542 100644 --- a/api/src/main/java/io/jsonwebtoken/security/AeadRequest.java +++ b/api/src/main/java/io/jsonwebtoken/security/AeadRequest.java @@ -17,6 +17,7 @@ import javax.crypto.SecretKey; import java.io.InputStream; +import java.io.OutputStream; /** * A request to an {@link AeadAlgorithm} to perform authenticated encryption with a supplied symmetric @@ -27,4 +28,46 @@ * @since 0.12.0 */ public interface AeadRequest extends SecureRequest, AssociatedDataSupplier { + + /** + * Named parameters (setters) used to configure an {@link AeadRequest AeadRequest} instance. + * + * @param the instance type returned for method chaining. + * @since JJWT_RELEASE_VERSION + */ + interface Params> extends SecureRequest.Params { + + /** + * Sets any "associated data" that must be integrity protected (but not encrypted) when performing + * AEAD encryption or decryption. + * + * @param aad the {@code InputStream} containing any associated data that must be integrity protected or + * verified during AEAD encryption or decryption. + * @return the instance for method chaining. + * @see AeadAlgorithm#encrypt(AeadRequest, AeadResult) + * @see AeadAlgorithm#decrypt(DecryptAeadRequest, OutputStream) + */ + M associatedData(InputStream aad); + } + + /** + * A builder for creating new immutable {@link AeadRequest} instances used for AEAD encryption via + * {@link AeadAlgorithm#encrypt(AeadRequest, AeadResult)}. + * + * @since JJWT_RELEASE_VERSION + */ + interface Builder extends Params, io.jsonwebtoken.lang.Builder { + } + + /** + * Returns a new {@link AeadRequest.Builder} for creating immutable {@link AeadRequest}s used for AEAD encryption + * via {@link AeadAlgorithm#encrypt(AeadRequest, AeadResult)}. + * + * @return a new {@link AeadRequest.Builder} for creating immutable {@link AeadRequest}s used for AEAD encryption + * via {@link AeadAlgorithm#encrypt(AeadRequest, AeadResult)}. + * @since JJWT_RELEASE_VERSION + */ + static AeadRequest.Builder builder() { + return Suppliers.AEAD_REQUEST_BUILDER.get(); + } } diff --git a/api/src/main/java/io/jsonwebtoken/security/AeadResult.java b/api/src/main/java/io/jsonwebtoken/security/AeadResult.java index c8734b5a3..51e9164b1 100644 --- a/api/src/main/java/io/jsonwebtoken/security/AeadResult.java +++ b/api/src/main/java/io/jsonwebtoken/security/AeadResult.java @@ -24,7 +24,7 @@ * * @since 0.12.0 */ -public interface AeadResult { +public interface AeadResult extends DigestSupplier, IvSupplier { /** * Returns the {@code OutputStream} the AeadAlgorithm will use to write the resulting ciphertext during @@ -50,4 +50,18 @@ public interface AeadResult { * @return the AeadResult for method chaining. */ AeadResult setIv(byte[] iv); + + /** + * Returns a new {@link AeadResult} with the specified {@link OutputStream} that will be used to write the + * resulting ciphertext during encryption or plaintext during decryption. + * + * @param out the {@link OutputStream} that will be used to write the resulting ciphertext during encryption + * @return a new {@link AeadResult} with the specified {@link OutputStream} that will be used to write the + * resulting ciphertext during encryption. + * @since JJWT_RELEASE_VERSION + */ + static AeadResult with(OutputStream out) { + return Suppliers.AEAD_RESULT_FACTORY.apply(out); + } + } diff --git a/api/src/main/java/io/jsonwebtoken/security/DecryptAeadRequest.java b/api/src/main/java/io/jsonwebtoken/security/DecryptAeadRequest.java index 5faf1f6a5..b49d35a5f 100644 --- a/api/src/main/java/io/jsonwebtoken/security/DecryptAeadRequest.java +++ b/api/src/main/java/io/jsonwebtoken/security/DecryptAeadRequest.java @@ -16,13 +16,61 @@ package io.jsonwebtoken.security; import javax.crypto.SecretKey; +import java.io.OutputStream; /** - * A request to an {@link AeadAlgorithm} to decrypt ciphertext and perform integrity-protection with a supplied + * A request to an {@link AeadAlgorithm} to decrypt ciphertext with integrity verification with a supplied * decryption {@link SecretKey}. Extends both {@link IvSupplier} and {@link DigestSupplier} to * ensure the respective required IV and AAD tag returned from an {@link AeadResult} are available for decryption. * * @since 0.12.0 */ public interface DecryptAeadRequest extends AeadRequest, IvSupplier, DigestSupplier { + + /** + * Named parameters (setters) used to configure an {@link AeadRequest AeadRequest} instance. + * + * @param the instance type returned for method chaining. + * @since JJWT_RELEASE_VERSION + */ + interface Params> extends AeadRequest.Params { + + /** + * Sets the required initialization vector used during AEAD decryption. + * + * @param iv the required initialization vector used during AEAD decryption. + * @return the instance for method chaining. + */ + M iv(byte[] iv); + + /** + * Sets the required AEAD Authentication Tag used to verify message authenticity during AEAD decryption. + * + * @param digest the required AEAD Authentication Tag used to verify message authenticity during AEAD decryption. + * @return the instance for method chaining. + */ + M digest(byte[] digest); + } + + /** + * A builder for creating new immutable {@link DecryptAeadRequest}s used for AEAD decryption via + * {@link AeadAlgorithm#decrypt(DecryptAeadRequest, OutputStream)}. + * + * @since JJWT_RELEASE_VERSION + */ + interface Builder extends io.jsonwebtoken.lang.Builder, Params { + } + + /** + * Returns a new {@link DecryptAeadRequest.Builder} for creating immutable {@link DecryptAeadRequest}s used for + * AEAD decryption via {@link AeadAlgorithm#decrypt(DecryptAeadRequest, OutputStream)}. + * + * @return a new {@link DecryptAeadRequest.Builder} for creating immutable {@link DecryptAeadRequest}s used for + * AEAD decryption via {@link AeadAlgorithm#decrypt(DecryptAeadRequest, OutputStream)}. + * @since JJWT_RELEASE_VERSION + */ + static DecryptAeadRequest.Builder builder() { + return Suppliers.DECRYPT_AEAD_REQUEST_BUILDER.get(); + } + } diff --git a/api/src/main/java/io/jsonwebtoken/security/DecryptionKeyRequest.java b/api/src/main/java/io/jsonwebtoken/security/DecryptionKeyRequest.java index 893cad946..ab6677851 100644 --- a/api/src/main/java/io/jsonwebtoken/security/DecryptionKeyRequest.java +++ b/api/src/main/java/io/jsonwebtoken/security/DecryptionKeyRequest.java @@ -35,8 +35,46 @@ * {@code JWE Encrypted Key} (such as an initialization vector, authentication tag, ephemeral key, etc) is expected * to be available in the JWE protected header, accessible via {@link #getHeader()}.

* - * @param the type of {@link Key} used during the request to obtain the resulting decryption key. + * @param the type of key used by the {@link KeyAlgorithm} to obtain the JWE Content Encryption Key (CEK). + * @see KeyAlgorithm#getDecryptionKey(DecryptionKeyRequest) * @since 0.12.0 */ public interface DecryptionKeyRequest extends SecureRequest, KeyRequest { + + /** + * Named parameters (setters) used to configure a {@link DecryptionKeyRequest DecryptionKeyRequest} + * instance. + * + * @param the type of key used by the {@link KeyAlgorithm} to obtain the JWE Content Encryption Key (CEK). + * @param the instance type returned for method chaining. + * @since JJWT_RELEASE_VERSION + */ + interface Params> extends KeyRequest.Params, + SecureRequest.Params { + } + + /** + * A builder for creating new immutable {@link DecryptionKeyRequest} instances used to get a JWE + * decryption key via {@link KeyAlgorithm#getDecryptionKey(DecryptionKeyRequest)}. + * + * @param the type of key used by the {@link KeyAlgorithm} to obtain the JWE Content Encryption Key (CEK). + * @since JJWT_RELEASE_VERSION + */ + interface Builder extends Params>, io.jsonwebtoken.lang.Builder> { + } + + /** + * Returns a new {@link DecryptionKeyRequest.Builder} for creating immutable {@link DecryptionKeyRequest}s used to + * get a JWE decryption key via {@link KeyAlgorithm#getDecryptionKey(DecryptionKeyRequest)}. + * + * @param the type of key used to obtain the JWE decryption key via + * {@link KeyAlgorithm#getDecryptionKey(DecryptionKeyRequest)}. + * @return a new {@link DecryptionKeyRequest.Builder} for creating immutable {@link DecryptionKeyRequest}s used to + * get a JWE decryption key via {@link KeyAlgorithm#getDecryptionKey(DecryptionKeyRequest)}. + * @since JJWT_RELEASE_VERSION + */ + @SuppressWarnings("unchecked") + static DecryptionKeyRequest.Builder builder() { + return (DecryptionKeyRequest.Builder) Suppliers.DECRYPTION_KEY_REQUEST_BUILDER.get(); + } } diff --git a/api/src/main/java/io/jsonwebtoken/security/HashAlgorithm.java b/api/src/main/java/io/jsonwebtoken/security/HashAlgorithm.java index 3bc4ec449..6adf21912 100644 --- a/api/src/main/java/io/jsonwebtoken/security/HashAlgorithm.java +++ b/api/src/main/java/io/jsonwebtoken/security/HashAlgorithm.java @@ -16,8 +16,10 @@ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.lang.Assert; import java.io.InputStream; +import java.util.function.Consumer; /** * A {@link DigestAlgorithm} that computes and verifies digests without the use of a cryptographic key, such as for @@ -42,4 +44,82 @@ * @since 0.12.0 */ public interface HashAlgorithm extends DigestAlgorithm, VerifyDigestRequest> { + + /** + * Computes a digest of a request {@link Request#getPayload() payload} {@code InputStream} using configured + * parameters. This is a lambda-style method to execute the request in-line instead of requiring the caller to + * first use a {@link Request.Builder} to construct the request. + * + *

Callers are expected to {@link InputStream#close() close} or {@link InputStream#reset() reset} the request + * payload stream if necessary after calling this method.

+ * + * @param c consumer supporting lambda-style specification of digest {@link Request.Params}. + * @return the computed digest of the request {@link Request#getPayload() payload}. + * @since JJWT_RELEASE_VERSION + */ + default byte[] digest(Consumer> c) { + Assert.notNull(c, "Consumer cannot be null"); + Request.Builder b = Request.builder(); + c.accept(b); + Request r = b.build(); + return digest(r); + } + + /** + * Computes a digest of the specified {@code is} input stream. + * + *

Callers are expected to {@link InputStream#close() close} or {@link InputStream#reset() reset} the payload + * stream if necessary after calling this method.

+ * + * @param is the {@code InputStream} that will be consumed to compute the digest. Callers are expected to + * {@link InputStream#close() close} or {@link InputStream#reset() reset} the payload stream if necessary + * after calling this method. + * @return the computed digest of the specified {@code is} input stream. + * @since JJWT_RELEASE_VERSION + */ + default byte[] digest(InputStream is) { + return digest(c -> c.payload(is)); + } + + /** + * Returns {@code true} if the request's specified {@link VerifyDigestRequest#getDigest() digest} matches (equals) + * the algorithm's computed digest of the request {@link VerifyDigestRequest#getPayload() payload}, {@code false} + * otherwise. This is a lambda-style method to execute the request in-line instead of requiring the caller to first + * use a {@link VerifyDigestRequest.Builder} to construct the request. + * + *

Callers are expected to {@link InputStream#close() close} or {@link InputStream#reset() reset} the request + * payload stream if necessary after calling this method.

+ * + * @param c consumer supporting lambda-style specification of {@link VerifyDigestRequest.Params}. + * @return {@code true} if the request's specified {@link VerifyDigestRequest#getDigest() digest} matches (equals) + * the algorithm's computed digest of the request {@link VerifyDigestRequest#getPayload() payload}, {@code false} + * otherwise. + * @since JJWT_RELEASE_VERSION + */ + default boolean verify(Consumer> c) { + Assert.notNull(c, "Consumer cannot be null"); + VerifyDigestRequest.Builder b = VerifyDigestRequest.builder(); + c.accept(b); + VerifyDigestRequest r = b.build(); + return verify(r); + } + + /** + * Returns {@code true} if the specified {@code digest} matches (equals) the algorithm's computed digest of the + * specified {@code is} input stream, {@code false} otherwise. + * + *

Callers are expected to {@link InputStream#close() close} or {@link InputStream#reset() reset} the payload + * stream if necessary after calling this method.

+ * + * @param is the {@code InputStream} that will be consumed to compute the digest. Callers are expected to + * {@link InputStream#close() close} or {@link InputStream#reset() reset} the payload stream if + * necessary after calling this method. + * @param digest the previously-computed digest to compare with the algorithm's computed digest of {@code is}. + * @return {@code true} if the specified {@code digest} matches (equals) the algorithm's computed digest of the + * specified {@code is} input stream, {@code false} otherwise. + * @since JJWT_RELEASE_VERSION + */ + default boolean verify(InputStream is, byte[] digest) { + return verify(c -> c.payload(is).digest(digest)); + } } diff --git a/api/src/main/java/io/jsonwebtoken/security/KeyAlgorithm.java b/api/src/main/java/io/jsonwebtoken/security/KeyAlgorithm.java index e3cd13c65..3c5494f26 100644 --- a/api/src/main/java/io/jsonwebtoken/security/KeyAlgorithm.java +++ b/api/src/main/java/io/jsonwebtoken/security/KeyAlgorithm.java @@ -16,10 +16,13 @@ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.lang.Assert; import javax.crypto.SecretKey; import java.security.Key; +import java.util.function.Consumer; /** * A {@code KeyAlgorithm} produces the {@link SecretKey} used to encrypt or decrypt a JWE. The {@code KeyAlgorithm} @@ -65,6 +68,17 @@ public interface KeyAlgorithm extends Identifiable */ KeyResult getEncryptionKey(KeyRequest request) throws SecurityException; + default KeyResult getEncryptionKey(Consumer> p) throws SecurityException { + Assert.notNull(p, "Consumer cannot be null"); + KeyRequest.Builder builder = KeyRequest.builder(); + p.accept(builder); + return getEncryptionKey(builder.build()); + } + + default KeyResult getEncryptionKey(E key, JweHeader header, AeadAlgorithm enc) throws SecurityException { + return getEncryptionKey(p -> p.payload(key).header(header).encryptionAlgorithm(enc)); + } + /** * Return the {@link SecretKey} that should be used to decrypt a JWE via the request's specified * {@link DecryptionKeyRequest#getEncryptionAlgorithm() AeadAlgorithm}. @@ -81,4 +95,15 @@ public interface KeyAlgorithm extends Identifiable * @throws SecurityException if there is a problem obtaining or decrypting the AEAD {@code SecretKey}. */ SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException; + + default SecretKey getDecryptionKey(Consumer> p) throws SecurityException { + Assert.notNull(p, "Consumer cannot be null"); + DecryptionKeyRequest.Builder builder = DecryptionKeyRequest.builder(); + p.accept(builder); + return getDecryptionKey(builder.build()); + } + + default SecretKey getDecryptionKey(byte[] cekCiphertext, D decryptionKey, JweHeader header, AeadAlgorithm enc) throws SecurityException { + return getDecryptionKey(p -> p.payload(cekCiphertext).key(decryptionKey).header(header).encryptionAlgorithm(enc)); + } } diff --git a/api/src/main/java/io/jsonwebtoken/security/KeyRequest.java b/api/src/main/java/io/jsonwebtoken/security/KeyRequest.java index ffe22061e..1e1adf6f0 100644 --- a/api/src/main/java/io/jsonwebtoken/security/KeyRequest.java +++ b/api/src/main/java/io/jsonwebtoken/security/KeyRequest.java @@ -17,6 +17,8 @@ import io.jsonwebtoken.JweHeader; +import java.security.Key; + /** * A request to a {@link KeyAlgorithm} to obtain the key necessary for AEAD encryption or decryption. The exact * {@link AeadAlgorithm} that will be used is accessible via {@link #getEncryptionAlgorithm()}. @@ -74,4 +76,73 @@ public interface KeyRequest extends Request { * reading or writing any {@link KeyAlgorithm}-specific information. */ JweHeader getHeader(); + + /** + * Named parameters (setters) used to configure a {@link KeyRequest KeyRequest} instance. + * + * @param the type of request payload. For an encryption key request, this will be the + * key used to obtain the encryption key. For a decryption key request, this will be the encrypted CEK + * (Content Encryption Key) ciphertext byte array. + * @param the instance type returned for method chaining. + * @since JJWT_RELEASE_VERSION + */ + interface Params> extends Request.Params { + + /** + * Sets the {@link JweHeader} that will be used to construct the final JWE header, available for + * reading or writing any {@link KeyAlgorithm}-specific information. + * + *

For an encryption key request, any public information specific to the called {@code KeyAlgorithm} + * implementation that is required to be transmitted in the JWE (such as an initialization vector, + * authentication tag or ephemeral key, etc) is expected to be added to this header. Although the header is + * checked for authenticity and integrity, it itself is not encrypted, so + * {@link KeyAlgorithm}s should never place any secret or private information in the header.

+ * + *

For a decryption request, any public information necessary by the called {@link KeyAlgorithm} + * (such as an initialization vector, authentication tag, ephemeral key, etc) is expected to be available in + * this header.

+ * + * @param header the {@link JweHeader} that will be used to construct the final JWE header, available for + * reading or writing any {@link KeyAlgorithm}-specific information. + * @return the instance for method chaining. + */ + M header(JweHeader header); + + /** + * Sets the {@link AeadAlgorithm} that will be called for encryption or decryption after processing the + * {@code KeyRequest}. {@link KeyAlgorithm} implementations that generate an ephemeral {@code SecretKey} to use + * as what the JWE specification calls a + * "Content Encryption Key (CEK)" should call the {@code AeadAlgorithm}'s + * {@link AeadAlgorithm#key() key()} builder to create a key suitable for that exact {@code AeadAlgorithm}. + * + * @param alg the {@link AeadAlgorithm} that will be called for encryption or decryption after processing the + * {@code KeyRequest}. + * @return the instance for method chaining. + */ + M encryptionAlgorithm(AeadAlgorithm alg); + } + + /** + * A builder for creating {@link KeyRequest}s used to get a JWE encryption key via + * {@link KeyAlgorithm#getEncryptionKey(KeyRequest)}. + * + * @param the type of {@link java.security.Key Key} used to obtain the encryption key. + * @since JJWT_RELEASE_VERSION + */ + interface Builder extends Params>, io.jsonwebtoken.lang.Builder> { + } + + /** + * Returns a new {@link KeyRequest.Builder} for creating immutable {@link KeyRequest}s used to get a JWE + * encryption key via {@link KeyAlgorithm#getEncryptionKey(KeyRequest)}. + * + * @param the type of {@link java.security.Key Key} used to obtain the JWE content encryption key. + * @return a new {@link KeyRequest.Builder} for creating immutable {@link KeyRequest}s used to get a JWE + * encryption key via {@link KeyAlgorithm#getEncryptionKey(KeyRequest)}. + * @since JJWT_RELEASE_VERSION + */ + @SuppressWarnings("unchecked") + static KeyRequest.Builder builder() { + return (KeyRequest.Builder) Suppliers.KEY_REQUEST_BUILDER.get(); + } } diff --git a/api/src/main/java/io/jsonwebtoken/security/Request.java b/api/src/main/java/io/jsonwebtoken/security/Request.java index 77e0d32f9..c6b8a6ee1 100644 --- a/api/src/main/java/io/jsonwebtoken/security/Request.java +++ b/api/src/main/java/io/jsonwebtoken/security/Request.java @@ -54,4 +54,63 @@ public interface Request extends Message { * {@code null} if a default {@link SecureRandom} should be used. */ SecureRandom getSecureRandom(); + + /** + * Named parameters (setters) used to configure a {@link Request Request} instance. + * + * @param the type of payload in the request. + * @param the instance type returned for method chaining. + * @since JJWT_RELEASE_VERSION + */ + interface Params> { + + /** + * Sets the JCA provider that should be used for cryptographic operations during the request. A {@code null} + * value indicates that the JCA subsystem preferred provider should be used by default. + * + * @param provider the JCA provider that should be used for cryptographic operations during the request, or + * {@code null} to use the JCA subsystem preferred provider. + * @return the instance for method chaining. + */ + M provider(Provider provider); + + /** + * Sets the {@code SecureRandom} to use when performing cryptographic operations during the request. A + * {@code null} value ensures a default {@link SecureRandom} should be used. + * + * @param random the {@code SecureRandom} to use when performing cryptographic operations during the request, + * or {@code null} if a default {@link SecureRandom} should be used. + * @return the instance for method chaining. + */ + M random(SecureRandom random); + + /** + * Sets the request payload. + * + * @param payload the request payload. + * @return the instance for method chaining. + */ + M payload(T payload); + } + + /** + * A builder for creating new immutable {@link Request} instances. + * + * @param the type of payload in the request. + * @since JJWT_RELEASE_VERSION + */ + interface Builder extends io.jsonwebtoken.lang.Builder>, Params> { + } + + /** + * Returns a new {@link Request.Builder} for creating immutable {@link Request}s. + * + * @param the type of payload in the request. + * @return a new {@link Request.Builder} for creating immutable {@link Request}s. + * @since JJWT_RELEASE_VERSION + */ + @SuppressWarnings("unchecked") + static Builder builder() { + return (Builder) Suppliers.REQUEST_BUILDER.get(); + } } diff --git a/api/src/main/java/io/jsonwebtoken/security/SecureDigestAlgorithm.java b/api/src/main/java/io/jsonwebtoken/security/SecureDigestAlgorithm.java index fbe671d72..fd56e4792 100644 --- a/api/src/main/java/io/jsonwebtoken/security/SecureDigestAlgorithm.java +++ b/api/src/main/java/io/jsonwebtoken/security/SecureDigestAlgorithm.java @@ -16,9 +16,11 @@ package io.jsonwebtoken.security; import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.lang.Assert; import java.io.InputStream; import java.security.Key; +import java.util.function.Consumer; /** * A {@link DigestAlgorithm} that requires a {@link Key} to compute and verify the authenticity of digests using either @@ -52,4 +54,99 @@ */ public interface SecureDigestAlgorithm extends DigestAlgorithm, VerifySecureDigestRequest> { + + /** + * Computes a mac or signature of an {@link InputStream} using named parameters. At least the + * {@link SecureRequest#getPayload() payload} and mac or signing {@link SecureRequest#getKey() key} parameters + * must be specified. For example: + * + *

+ * alg.digest(r -> r.{@link SecureRequest.Params#payload(Object) payload}(is).{@link SecureRequest.Params#key(Key) key}(key)); + *

+ * + *

Callers are expected to {@link InputStream#close() close} or {@link InputStream#reset() reset} the + * payload {@code InputStream} if necessary after calling this method.

+ * + * @param c consumer supporting lambda-style specification of named digest {@link SecureRequest.Params}. + * @return the computed mac or signature for the request {@link SecureRequest#getPayload() payload}. + * @since JJWT_RELEASE_VERSION + */ + default byte[] digest(Consumer> c) { + Assert.notNull(c, "Consumer cannot be null"); + SecureRequest.Builder b = SecureRequest.builder(); + c.accept(b); + SecureRequest r = b.build(); + return digest(r); + } + + /** + * Computes a mac or signature of the specified {@code is} input stream using the specified {@code key}. This is + * a convenience method equivalent to: + * + *

+ * {@link #digest(Request) digest}(r -> r.{@link SecureRequest.Params#payload(Object) payload}(is).{@link SecureRequest.Params#key(Key) key}(key)); + *

+ * + *

Callers are expected to {@link InputStream#close() close} or {@link InputStream#reset() reset} the + * {@code is} input stream if necessary after calling this method.

+ * + * @param key the key used to compute the mac or signature + * @param is the {@code InputStream} that will be consumed to compute the mac or signature. Callers are expected to + * {@link InputStream#close() close} or {@link InputStream#reset() reset} the {@code is} input stream if + * necessary after calling this method. + * @return the computed mac or signature of the specified {@code is} input stream. + * @since JJWT_RELEASE_VERSION + */ + default byte[] digest(S key, InputStream is) { + return digest(c -> c.payload(is).key(key)); + } + + /** + * Returns {@code true} if a given mac or signature for an {@link InputStream} is authentic, {@code false} + * otherwise. At least the {@link VerifySecureDigestRequest#getPayload() payload} stream, verification + * {@link VerifySecureDigestRequest#getKey() key}, and {@link VerifySecureDigestRequest#getDigest() digest} + * parameters must be specified. For example: + * + *

+ * alg.verify(r -> r.{@link VerifySecureDigestRequest.Params#key(Key) key}(key).{@link VerifySecureDigestRequest.Params#payload(Object) payload}(is).{@link VerifySecureDigestRequest.Params#digest(byte[]) digest}(macOrSignature)); + *

+ * + *

Callers are expected to {@link InputStream#close() close} or {@link InputStream#reset() reset} the request + * payload stream if necessary after calling this method.

+ * + * @param c consumer supporting lambda-style specification of named {@link VerifySecureDigestRequest.Params}. + * @return {@code true} if a given mac or signature for an {@code InputStream} is authentic, {@code false} otherwise. + * @since JJWT_RELEASE_VERSION + */ + default boolean verify(Consumer> c) { + Assert.notNull(c, "Consumer cannot be null"); + VerifySecureDigestRequest.Builder b = VerifySecureDigestRequest.builder(); + c.accept(b); + VerifySecureDigestRequest r = b.build(); + return verify(r); + } + + /** + * Returns {@code true} if the given {@code macOrSignature} for the {@code is} input stream is + * authentic, {@code false} otherwise. This is a convenience method equivalent to: + * + *

+ * {@link #verify(VerifyDigestRequest) verify}(r -> r.{@link VerifySecureDigestRequest.Params#key(Key) key}(key).{@link VerifySecureDigestRequest.Params#payload(Object) payload}(is).{@link VerifySecureDigestRequest.Params#digest(byte[]) digest}(macOrSignature)); + *

+ * + *

Callers are expected to {@link InputStream#close() close} or {@link InputStream#reset() reset} the + * {@code is} input stream if necessary after calling this method.

+ * + * @param key the key used to verify the mac or signature + * @param is the data claimed as authentic by {@code macOrSignature}, which will be consumed to + * verify authenticity. Callers are expected to {@link InputStream#close() close} or + * {@link InputStream#reset() reset} this stream if necessary after calling this method. + * @param macOrSignature the mac or signature claimed to authenticate the {@code is} input stream. + * @return {@code true} if the given {@code macOrSignature} for the {@code is} input stream is authentic, + * {@code false} otherwise. + * @since JJWT_RELEASE_VERSION + */ + default boolean verify(V key, InputStream is, byte[] macOrSignature) { + return verify(c -> c.payload(is).key(key).digest(macOrSignature)); + } } diff --git a/api/src/main/java/io/jsonwebtoken/security/SecureRequest.java b/api/src/main/java/io/jsonwebtoken/security/SecureRequest.java index 4e65c3076..52e6a48ad 100644 --- a/api/src/main/java/io/jsonwebtoken/security/SecureRequest.java +++ b/api/src/main/java/io/jsonwebtoken/security/SecureRequest.java @@ -25,4 +25,49 @@ * @since 0.12.0 */ public interface SecureRequest extends Request, KeySupplier { + + /** + * Named parameters (setters) used to configure a {@link SecureRequest SecureRequest} instance. + * + * @param the type of payload in the request. + * @param the type of key used by the algorithm during the request. + * @param the instance type returned for method chaining. + * @since JJWT_RELEASE_VERSION + */ + interface Params> extends Request.Params { + + /** + * Sets the key used by the algorithm during the request, must be compatible with the target algorithm. + * + * @param key the algorithm key to use during the request. + * @return the instance for method chaining. + */ + M key(K key); + } + + /** + * A builder for creating {@link SecureRequest}s used to compute a mac or signature via + * {@link SecureDigestAlgorithm#digest(Request)}. + * + * @param the type of payload in the request. + * @param the type of key used by the algorithm during the request. + * @since JJWT_RELEASE_VERSION + */ + interface Builder extends Params>, io.jsonwebtoken.lang.Builder> { + } + + /** + * Returns a new {@link SecureRequest.Builder} for creating {@link SecureRequest}s used to compute a mac or + * signature via {@link SecureDigestAlgorithm#digest(Request)}. + * + * @param the type of payload in the request. + * @param the type of key used by the algorithm to compute the digest. + * @return a new {@link SecureRequest.Builder} for creating {@link SecureRequest}s used to compute a mac or + * signature via {@link SecureDigestAlgorithm#digest(Request)}. + * @since JJWT_RELEASE_VERSION + */ + @SuppressWarnings("unchecked") + static SecureRequest.Builder builder() { + return (SecureRequest.Builder) Suppliers.SECURE_REQUEST_BUILDER.get(); + } } diff --git a/api/src/main/java/io/jsonwebtoken/security/Suppliers.java b/api/src/main/java/io/jsonwebtoken/security/Suppliers.java new file mode 100644 index 000000000..cf145b633 --- /dev/null +++ b/api/src/main/java/io/jsonwebtoken/security/Suppliers.java @@ -0,0 +1,60 @@ +/* + * Copyright © 2025 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.lang.Classes; + +import java.io.OutputStream; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Package-private on purpose - this is an internal utility use only. + * + * @since JJWT_RELEASE_VERSION + */ +final class Suppliers { + + private Suppliers() { // for coverage + } + + static final Supplier> REQUEST_BUILDER = + Classes.newInstance("io.jsonwebtoken.impl.security.DefaultRequest$Builder$Supplier"); + + static final Supplier VERIFY_DIGEST_REQUEST_BUILDER = + Classes.newInstance("io.jsonwebtoken.impl.security.DefaultVerifyDigestRequest$Builder$Supplier"); + + static final Supplier> SECURE_REQUEST_BUILDER = + Classes.newInstance("io.jsonwebtoken.impl.security.DefaultSecureRequest$Builder$Supplier"); + + static final Supplier> VERIFY_SECURE_DIGEST_REQUEST_BUILDER = + Classes.newInstance("io.jsonwebtoken.impl.security.DefaultVerifySecureDigestRequest$Builder$Supplier"); + + static final Supplier> KEY_REQUEST_BUILDER = + Classes.newInstance("io.jsonwebtoken.impl.security.DefaultKeyRequest$Builder$Supplier"); + + static final Supplier> DECRYPTION_KEY_REQUEST_BUILDER = + Classes.newInstance("io.jsonwebtoken.impl.security.DefaultDecryptionKeyRequest$Builder$Supplier"); + + static final Supplier AEAD_REQUEST_BUILDER = + Classes.newInstance("io.jsonwebtoken.impl.security.DefaultAeadRequest$Builder$Supplier"); + + static final Supplier DECRYPT_AEAD_REQUEST_BUILDER = + Classes.newInstance("io.jsonwebtoken.impl.security.DefaultDecryptAeadRequest$Builder$Supplier"); + + static final Function AEAD_RESULT_FACTORY = + Classes.newInstance("io.jsonwebtoken.impl.security.DefaultAeadResult$Factory"); +} diff --git a/api/src/main/java/io/jsonwebtoken/security/VerifyDigestRequest.java b/api/src/main/java/io/jsonwebtoken/security/VerifyDigestRequest.java index 34fbf16c7..80df532d3 100644 --- a/api/src/main/java/io/jsonwebtoken/security/VerifyDigestRequest.java +++ b/api/src/main/java/io/jsonwebtoken/security/VerifyDigestRequest.java @@ -30,4 +30,41 @@ * @since 0.12.0 */ public interface VerifyDigestRequest extends Request, DigestSupplier { + + /** + * Named parameters (setters) used to configure a {@link VerifyDigestRequest VerifyDigestRequest} instance. + * + * @param the instance type returned for method chaining. + * @since JJWT_RELEASE_VERSION + */ + interface Params> extends Request.Params { + + /** + * The digest to verify against the one computed for the given {@link #getPayload() payload}. + * + * @param digest the digest to verify against the one computed for the given {@link #getPayload() payload}. + * @return the instance for method chaining. + */ + M digest(byte[] digest); + } + + /** + * A builder for creating new immutable {@link VerifyDigestRequest} instances. + * + * @since JJWT_RELEASE_VERSION + */ + interface Builder extends Params, io.jsonwebtoken.lang.Builder { + } + + /** + * Returns a new {@link VerifyDigestRequest.Builder} for creating {@link VerifyDigestRequest}s to verify a + * digest via {@link HashAlgorithm#verify(VerifyDigestRequest)}. + * + * @return a new {@link VerifyDigestRequest.Builder} for creating {@link VerifyDigestRequest}s to verify a + * digest via {@link HashAlgorithm#verify(VerifyDigestRequest)}. + * @since JJWT_RELEASE_VERSION + */ + static VerifyDigestRequest.Builder builder() { + return Suppliers.VERIFY_DIGEST_REQUEST_BUILDER.get(); + } } diff --git a/api/src/main/java/io/jsonwebtoken/security/VerifySecureDigestRequest.java b/api/src/main/java/io/jsonwebtoken/security/VerifySecureDigestRequest.java index a1ddbd571..4ca832990 100644 --- a/api/src/main/java/io/jsonwebtoken/security/VerifySecureDigestRequest.java +++ b/api/src/main/java/io/jsonwebtoken/security/VerifySecureDigestRequest.java @@ -31,4 +31,40 @@ * @since 0.12.0 */ public interface VerifySecureDigestRequest extends SecureRequest, VerifyDigestRequest { + + /** + * Named parameters (setters) used to configure a {@link VerifySecureDigestRequest VerifySecureDigestRequest} + * instance. + * + * @param type of key to use to verify the digest. + * @param the instance type returned for method chaining. + * @since JJWT_RELEASE_VERSION + */ + interface Params> extends SecureRequest.Params, + VerifyDigestRequest.Params { + } + + /** + * A builder for creating {@link VerifySecureDigestRequest}s used to verify a mac or signature via + * {@link SecureDigestAlgorithm#verify(VerifyDigestRequest)}. + * + * @param type of key used to verify the digest. + * @since JJWT_RELEASE_VERSION + */ + interface Builder extends Params>, io.jsonwebtoken.lang.Builder> { + } + + /** + * Returns a new {@link VerifySecureDigestRequest.Builder} for creating {@link VerifySecureDigestRequest}s used + * to verify a mac or signature via {@link SecureDigestAlgorithm#verify(VerifyDigestRequest)}. + * + * @param type of key used to verify the digest. + * @return a new {@link VerifySecureDigestRequest.Builder} for creating {@link VerifySecureDigestRequest}s used + * to verify a mac or signature via {@link SecureDigestAlgorithm#verify(VerifyDigestRequest)}. + * @since JJWT_RELEASE_VERSION + */ + @SuppressWarnings("unchecked") + static VerifySecureDigestRequest.Builder builder() { + return (VerifySecureDigestRequest.Builder) Suppliers.VERIFY_SECURE_DIGEST_REQUEST_BUILDER.get(); + } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java index 56706ba30..de01e466c 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java @@ -32,10 +32,6 @@ import io.jsonwebtoken.impl.lang.Functions; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Services; -import io.jsonwebtoken.impl.security.DefaultAeadRequest; -import io.jsonwebtoken.impl.security.DefaultAeadResult; -import io.jsonwebtoken.impl.security.DefaultKeyRequest; -import io.jsonwebtoken.impl.security.DefaultSecureRequest; import io.jsonwebtoken.impl.security.Pbes2HsAkwAlgorithm; import io.jsonwebtoken.impl.security.ProviderKey; import io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms; @@ -601,7 +597,9 @@ private String sign(final Payload payload, final Key key, final Provider provide byte[] signature; try { - SecureRequest request = new DefaultSecureRequest<>(signingInput, provider, secureRandom, key); + SecureRequest request = + SecureRequest.builder().payload(signingInput).key(key) + .provider(provider).random(secureRandom).build(); signature = signFunction.apply(request); // now that we've calculated the signature, if using the b64 extension, and the payload is @@ -679,7 +677,10 @@ private String encrypt(final Payload content, final Key key, final Provider keyP //only expose (mutable) JweHeader functionality to KeyAlgorithm instances, not the full headerBuilder // (which exposes this JwtBuilder and shouldn't be referenced by KeyAlgorithms): JweHeader delegate = new DefaultMutableJweHeader(this.headerBuilder); - KeyRequest keyRequest = new DefaultKeyRequest<>(key, keyProvider, this.secureRandom, delegate, enc); + KeyRequest keyRequest = KeyRequest.builder() + .provider(keyProvider).random(this.secureRandom) + .payload(key).header(delegate).encryptionAlgorithm(enc) + .build(); KeyResult keyResult = keyAlgFunction.apply(keyRequest); Assert.stateNotNull(keyResult, "KeyAlgorithm must return a KeyResult."); @@ -705,11 +706,12 @@ private String encrypt(final Payload content, final Key key, final Provider keyP // During encryption, the configured Provider applies to the KeyAlgorithm, not the AeadAlgorithm, mostly // because all JVMs support the standard AeadAlgorithms (especially with BouncyCastle in the classpath). - // As such, the provider here is intentionally omitted (null): + // As such, the provider here is intentionally omitted when building the AeadRequest: // TODO: add encProvider(Provider) builder method that applies to this request only? ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream(8192); - AeadRequest req = new DefaultAeadRequest(plaintext, null, secureRandom, cek, aad); - DefaultAeadResult res = new DefaultAeadResult(ciphertextOut); + AeadRequest req = AeadRequest.builder().random(secureRandom) // no .provider call, see message above + .payload(plaintext).key(cek).associatedData(aad).build(); + AeadResult res = AeadResult.with(ciphertextOut); encrypt(req, res); byte[] iv = Assert.notEmpty(res.getIv(), "Encryption result must have a non-empty initialization vector."); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java index b646f7d05..7b03ab93c 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java @@ -46,9 +46,6 @@ import io.jsonwebtoken.impl.io.UncloseableInputStream; import io.jsonwebtoken.impl.lang.Bytes; import io.jsonwebtoken.impl.lang.RedactedConfidentialValue; -import io.jsonwebtoken.impl.security.DefaultDecryptAeadRequest; -import io.jsonwebtoken.impl.security.DefaultDecryptionKeyRequest; -import io.jsonwebtoken.impl.security.DefaultVerifySecureDigestRequest; import io.jsonwebtoken.impl.security.LocatingKeyResolver; import io.jsonwebtoken.impl.security.ProviderKey; import io.jsonwebtoken.io.CompressionAlgorithm; @@ -332,8 +329,9 @@ private byte[] verifySignature(final TokenizedJwt tokenized, final JwsHeader jws } try { - VerifySecureDigestRequest request = - new DefaultVerifySecureDigestRequest<>(verificationInput, provider, null, key, signature); + VerifySecureDigestRequest request = VerifySecureDigestRequest.builder() + .key(key).payload(verificationInput).digest(signature) + .provider(provider).build(); if (!algorithm.verify(request)) { String msg = "JWT signature does not match locally computed signature. JWT validity cannot be " + "asserted and should not be trusted."; @@ -548,8 +546,9 @@ private byte[] verifySignature(final TokenizedJwt tokenized, final JwsHeader jws // extract key-specific provider if necessary; Provider provider = ProviderKey.getProvider(key, this.provider); key = ProviderKey.getKey(key); // this must be called after ProviderKey.getProvider - DecryptionKeyRequest request = - new DefaultDecryptionKeyRequest<>(cekBytes, provider, null, jweHeader, encAlg, key); + DecryptionKeyRequest request = DecryptionKeyRequest.builder().provider(provider) + .payload(cekBytes).header(jweHeader).encryptionAlgorithm(encAlg) + .key(key).build(); final SecretKey cek = keyAlg.getDecryptionKey(request); if (cek == null) { String msg = "The '" + keyAlg.getId() + "' JWE key algorithm did not return a decryption key. " + @@ -563,7 +562,9 @@ private byte[] verifySignature(final TokenizedJwt tokenized, final JwsHeader jws // TODO: add encProvider(Provider) builder method that applies to this request only? InputStream ciphertext = payload.toInputStream(); ByteArrayOutputStream plaintext = new ByteArrayOutputStream(8192); - DecryptAeadRequest dreq = new DefaultDecryptAeadRequest(ciphertext, cek, aad, iv, digest); + DecryptAeadRequest dreq = DecryptAeadRequest.builder() + .payload(ciphertext).key(cek).associatedData(aad).iv(iv).digest(digest) + .build(); encAlg.decrypt(dreq, plaintext); payload = new Payload(plaintext.toByteArray(), header.getContentType()); diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/BiConsumer.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/BiConsumer.java deleted file mode 100644 index 6ea0f31f0..000000000 --- a/impl/src/main/java/io/jsonwebtoken/impl/lang/BiConsumer.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright © 2023 jsonwebtoken.io - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.jsonwebtoken.impl.lang; - -public interface BiConsumer { - - void accept(T t, U u); -} diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java b/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java index 1fc849044..597f4fef6 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/AbstractJwk.java @@ -32,7 +32,6 @@ import io.jsonwebtoken.security.Jwks; import io.jsonwebtoken.security.KeyOperation; -import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.security.Key; import java.security.PrivateKey; @@ -152,8 +151,10 @@ public JwkThumbprint thumbprint(final HashAlgorithm alg) { String json = toThumbprintJson(); Assert.hasText(json, "Canonical JWK Thumbprint JSON cannot be null or empty."); byte[] bytes = json.getBytes(StandardCharsets.UTF_8); // https://www.rfc-editor.org/rfc/rfc7638#section-3 #2 - InputStream in = Streams.of(bytes); - byte[] digest = alg.digest(new DefaultRequest<>(in, this.context.getProvider(), this.context.getRandom())); + byte[] digest = alg.digest(r -> + r.provider(this.context.getProvider()) + .random(this.context.getRandom()) + .payload(Streams.of(bytes))); return new DefaultJwkThumbprint(digest, alg); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/AesGcmKeyAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/AesGcmKeyAlgorithm.java index 2a98a07f8..1aa73dd74 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/AesGcmKeyAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/AesGcmKeyAlgorithm.java @@ -18,7 +18,6 @@ import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.impl.DefaultJweHeader; import io.jsonwebtoken.impl.lang.Bytes; -import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.RequiredParameterReader; import io.jsonwebtoken.io.Encoders; @@ -55,12 +54,9 @@ public KeyResult getEncryptionKey(final KeyRequest request) throws Se final byte[] iv = ensureInitializationVector(request); final AlgorithmParameterSpec ivSpec = getIvSpec(iv); - byte[] taggedCiphertext = jca(request).withCipher(new CheckedFunction() { - @Override - public byte[] apply(Cipher cipher) throws Exception { - cipher.init(Cipher.WRAP_MODE, kek, ivSpec); - return cipher.wrap(cek); - } + byte[] taggedCiphertext = jca(request).withCipher(cipher -> { + cipher.init(Cipher.WRAP_MODE, kek, ivSpec); + return cipher.wrap(cek); }); int tagByteLength = this.tagBitLength / Byte.SIZE; @@ -94,14 +90,11 @@ public SecretKey getDecryptionKey(DecryptionKeyRequest request) throw //for tagged GCM, the JCA spec requires that the tag be appended to the end of the ciphertext byte array: final byte[] taggedCiphertext = Bytes.concat(cekBytes, tag); - return jca(request).withCipher(new CheckedFunction() { - @Override - public SecretKey apply(Cipher cipher) throws Exception { - cipher.init(Cipher.UNWRAP_MODE, kek, ivSpec); - Key key = cipher.unwrap(taggedCiphertext, KEY_ALG_NAME, Cipher.SECRET_KEY); - Assert.state(key instanceof SecretKey, "cipher.unwrap must produce a SecretKey instance."); - return (SecretKey) key; - } + return jca(request).withCipher(cipher -> { + cipher.init(Cipher.UNWRAP_MODE, kek, ivSpec); + Key key = cipher.unwrap(taggedCiphertext, KEY_ALG_NAME, Cipher.SECRET_KEY); + Assert.state(key instanceof SecretKey, "cipher.unwrap must produce a SecretKey instance."); + return (SecretKey) key; }); } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/AesWrapKeyAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/AesWrapKeyAlgorithm.java index 05b5a65dd..caaf6ae8d 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/AesWrapKeyAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/AesWrapKeyAlgorithm.java @@ -15,7 +15,6 @@ */ package io.jsonwebtoken.impl.security; -import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.DecryptionKeyRequest; import io.jsonwebtoken.security.KeyRequest; @@ -44,12 +43,9 @@ public KeyResult getEncryptionKey(final KeyRequest request) throws Se final SecretKey kek = assertKey(request.getPayload()); final SecretKey cek = generateCek(request); - byte[] ciphertext = jca(request).withCipher(new CheckedFunction() { - @Override - public byte[] apply(Cipher cipher) throws Exception { - cipher.init(Cipher.WRAP_MODE, kek); - return cipher.wrap(cek); - } + byte[] ciphertext = jca(request).withCipher(cipher -> { + cipher.init(Cipher.WRAP_MODE, kek); + return cipher.wrap(cek); }); return new DefaultKeyResult(cek, ciphertext); @@ -61,14 +57,11 @@ public SecretKey getDecryptionKey(DecryptionKeyRequest request) throw final SecretKey kek = assertKey(request.getKey()); final byte[] cekBytes = Assert.notEmpty(request.getPayload(), "Request content (encrypted key) cannot be null or empty."); - return jca(request).withCipher(new CheckedFunction() { - @Override - public SecretKey apply(Cipher cipher) throws Exception { - cipher.init(Cipher.UNWRAP_MODE, kek); - Key key = cipher.unwrap(cekBytes, KEY_ALG_NAME, Cipher.SECRET_KEY); - Assert.state(key instanceof SecretKey, "Cipher unwrap must return a SecretKey instance."); - return (SecretKey) key; - } + return jca(request).withCipher(cipher -> { + cipher.init(Cipher.UNWRAP_MODE, kek); + Key key = cipher.unwrap(cekBytes, KEY_ALG_NAME, Cipher.SECRET_KEY); + Assert.state(key instanceof SecretKey, "Cipher unwrap must return a SecretKey instance."); + return (SecretKey) key; }); } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultAeadRequest.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultAeadRequest.java index 592ded197..d8cf399dc 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultAeadRequest.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultAeadRequest.java @@ -40,7 +40,7 @@ public class DefaultAeadRequest extends DefaultSecureRequest> + extends AbstractSecureRequestParams + implements AeadRequest.Params { + + protected InputStream aad; + + @Override + public M associatedData(InputStream aad) { + this.aad = aad; + return self(); + } + } + + @SuppressWarnings("unused") // instantiated via reflection in io.jsonwebtoken.security.Suppliers + public static class Builder extends AbstractAeadRequestParams + implements AeadRequest.Builder { + + @Override + public AeadRequest build() { + return new DefaultAeadRequest(this.payload, this.provider, this.random, this.key, this.aad); + } + + public static class Supplier implements java.util.function.Supplier { + @Override + public AeadRequest.Builder get() { + return new Builder(); + } + } + } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultAeadResult.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultAeadResult.java index 96bae63ba..f5819807c 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultAeadResult.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultAeadResult.java @@ -21,14 +21,16 @@ import io.jsonwebtoken.security.IvSupplier; import java.io.OutputStream; +import java.util.function.Function; +@SuppressWarnings("unused") //used via reflection as io.jsonwebtoken.security.Suppliers.AEAD_RESULT_FACTORY public class DefaultAeadResult implements AeadResult, DigestSupplier, IvSupplier { private final OutputStream out; private byte[] tag; private byte[] iv; - public DefaultAeadResult(OutputStream out) { + private DefaultAeadResult(OutputStream out) { this.out = Assert.notNull(out, "OutputStream cannot be null."); } @@ -58,4 +60,12 @@ public AeadResult setIv(byte[] iv) { public byte[] getIv() { return this.iv; } + + @SuppressWarnings("unused") // instantiated via reflection as io.jsonwebtoken.security.Suppliers.AEAD_RESULT_FACTORY + public static class Factory implements Function { + @Override + public AeadResult apply(OutputStream out) { + return new DefaultAeadResult(out); + } + } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDecryptAeadRequest.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDecryptAeadRequest.java index f030721e6..a8dd0eed2 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDecryptAeadRequest.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDecryptAeadRequest.java @@ -38,4 +38,36 @@ public DefaultDecryptAeadRequest(InputStream payload, SecretKey key, InputStream public byte[] getDigest() { return this.TAG; } + + @SuppressWarnings("unused") // instantiated via reflection in io.jsonwebtoken.security.Suppliers + public static class Builder extends AbstractAeadRequestParams + implements DecryptAeadRequest.Builder { + + private byte[] iv; + private byte[] tag; + + @Override + public DecryptAeadRequest.Builder iv(byte[] iv) { + this.iv = iv; + return self(); + } + + @Override + public DecryptAeadRequest.Builder digest(byte[] digest) { + this.tag = digest; + return self(); + } + + @Override + public DecryptAeadRequest build() { + return new DefaultDecryptAeadRequest(this.payload, this.key, this.aad, this.iv, this.tag); + } + + public static class Supplier implements java.util.function.Supplier { + @Override + public DecryptAeadRequest.Builder get() { + return new Builder(); + } + } + } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDecryptionKeyRequest.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDecryptionKeyRequest.java index fde6cb4d5..bb8c758ca 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDecryptionKeyRequest.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultDecryptionKeyRequest.java @@ -43,4 +43,29 @@ protected void assertBytePayload(byte[] payload) { public K getKey() { return this.decryptionKey; } + + @SuppressWarnings("unused") // instantiated via reflection in io.jsonwebtoken.security.Suppliers + public static class Builder extends AbstractKeyRequestParams> + implements DecryptionKeyRequest.Builder { + + private K decryptionKey; + + @Override + public DecryptionKeyRequest.Builder key(K decryptionKey) { + this.decryptionKey = decryptionKey; + return self(); + } + + @Override + public DecryptionKeyRequest build() { + return new DefaultDecryptionKeyRequest<>(this.payload, this.provider, this.random, this.header, this.aeadAlg, this.decryptionKey); + } + + public static class Supplier implements java.util.function.Supplier> { + @Override + public DecryptionKeyRequest.Builder get() { + return new Builder<>(); + } + } + } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultHashAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultHashAlgorithm.java index 18e847124..516330afa 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultHashAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultHashAlgorithm.java @@ -15,13 +15,11 @@ */ package io.jsonwebtoken.impl.security; -import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.HashAlgorithm; import io.jsonwebtoken.security.Request; import io.jsonwebtoken.security.VerifyDigestRequest; -import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; import java.util.Locale; @@ -38,17 +36,14 @@ public final class DefaultHashAlgorithm extends CryptoAlgorithm implements HashA public byte[] digest(final Request request) { Assert.notNull(request, "Request cannot be null."); final InputStream payload = Assert.notNull(request.getPayload(), "Request payload cannot be null."); - return jca(request).withMessageDigest(new CheckedFunction() { - @Override - public byte[] apply(MessageDigest md) throws IOException { - byte[] buf = new byte[1024]; - int len = 0; - while (len != -1) { - len = payload.read(buf); - if (len > 0) md.update(buf, 0, len); - } - return md.digest(); + return jca(request).withMessageDigest(md -> { + byte[] buf = new byte[1024]; + int len = 0; + while (len != -1) { + len = payload.read(buf); + if (len > 0) md.update(buf, 0, len); } + return md.digest(); }); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyRequest.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyRequest.java index a3699994f..a54adec7f 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyRequest.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultKeyRequest.java @@ -20,6 +20,7 @@ import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.KeyRequest; +import java.security.Key; import java.security.Provider; import java.security.SecureRandom; @@ -43,4 +44,40 @@ public JweHeader getHeader() { public AeadAlgorithm getEncryptionAlgorithm() { return this.encryptionAlgorithm; } + + static abstract class AbstractKeyRequestParams> + extends AbstractRequestParams implements KeyRequest.Params { + + protected AeadAlgorithm aeadAlg; + protected JweHeader header; + + @Override + public M encryptionAlgorithm(AeadAlgorithm aeadAlg) { + this.aeadAlg = aeadAlg; + return self(); + } + + @Override + public M header(JweHeader header) { + this.header = header; + return self(); + } + } + + @SuppressWarnings("unused") // instantiated via reflection in io.jsonwebtoken.security.Suppliers + public static class Builder extends AbstractKeyRequestParams> + implements KeyRequest.Builder { + + @Override + public KeyRequest build() { + return new DefaultKeyRequest<>(this.payload, this.provider, this.random, this.header, this.aeadAlg); + } + + public static class Supplier implements java.util.function.Supplier> { + @Override + public KeyRequest.Builder get() { + return new Builder<>(); + } + } + } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRequest.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRequest.java index 4c7c07bb7..74bd694dc 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRequest.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRequest.java @@ -25,7 +25,7 @@ public class DefaultRequest extends DefaultMessage implements Request { private final Provider provider; private final SecureRandom secureRandom; - public DefaultRequest(T payload, Provider provider, SecureRandom secureRandom) { + DefaultRequest(T payload, Provider provider, SecureRandom secureRandom) { super(payload); this.provider = provider; this.secureRandom = secureRandom; @@ -40,4 +40,51 @@ public Provider getProvider() { public SecureRandom getSecureRandom() { return this.secureRandom; } + + static abstract class AbstractRequestParams> + implements Params { + + protected Provider provider; + protected SecureRandom random; + protected T payload; + + @SuppressWarnings("unchecked") + protected final M self() { + return (M) this; + } + + @Override + public M payload(T payload) { + this.payload = payload; + return self(); + } + + @Override + public M provider(Provider provider) { + this.provider = provider; + return self(); + } + + @Override + public M random(SecureRandom random) { + this.random = random; + return self(); + } + } + + @SuppressWarnings("unused") // instantiated via reflection in io.jsonwebtoken.security.Suppliers + public static class Builder extends AbstractRequestParams> implements Request.Builder { + + @Override + public Request build() { + return new DefaultRequest<>(this.payload, this.provider, this.random); + } + + public static class Supplier implements java.util.function.Supplier> { + @Override + public Builder get() { + return new Builder<>(); + } + } + } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaKeyAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaKeyAlgorithm.java index 7a09b8141..52741e9b8 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaKeyAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultRsaKeyAlgorithm.java @@ -15,7 +15,6 @@ */ package io.jsonwebtoken.impl.security; -import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.security.DecryptionKeyRequest; import io.jsonwebtoken.security.InvalidKeyException; @@ -88,16 +87,13 @@ public KeyResult getEncryptionKey(final KeyRequest request) throws Se validate(kek, true); final SecretKey cek = generateCek(request); - byte[] ciphertext = jca(request).withCipher(new CheckedFunction() { - @Override - public byte[] apply(Cipher cipher) throws Exception { - if (SPEC == null) { - cipher.init(Cipher.WRAP_MODE, kek, ensureSecureRandom(request)); - } else { - cipher.init(Cipher.WRAP_MODE, kek, SPEC, ensureSecureRandom(request)); - } - return cipher.wrap(cek); + byte[] ciphertext = jca(request).withCipher(cipher -> { + if (SPEC == null) { + cipher.init(Cipher.WRAP_MODE, kek, ensureSecureRandom(request)); + } else { + cipher.init(Cipher.WRAP_MODE, kek, SPEC, ensureSecureRandom(request)); } + return cipher.wrap(cek); }); return new DefaultKeyResult(cek, ciphertext); @@ -110,17 +106,14 @@ public SecretKey getDecryptionKey(DecryptionKeyRequest request) thro validate(kek, false); final byte[] cekBytes = Assert.notEmpty(request.getPayload(), "Request content (encrypted key) cannot be null or empty."); - return jca(request).withCipher(new CheckedFunction() { - @Override - public SecretKey apply(Cipher cipher) throws Exception { - if (SPEC == null) { - cipher.init(Cipher.UNWRAP_MODE, kek); - } else { - cipher.init(Cipher.UNWRAP_MODE, kek, SPEC); - } - Key key = cipher.unwrap(cekBytes, AesAlgorithm.KEY_ALG_NAME, Cipher.SECRET_KEY); - return Assert.isInstanceOf(SecretKey.class, key, "Cipher unwrap must return a SecretKey instance."); + return jca(request).withCipher(cipher -> { + if (SPEC == null) { + cipher.init(Cipher.UNWRAP_MODE, kek); + } else { + cipher.init(Cipher.UNWRAP_MODE, kek, SPEC); } + Key key = cipher.unwrap(cekBytes, AesAlgorithm.KEY_ALG_NAME, Cipher.SECRET_KEY); + return Assert.isInstanceOf(SecretKey.class, key, "Cipher unwrap must return a SecretKey instance."); }); } } \ No newline at end of file diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultSecureRequest.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultSecureRequest.java index 776dc8ade..405a8529b 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultSecureRequest.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultSecureRequest.java @@ -35,4 +35,33 @@ public DefaultSecureRequest(T payload, Provider provider, SecureRandom secureRan public K getKey() { return this.KEY; } + + static abstract class AbstractSecureRequestParams> + extends AbstractRequestParams implements SecureRequest.Params { + + protected K key; + + @Override + public M key(K key) { + this.key = key; + return self(); + } + } + + @SuppressWarnings("unused") // instantiated via reflection in io.jsonwebtoken.security.Suppliers + public static class Builder extends AbstractSecureRequestParams> + implements SecureRequest.Builder { + + @Override + public SecureRequest build() { + return new DefaultSecureRequest<>(this.payload, this.provider, this.random, this.key); + } + + public static class Supplier implements java.util.function.Supplier> { + @Override + public Builder get() { + return new Builder<>(); + } + } + } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultVerifyDigestRequest.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultVerifyDigestRequest.java index 1d89bbcdb..1fbddc7ea 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultVerifyDigestRequest.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultVerifyDigestRequest.java @@ -22,6 +22,7 @@ import java.security.Provider; import java.security.SecureRandom; +@SuppressWarnings("unused") public class DefaultVerifyDigestRequest extends DefaultRequest implements VerifyDigestRequest { private final byte[] digest; @@ -35,4 +36,29 @@ public DefaultVerifyDigestRequest(InputStream payload, Provider provider, Secure public byte[] getDigest() { return this.digest; } + + @SuppressWarnings("unused") // instantiated via reflection in io.jsonwebtoken.security.Suppliers + public static class Builder extends AbstractRequestParams + implements VerifyDigestRequest.Builder { + + private byte[] digest; + + @Override + public VerifyDigestRequest.Builder digest(byte[] digest) { + this.digest = digest; + return self(); + } + + @Override + public VerifyDigestRequest build() { + return new DefaultVerifyDigestRequest(this.payload, this.provider, this.random, this.digest); + } + + public static class Supplier implements java.util.function.Supplier { + @Override + public Builder get() { + return new Builder(); + } + } + } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultVerifySecureDigestRequest.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultVerifySecureDigestRequest.java index 7be699174..31394fa45 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultVerifySecureDigestRequest.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultVerifySecureDigestRequest.java @@ -36,4 +36,29 @@ public DefaultVerifySecureDigestRequest(InputStream payload, Provider provider, public byte[] getDigest() { return this.digest; } + + @SuppressWarnings("unused") // instantiated via reflection in io.jsonwebtoken.security.Suppliers + public static class Builder extends AbstractSecureRequestParams> + implements VerifySecureDigestRequest.Builder { + + private byte[] digest; + + @Override + public VerifySecureDigestRequest.Builder digest(byte[] digest) { + this.digest = digest; + return self(); + } + + @Override + public VerifySecureDigestRequest build() { + return new DefaultVerifySecureDigestRequest<>(this.payload, this.provider, this.random, this.key, this.digest); + } + + public static class Supplier implements java.util.function.Supplier> { + @Override + public VerifySecureDigestRequest.Builder get() { + return new Builder<>(); + } + } + } } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/EcdhKeyAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/EcdhKeyAlgorithm.java index 114cccd4e..917ffafef 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/EcdhKeyAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/EcdhKeyAlgorithm.java @@ -18,7 +18,6 @@ import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.impl.DefaultJweHeader; import io.jsonwebtoken.impl.lang.Bytes; -import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.RequiredParameterReader; import io.jsonwebtoken.lang.Arrays; @@ -40,7 +39,6 @@ import io.jsonwebtoken.security.SecureRequest; import io.jsonwebtoken.security.SecurityException; -import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.security.Key; @@ -90,13 +88,10 @@ protected KeyPair generateKeyPair(Curve curve, Provider provider, SecureRandom r } protected byte[] generateZ(final KeyRequest request, final PublicKey pub, final PrivateKey priv) { - return jca(request).withKeyAgreement(new CheckedFunction() { - @Override - public byte[] apply(KeyAgreement keyAgreement) throws Exception { - keyAgreement.init(KeysBridge.root(priv), ensureSecureRandom(request)); - keyAgreement.doPhase(pub, true); - return keyAgreement.generateSecret(); - } + return jca(request).withKeyAgreement(keyAgreement -> { + keyAgreement.init(KeysBridge.root(priv), ensureSecureRandom(request)); + keyAgreement.doPhase(pub, true); + return keyAgreement.generateSecret(); }); } @@ -194,8 +189,10 @@ public KeyResult getEncryptionKey(KeyRequest request) throws Security final SecretKey derived = deriveKey(request, publicKey, pair.getPrivate()); - KeyRequest wrapReq = new DefaultKeyRequest<>(derived, request.getProvider(), - request.getSecureRandom(), request.getHeader(), request.getEncryptionAlgorithm()); + KeyRequest wrapReq = KeyRequest.builder() + .provider(request.getProvider()).random(request.getSecureRandom()) + .payload(derived).header(request.getHeader()).encryptionAlgorithm(request.getEncryptionAlgorithm()) + .build(); KeyResult result = WRAP_ALG.getEncryptionKey(wrapReq); header.put(DefaultJweHeader.EPK.getId(), jwk); @@ -228,8 +225,11 @@ public SecretKey getDecryptionKey(DecryptionKeyRequest request) thro final SecretKey derived = deriveKey(request, epk.toKey(), privateKey); - DecryptionKeyRequest unwrapReq = new DefaultDecryptionKeyRequest<>(request.getPayload(), - null, request.getSecureRandom(), header, request.getEncryptionAlgorithm(), derived); + DecryptionKeyRequest unwrapReq = DecryptionKeyRequest.builder() + .random(request.getSecureRandom()) + .payload(request.getPayload()).key(derived) + .header(header).encryptionAlgorithm(request.getEncryptionAlgorithm()) + .build(); return WRAP_ALG.getDecryptionKey(unwrapReq); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java index 970785f66..b9880a18e 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java @@ -150,11 +150,8 @@ private byte[] sign(byte[] aad, byte[] iv, InputStream ciphertext, byte[] macKey streams.add(ciphertext); streams.add(Streams.of(AL)); InputStream in = new SequenceInputStream(Collections.enumeration(streams)); - SecretKey key = new SecretKeySpec(macKeyBytes, SIGALG.getJcaName()); - SecureRequest request = - new DefaultSecureRequest<>(in, null, null, key); - byte[] digest = SIGALG.digest(request); + byte[] digest = SIGALG.digest(key, in); // https://tools.ietf.org/html/rfc7518#section-5.2.2.1 #5 requires truncating the signature // to be the same length as the macKey/encKey: diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/Pbes2HsAkwAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/Pbes2HsAkwAlgorithm.java index 796c78c8e..302968cfd 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/Pbes2HsAkwAlgorithm.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/Pbes2HsAkwAlgorithm.java @@ -19,7 +19,6 @@ import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.impl.DefaultJweHeader; import io.jsonwebtoken.impl.lang.Bytes; -import io.jsonwebtoken.impl.lang.CheckedFunction; import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.ParameterReadable; import io.jsonwebtoken.impl.lang.RequiredParameterReader; @@ -133,12 +132,7 @@ protected SecretKey deriveKey(SecretKeyFactory factory, final char[] password, f private SecretKey deriveKey(final KeyRequest request, final char[] password, final byte[] salt, final int iterations) { try { Assert.notEmpty(password, "Key password character array cannot be null or empty."); - return jca(request).withSecretKeyFactory(new CheckedFunction() { - @Override - public SecretKey apply(SecretKeyFactory factory) throws Exception { - return deriveKey(factory, password, salt, iterations); - } - }); + return jca(request).withSecretKeyFactory(factory -> deriveKey(factory, password, salt, iterations)); } finally { java.util.Arrays.fill(password, '\u0000'); } @@ -173,8 +167,10 @@ public KeyResult getEncryptionKey(final KeyRequest request) throws Sec final SecretKey derivedKek = deriveKey(request, password, rfcSalt, iterations); // now get a new CEK that is encrypted ('wrapped') with the PBE-derived key: - KeyRequest wrapReq = new DefaultKeyRequest<>(derivedKek, request.getProvider(), - request.getSecureRandom(), request.getHeader(), request.getEncryptionAlgorithm()); + KeyRequest wrapReq = KeyRequest.builder() + .provider(request.getProvider()).random(request.getSecureRandom()) + .payload(derivedKek).header(request.getHeader()).encryptionAlgorithm(request.getEncryptionAlgorithm()) + .build(); KeyResult result = wrapAlg.getEncryptionKey(wrapReq); request.getHeader().put(DefaultJweHeader.P2S.getId(), inputSalt); //retain for recipients @@ -203,9 +199,11 @@ public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws final char[] password = key.toCharArray(); // password will be safely cleaned/zeroed in deriveKey next: final SecretKey derivedKek = deriveKey(request, password, rfcSalt, iterations); - DecryptionKeyRequest unwrapReq = - new DefaultDecryptionKeyRequest<>(request.getPayload(), request.getProvider(), - request.getSecureRandom(), header, request.getEncryptionAlgorithm(), derivedKek); + DecryptionKeyRequest unwrapReq = DecryptionKeyRequest.builder() + .provider(request.getProvider()).random(request.getSecureRandom()) + .key(derivedKek).payload(request.getPayload()) + .header(header).encryptionAlgorithm(request.getEncryptionAlgorithm()) + .build(); return wrapAlg.getDecryptionKey(unwrapReq); } diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/X509BuilderSupport.java b/impl/src/main/java/io/jsonwebtoken/impl/security/X509BuilderSupport.java index 4ab1a821c..ffa8283ab 100644 --- a/impl/src/main/java/io/jsonwebtoken/impl/security/X509BuilderSupport.java +++ b/impl/src/main/java/io/jsonwebtoken/impl/security/X509BuilderSupport.java @@ -23,7 +23,6 @@ import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.security.HashAlgorithm; import io.jsonwebtoken.security.Jwks; -import io.jsonwebtoken.security.Request; import io.jsonwebtoken.security.X509Builder; import java.io.InputStream; @@ -95,8 +94,7 @@ public X509BuilderSupport x509Sha256Thumbprint(boolean enable) { private byte[] computeThumbprint(final X509Certificate cert, HashAlgorithm alg) { byte[] encoded = GET_X509_BYTES.apply(cert); InputStream in = Streams.of(encoded); - Request request = new DefaultRequest<>(in, null, null); - return alg.digest(request); + return alg.digest(in); } public void apply() { diff --git a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy index c232a1ade..e0ee721e3 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy @@ -141,8 +141,7 @@ class JwtsTest { def c = base64Url('{"sub":"joe","exp":"-42-"}') def data = Strings.utf8(("$h.$c" as String)) def payload = Streams.of(data) - def request = new DefaultSecureRequest<>(payload, null, null, key) - def result = Jwts.SIG.HS256.digest(request) + def result = Jwts.SIG.HS256.digest(key, payload) def sig = Encoders.BASE64URL.encode(result) def compact = "$h.$c.$sig" as String try { diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtHeaderBuilderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtHeaderBuilderTest.groovy index 049f56447..2ebedf584 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtHeaderBuilderTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtHeaderBuilderTest.groovy @@ -22,7 +22,6 @@ import io.jsonwebtoken.ProtectedHeader import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.security.DefaultHashAlgorithm -import io.jsonwebtoken.impl.security.DefaultRequest import io.jsonwebtoken.impl.security.TestKeys import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Collections @@ -362,8 +361,7 @@ class DefaultJwtHeaderBuilderTest { @Test void testX509CertificateSha1Thumbprint() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) - def request = new DefaultRequest(payload, null, null) - def x5t = DefaultHashAlgorithm.SHA1.digest(request) + def x5t = DefaultHashAlgorithm.SHA1.digest(payload) String encoded = Encoders.BASE64URL.encode(x5t) header = jws().x509Sha1Thumbprint(x5t).build() as JwsHeader @@ -375,8 +373,7 @@ class DefaultJwtHeaderBuilderTest { void testX509CertificateSha1ThumbprintEnabled() { def chain = TestKeys.RS256.chain def payload = Streams.of(chain[0].getEncoded()) - def request = new DefaultRequest(payload, null, null) - def x5t = DefaultHashAlgorithm.SHA1.digest(request) + def x5t = DefaultHashAlgorithm.SHA1.digest(payload) String encoded = Encoders.BASE64URL.encode(x5t) header = jws().x509Chain(chain).x509Sha1Thumbprint(true).build() as JwsHeader assertArrayEquals x5t, header.getX509Sha1Thumbprint() @@ -390,8 +387,7 @@ class DefaultJwtHeaderBuilderTest { @Test void testX509CertificateSha256Thumbprint() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) - def request = new DefaultRequest(payload, null, null) - def x5tS256 = Jwks.HASH.@SHA256.digest(request) + def x5tS256 = Jwks.HASH.@SHA256.digest(payload) String encoded = Encoders.BASE64URL.encode(x5tS256) header = jws().x509Sha256Thumbprint(x5tS256).build() as JwsHeader assertArrayEquals x5tS256, header.getX509Sha256Thumbprint() @@ -402,8 +398,7 @@ class DefaultJwtHeaderBuilderTest { void testX509CertificateSha256ThumbprintEnabled() { def chain = TestKeys.RS256.chain def payload = Streams.of(chain[0].getEncoded()) - def request = new DefaultRequest(payload, null, null) - def x5tS256 = Jwks.HASH.SHA256.digest(request) + def x5tS256 = Jwks.HASH.SHA256.digest(payload) String encoded = Encoders.BASE64URL.encode(x5tS256) header = jws().x509Chain(chain).x509Sha256Thumbprint(true).build() as JwsHeader assertArrayEquals x5tS256, header.getX509Sha256Thumbprint() diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultMutableJweHeaderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultMutableJweHeaderTest.groovy index 76b647341..4ec9899a3 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultMutableJweHeaderTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultMutableJweHeaderTest.groovy @@ -19,7 +19,6 @@ import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.security.DefaultHashAlgorithm -import io.jsonwebtoken.impl.security.DefaultRequest import io.jsonwebtoken.impl.security.TestKeys import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Strings @@ -271,8 +270,7 @@ class DefaultMutableJweHeaderTest { @Test void testX509Sha1Thumbprint() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) - def request = new DefaultRequest(payload, null, null) - def x5t = DefaultHashAlgorithm.SHA1.digest(request) + def x5t = DefaultHashAlgorithm.SHA1.digest(payload) String encoded = Encoders.BASE64URL.encode(x5t) header.x509Sha1Thumbprint(x5t) @@ -287,8 +285,7 @@ class DefaultMutableJweHeaderTest { @Test void testX509Sha256Thumbprint() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) - def request = new DefaultRequest(payload, null, null) - def x5tS256 = Jwks.HASH.@SHA256.digest(request) + def x5tS256 = Jwks.HASH.@SHA256.digest(payload) String encoded = Encoders.BASE64URL.encode(x5tS256) header.x509Sha256Thumbprint(x5tS256) diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilderTest.groovy index 514c9ccd7..0e3357fb0 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilderTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilderTest.groovy @@ -17,7 +17,10 @@ package io.jsonwebtoken.impl.security import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.io.Encoders -import io.jsonwebtoken.security.* +import io.jsonwebtoken.security.EcPrivateJwk +import io.jsonwebtoken.security.EcPublicJwk +import io.jsonwebtoken.security.Jwks +import io.jsonwebtoken.security.RsaPublicJwkBuilder import org.junit.Test import java.security.cert.X509Certificate @@ -66,8 +69,7 @@ class AbstractAsymmetricJwkBuilderTest { @Test void testX509CertificateSha1Thumbprint() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) - Request request = new DefaultRequest(payload, null, null) - def x5t = DefaultHashAlgorithm.SHA1.digest(request) + def x5t = DefaultHashAlgorithm.SHA1.digest(payload) def encoded = Encoders.BASE64URL.encode(x5t) def jwk = builder().x509Sha1Thumbprint(x5t).build() assertArrayEquals x5t, jwk.getX509Sha1Thumbprint() @@ -77,8 +79,7 @@ class AbstractAsymmetricJwkBuilderTest { @Test void testX509CertificateSha1ThumbprintEnabled() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) - Request request = new DefaultRequest(payload, null, null) - def x5t = DefaultHashAlgorithm.SHA1.digest(request) + def x5t = DefaultHashAlgorithm.SHA1.digest(payload) def encoded = Encoders.BASE64URL.encode(x5t) def jwk = builder().x509Chain(CHAIN).x509Sha1Thumbprint(true).build() assertArrayEquals x5t, jwk.getX509Sha1Thumbprint() @@ -88,8 +89,7 @@ class AbstractAsymmetricJwkBuilderTest { @Test void testX509CertificateSha256Thumbprint() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) - Request request = new DefaultRequest(payload, null, null) - def x5tS256 = Jwks.HASH.SHA256.digest(request) + def x5tS256 = Jwks.HASH.SHA256.digest(payload) def encoded = Encoders.BASE64URL.encode(x5tS256) def jwk = builder().x509Sha256Thumbprint(x5tS256).build() assertArrayEquals x5tS256, jwk.getX509Sha256Thumbprint() @@ -99,8 +99,7 @@ class AbstractAsymmetricJwkBuilderTest { @Test void testX509CertificateSha256ThumbprintEnabled() { def payload = Streams.of(TestKeys.RS256.cert.getEncoded()) - Request request = new DefaultRequest(payload, null, null) - def x5tS256 = Jwks.HASH.SHA256.digest(request) + def x5tS256 = Jwks.HASH.SHA256.digest(payload) def encoded = Encoders.BASE64URL.encode(x5tS256) def jwk = builder().x509Chain(CHAIN).x509Sha256Thumbprint(true).build() assertArrayEquals x5tS256, jwk.getX509Sha256Thumbprint() diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractSecureDigestAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractSecureDigestAlgorithmTest.groovy index 166762c4d..42e86f38f 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractSecureDigestAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AbstractSecureDigestAlgorithmTest.groovy @@ -39,7 +39,7 @@ class AbstractSecureDigestAlgorithmTest { def pair = Jwts.SIG.RS256.keyPair().build() byte[] data = Strings.utf8('foo') def payload = Streams.of(data) - byte[] signature = Jwts.SIG.RS256.digest(new DefaultSecureRequest<>(payload, provider, null, pair.getPrivate())) + byte[] signature = Jwts.SIG.RS256.digest( r -> r.provider(provider).payload(payload).key(pair.getPrivate())) payload.reset() assertTrue Jwts.SIG.RS256.verify(new DefaultVerifySecureDigestRequest(payload, provider, null, pair.getPublic(), signature)) } @@ -56,7 +56,7 @@ class AbstractSecureDigestAlgorithmTest { } try { def payload = Streams.of(Strings.utf8('foo')) - alg.digest(new DefaultSecureRequest(payload, null, null, pair.getPrivate())) + alg.digest(pair.getPrivate(), payload) } catch (SignatureException e) { assertTrue e.getMessage().startsWith('Unable to compute test signature with JCA algorithm \'test\' using key {') assertTrue e.getMessage().endsWith('}: foo') @@ -77,9 +77,9 @@ class AbstractSecureDigestAlgorithmTest { def data = Strings.utf8('foo') def payload = Streams.of(data) try { - byte[] signature = alg.digest(new DefaultSecureRequest(payload, null, null, pair.getPrivate())) + byte[] signature = alg.digest(pair.getPrivate(), payload) payload.reset() - alg.verify(new DefaultVerifySecureDigestRequest(payload, null, null, pair.getPublic(), signature)) + alg.verify(pair.getPublic(), payload, signature) } catch (SignatureException e) { assertTrue e.getMessage().startsWith('Unable to verify test signature with JCA algorithm \'test\' using key {') assertTrue e.getMessage().endsWith('}: foo') diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AesAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AesAlgorithmTest.groovy index ed4a6633a..8123b6844 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AesAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AesAlgorithmTest.groovy @@ -43,8 +43,6 @@ class AesAlgorithmTest { SecretKey key = TestKeys.A128GCM //weaker than required - Request request = new DefaultSecureRequest(new byte[1], null, null, key) - try { alg.assertKey(key) fail() @@ -113,7 +111,8 @@ class AesAlgorithmTest { def ins = Streams.of('data') def key = TestKeys.A256GCM def aad = Strings.utf8('aad') - def req = new DefaultAeadRequest(ins, null, secureRandom, key, Streams.of(aad)) + def req = AeadRequest.builder().payload(ins).random(secureRandom).key(key) + .associatedData(Streams.of(aad)).build() def returnedSecureRandom = alg.ensureSecureRandom(req) diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AesGcmKeyAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AesGcmKeyAlgorithmTest.groovy index 54f444b1a..ebd626dc2 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/AesGcmKeyAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/AesGcmKeyAlgorithmTest.groovy @@ -23,11 +23,13 @@ import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.CheckedFunction import io.jsonwebtoken.lang.Arrays +import io.jsonwebtoken.security.AeadResult import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.SecretKeyBuilder import org.junit.Test import javax.crypto.Cipher +import javax.crypto.SecretKey import javax.crypto.spec.GCMParameterSpec import static org.junit.Assert.* @@ -71,7 +73,7 @@ class AesGcmKeyAlgorithmTest { def out = new ByteArrayOutputStream(8192) def encRequest = new DefaultAeadRequest(Streams.of(cek.getEncoded()), null, null, kek, null, iv) - def encResult = new DefaultAeadResult(out) + def encResult = AeadResult.with(out) Jwts.ENC.A256GCM.encrypt(encRequest, encResult) assertArrayEquals tag, encResult.digest @@ -87,7 +89,7 @@ class AesGcmKeyAlgorithmTest { def template = new JcaTemplate('AES') def header = Jwts.header().add('alg', alg.id).add('enc', 'foo') - def kek = template.generateSecretKey(keyLength) + SecretKey kek = template.generateSecretKey(keyLength) def cek = template.generateSecretKey(keyLength) def enc = new GcmAesAeadAlgorithm(keyLength) { @Override @@ -97,9 +99,7 @@ class AesGcmKeyAlgorithmTest { } def delegate = new DefaultMutableJweHeader(header) - def ereq = new DefaultKeyRequest(kek, null, null, delegate, enc) - - def result = alg.getEncryptionKey(ereq) + def result = alg.getEncryptionKey(kek, delegate, enc) byte[] encryptedKeyBytes = result.getPayload() assertFalse "encryptedKey must be populated", Arrays.length(encryptedKeyBytes) == 0 @@ -135,8 +135,7 @@ class AesGcmKeyAlgorithmTest { } } def delegate = new DefaultMutableJweHeader(headerBuilder) - def ereq = new DefaultKeyRequest(kek, null, null, delegate, enc) - def result = alg.getEncryptionKey(ereq) + def result = alg.getEncryptionKey(kek, delegate, enc) headerBuilder.remove(headerName) @@ -147,7 +146,7 @@ class AesGcmKeyAlgorithmTest { def header = headerBuilder.build() as JweHeader try { - alg.getDecryptionKey(new DefaultDecryptionKeyRequest(encryptedKeyBytes, null, null, header, enc, kek)) + alg.getDecryptionKey(encryptedKeyBytes, kek, header, enc) fail() } catch (MalformedJwtException iae) { assertEquals exmsg, iae.getMessage() diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultHashAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultHashAlgorithmTest.groovy index d9e1435b3..97d967a98 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultHashAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultHashAlgorithmTest.groovy @@ -32,9 +32,9 @@ class DefaultHashAlgorithmTest { byte[] data = Strings.utf8('Hello World') InputStream payload = Streams.of(data) for (HashAlgorithm alg : algs) { - byte[] hash = alg.digest(new DefaultRequest<>(payload, null, null)) + byte[] hash = alg.digest(payload) payload.reset() - assertTrue alg.verify(new DefaultVerifyDigestRequest(payload, null, null, hash)) + assertTrue alg.verify(payload, hash) payload.reset() } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkThumbprintTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkThumbprintTest.groovy index a6af05791..a7349163d 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkThumbprintTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DefaultJwkThumbprintTest.groovy @@ -31,7 +31,7 @@ class DefaultJwkThumbprintTest { private static String content = "Hello World" private static HashAlgorithm alg = Jwks.HASH.SHA256 - private static byte[] digest = alg.digest(new DefaultRequest(Streams.of(content), null, null)) + private static byte[] digest = alg.digest(Streams.of(content)) private static String expectedToString = Encoders.BASE64URL.encode(digest) private static String expectedUriString = DefaultJwkThumbprint.URI_PREFIX + alg.getId() + ":" + expectedToString private static URI expectedUri = URI.create(expectedUriString) @@ -83,7 +83,7 @@ class DefaultJwkThumbprintTest { // same alg, different digest: def payload = Streams.of(Strings.utf8('Hello World!')) - byte[] digest2 = alg.digest(new DefaultRequest<>(payload, null, null)) + byte[] digest2 = alg.digest(payload) assertFalse thumbprint == new DefaultJwkThumbprint(digest2, DefaultHashAlgorithm.SHA1) } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DirectKeyAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DirectKeyAlgorithmTest.groovy index 82a2c08a1..d6e8c45f7 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/DirectKeyAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/DirectKeyAlgorithmTest.groovy @@ -19,6 +19,7 @@ import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.DefaultJweHeader import io.jsonwebtoken.lang.Arrays import io.jsonwebtoken.security.DecryptionKeyRequest +import io.jsonwebtoken.security.KeyRequest import org.junit.Test import javax.crypto.spec.SecretKeySpec @@ -39,15 +40,15 @@ class DirectKeyAlgorithmTest { void testGetEncryptionKey() { def alg = new DirectKeyAlgorithm() def key = new SecretKeySpec(new byte[1], "AES") - def request = new DefaultKeyRequest(key, null, null, new DefaultJweHeader([:]), Jwts.ENC.A128GCM) - def result = alg.getEncryptionKey(request) + def header = new DefaultJweHeader([:]) + def result = alg.getEncryptionKey(key, header, Jwts.ENC.A128GCM) assertSame key, result.getKey() assertEquals 0, Arrays.length(result.getPayload()) //must not have an encrypted key } @Test(expected = IllegalArgumentException) void testGetEncryptionKeyWithNullRequest() { - new DirectKeyAlgorithm().getEncryptionKey(null) + new DirectKeyAlgorithm().getEncryptionKey(null as KeyRequest) } @Test(expected = IllegalArgumentException) @@ -76,7 +77,7 @@ class DirectKeyAlgorithmTest { @Test(expected = IllegalArgumentException) void testGetDecryptionKeyWithNullRequest() { - new DirectKeyAlgorithm().getDecryptionKey(null) + new DirectKeyAlgorithm().getDecryptionKey(null as DecryptionKeyRequest) } @Test(expected = IllegalArgumentException) diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/EcSignatureAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/EcSignatureAlgorithmTest.groovy index a4993bc27..9fabb7f07 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/EcSignatureAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/EcSignatureAlgorithmTest.groovy @@ -23,6 +23,7 @@ import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.io.Decoders import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.InvalidKeyException +import io.jsonwebtoken.security.SecureRequest import io.jsonwebtoken.security.SignatureException import org.junit.Test @@ -120,7 +121,7 @@ class EcSignatureAlgorithmTest { @Test void testSignWithPublicKey() { ECPublicKey key = TestKeys.ES256.pair.public as ECPublicKey - def request = new DefaultSecureRequest(Streams.of(new byte[1]), null, null, key) + def request = SecureRequest.builder().payload(Streams.of(new byte[1])).key(key).build() def alg = Jwts.SIG.ES256 try { alg.digest(request) @@ -137,9 +138,8 @@ class EcSignatureAlgorithmTest { BigInteger order = BigInteger.ONE ECParameterSpec spec = new ECParameterSpec(new EllipticCurve(new TestECField(), BigInteger.ONE, BigInteger.ONE), new ECPoint(BigInteger.ONE, BigInteger.ONE), order, 1) ECPrivateKey priv = new TestECPrivateKey(algorithm: 'EC', params: spec) - def request = new DefaultSecureRequest(Streams.of(new byte[1]), null, null, priv) try { - it.digest(request) + it.digest(priv, Streams.of(new byte[1])) } catch (InvalidKeyException expected) { String msg = "The provided Elliptic Curve signing key size (aka order bit length) is " + "${Bytes.bitsMsg(order.bitLength())}, but the '${it.getId()}' algorithm requires EC Keys with " + @@ -154,9 +154,8 @@ class EcSignatureAlgorithmTest { void testSignWithInvalidKeyFieldLength() { def keypair = Jwts.SIG.ES256.keyPair().build() def data = "foo".getBytes(StandardCharsets.UTF_8) - def req = new DefaultSecureRequest(Streams.of(data), null, null, keypair.private) try { - Jwts.SIG.ES384.digest(req) + Jwts.SIG.ES384.digest(keypair.private, Streams.of(data)) } catch (InvalidKeyException expected) { String msg = "The provided Elliptic Curve signing key size (aka order bit length) is " + "256 bits (32 bytes), but the 'ES384' algorithm requires EC Keys with " + @@ -174,8 +173,7 @@ class EcSignatureAlgorithmTest { payload.reset() def pair = it.keyPair().build() def key = pair.getPrivate() - def signRequest = new DefaultSecureRequest(payload, null, null, key) - byte[] signature = it.digest(signRequest) + byte[] signature = it.digest(key, payload) payload.reset() def verifyRequest = new DefaultVerifySecureDigestRequest(payload, null, null, key, signature) try { @@ -330,7 +328,7 @@ class EcSignatureAlgorithmTest { assertTrue keypair.getPrivate() instanceof ECPrivateKey def data = Strings.ascii(withoutSignature) def payload = Streams.of(data) - def signature = alg.digest(new DefaultSecureRequest<>(payload, null, null, keypair.private)) + def signature = alg.digest(keypair.private, payload) payload.reset() assertTrue alg.verify(new DefaultVerifySecureDigestRequest(payload, null, null, keypair.public, signature)) } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/EcdhKeyAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/EcdhKeyAlgorithmTest.groovy index 00907b426..2cf897ee5 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/EcdhKeyAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/EcdhKeyAlgorithmTest.groovy @@ -23,6 +23,7 @@ import io.jsonwebtoken.impl.DefaultMutableJweHeader import io.jsonwebtoken.security.DecryptionKeyRequest import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.Jwks +import io.jsonwebtoken.security.KeyRequest import org.junit.Test import java.security.PrivateKey @@ -49,8 +50,9 @@ class EcdhKeyAlgorithmTest { PublicKey encKey = TestKeys.X25519.pair.public as PublicKey def header = new DefaultMutableJweHeader(Jwts.header()) def provider = TestKeys.BC - def request = new DefaultKeyRequest(encKey, provider, null, header, Jwts.ENC.A128GCM) - def result = alg.getEncryptionKey(request) + def req = KeyRequest.builder().payload(encKey).provider(provider).header(header) + .encryptionAlgorithm(Jwts.ENC.A128GCM).build() + def result = alg.getEncryptionKey(req) assertNotNull result.getKey() } @@ -124,9 +126,8 @@ class EcdhKeyAlgorithmTest { def alg = new EcdhKeyAlgorithm() PublicKey encKey = TestKeys.RS256.pair.public as PublicKey // not an elliptic curve key, must fail def header = new DefaultMutableJweHeader(Jwts.header()) - def request = new DefaultKeyRequest(encKey, null, null, header, Jwts.ENC.A128GCM) try { - alg.getEncryptionKey(request) + alg.getEncryptionKey(encKey, header, Jwts.ENC.A128GCM) fail() } catch (InvalidKeyException expected) { String msg = "Unable to determine JWA-standard Elliptic Curve for encryption key [${KeysBridge.toString(encKey)}]" diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/EdSignatureAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/EdSignatureAlgorithmTest.groovy index a7c9b9848..08011d3e4 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/EdSignatureAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/EdSignatureAlgorithmTest.groovy @@ -17,6 +17,7 @@ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.UnsupportedJwtException +import io.jsonwebtoken.security.SecureRequest import io.jsonwebtoken.security.SignatureException import org.junit.Test @@ -60,7 +61,7 @@ class EdSignatureAlgorithmTest { void testGetRequestJcaNameByKeyAlgorithmNameOnly() { def key = new TestKey(algorithm: EdwardsCurve.X25519.OID) def payload = [0x00] as byte[] - def req = new DefaultSecureRequest(payload, null, null, key) + def req = SecureRequest.builder().payload(payload).key(key).build() assertEquals 'X25519', alg.getJcaName(req) // Not the EdDSA default } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithmTest.groovy index 04264341a..228f556de 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithmTest.groovy @@ -17,6 +17,8 @@ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams +import io.jsonwebtoken.security.AeadResult +import io.jsonwebtoken.security.DecryptAeadRequest import org.junit.Test import javax.crypto.SecretKey @@ -63,7 +65,7 @@ class GcmAesAeadAlgorithmTest { def ins = Streams.of(P) def aad = Streams.of(AAD) def out = new ByteArrayOutputStream(8192) - def res = new DefaultAeadResult(out) + def res = AeadResult.with(out) def req = new DefaultAeadRequest(ins, null, null, KEY, aad, IV) alg.encrypt(req, res) @@ -77,7 +79,8 @@ class GcmAesAeadAlgorithmTest { // now test decryption: out = new ByteArrayOutputStream(8192) - def dreq = new DefaultDecryptAeadRequest(Streams.of(ciphertext), KEY, aad, res.iv, res.digest) + def dreq = DecryptAeadRequest.builder().payload(Streams.of(ciphertext)) + .key(KEY).associatedData(aad).iv(res.iv).digest(res.digest).build() alg.decrypt(dreq, out) assertArrayEquals(P, out.toByteArray()) } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/HashAlgorithmsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/HashAlgorithmsTest.groovy index 463ae739c..f021e525d 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/HashAlgorithmsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/HashAlgorithmsTest.groovy @@ -21,8 +21,6 @@ import io.jsonwebtoken.security.HashAlgorithm import io.jsonwebtoken.security.Jwks import org.junit.Test -import java.nio.charset.StandardCharsets - import static org.junit.Assert.* class HashAlgorithmsTest { @@ -79,18 +77,12 @@ class HashAlgorithmsTest { assertNull reg.get('invalid') } - static DefaultRequest request(String msg) { - byte[] data = msg.getBytes(StandardCharsets.UTF_8) - InputStream payload = Streams.of(data) - return new DefaultRequest(payload, null, null) - } - static void testSha(HashAlgorithm alg) { String id = alg.getId() int c = ('-' as char) as int def digestLength = id.substring(id.lastIndexOf(c) + 1) as int assertTrue alg.getJcaName().endsWith('' + digestLength) - def digest = alg.digest(request("hello")) + def digest = alg.digest(Streams.of("hello")) assertEquals digestLength, (digest.length * Byte.SIZE) } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithmTest.groovy index 4c0bd0935..97d466755 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithmTest.groovy @@ -20,6 +20,8 @@ import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.lang.Strings import io.jsonwebtoken.security.AeadAlgorithm +import io.jsonwebtoken.security.AeadRequest +import io.jsonwebtoken.security.DecryptAeadRequest import io.jsonwebtoken.security.SignatureException import org.junit.Test @@ -66,10 +68,8 @@ class HmacAesAeadAlgorithmTest { def plaintext = Streams.of(data) ByteArrayOutputStream out = new ByteArrayOutputStream(8192) - def res = new DefaultAeadResult(out) - def req = new DefaultAeadRequest(plaintext, null, null, key, null) - - alg.encrypt(req, res) + def req = AeadRequest.builder().payload(plaintext).key(key).build() + def res = alg.encrypt(req, out) def iv = res.getIv() def realTag = res.getDigest() @@ -80,7 +80,8 @@ class HmacAesAeadAlgorithmTest { byte[] ciphertext = out.toByteArray() out = new ByteArrayOutputStream(8192) - def dreq = new DefaultDecryptAeadRequest(Streams.of(ciphertext), key, null, iv, fakeTag) + def dreq = DecryptAeadRequest.builder().payload(Streams.of(ciphertext)) + .key(key).iv(iv).digest(fakeTag).build(); alg.decrypt(dreq, out) } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkThumbprintsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkThumbprintsTest.groovy index 008a24f55..83d7b7657 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkThumbprintsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/JwkThumbprintsTest.groovy @@ -33,8 +33,7 @@ class JwkThumbprintsTest { static byte[] digest(String json, HashAlgorithm alg) { def payload = Streams.of(json) - def req = new DefaultRequest(payload, null, null) - return alg.digest(req) + return alg.digest(payload) } static JwkThumbprint thumbprint(String json, HashAlgorithm alg) { diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/Pbes2HsAkwAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/Pbes2HsAkwAlgorithmTest.groovy index 242308bf3..2876672e0 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/Pbes2HsAkwAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/Pbes2HsAkwAlgorithmTest.groovy @@ -21,7 +21,6 @@ import io.jsonwebtoken.impl.DefaultJweHeaderMutator import io.jsonwebtoken.impl.DefaultMutableJweHeader import io.jsonwebtoken.io.Encoders import io.jsonwebtoken.lang.Strings -import io.jsonwebtoken.security.KeyRequest import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.Password import org.junit.Test @@ -43,9 +42,8 @@ class Pbes2HsAkwAlgorithmTest { int iterations = 50 // must be 1000 or more def header = Jwts.header().pbes2Count(iterations) as DefaultJweHeaderMutator def mutable = new DefaultMutableJweHeader(header) - KeyRequest req = new DefaultKeyRequest<>(KEY, null, null, mutable, Jwts.ENC.A256GCM) try { - alg.getEncryptionKey(req) + alg.getEncryptionKey(KEY, mutable, Jwts.ENC.A256GCM) fail() } catch (IllegalArgumentException iae) { assertEquals Pbes2HsAkwAlgorithm.MIN_ITERATIONS_MSG_PREFIX + iterations, iae.getMessage() diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/PrivateConstructorsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/PrivateConstructorsTest.groovy index ea01367d9..d60e8b2e8 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/PrivateConstructorsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/PrivateConstructorsTest.groovy @@ -19,6 +19,7 @@ import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.lang.Functions import io.jsonwebtoken.lang.Classes import io.jsonwebtoken.security.Jwks +import io.jsonwebtoken.security.Suppliers import org.junit.Test class PrivateConstructorsTest { @@ -36,5 +37,6 @@ class PrivateConstructorsTest { new Jwks.CRV() new Jwks.HASH() new Jwks.OP() + new Suppliers() } } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB1Test.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB1Test.groovy index 246b82871..995fe3ea4 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB1Test.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB1Test.groovy @@ -17,6 +17,7 @@ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams +import io.jsonwebtoken.security.DecryptAeadRequest import org.junit.Test import javax.crypto.SecretKey @@ -91,9 +92,8 @@ class RFC7518AppendixB1Test { def alg = Jwts.ENC.A128CBC_HS256 def aad = Streams.of(A) def out = new ByteArrayOutputStream(8192) - def result = new DefaultAeadResult(out) def request = new DefaultAeadRequest(Streams.of(P), null, null, KEY, aad, IV) - alg.encrypt(request, result) + def result = alg.encrypt(request, out) byte[] ciphertext = out.toByteArray() byte[] tag = result.getDigest() @@ -105,7 +105,8 @@ class RFC7518AppendixB1Test { // now test decryption: out = new ByteArrayOutputStream(8192) - def dreq = new DefaultDecryptAeadRequest(Streams.of(ciphertext), KEY, aad, iv, tag) + def dreq = DecryptAeadRequest.builder() + .payload(Streams.of(ciphertext)).key(KEY).associatedData(aad).iv(iv).digest(tag).build() alg.decrypt(dreq, out) assertArrayEquals(P, out.toByteArray()) } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB2Test.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB2Test.groovy index 8272af884..4b0a7729e 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB2Test.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB2Test.groovy @@ -18,6 +18,7 @@ package io.jsonwebtoken.impl.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams import io.jsonwebtoken.security.AeadRequest +import io.jsonwebtoken.security.DecryptAeadRequest import org.junit.Test import javax.crypto.SecretKey @@ -87,9 +88,8 @@ class RFC7518AppendixB2Test { def alg = Jwts.ENC.A192CBC_HS384 def aad = Streams.of(A) def out = new ByteArrayOutputStream(8192) - def result = new DefaultAeadResult(out) AeadRequest req = new DefaultAeadRequest(Streams.of(P), null, null, KEY, aad, IV) - alg.encrypt(req, result) + def result = alg.encrypt(req, out) byte[] ciphertext = out.toByteArray() byte[] tag = result.getDigest() @@ -101,7 +101,8 @@ class RFC7518AppendixB2Test { // now test decryption: out = new ByteArrayOutputStream(8192) - def dreq = new DefaultDecryptAeadRequest(Streams.of(ciphertext), KEY, aad, iv, tag) + def dreq = DecryptAeadRequest.builder().payload(Streams.of(ciphertext)) + .key(KEY).associatedData(aad).iv(iv).digest(tag).build() alg.decrypt(dreq, out) assertArrayEquals(P, out.toByteArray()) } diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB3Test.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB3Test.groovy index 80fff053b..4d3bfa592 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB3Test.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RFC7518AppendixB3Test.groovy @@ -89,9 +89,8 @@ class RFC7518AppendixB3Test { def alg = Jwts.ENC.A256CBC_HS512 def aad = Streams.of(A) def out = new ByteArrayOutputStream(8192) - def res = new DefaultAeadResult(out) AeadRequest req = new DefaultAeadRequest(Streams.of(P), null, null, KEY, aad, IV) - alg.encrypt(req, res) + def res = alg.encrypt(req, out) byte[] ciphertext = out.toByteArray() byte[] tag = res.getDigest() byte[] iv = res.getIv() diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RsaSignatureAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RsaSignatureAlgorithmTest.groovy index 05d24d73a..446dea026 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/impl/security/RsaSignatureAlgorithmTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/impl/security/RsaSignatureAlgorithmTest.groovy @@ -21,6 +21,7 @@ import io.jsonwebtoken.impl.lang.Bytes import io.jsonwebtoken.impl.lang.CheckedFunction import io.jsonwebtoken.lang.Assert import io.jsonwebtoken.security.InvalidKeyException +import io.jsonwebtoken.security.SecureRequest import io.jsonwebtoken.security.WeakKeyException import org.junit.Test @@ -88,7 +89,7 @@ class RsaSignatureAlgorithmTest { @Test void testValidateSigningKeyNotPrivate() { RSAPublicKey key = createMock(RSAPublicKey) - def request = new DefaultSecureRequest(Streams.of(new byte[1]), null, null, key) + def request = SecureRequest.builder().payload(Streams.of(new byte[1])).key(key).build() try { Jwts.SIG.RS256.digest(request) fail() @@ -116,9 +117,8 @@ class RsaSignatureAlgorithmTest { algs.each { def pair = it.getId().startsWith("PS") ? pssPair : rsaPair - def request = new DefaultSecureRequest(Streams.of(new byte[1]), null, null, pair.getPrivate()) try { - it.digest(request) + it.digest(pair.getPrivate(), Streams.of(new byte[1])) fail() } catch (WeakKeyException expected) { String id = it.getId() diff --git a/impl/src/test/groovy/io/jsonwebtoken/security/EncryptionAlgorithmsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/security/EncryptionAlgorithmsTest.groovy index dabb50d82..4daef6019 100644 --- a/impl/src/test/groovy/io/jsonwebtoken/security/EncryptionAlgorithmsTest.groovy +++ b/impl/src/test/groovy/io/jsonwebtoken/security/EncryptionAlgorithmsTest.groovy @@ -17,9 +17,6 @@ package io.jsonwebtoken.security import io.jsonwebtoken.Jwts import io.jsonwebtoken.impl.io.Streams -import io.jsonwebtoken.impl.security.DefaultAeadRequest -import io.jsonwebtoken.impl.security.DefaultAeadResult -import io.jsonwebtoken.impl.security.DefaultDecryptAeadRequest import io.jsonwebtoken.impl.security.GcmAesAeadAlgorithm import io.jsonwebtoken.lang.Registry import org.junit.Test @@ -106,10 +103,8 @@ class EncryptionAlgorithmsTest { def key = alg.key().build() def out = new ByteArrayOutputStream() - def request = new DefaultAeadRequest(Streams.of(PLAINTEXT_BYTES), null, null, key, null) - def result = new DefaultAeadResult(out) - - alg.encrypt(request, result) + def request = AeadRequest.builder().payload(Streams.of(PLAINTEXT_BYTES)).key(key).build() + def result = alg.encrypt(request, out) byte[] iv = result.getIv() byte[] tag = result.getDigest() //there is always a tag, even if there is no AAD assertNotNull tag @@ -123,7 +118,7 @@ class EncryptionAlgorithmsTest { def ciphertext = Streams.of(ciphertextBytes) out = new ByteArrayOutputStream(8192) - def dreq = new DefaultDecryptAeadRequest(ciphertext, key, null, iv, tag) + def dreq = DecryptAeadRequest.builder().payload(ciphertext).key(key).iv(iv).digest(tag).build() alg.decrypt(dreq, out) byte[] decryptedPlaintextBytes = out.toByteArray() @@ -141,10 +136,8 @@ class EncryptionAlgorithmsTest { def plaintextIn = Streams.of(PLAINTEXT_BYTES) def out = new ByteArrayOutputStream(8192) def aad = Streams.of(AAD_BYTES) - def req = new DefaultAeadRequest(plaintextIn, null, null, key, aad) - def res = new DefaultAeadResult(out) - - alg.encrypt(req, res) + def req = AeadRequest.builder().payload(plaintextIn).key(key).associatedData(aad).build() + def res = alg.encrypt(req, out) byte[] iv = res.getIv() byte[] tag = res.getDigest() byte[] ciphertextBytes = out.toByteArray() @@ -157,7 +150,8 @@ class EncryptionAlgorithmsTest { def ciphertext = Streams.of(ciphertextBytes) out = new ByteArrayOutputStream(8192) - def dreq = new DefaultDecryptAeadRequest(ciphertext, key, aad, iv, tag) + def dreq = DecryptAeadRequest.builder() + .payload(ciphertext).key(key).associatedData(aad).iv(iv).digest(tag).build() alg.decrypt(dreq, out) byte[] decryptedPlaintextBytes = out.toByteArray() assertArrayEquals(PLAINTEXT_BYTES, decryptedPlaintextBytes) diff --git a/impl/src/test/groovy/io/jsonwebtoken/security/SecureDigestAlgorithmTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/security/SecureDigestAlgorithmTest.groovy new file mode 100644 index 000000000..9564e26e2 --- /dev/null +++ b/impl/src/test/groovy/io/jsonwebtoken/security/SecureDigestAlgorithmTest.groovy @@ -0,0 +1,54 @@ +/* + * Copyright © 2025 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security + +import io.jsonwebtoken.Jwts +import org.junit.Test + +import java.nio.charset.StandardCharsets + +import static org.junit.Assert.assertTrue + +class SecureDigestAlgorithmTest { + + // only need one each of mac algorithm and signature algorithm - no need to take time for key generation + static final def algs = [Jwts.SIG.HS256, Jwts.SIG.ES256] + + @Test + void testRoundtrip() { + + final msg = 'hello world' + final is = new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8)) + + algs.each { alg -> + def skey + def vkey + + if (alg instanceof KeyPairBuilderSupplier) { + def pair = alg.keyPair().build() + skey = pair.getPrivate() + vkey = pair.getPublic() + } else { + skey = vkey = alg.key().build() + } + + byte[] digest = alg.digest(skey, is) + is.reset() + assertTrue alg.verify(vkey, is, digest) + is.reset() + } + } +}