Builder NestedCollection support (#841)

- Added Conjunctor, CollectionMutator, and NestedCollection
- Added JwkBuilder#operations() NestedCollection builder and removed #operation(KeyOperation) and #operations(Collection<KeyOperation>)
- KeyOperationPolicyBuilder now extends CollectionMutator

- Replaced ProtectedHeaderMutator#critical* methods with critical() NestedCollection

- Replaced JwtParserBuilder#critical* methods with critical() NestedCollection

- Replaced ClaimsMutator#audience* methods with audience() AudienceCollection

- Replaced JwtParserBuilder#add* methods with new collection builder methods: enc(), key(), sig() and zip()
This commit is contained in:
lhazlewood 2023-09-29 13:42:36 -07:00 committed by GitHub
parent 20b2fa9d50
commit 854bb8944c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 1215 additions and 817 deletions

View File

@ -15,6 +15,8 @@
*/ */
package io.jsonwebtoken; package io.jsonwebtoken;
import io.jsonwebtoken.lang.NestedCollection;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
@ -77,61 +79,29 @@ public interface ClaimsMutator<T extends ClaimsMutator<T>> {
* claim</a> as <em>a single String, <b>NOT</b> a String array</em>. This method exists only for producing * claim</a> as <em>a single String, <b>NOT</b> a String array</em>. This method exists only for producing
* JWTs sent to legacy recipients that are unable to interpret the {@code aud} value as a JSON String Array; it is * JWTs sent to legacy recipients that are unable to interpret the {@code aud} value as a JSON String Array; it is
* strongly recommended to avoid calling this method whenever possible and favor the * strongly recommended to avoid calling this method whenever possible and favor the
* {@link #audience(String)} or {@link #audience(Collection)} methods instead, as they ensure a single deterministic * {@link #audience()}.{@link AudienceCollection#add(Object) add(String)} and
* data type for recipients. * {@link AudienceCollection#add(Collection) add(Collection)} methods instead, as they ensure a single
* deterministic data type for recipients.
* *
* @param aud the JWT {@code aud} value or {@code null} to remove the property from the JSON map. * @param aud the JWT {@code aud} value or {@code null} to remove the property from the JSON map.
* @return the {@code Claims} instance for method chaining. * @return the {@code Claims} instance for method chaining.
* @deprecated since JJWT_RELEASE_VERSION in favor of the shorter and more modern builder-style named * @deprecated since JJWT_RELEASE_VERSION in favor of {@link #audience()}. This method will be removed before
* {@link #audience(String)}. This method will be removed before the JJWT 1.0 release. * the JJWT 1.0 release.
*/ */
@Deprecated @Deprecated
T setAudience(String aud); T setAudience(String aud);
/** /**
* Sets the JWT <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3"><code>aud</code> (audience) * Configures the JWT
* Claim</a> as <em>a single String, <b>NOT</b> a String array</em>. This method exists only for producing
* JWTs sent to legacy recipients that are unable to interpret the {@code aud} value as a JSON String Array; it is
* strongly recommended to avoid calling this method whenever possible and favor the
* {@link #audience(String)} or {@link #audience(Collection)} methods instead, as they ensure a single deterministic
* data type for recipients.
*
* @param aud the value to use as the {@code aud} Claim single-String value (and not an array of Strings), or
* {@code null}, empty or whitespace to remove the property from the JSON map.
* @return the instance for method chaining
* @since JJWT_RELEASE_VERSION
* @deprecated This is technically not deprecated because the JWT RFC mandates support for single string values,
* but it is marked as deprecated to discourage its use when possible.
*/
// DO NOT REMOVE EVER. This is a required RFC feature, but marked as deprecated to discourage its use
@Deprecated
T audienceSingle(String aud);
/**
* Adds (appends) the specified {@code aud} value to the {@link #audience(Collection) audience} Claim set
* (JSON Array) unless it is {@code null}, empty, whitespace-only or already exists in the set.
*
* <p>This method may be called multiple times.</p>
*
* @param aud a JWT {@code aud} value to add to the {@link #audience(Collection) audience} Claim set.
* @return the {@code Claims} instance for method chaining.
* @throws IllegalArgumentException if the {@code aud} argument is null or empty.
* @since JJWT_RELEASE_VERSION
*/
T audience(String aud);
/**
* Adds (appends) the specified values to the JWT
* <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3"><code>aud</code></a> (audience) Claim * <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3"><code>aud</code></a> (audience) Claim
* set, quietly ignoring any null, empty, whitespace-only, or existing value already in the set. * set, quietly ignoring any null, empty, whitespace-only, or existing value already in the set.
* *
* <p>This method may be called multiple times.</p> * @return the {@link AudienceCollection AudienceCollection} to use for {@code aud} configuration.
* * @see AudienceCollection AudienceCollection
* @param aud the values to add to the {@code aud} Claim set (JSON Array) * @see AudienceCollection#single(String) AudienceCollection.single(String)
* @return the instance for method chaining
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
T audience(Collection<String> aud); AudienceCollection<T> audience();
/** /**
* Sets the JWT <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4"> * Sets the JWT <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4">
@ -246,4 +216,34 @@ public interface ClaimsMutator<T extends ClaimsMutator<T>> {
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
T id(String jti); T id(String jti);
/**
* A {@code NestedCollection} for setting {@link #audience()} values that also allows overriding the collection
* to be a {@link #single(String) single string value} for legacy JWT recipients if necessary.
*
* @param <P> the type of ClaimsMutator to return for method chaining.
* @see #single(String)
* @since JJWT_RELEASE_VERSION
*/
interface AudienceCollection<P> extends NestedCollection<String, P> {
/**
* Sets the JWT <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3"><code>aud</code> (audience)
* Claim</a> as <em>a single String, <b>NOT</b> a String array</em>. This method exists only for producing
* JWTs sent to legacy recipients that are unable to interpret the {@code aud} value as a JSON String Array;
* it is strongly recommended to avoid calling this method whenever possible and favor the
* {@link #add(Object) add(String)} or {@link #add(Collection)} methods instead, as they ensure a single
* deterministic data type for recipients.
*
* @param aud the value to use as the {@code aud} Claim single-String value (and not an array of Strings), or
* {@code null}, empty or whitespace to remove the property from the JSON map.
* @return the instance for method chaining
* @since JJWT_RELEASE_VERSION
* @deprecated This is technically not deprecated because the JWT RFC mandates support for single string values,
* but it is marked as deprecated to discourage its use when possible.
*/
// DO NOT REMOVE EVER. This is a required RFC feature, but marked as deprecated to discourage its use
@Deprecated
P single(String aud);
}
} }

View File

@ -15,8 +15,6 @@
*/ */
package io.jsonwebtoken; package io.jsonwebtoken;
import java.util.Collection;
/** /**
* Looks for a JWT {@code zip} header, and if found, returns the corresponding {@link CompressionCodec} the parser * Looks for a JWT {@code zip} header, and if found, returns the corresponding {@link CompressionCodec} the parser
* can use to decompress the JWT body. * can use to decompress the JWT body.
@ -31,9 +29,9 @@ import java.util.Collection;
* {@link io.jsonwebtoken.JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.</p> * {@link io.jsonwebtoken.JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.</p>
* *
* @see JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver) * @see JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver)
* @see JwtParserBuilder#addCompressionAlgorithms(Collection) * @see JwtParserBuilder#zip()
* @since 0.6.0 * @since 0.6.0
* @deprecated in favor of {@link JwtParserBuilder#addCompressionAlgorithms(Collection)} * @deprecated in favor of {@link JwtParserBuilder#zip()}
*/ */
@SuppressWarnings("DeprecatedIsStillUsed") @SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated @Deprecated

View File

@ -20,6 +20,7 @@ import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoder; import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.io.Serializer;
import io.jsonwebtoken.lang.Conjunctor;
import io.jsonwebtoken.lang.MapMutator; import io.jsonwebtoken.lang.MapMutator;
import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.InvalidKeyException;
@ -402,9 +403,9 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* String jwt = Jwts.builder() * String jwt = Jwts.builder()
* *
* <b>.claims() * <b>.claims()
* .subject("Joe")
* .audience("you")
* .issuer("me") * .issuer("me")
* .subject("Joe")
* .audience().add("you").and()
* .add("customClaim", customValue) * .add("customClaim", customValue)
* .add(myClaimsMap) * .add(myClaimsMap)
* // ... etc ... * // ... etc ...
@ -514,20 +515,6 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
// for better/targeted JavaDoc // for better/targeted JavaDoc
JwtBuilder subject(String sub); JwtBuilder subject(String sub);
/**
* Sets the JWT Claims <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.3">
* <code>aud</code></a> (audience) claim. A {@code null} value will remove the property from the Claims.
* This is a convenience wrapper for:
* <blockquote><pre>
* {@link #claims()}.{@link ClaimsMutator#audience(String) audience(aud)}.{@link BuilderClaims#and() and()}</pre></blockquote>
*
* @param aud the JWT {@code aud} value or {@code null} to remove the property from the Claims map.
* @return the builder instance for method chaining.
*/
@Override
// for better/targeted JavaDoc
JwtBuilder audience(String aud);
/** /**
* Sets the JWT Claims <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4"> * Sets the JWT Claims <a href="https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4">
* <code>exp</code></a> (expiration) claim. A {@code null} value will remove the property from the Claims. * <code>exp</code></a> (expiration) claim. A {@code null} value will remove the property from the Claims.
@ -1052,14 +1039,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* *
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
interface BuilderClaims extends MapMutator<String, Object, BuilderClaims>, ClaimsMutator<BuilderClaims> { interface BuilderClaims extends MapMutator<String, Object, BuilderClaims>, ClaimsMutator<BuilderClaims>,
Conjunctor<JwtBuilder> {
/**
* Returns the associated JwtBuilder for continued configuration.
*
* @return the associated JwtBuilder for continued configuration.
*/
JwtBuilder and();
} }
/** /**
@ -1069,13 +1050,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* *
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
interface BuilderHeader extends JweHeaderMutator<BuilderHeader>, X509Builder<BuilderHeader> { interface BuilderHeader extends JweHeaderMutator<BuilderHeader>, X509Builder<BuilderHeader>,
Conjunctor<JwtBuilder> {
/**
* Returns the associated JwtBuilder for continued configuration.
*
* @return the associated JwtBuilder for continued configuration.
*/
JwtBuilder and();
} }
} }

View File

@ -30,6 +30,12 @@ package io.jsonwebtoken;
*/ */
public abstract class JwtHandlerAdapter<T> implements JwtHandler<T> { public abstract class JwtHandlerAdapter<T> implements JwtHandler<T> {
/**
* Default constructor, does not initialize any internal state.
*/
public JwtHandlerAdapter() {
}
@Override @Override
public T onContentJwt(Jwt<Header, byte[]> jwt) { public T onContentJwt(Jwt<Header, byte[]> jwt) {
throw new UnsupportedJwtException("Unprotected content JWTs are not supported."); throw new UnsupportedJwtException("Unprotected content JWTs are not supported.");

View File

@ -19,6 +19,8 @@ import io.jsonwebtoken.io.CompressionAlgorithm;
import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.lang.Builder; import io.jsonwebtoken.lang.Builder;
import io.jsonwebtoken.lang.Conjunctor;
import io.jsonwebtoken.lang.NestedCollection;
import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.SecureDigestAlgorithm; import io.jsonwebtoken.security.SecureDigestAlgorithm;
@ -29,10 +31,8 @@ import java.security.Key;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.Provider; import java.security.Provider;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* A builder to construct a {@link JwtParser}. Example usage: * A builder to construct a {@link JwtParser}. Example usage:
@ -96,25 +96,22 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
*/ */
JwtParserBuilder unsecuredDecompression(); JwtParserBuilder unsecuredDecompression();
JwtParserBuilder critical(String crit);
/** /**
* Specifies the {@link ProtectedHeader} parameter names used in JWT extensions supported by the application. If * Configures the {@link ProtectedHeader} parameter names used in JWT extensions supported by the application. If
* the parser encounters a Protected JWT that {@link ProtectedHeader#getCritical() requires} extensions, and * the parser encounters a Protected JWT that {@link ProtectedHeader#getCritical() requires} extensions, and
* those extensions' header names are not specified via this method, the parser will reject that JWT. * those extensions' header names are not specified via this method, the parser will reject that JWT.
* *
* <p><b>Extension Behavior</b></p> * <p><b>Extension Behavior</b></p>
* *
* <p>The {@code crit} set argument only identifies header parameter names that are used in extensions supported * <p>The {@code critical} collection only identifies header parameter names that are used in extensions supported
* by the application. <b>Application developers, <em>not JJWT</em>, MUST perform the associated extension behavior * by the application. <b>Application developers, <em>not JJWT</em>, MUST perform the associated extension behavior
* using the parsed JWT</b>.</p> * using the parsed JWT</b>.</p>
* *
* @param crit the header parameter names used in JWT extensions supported by the application. * @return the {@link NestedCollection} to use for {@code crit} configuration.
* @return the builder for method chaining.
* @see ProtectedHeader#getCritical() * @see ProtectedHeader#getCritical()
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
JwtParserBuilder critical(Set<String> crit); NestedCollection<String, JwtParserBuilder> critical();
/** /**
* Sets the JCA Provider to use during cryptographic signature and key decryption operations, or {@code null} if the * Sets the JCA Provider to use during cryptographic signature and key decryption operations, or {@code null} if the
@ -552,117 +549,174 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
JwtParserBuilder setSigningKeyResolver(SigningKeyResolver signingKeyResolver); JwtParserBuilder setSigningKeyResolver(SigningKeyResolver signingKeyResolver);
/** /**
* Adds the specified compression algorithms to the parser's total set of supported compression algorithms, * Configures the parser's supported {@link AeadAlgorithm}s used to decrypt JWE payloads. If the parser
* replacing any existing ones with identical {@link Identifiable#getId() id}s. * encounters a JWE {@link JweHeader#getEncryptionAlgorithm() enc} header value that equals an
* If the parser encounters a JWT {@code zip} header value that equals a compression algorithm's * AEAD algorithm's {@link Identifiable#getId() id}, that algorithm will be used to decrypt the JWT
* {@link Identifiable#getId() id}, that algorithm will be used for decompression. * payload.
* *
* <p>There may be only one registered {@code CompressionAlgorithm} per CaSe-SeNsItIvE {@code id}, and the * <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
* {@code algs} collection is added in iteration order; if a duplicate id is found when iterating the {@code algs} * configuration, for example:</p>
* collection, the later algorithm will evict any existing algorithm with the same {@code id}.</p> * <blockquote><pre>
* parserBuilder.enc().add(anAeadAlgorithm).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
* *
* <p><b>Standard Algorithms and Overrides</b></p> * <p><b>Standard Algorithms and Overrides</b></p>
* *
* <p>The {@link Jwts.ZIP} compression algorithms are supported by default and do not need * <p>All JWA-standard AEAD encryption algorithms in the {@link Jwts.ENC} registry are supported by default and
* to be added via this method, but beware: <b>any algorithm in the {@code algs} collection with a * do not need to be added. The collection may be useful however for removing some algorithms (for example,
* JWA standard {@link Identifiable#getId() id} will replace the JJWT standard algorithm implementation</b>. * any algorithms not used by the application, or those not compatible with application security requirements),
* This is to allow application developers to favor their own implementations over JJWT's default implementations * or for adding custom implementations.</p>
* if necessary (for example, to support legacy or custom behavior).
* *
* @param algs collection of compression algorithms to add to the parser's total set of supported compression * <p><b>Custom Implementations</b></p>
* algorithms, replacing any existing ones with the same exact (CaSe-SeNsItIvE) *
* {@link CompressionAlgorithm#getId() id}s. * <p>There may be only one registered {@code AeadAlgorithm} per algorithm {@code id}, and any algorithm
* @return the builder for method chaining. * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a
* @see Jwts.ZIP * duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. <b>But beware:</b>
*
* <blockquote><b>
* Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will
* replace (override) the JJWT standard algorithm implementation</b>.</blockquote>
*
* <p>This is to allow application developers to favor their
* own implementations over JJWT's default implementations if necessary (for example, to support legacy or
* custom behavior).</p>
*
* @return the {@link NestedCollection} to use to configure the AEAD encryption algorithms available when parsing.
* @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm)
* @see Jwts.ENC
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.2">&quot;enc&quot; (Encryption Algorithm) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1.1">Encryption Algorithm Name (id) requirements</a>
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
JwtParserBuilder addCompressionAlgorithms(Collection<? extends CompressionAlgorithm> algs); NestedCollection<AeadAlgorithm, JwtParserBuilder> enc();
/** /**
* Adds the specified AEAD encryption algorithms to the parser's total set of supported encryption algorithms, * Configures the parser's supported {@link KeyAlgorithm}s used to obtain a JWE's decryption key. If the
* replacing any existing algorithms with the same exact (CaSe-SeNsItIvE) {@link AeadAlgorithm#getId() id}s. * parser encounters a JWE {@link JweHeader#getAlgorithm()} alg} header value that equals a {@code KeyAlgorithm}'s
* If the parser encounters a JWT {@code enc} header value that equals an encryption algorithm's * {@link Identifiable#getId() id}, that key algorithm will be used to obtain the JWE's decryption key.
* {@link Identifiable#getId() id}, that algorithm will be used for decryption.
* *
* <p>There may be only one registered {@code AeadAlgorithm} per algorithm {@code id}, and the {@code algs} * <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
* collection is added in iteration order; if a duplicate id is found when iterating the {@code algs} * configuration, for example:</p>
* collection, the later algorithm will evict any existing algorithm with the same {@code id}.</p> * <blockquote><pre>
* parserBuilder.key().add(aKeyAlgorithm).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
* *
* <p><b>Standard Algorithms and Overrides</b></p> * <p><b>Standard Algorithms and Overrides</b></p>
* *
* <p>All JWA standard encryption algorithms in {@link Jwts.ENC} are supported by default and do not need * <p>All JWA-standard key encryption algorithms in the {@link Jwts.KEY} registry are supported by default and
* to be added via this method, but beware: <b>any algorithm in the {@code algs} collection with a * do not need to be added. The collection may be useful however for removing some algorithms (for example,
* JWA standard {@link Identifiable#getId() id} will replace the JJWT standard algorithm implementation</b>. * any algorithms not used by the application, or those not compatible with application security requirements),
* This is to allow application developers to favor their own implementations over JJWT's default implementations * or for adding custom implementations.</p>
* if necessary (for example, to support legacy or custom behavior).
* *
* @param algs collection of AEAD encryption algorithms to add to the parser's total set of supported * <p><b>Custom Implementations</b></p>
* encryption algorithms, replacing any existing algorithms with the same *
* {@link AeadAlgorithm#getId() id}s. * <p>There may be only one registered {@code KeyAlgorithm} per algorithm {@code id}, and any algorithm
* @return the builder for method chaining. * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a
* duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. <b>But beware:</b>
*
* <blockquote><b>
* Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will
* replace (override) the JJWT standard algorithm implementation</b>.</blockquote>
*
* <p>This is to allow application developers to favor their
* own implementations over JJWT's default implementations if necessary (for example, to support legacy or
* custom behavior).</p>
*
* @return the {@link NestedCollection} to use to configure the key algorithms available when parsing.
* @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm)
* @see Jwts.KEY
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.1">JWE &quot;alg&quot; (Algorithm) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1.1">Key Algorithm Name (id) requirements</a>
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
JwtParserBuilder addEncryptionAlgorithms(Collection<? extends AeadAlgorithm> algs); NestedCollection<KeyAlgorithm<?, ?>, JwtParserBuilder> key();
/** /**
* Adds the specified signature algorithms to the parser's total set of supported signature algorithms, * Configures the parser's supported
* replacing any existing algorithms with the same exact (CaSe-SeNsItIvE) {@link Identifiable#getId() id}s. * {@link io.jsonwebtoken.security.SignatureAlgorithm SignatureAlgorithm} and
* If the parser encounters a JWS {@code alg} header value that equals a signature algorithm's * {@link io.jsonwebtoken.security.MacAlgorithm MacAlgorithm}s used to verify JWS signatures. If the parser
* {@link Identifiable#getId() id}, that algorithm will be used for signature verification. * encounters a JWS {@link ProtectedHeader#getAlgorithm() alg} header value that equals a signature or MAC
* algorithm's {@link Identifiable#getId() id}, that algorithm will be used to verify the JWS signature.
* *
* <p>There may be only one registered {@code SecureDigestAlgorithm} per algorithm {@code id}, and the * <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
* {@code algs} collection is added in iteration order; if a duplicate id is found when iterating the * configuration, for example:</p>
* {@code algs} argument, the later algorithm will evict any existing algorithm with the same {@code id}.</p> * <blockquote><pre>
* parserBuilder.sig().add(aSignatureAlgorithm).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
* *
* <p><b>Standard Algorithms and Overrides</b></p> * <p><b>Standard Algorithms and Overrides</b></p>
* *
* <p>All JWA standard signature and MAC algorithms in {@link Jwts.SIG} are supported by default and do not need * <p>All JWA-standard signature and MAC algorithms in the {@link Jwts.SIG} registry are supported by default and
* to be added via this method, but beware: <b>any algorithm in the {@code algs} collection with a * do not need to be added. The collection may be useful however for removing some algorithms (for example,
* JWA standard {@link Identifiable#getId() id} will replace the JJWT standard * any algorithms not used by the application, or those not compatible with application security requirements), or
* algorithm implementation</b>. This is to allow application developers to favor their own implementations over * for adding custom implementations.</p>
* JJWT's default implementations if necessary (for example, to support legacy or custom behavior).
* *
* @param algs collection of signature algorithms to add to the parser's total set of supported signature * <p><b>Custom Implementations</b></p>
* algorithms, replacing any existing algorithms with the same exact (CaSe-SeNsItIvE) *
* {@link Identifiable#getId() id}s. * <p>There may be only one registered {@code SecureDigestAlgorithm} per algorithm {@code id}, and any algorithm
* @return the builder for method chaining. * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a
* duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. <b>But beware:</b>
*
* <blockquote><b>
* Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will
* replace (override) the JJWT standard algorithm implementation</b>.</blockquote>
*
* <p>This is to allow application developers to favor their
* own implementations over JJWT's default implementations if necessary (for example, to support legacy or
* custom behavior).</p>
*
* @return the {@link NestedCollection} to use to configure the signature and MAC algorithms available when parsing.
* @see JwtBuilder#signWith(Key, SecureDigestAlgorithm)
* @see Jwts.SIG
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.1">JWS &quot;alg&quot; (Algorithm) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1.1">Algorithm Name (id) requirements</a> * @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1.1">Algorithm Name (id) requirements</a>
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
JwtParserBuilder addSignatureAlgorithms(Collection<? extends SecureDigestAlgorithm<?, ?>> algs); NestedCollection<SecureDigestAlgorithm<?, ?>, JwtParserBuilder> sig();
/** /**
* Adds the specified key management algorithms to the parser's total set of supported key algorithms, * Configures the parser's supported {@link CompressionAlgorithm}s used to decompress JWT payloads. If the parser
* replacing any existing algorithms with the same exact (CaSe-SeNsItIvE) {@link KeyAlgorithm#getId() id}s. * encounters a JWT {@link ProtectedHeader#getCompressionAlgorithm() zip} header value that equals a
* If the parser encounters a JWE {@code alg} header value that equals a key management algorithm's * compression algorithm's {@link Identifiable#getId() id}, that algorithm will be used to decompress the JWT
* {@link Identifiable#getId() id}, that algorithm will be used to obtain the content decryption key. * payload.
* *
* <p>There may be only one registered {@code KeyAlgorithm} per algorithm {@code id}, and the {@code algs} * <p>The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser
* collection is added in iteration order; if a duplicate id is found when iterating the {@code algs} * configuration, for example:</p>
* argument, the later algorithm will evict any existing algorithm with the same {@code id}.</p> * <blockquote><pre>
* parserBuilder.zip().add(aCompressionAlgorithm).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
* *
* <p><b>Standard Algorithms and Overrides</b></p> * <p><b>Standard Algorithms and Overrides</b></p>
* *
* <p>All JWA standard key management algorithms in {@link Jwts.KEY} are supported by default and do not need * <p>All JWA-standard compression algorithms in the {@link Jwts.ZIP} registry are supported by default and
* to be added via this method, but beware: <b>any algorithm in the {@code algs} collection with a * do not need to be added. The collection may be useful however for removing some algorithms (for example,
* JWA standard {@link Identifiable#getId() id} will replace the JJWT standard algorithm implementation</b>. * any algorithms not used by the application), or for adding custom implementations.</p>
* This is to allow application developers to favor their own implementations over JJWT's default implementations
* if necessary (for example, to support legacy or custom behavior).
* *
* @param algs collection of key management algorithms to add to the parser's total set of supported key * <p><b>Custom Implementations</b></p>
* management algorithms, replacing any existing algorithms with the same exact (CaSe-SeNsItIvE) *
* {@link KeyAlgorithm#getId() id}s. * <p>There may be only one registered {@code CompressionAlgorithm} per algorithm {@code id}, and any algorithm
* @return the builder for method chaining. * instances that are {@link io.jsonwebtoken.lang.CollectionMutator#add(Object) add}ed to this collection with a
* duplicate ID will evict any existing or previously-added algorithm with the same {@code id}. <b>But beware:</b>
*
* <blockquote><b>
* Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will
* replace (override) the JJWT standard algorithm implementation</b>.</blockquote>
*
* <p>This is to allow application developers to favor their
* own implementations over JJWT's default implementations if necessary (for example, to support legacy or
* custom behavior).</p>
*
* @return the {@link NestedCollection} to use to configure the compression algorithms available when parsing.
* @see JwtBuilder#compressWith(CompressionAlgorithm)
* @see Jwts.ZIP
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516#section-4.1.3">&quot;zip&quot; (Compression Algorithm) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7518.html#section-7.3.1">Compression Algorithm Name (id) requirements</a>
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
JwtParserBuilder addKeyAlgorithms(Collection<? extends KeyAlgorithm<?, ?>> algs); NestedCollection<CompressionAlgorithm, JwtParserBuilder> zip();
/** /**
* <p><b>Deprecated as of JJWT JJWT_RELEASE_VERSION. This method will be removed before the 1.0 release.</b></p> * <p><b>Deprecated as of JJWT JJWT_RELEASE_VERSION. This method will be removed before the 1.0 release.</b></p>
* *
* <p>This method has been deprecated as of JJWT version JJWT_RELEASE_VERSION because it imposed unnecessary * <p>This method has been deprecated as of JJWT version JJWT_RELEASE_VERSION because it imposed unnecessary
* implementation requirements on application developers when simply adding to a compression algorithm collection * implementation requirements on application developers when simply adding to a compression algorithm collection
* would suffice. Use the {@link #addCompressionAlgorithms(Collection)} method instead to add * would suffice. Use the {@link #zip()} method instead to add
* any custom algorithm implementations without needing to also implement a Locator implementation.</p> * any custom algorithm implementations without needing to also implement a Locator implementation.</p>
* *
* <p><b>Previous Documentation</b></p> * <p><b>Previous Documentation</b></p>
@ -682,8 +736,8 @@ public interface JwtParserBuilder extends Builder<JwtParser> {
* *
* @param compressionCodecResolver the compression codec resolver used to decompress the JWT body. * @param compressionCodecResolver the compression codec resolver used to decompress the JWT body.
* @return the parser builder for method chaining. * @return the parser builder for method chaining.
* @deprecated since JJWT_RELEASE_VERSION in favor of {@link #addCompressionAlgorithms(Collection)}. * @deprecated since JJWT_RELEASE_VERSION in favor of {@link #zip()}. This method will be removed before the
* This method will be removed before the 1.0 release. * 1.0 release.
*/ */
@Deprecated @Deprecated
JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver); JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver);

View File

@ -23,6 +23,7 @@ import io.jsonwebtoken.lang.Assert;
* {@link #locate(JweHeader)} methods for type-specific logic if desired when the encountered header is an * {@link #locate(JweHeader)} methods for type-specific logic if desired when the encountered header is an
* unprotected JWT, or an integrity-protected JWT (either a JWS or JWE). * unprotected JWT, or an integrity-protected JWT (either a JWS or JWE).
* *
* @param <T> the type of object to locate
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
public abstract class LocatorAdapter<T> implements Locator<T> { public abstract class LocatorAdapter<T> implements Locator<T> {

View File

@ -15,11 +15,12 @@
*/ */
package io.jsonwebtoken; package io.jsonwebtoken;
import io.jsonwebtoken.lang.Conjunctor;
import io.jsonwebtoken.lang.NestedCollection;
import io.jsonwebtoken.security.PublicJwk; import io.jsonwebtoken.security.PublicJwk;
import io.jsonwebtoken.security.X509Mutator; import io.jsonwebtoken.security.X509Mutator;
import java.net.URI; import java.net.URI;
import java.util.Collection;
/** /**
* Mutation (modifications) to a {@link ProtectedHeader Header} instance. * Mutation (modifications) to a {@link ProtectedHeader Header} instance.
@ -30,29 +31,17 @@ import java.util.Collection;
public interface ProtectedHeaderMutator<T extends ProtectedHeaderMutator<T>> extends HeaderMutator<T>, X509Mutator<T> { public interface ProtectedHeaderMutator<T extends ProtectedHeaderMutator<T>> extends HeaderMutator<T>, X509Mutator<T> {
/** /**
* Adds the name of a header parameter used by a JWT or JWA specification extension that <em>MUST</em> be understood * Configures names of header parameters used by JWT or JWA specification extensions that <em>MUST</em> be
* and supported by the JWT recipient. A {@code null}, empty, whitespace-only or already existing value is ignored. * understood and supported by the JWT recipient. When finished, use the collection's
* {@link Conjunctor#and() and()} method to return to the header builder, for example:
* <blockquote><pre>
* builder.critical().add("headerName").{@link Conjunctor#and() and()} // etc...</pre></blockquote>
* *
* @param crit the name of a header parameter used by a JWT or JWA specification extension that <em>MUST</em> be * @return the {@link NestedCollection} to use for {@code crit} configuration.
* understood and supported by the JWT recipient.
* @return the header for method chaining.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11">JWS <code>crit</code> (Critical) Header Parameter</a> * @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11">JWS <code>crit</code> (Critical) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.13">JWS <code>crit</code> (Critical) Header Parameter</a> * @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.13">JWS <code>crit</code> (Critical) Header Parameter</a>
*/ */
T critical(String crit); NestedCollection<String, T> critical();
/**
* Adds names of header parameters used by JWT or JWA specification extensions that <em>MUST</em> be
* understood and supported by the JWT recipient. {@code null}, empty, whitespace-only or already existing
* values are ignored.
*
* @param crit names of header parameters used by JWT or JWA specification extensions that <em>MUST</em> be
* understood and supported by the JWT recipient
* @return the header for method chaining.
* @see <a href="https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11">JWS <code>crit</code> (Critical) Header Parameter</a>
* @see <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.13">JWS <code>crit</code> (Critical) Header Parameter</a>
*/
T critical(Collection<String> crit);
/** /**
* Sets the {@code jwk} (JSON Web Key) associated with the JWT. When set for a {@link JwsHeader}, the * Sets the {@code jwk} (JSON Web Key) associated with the JWT. When set for a {@link JwsHeader}, the

View File

@ -36,6 +36,12 @@ public abstract class AbstractDeserializer<T> implements Deserializer<T> {
private static final byte[] EMPTY_BYTES = new byte[0]; private static final byte[] EMPTY_BYTES = new byte[0];
/**
* Default constructor, does not initialize any internal state.
*/
protected AbstractDeserializer() {
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -67,7 +73,7 @@ public abstract class AbstractDeserializer<T> implements Deserializer<T> {
* *
* @param in the input stream to read * @param in the input stream to read
* @return the deserialized Java object * @return the deserialized Java object
* @throws DeserializationException if there is a problem reading the stream or creating the expected Java object * @throws Exception if there is a problem reading the stream or creating the expected Java object
*/ */
protected abstract T doDeserialize(InputStream in) throws Exception; protected abstract T doDeserialize(InputStream in) throws Exception;
} }

View File

@ -29,6 +29,12 @@ import java.io.OutputStream;
*/ */
public abstract class AbstractSerializer<T> implements Serializer<T> { public abstract class AbstractSerializer<T> implements Serializer<T> {
/**
* Default constructor, does not initialize any internal state.
*/
protected AbstractSerializer() {
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -61,8 +67,8 @@ public abstract class AbstractSerializer<T> implements Serializer<T> {
* *
* @param t the object to convert to a byte stream * @param t the object to convert to a byte stream
* @param out the stream to write to * @param out the stream to write to
* @throws SerializationException if there is a problem converting the object to a byte stream or writing the * @throws Exception if there is a problem converting the object to a byte stream or writing the
* bytes to the {@code out}put stream. * bytes to the {@code out}put stream.
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
protected abstract void doSerialize(T t, OutputStream out) throws Exception; protected abstract void doSerialize(T t, OutputStream out) throws Exception;

View File

@ -16,11 +16,12 @@
package io.jsonwebtoken.io; package io.jsonwebtoken.io;
import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParserBuilder;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Collection;
/** /**
* Compresses and decompresses byte streams. * Compresses and decompresses byte streams.
@ -32,10 +33,12 @@ import java.util.Collection;
* <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.3"><code>zip</code></a> header value.</p> * <a href="https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.3"><code>zip</code></a> header value.</p>
* *
* <p><b>Custom Implementations</b></p> * <p><b>Custom Implementations</b></p>
*
* <p>A custom implementation of this interface may be used when creating a JWT by calling the * <p>A custom implementation of this interface may be used when creating a JWT by calling the
* {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionAlgorithm)} method. To ensure that parsing is * {@link JwtBuilder#compressWith(CompressionAlgorithm)} method.</p>
* possible, the parser must be aware of the implementation by calling *
* {@link io.jsonwebtoken.JwtParserBuilder#addCompressionAlgorithms(Collection)} during parser construction.</p> * <p>To ensure that parsing is possible, the parser must be aware of the implementation by adding it to the
* {@link JwtParserBuilder#zip()} collection during parser construction.</p>
* *
* @see Jwts.ZIP#DEF * @see Jwts.ZIP#DEF
* @see Jwts.ZIP#GZIP * @see Jwts.ZIP#GZIP

View File

@ -18,6 +18,8 @@ package io.jsonwebtoken.io;
/** /**
* A decoder converts an already-encoded data value to a desired data type. * A decoder converts an already-encoded data value to a desired data type.
* *
* @param <T> decoding input type
* @param <R> decoding output type
* @since 0.10.0 * @since 0.10.0
*/ */
public interface Decoder<T, R> { public interface Decoder<T, R> {

View File

@ -330,6 +330,15 @@ public final class Classes {
} }
} }
/**
* Returns the {@code instance}'s named (declared) field value.
*
* @param instance the instance with the internal field
* @param fieldName the name of the field to inspect
* @param fieldType the type of field to inspect
* @param <T> field instance value type
* @return the field value
*/
public static <T> T getFieldValue(Object instance, String fieldName, Class<T> fieldType) { public static <T> T getFieldValue(Object instance, String fieldName, Class<T> fieldType) {
if (instance == null) return null; if (instance == null) return null;
try { try {

View File

@ -0,0 +1,61 @@
/*
* 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.lang;
import java.util.Collection;
/**
* Mutation (modifications) to a {@link java.util.Collection} instance while also supporting method chaining. The
* {@link Collection#add(Object)}, {@link Collection#addAll(Collection)}, {@link Collection#remove(Object)}, and
* {@link Collection#clear()} methods do not support method chaining, so this interface enables that behavior.
*
* @param <E> the type of elements in the collection
* @param <M> the mutator subtype, for method chaining
* @since JJWT_RELEASE_VERSION
*/
public interface CollectionMutator<E, M extends CollectionMutator<E, M>> {
/**
* Adds the specified element to the collection.
*
* @param e the element to add.
* @return the mutator/builder for method chaining.
*/
M add(E e);
/**
* Adds the elements to the collection in iteration order.
*
* @param c the collection to add
* @return the mutator/builder for method chaining.
*/
M add(Collection<? extends E> c);
/**
* Removes all elements in the collection.
*
* @return the mutator/builder for method chaining.
*/
M clear();
/**
* Removes the specified element from the collection.
*
* @param e the element to remove.
* @return the mutator/builder for method chaining.
*/
M remove(E e);
}

View File

@ -0,0 +1,33 @@
/*
* 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.lang;
/**
* A {@code Conjunctor} supplies a joined object. It is typically used for nested builders to return
* to the source/original builder.
*
* @param <T> the type of joined object to return.
* @since JJWT_RELEASE_VERSION
*/
public interface Conjunctor<T> {
/**
* Returns the joined object.
*
* @return the joined object.
*/
T and();
}

View File

@ -0,0 +1,27 @@
/*
* 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.lang;
/**
* A {@link CollectionMutator} that can return access to its parent via the {@link Conjunctor#and() and()} method for
* continued configuration.
*
* @param <E> the type of elements in the collection
* @param <P> the parent to return
* @since JJWT_RELEASE_VERSION
*/
public interface NestedCollection<E, P> extends CollectionMutator<E, NestedCollection<E, P>>, Conjunctor<P> {
}

View File

@ -275,6 +275,12 @@ public final class Strings {
return new String(asciiBytes, StandardCharsets.US_ASCII); return new String(asciiBytes, StandardCharsets.US_ASCII);
} }
/**
* Returns the {@link StandardCharsets#US_ASCII US_ASCII}-encoded bytes of the specified {@code CharSequence}.
*
* @param s the {@code CharSequence} to encode to {@code US_ASCII}.
* @return the {@link StandardCharsets#US_ASCII US_ASCII}-encoded bytes of the specified {@code CharSequence}.
*/
public static byte[] ascii(CharSequence s) { public static byte[] ascii(CharSequence s) {
byte[] bytes = null; byte[] bytes = null;
if (s != null) { if (s != null) {

View File

@ -16,7 +16,6 @@
package io.jsonwebtoken.security; package io.jsonwebtoken.security;
import java.security.Key; import java.security.Key;
import java.util.Collection;
/** /**
* A {@link JwkBuilder} that builds asymmetric (public or private) JWKs. * A {@link JwkBuilder} that builds asymmetric (public or private) JWKs.
@ -69,7 +68,7 @@ public interface AsymmetricJwkBuilder<K extends Key, J extends AsymmetricJwk<K>,
* *
* <p>Per * <p>Per
* <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">JWK RFC 7517, Section 4.3, last paragraph</a>, * <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">JWK RFC 7517, Section 4.3, last paragraph</a>,
* the <code>use (Public Key Use)</code> and {@link #operations(Collection) key_ops (Key Operations)} members * the <code>use (Public Key Use)</code> and {@link #operations() key_ops (Key Operations)} members
* <em>SHOULD NOT</em> be used together; however, if both are used, the information they convey <em>MUST</em> be * <em>SHOULD NOT</em> be used together; however, if both are used, the information they convey <em>MUST</em> be
* consistent. Applications should specify which of these members they use, if either is to be used by the * consistent. Applications should specify which of these members they use, if either is to be used by the
* application.</p> * application.</p>

View File

@ -15,10 +15,11 @@
*/ */
package io.jsonwebtoken.security; package io.jsonwebtoken.security;
import io.jsonwebtoken.lang.Conjunctor;
import io.jsonwebtoken.lang.MapMutator; import io.jsonwebtoken.lang.MapMutator;
import io.jsonwebtoken.lang.NestedCollection;
import java.security.Key; import java.security.Key;
import java.util.Collection;
/** /**
* A {@link SecurityBuilder} that produces a JWK. A JWK is an immutable set of name/value pairs that represent a * A {@link SecurityBuilder} that produces a JWK. A JWK is an immutable set of name/value pairs that represent a
@ -104,83 +105,34 @@ public interface JwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilde
T idFromThumbprint(HashAlgorithm alg); T idFromThumbprint(HashAlgorithm alg);
/** /**
* Specifies an operation for which the key may be used by adding it to the * Configures the <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">key operations</a> for which
* JWK <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">{@code key_ops} (Key Operations) * the key is intended to be used. When finished, use the collection's {@link Conjunctor#and() and()} method to
* Parameter</a> values. This method may be called multiple times. * return to the JWK builder, for example:
* <blockquote><pre>
* jwkBuilder.operations().add(aKeyOperation).{@link Conjunctor#and() and()} // etc...</pre></blockquote>
* *
* <p>The {@code key_ops} (key operations) parameter identifies the operation(s) for which the key is * <p>The {@code and()} method will throw an {@link IllegalArgumentException} if any of the specified
* intended to be used. The {@code key_ops} parameter is intended for use cases in which public, * {@code KeyOperation}s are not permitted by the JWK's
* private, or symmetric keys may be present.</p> * {@link #operationPolicy(KeyOperationPolicy) operationPolicy}. See that documentation for more
* * information on security vulnerabilities when using the same key with multiple algorithms.</p>
* <p><b>Security Vulnerability Notice</b></p>
*
* <p>Multiple unrelated key operations <em>SHOULD NOT</em> be specified for a key because of the potential
* vulnerabilities associated with using the same key with multiple algorithms. Thus, the combinations
* {@link Jwks.OP#SIGN sign} with {@link Jwks.OP#VERIFY verify},
* {@link Jwks.OP#ENCRYPT encrypt} with {@link Jwks.OP#DECRYPT decrypt}, and
* {@link Jwks.OP#WRAP_KEY wrapKey} with {@link Jwks.OP#UNWRAP_KEY unwrapKey} are permitted, but other combinations
* <em>SHOULD NOT</em> be used. This is enforced by the builder's key operation
* {@link #operationPolicy(KeyOperationPolicy) policy}.</p>
* *
* <p><b>Standard {@code KeyOperation}s and Overrides</b></p> * <p><b>Standard {@code KeyOperation}s and Overrides</b></p>
* *
* <p>All RFC-standard JWK Key Operations in the {@link Jwks.OP} registry are supported via the builder's default * <p>All RFC-standard JWK Key Operations in the {@link Jwks.OP} registry are supported via the builder's default
* operations {@link #operationPolicy(KeyOperationPolicy) policy}, but other (custom) values * {@link #operationPolicy(KeyOperationPolicy) operationPolicy}, but other (custom) values
* <em>MAY</em> be specified (for example, using a {@link Jwks.OP#builder()}).</p> * <em>MAY</em> be specified (for example, using a {@link Jwks.OP#builder()}).</p>
* *
* <p>If the {@code JwkBuilder} is being used to rebuild or parse an existing JWK however, any custom operations * <p>If the {@code JwkBuilder} is being used to rebuild or parse an existing JWK however, any custom operations
* should be enabled for the {@code JwkBuilder} by {@link #operationPolicy(KeyOperationPolicy) specifying} * should be enabled by configuring an {@link #operationPolicy(KeyOperationPolicy) operationPolicy}
* an operations policy that includes the custom values (e.g. via * that includes the custom values (e.g. via
* {@link Jwks.OP#policy()}.{@link KeyOperationPolicyBuilder#add(KeyOperation) add(customKeyOperation)}).</p> * {@link Jwks.OP#policy()}.{@link KeyOperationPolicyBuilder#add(KeyOperation) add(customKeyOperation)}).</p>
* *
* <p>For best interoperability with other applications however, it is recommended to use only the {@link Jwks.OP} * <p>For best interoperability with other applications however, it is recommended to use only the {@link Jwks.OP}
* constants.</p> * constants.</p>
* *
* @param operation the value to add to the JWK {@code key_ops} value set * @return the {@link NestedCollection} to use for {@code key_ops} configuration.
* @return the builder for method chaining.
* @throws IllegalArgumentException if the {@code op} is not permitted by the operations
* {@link #operationPolicy(KeyOperationPolicy) policy}.
* @see Jwks.OP * @see Jwks.OP
* @see <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">RFC 7517: key_ops (Key Operations) Parameter</a>
*/ */
T operation(KeyOperation operation) throws IllegalArgumentException; NestedCollection<KeyOperation, T> operations();
/**
* Adds {@code ops} to the JWK <a href="https://www.rfc-editor.org/rfc/rfc7517.html#section-4.3">{@code key_ops}
* (Key Operations) Parameter</a> values.
*
* <p>The {@code key_ops} (key operations) parameter identifies the operation(s) for which the key is
* intended to be used. The {@code key_ops} parameter is intended for use cases in which public,
* private, or symmetric keys may be present.</p>
*
* <p><b>Security Vulnerability Notice</b></p>
*
* <p>Multiple unrelated key operations <em>SHOULD NOT</em> be specified for a key because of the potential
* vulnerabilities associated with using the same key with multiple algorithms. Thus, the combinations
* {@link Jwks.OP#SIGN sign} with {@link Jwks.OP#VERIFY verify},
* {@link Jwks.OP#ENCRYPT encrypt} with {@link Jwks.OP#DECRYPT decrypt}, and
* {@link Jwks.OP#WRAP_KEY wrapKey} with {@link Jwks.OP#UNWRAP_KEY unwrapKey} are permitted, but other combinations
* <em>SHOULD NOT</em> be used. This is enforced by the builder's default
* operation {@link #operationPolicy(KeyOperationPolicy) policy}.</p>
*
* <p><b>Standard {@code KeyOperation}s and Overrides</b></p>
*
* <p>All RFC-standard JWK Key Operations in the {@link Jwks.OP} registry are supported via the builder's default
* operations {@link #operationPolicy(KeyOperationPolicy) policy}, but other (custom) values
* <em>MAY</em> be specified (for example, using a {@link Jwks.OP#builder()}).</p>
*
* <p>If the {@code JwkBuilder} is being used to rebuild or parse an existing JWK however, any custom operations
* should be enabled for the {@code JwkBuilder} by {@link #operationPolicy(KeyOperationPolicy) specifying}
* an operations policy that includes the custom values (e.g. via
* {@link Jwks.OP#policy()}.{@link KeyOperationPolicyBuilder#add(KeyOperation) add(customKeyOperation)}).</p>
*
* <p>For best interoperability with other applications however, it is recommended to use only the {@link Jwks.OP}
* constants.</p>
*
* @param ops the {@code KeyOperation} values to add to the JWK {@code key_ops} value set
* @return the builder for method chaining.
* @throws IllegalArgumentException if any of the operations are not permitted by the operations
* {@link #operationPolicy(KeyOperationPolicy) policy}.
* @see Jwks.OP
*/
T operations(Collection<KeyOperation> ops) throws IllegalArgumentException;
} }

View File

@ -17,11 +17,9 @@ package io.jsonwebtoken.security;
import io.jsonwebtoken.lang.Builder; import io.jsonwebtoken.lang.Builder;
import java.util.Collection;
/** /**
* A {@code KeyOperationBuilder} produces {@link KeyOperation} instances that may be added to a JWK's * A {@code KeyOperationBuilder} produces {@link KeyOperation} instances that may be added to a JWK's
* {@link JwkBuilder#operations(Collection) key operations} parameter. This is primarily only useful for creating * {@link JwkBuilder#operations() key operations} parameter. This is primarily only useful for creating
* custom (non-standard) {@code KeyOperation}s for use with a custom {@link KeyOperationPolicy}, as all standard ones * custom (non-standard) {@code KeyOperation}s for use with a custom {@link KeyOperationPolicy}, as all standard ones
* are available already via the {@link Jwks.OP} registry singleton. * are available already via the {@link Jwks.OP} registry singleton.
* *

View File

@ -33,8 +33,10 @@ public interface KeyOperationPolicied<T extends KeyOperationPolicied<T>> {
* <blockquote><pre> * <blockquote><pre>
* Multiple unrelated key operations SHOULD NOT be specified for a key * Multiple unrelated key operations SHOULD NOT be specified for a key
* because of the potential vulnerabilities associated with using the * because of the potential vulnerabilities associated with using the
* same key with multiple algorithms. * same key with multiple algorithms. Thus, the combinations "{@link Jwks.OP#SIGN sign}"
* </pre></blockquote></li> * with "{@link Jwks.OP#VERIFY verify}", "{@link Jwks.OP#ENCRYPT encrypt}" with "{@link Jwks.OP#DECRYPT decrypt}", and "{@link Jwks.OP#WRAP_KEY wrapKey}" with
* "{@link Jwks.OP#UNWRAP_KEY unwrapKey}" are permitted, but other combinations SHOULD NOT be used.</pre></blockquote>
* </li>
* </ul> * </ul>
* *
* <p>If you wish to enable a different policy, perhaps to support additional custom {@code KeyOperation} values, * <p>If you wish to enable a different policy, perhaps to support additional custom {@code KeyOperation} values,

View File

@ -20,8 +20,8 @@ import java.util.Collection;
/** /**
* A key operation policy determines which {@link KeyOperation}s may be assigned to a JWK. * A key operation policy determines which {@link KeyOperation}s may be assigned to a JWK.
* *
* @since JJWT_RELEASE_VERSION
* @see JwkBuilder#operationPolicy(KeyOperationPolicy) * @see JwkBuilder#operationPolicy(KeyOperationPolicy)
* @since JJWT_RELEASE_VERSION
*/ */
public interface KeyOperationPolicy { public interface KeyOperationPolicy {
@ -39,5 +39,5 @@ public interface KeyOperationPolicy {
* @param ops the operations to validate * @param ops the operations to validate
*/ */
@SuppressWarnings("GrazieInspection") @SuppressWarnings("GrazieInspection")
void validate(Collection<KeyOperation> ops) throws IllegalArgumentException; void validate(Collection<? extends KeyOperation> ops) throws IllegalArgumentException;
} }

View File

@ -17,6 +17,7 @@ package io.jsonwebtoken.security;
import io.jsonwebtoken.Identifiable; import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.lang.Builder; import io.jsonwebtoken.lang.Builder;
import io.jsonwebtoken.lang.CollectionMutator;
import java.util.Collection; import java.util.Collection;
@ -32,7 +33,8 @@ import java.util.Collection;
* @see Jwks.OP#builder() * @see Jwks.OP#builder()
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
public interface KeyOperationPolicyBuilder extends Builder<KeyOperationPolicy> { public interface KeyOperationPolicyBuilder extends CollectionMutator<KeyOperation, KeyOperationPolicyBuilder>,
Builder<KeyOperationPolicy> {
/** /**
* Allows a JWK to have unrelated {@link KeyOperation}s in its {@code key_ops} parameter values. <b>Be careful * Allows a JWK to have unrelated {@link KeyOperation}s in its {@code key_ops} parameter values. <b>Be careful
@ -72,8 +74,10 @@ public interface KeyOperationPolicyBuilder extends Builder<KeyOperationPolicy> {
* @see Jwks.OP * @see Jwks.OP
* @see Jwks.OP#builder() * @see Jwks.OP#builder()
* @see JwkBuilder#operationPolicy(KeyOperationPolicy) * @see JwkBuilder#operationPolicy(KeyOperationPolicy)
* @see JwkBuilder#operations(Collection) * @see JwkBuilder#operations()
*/ */
@Override
// for better JavaDoc
KeyOperationPolicyBuilder add(KeyOperation op); KeyOperationPolicyBuilder add(KeyOperation op);
/** /**
@ -101,8 +105,10 @@ public interface KeyOperationPolicyBuilder extends Builder<KeyOperationPolicy> {
* @see Jwks.OP * @see Jwks.OP
* @see Jwks.OP#builder() * @see Jwks.OP#builder()
* @see JwkBuilder#operationPolicy(KeyOperationPolicy) * @see JwkBuilder#operationPolicy(KeyOperationPolicy)
* @see JwkBuilder#operations(Collection) * @see JwkBuilder#operations()
*/ */
KeyOperationPolicyBuilder add(Collection<KeyOperation> ops); @Override
// for better JavaDoc
KeyOperationPolicyBuilder add(Collection<? extends KeyOperation> ops);
} }

View File

@ -20,6 +20,7 @@ import java.security.Key;
/** /**
* A request to a cryptographic algorithm requiring a {@link Key}. * A request to a cryptographic algorithm requiring a {@link Key}.
* *
* @param <T> the type of payload in the request
* @param <K> they type of key used by the algorithm during the request * @param <K> they type of key used by the algorithm during the request
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */

View File

@ -25,6 +25,7 @@ import java.security.SecureRandom;
* during instance creation, such as a {@link java.security.Provider} or {@link java.security.SecureRandom}. * during instance creation, such as a {@link java.security.Provider} or {@link java.security.SecureRandom}.
* *
* @param <T> The type of object that will be created each time {@link #build()} is invoked. * @param <T> The type of object that will be created each time {@link #build()} is invoked.
* @param <B> the type of SecurityBuilder returned for method chaining
* @see #provider(Provider) * @see #provider(Provider)
* @see #random(SecureRandom) * @see #random(SecureRandom)
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION

View File

@ -0,0 +1,34 @@
/*
* 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;
import io.jsonwebtoken.ClaimsMutator;
import io.jsonwebtoken.impl.lang.DefaultNestedCollection;
import java.util.Collection;
/**
* Abstract NestedCollection that requires the AudienceCollection interface to be implemented.
*
* @param <P> type of parent to return
* @since JJWT_RELEASE_VERSION
*/
abstract class AbstractAudienceCollection<P> extends DefaultNestedCollection<String, P>
implements ClaimsMutator.AudienceCollection<P> {
protected AbstractAudienceCollection(P parent, Collection<? extends String> seed) {
super(parent, seed);
}
}

View File

@ -16,19 +16,18 @@
package io.jsonwebtoken.impl; package io.jsonwebtoken.impl;
import io.jsonwebtoken.JweHeaderMutator; import io.jsonwebtoken.JweHeaderMutator;
import io.jsonwebtoken.impl.lang.DefaultNestedCollection;
import io.jsonwebtoken.impl.lang.DelegatingMapMutator; import io.jsonwebtoken.impl.lang.DelegatingMapMutator;
import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameter;
import io.jsonwebtoken.impl.security.X509BuilderSupport; import io.jsonwebtoken.impl.security.X509BuilderSupport;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.NestedCollection;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.PublicJwk; import io.jsonwebtoken.security.PublicJwk;
import java.net.URI; import java.net.URI;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* @param <T> return type for method chaining * @param <T> return type for method chaining
@ -105,29 +104,14 @@ public class DefaultJweHeaderMutator<T extends JweHeaderMutator<T>>
// ============================================================= // =============================================================
@Override @Override
public T critical(String crit) { public NestedCollection<String, T> critical() {
crit = Strings.clean(crit); return new DefaultNestedCollection<String, T>(self(), this.DELEGATE.get(DefaultProtectedHeader.CRIT)) {
if (Strings.hasText(crit)) { @Override
critical(Collections.setOf(crit)); public T and() {
} put(DefaultProtectedHeader.CRIT, Collections.asSet(getCollection()));
return self(); return super.and();
}
@Override
public T critical(Collection<String> crit) {
if (!Collections.isEmpty(crit)) {
Set<String> existing = Collections.nullSafe(this.DELEGATE.get(DefaultProtectedHeader.CRIT));
Set<String> set = new LinkedHashSet<>(existing.size() + crit.size());
set.addAll(existing);
for (String s : crit) {
s = Strings.clean(s);
if (s != null) {
set.add(s);
}
} }
put(DefaultProtectedHeader.CRIT, set); };
}
return self();
} }
@Override @Override

View File

@ -74,7 +74,6 @@ import java.security.PrivateKey;
import java.security.Provider; import java.security.Provider;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
@ -435,19 +434,8 @@ public class DefaultJwtBuilder implements JwtBuilder {
} }
@Override @Override
public JwtBuilder audienceSingle(String aud) { public AudienceCollection<JwtBuilder> audience() {
//noinspection deprecation return new DelegateAudienceCollection<>((JwtBuilder) this, claims().audience());
return claims().audienceSingle(aud).and();
}
@Override
public JwtBuilder audience(String aud) {
return claims().audience(aud).and();
}
@Override
public JwtBuilder audience(Collection<String> aud) {
return claims().audience(aud).and();
} }
@Override @Override
@ -586,7 +574,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
this.headerBuilder.add(DefaultHeader.ALGORITHM.getId(), sigAlg.getId()); this.headerBuilder.add(DefaultHeader.ALGORITHM.getId(), sigAlg.getId());
if (!this.encodePayload) { // b64 extension: if (!this.encodePayload) { // b64 extension:
String id = DefaultJwsHeader.B64.getId(); String id = DefaultJwsHeader.B64.getId();
this.headerBuilder.critical(id).add(id, false); this.headerBuilder.critical().add(id).and().add(id, false);
} }
final JwsHeader header = Assert.isInstanceOf(JwsHeader.class, this.headerBuilder.build()); final JwsHeader header = Assert.isInstanceOf(JwsHeader.class, this.headerBuilder.build());
encodeAndWrite("JWS Protected Header", header, jws); encodeAndWrite("JWS Protected Header", header, jws);

View File

@ -59,6 +59,7 @@ import io.jsonwebtoken.lang.Classes;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.DateFormats; import io.jsonwebtoken.lang.DateFormats;
import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.DecryptAeadRequest; import io.jsonwebtoken.security.DecryptAeadRequest;
@ -188,13 +189,13 @@ public class DefaultJwtParser implements JwtParser {
private final boolean unsecuredDecompression; private final boolean unsecuredDecompression;
private final Function<JwsHeader, SecureDigestAlgorithm<?, ?>> sigAlgFn; private final Function<JwsHeader, SecureDigestAlgorithm<?, ?>> sigAlgs;
private final Function<JweHeader, AeadAlgorithm> encAlgFn; private final Function<JweHeader, AeadAlgorithm> encAlgs;
private final Function<JweHeader, KeyAlgorithm<?, ?>> keyAlgFn; private final Function<JweHeader, KeyAlgorithm<?, ?>> keyAlgs;
private final Function<Header, CompressionAlgorithm> zipAlgFn; private final Function<Header, CompressionAlgorithm> zipAlgs;
private final Locator<? extends Key> keyLocator; private final Locator<? extends Key> keyLocator;
@ -224,10 +225,10 @@ public class DefaultJwtParser implements JwtParser {
Decoder<InputStream, InputStream> base64UrlDecoder, Decoder<InputStream, InputStream> base64UrlDecoder,
Deserializer<Map<String, ?>> deserializer, Deserializer<Map<String, ?>> deserializer,
CompressionCodecResolver compressionCodecResolver, CompressionCodecResolver compressionCodecResolver,
Collection<CompressionAlgorithm> extraZipAlgs, Registry<String, CompressionAlgorithm> zipAlgs,
Collection<SecureDigestAlgorithm<?, ?>> extraSigAlgs, Registry<String, SecureDigestAlgorithm<?, ?>> sigAlgs,
Collection<KeyAlgorithm<?, ?>> extraKeyAlgs, Registry<String, KeyAlgorithm<?, ?>> keyAlgs,
Collection<AeadAlgorithm> extraEncAlgs) { Registry<String, AeadAlgorithm> encAlgs) {
this.provider = provider; this.provider = provider;
this.unsecured = unsecured; this.unsecured = unsecured;
this.unsecuredDecompression = unsecuredDecompression; this.unsecuredDecompression = unsecuredDecompression;
@ -239,12 +240,11 @@ public class DefaultJwtParser implements JwtParser {
this.expectedClaims = Jwts.claims().add(expectedClaims); this.expectedClaims = Jwts.claims().add(expectedClaims);
this.decoder = Assert.notNull(base64UrlDecoder, "base64UrlDecoder cannot be null."); this.decoder = Assert.notNull(base64UrlDecoder, "base64UrlDecoder cannot be null.");
this.deserializer = Assert.notNull(deserializer, "JSON Deserializer cannot be null."); this.deserializer = Assert.notNull(deserializer, "JSON Deserializer cannot be null.");
this.sigAlgs = new IdLocator<>(DefaultHeader.ALGORITHM, sigAlgs, MISSING_JWS_ALG_MSG);
this.sigAlgFn = new IdLocator<>(DefaultHeader.ALGORITHM, Jwts.SIG.get(), extraSigAlgs, MISSING_JWS_ALG_MSG); this.keyAlgs = new IdLocator<>(DefaultHeader.ALGORITHM, keyAlgs, MISSING_JWE_ALG_MSG);
this.keyAlgFn = new IdLocator<>(DefaultHeader.ALGORITHM, Jwts.KEY.get(), extraKeyAlgs, MISSING_JWE_ALG_MSG); this.encAlgs = new IdLocator<>(DefaultJweHeader.ENCRYPTION_ALGORITHM, encAlgs, MISSING_ENC_MSG);
this.encAlgFn = new IdLocator<>(DefaultJweHeader.ENCRYPTION_ALGORITHM, Jwts.ENC.get(), extraEncAlgs, MISSING_ENC_MSG); this.zipAlgs = compressionCodecResolver != null ? new CompressionCodecLocator(compressionCodecResolver) :
this.zipAlgFn = compressionCodecResolver != null ? new CompressionCodecLocator(compressionCodecResolver) : new IdLocator<>(DefaultHeader.COMPRESSION_ALGORITHM, zipAlgs, null);
new IdLocator<>(DefaultHeader.COMPRESSION_ALGORITHM, Jwts.ZIP.get(), extraZipAlgs, null);
} }
@Override @Override
@ -271,7 +271,7 @@ public class DefaultJwtParser implements JwtParser {
SecureDigestAlgorithm<?, Key> algorithm; SecureDigestAlgorithm<?, Key> algorithm;
try { try {
algorithm = (SecureDigestAlgorithm<?, Key>) sigAlgFn.apply(jwsHeader); algorithm = (SecureDigestAlgorithm<?, Key>) sigAlgs.apply(jwsHeader);
} catch (UnsupportedJwtException e) { } catch (UnsupportedJwtException e) {
//For backwards compatibility. TODO: remove this try/catch block for 1.0 and let UnsupportedJwtException propagate //For backwards compatibility. TODO: remove this try/catch block for 1.0 and let UnsupportedJwtException propagate
String msg = "Unsupported signature algorithm '" + alg + "'"; String msg = "Unsupported signature algorithm '" + alg + "'";
@ -531,10 +531,10 @@ public class DefaultJwtParser implements JwtParser {
if (!Strings.hasText(enc)) { if (!Strings.hasText(enc)) {
throw new MalformedJwtException(MISSING_ENC_MSG); throw new MalformedJwtException(MISSING_ENC_MSG);
} }
final AeadAlgorithm encAlg = this.encAlgFn.apply(jweHeader); final AeadAlgorithm encAlg = this.encAlgs.apply(jweHeader);
Assert.stateNotNull(encAlg, "JWE Encryption Algorithm cannot be null."); Assert.stateNotNull(encAlg, "JWE Encryption Algorithm cannot be null.");
@SuppressWarnings("rawtypes") final KeyAlgorithm keyAlg = this.keyAlgFn.apply(jweHeader); @SuppressWarnings("rawtypes") final KeyAlgorithm keyAlg = this.keyAlgs.apply(jweHeader);
Assert.stateNotNull(keyAlg, "JWE Key Algorithm cannot be null."); Assert.stateNotNull(keyAlg, "JWE Key Algorithm cannot be null.");
Key key = this.keyLocator.locate(jweHeader); Key key = this.keyLocator.locate(jweHeader);
@ -578,7 +578,7 @@ public class DefaultJwtParser implements JwtParser {
integrityVerified = true; // no exception means signature verified integrityVerified = true; // no exception means signature verified
} }
final CompressionAlgorithm compressionAlgorithm = zipAlgFn.apply(header); final CompressionAlgorithm compressionAlgorithm = zipAlgs.apply(header);
if (compressionAlgorithm != null) { if (compressionAlgorithm != null) {
if (!integrityVerified) { if (!integrityVerified) {
if (!payloadBase64UrlEncoded) { if (!payloadBase64UrlEncoded) {

View File

@ -24,15 +24,22 @@ import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.Locator; import io.jsonwebtoken.Locator;
import io.jsonwebtoken.SigningKeyResolver; import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.impl.io.DelegateStringDecoder; import io.jsonwebtoken.impl.io.DelegateStringDecoder;
import io.jsonwebtoken.impl.io.StandardCompressionAlgorithms;
import io.jsonwebtoken.impl.lang.DefaultNestedCollection;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.impl.lang.Services; import io.jsonwebtoken.impl.lang.Services;
import io.jsonwebtoken.impl.security.ConstantKeyLocator; import io.jsonwebtoken.impl.security.ConstantKeyLocator;
import io.jsonwebtoken.impl.security.StandardEncryptionAlgorithms;
import io.jsonwebtoken.impl.security.StandardKeyAlgorithms;
import io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms;
import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.CompressionAlgorithm;
import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.NestedCollection;
import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.InvalidKeyException; import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyAlgorithm;
@ -45,9 +52,7 @@ import java.security.Key;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.Provider; import java.security.Provider;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -79,17 +84,18 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
@SuppressWarnings("deprecation") //TODO: remove for 1.0 @SuppressWarnings("deprecation") //TODO: remove for 1.0
private SigningKeyResolver signingKeyResolver = null; private SigningKeyResolver signingKeyResolver = null;
private final Collection<AeadAlgorithm> extraEncAlgs = new LinkedHashSet<>(); private Registry<String, AeadAlgorithm> encAlgs = Jwts.ENC.get();
private final Collection<KeyAlgorithm<?, ?>> extraKeyAlgs = new LinkedHashSet<>(); private Registry<String, KeyAlgorithm<?, ?>> keyAlgs = Jwts.KEY.get();
private final Collection<SecureDigestAlgorithm<?, ?>> extraSigAlgs = new LinkedHashSet<>(); private Registry<String, SecureDigestAlgorithm<?, ?>> sigAlgs = Jwts.SIG.get();
private final Collection<CompressionAlgorithm> extraZipAlgs = new LinkedHashSet<>(); private Registry<String, CompressionAlgorithm> zipAlgs = Jwts.ZIP.get();
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private CompressionCodecResolver compressionCodecResolver; private CompressionCodecResolver compressionCodecResolver;
@SuppressWarnings("deprecation")
private Decoder<InputStream, InputStream> decoder = new DelegateStringDecoder(Decoders.BASE64URL); private Decoder<InputStream, InputStream> decoder = new DelegateStringDecoder(Decoders.BASE64URL);
private Deserializer<Map<String, ?>> deserializer; private Deserializer<Map<String, ?>> deserializer;
@ -162,7 +168,7 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
@Override @Override
public JwtParserBuilder requireAudience(String audience) { public JwtParserBuilder requireAudience(String audience) {
expectedClaims.audience(audience); expectedClaims.audience().add(audience).and();
return this; return this;
} }
@ -211,20 +217,14 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
} }
@Override @Override
public JwtParserBuilder critical(String crit) { public NestedCollection<String, JwtParserBuilder> critical() {
if (Strings.hasText(crit)) { return new DefaultNestedCollection<String, JwtParserBuilder>(this, this.critical) {
Set<String> existing = Collections.nullSafe(this.critical); @Override
Set<String> newSet = new LinkedHashSet<>(existing); public JwtParserBuilder and() {
newSet.add(crit); critical = Collections.asSet(getCollection());
critical(newSet); return super.and();
} }
return this; };
}
@Override
public JwtParserBuilder critical(Set<String> crit) {
this.critical = Collections.immutable(new LinkedHashSet<>(Collections.nullSafe(crit)));
return this;
} }
@Override @Override
@ -301,31 +301,47 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
} }
@Override @Override
public JwtParserBuilder addCompressionAlgorithms(Collection<? extends CompressionAlgorithm> algs) { public NestedCollection<CompressionAlgorithm, JwtParserBuilder> zip() {
Assert.notEmpty(algs, "Additional CompressionAlgorithm collection cannot be null or empty."); return new DefaultNestedCollection<CompressionAlgorithm, JwtParserBuilder>(this, this.zipAlgs.values()) {
this.extraZipAlgs.addAll(algs); @Override
return this; public JwtParserBuilder and() {
zipAlgs = new IdRegistry<>(StandardCompressionAlgorithms.NAME, getCollection());
return super.and();
}
};
} }
@Override @Override
public JwtParserBuilder addEncryptionAlgorithms(Collection<? extends AeadAlgorithm> algs) { public NestedCollection<AeadAlgorithm, JwtParserBuilder> enc() {
Assert.notEmpty(algs, "Additional AeadAlgorithm collection cannot be null or empty."); return new DefaultNestedCollection<AeadAlgorithm, JwtParserBuilder>(this, this.encAlgs.values()) {
this.extraEncAlgs.addAll(algs); @Override
return this; public JwtParserBuilder and() {
encAlgs = new IdRegistry<>(StandardEncryptionAlgorithms.NAME, getCollection());
return super.and();
}
};
} }
@Override @Override
public JwtParserBuilder addSignatureAlgorithms(Collection<? extends SecureDigestAlgorithm<?, ?>> algs) { public NestedCollection<SecureDigestAlgorithm<?, ?>, JwtParserBuilder> sig() {
Assert.notEmpty(algs, "Additional SignatureAlgorithm collection cannot be null or empty."); return new DefaultNestedCollection<SecureDigestAlgorithm<?, ?>, JwtParserBuilder>(this, this.sigAlgs.values()) {
this.extraSigAlgs.addAll(algs); @Override
return this; public JwtParserBuilder and() {
sigAlgs = new IdRegistry<>(StandardSecureDigestAlgorithms.NAME, getCollection());
return super.and();
}
};
} }
@Override @Override
public JwtParserBuilder addKeyAlgorithms(Collection<? extends KeyAlgorithm<?, ?>> algs) { public NestedCollection<KeyAlgorithm<?, ?>, JwtParserBuilder> key() {
Assert.notEmpty(algs, "Additional KeyAlgorithm collection cannot be null or empty."); return new DefaultNestedCollection<KeyAlgorithm<?, ?>, JwtParserBuilder>(this, this.keyAlgs.values()) {
this.extraKeyAlgs.addAll(algs); @Override
return this; public JwtParserBuilder and() {
keyAlgs = new IdRegistry<>(StandardKeyAlgorithms.NAME, getCollection());
return super.and();
}
};
} }
@SuppressWarnings("deprecation") //TODO: remove for 1.0 @SuppressWarnings("deprecation") //TODO: remove for 1.0
@ -385,9 +401,9 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
"due to their security implications."; "due to their security implications.";
throw new IllegalStateException(msg); throw new IllegalStateException(msg);
} }
if (this.compressionCodecResolver != null && !Collections.isEmpty(extraZipAlgs)) { if (this.compressionCodecResolver != null && !Jwts.ZIP.get().equals(this.zipAlgs)) {
String msg = "Both 'addCompressionAlgorithms' and 'compressionCodecResolver' " + String msg = "Both 'zip()' and 'compressionCodecResolver' " +
"cannot be specified. Choose either."; "cannot be configured. Choose either.";
throw new IllegalStateException(msg); throw new IllegalStateException(msg);
} }
@ -409,10 +425,10 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
decoder, decoder,
deserializer, deserializer,
compressionCodecResolver, compressionCodecResolver,
extraZipAlgs, zipAlgs,
extraSigAlgs, sigAlgs,
extraKeyAlgs, keyAlgs,
extraEncAlgs encAlgs
); );
} }
} }

View File

@ -0,0 +1,69 @@
/*
* 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;
import io.jsonwebtoken.ClaimsMutator;
import io.jsonwebtoken.lang.Assert;
import java.util.Collection;
public class DelegateAudienceCollection<P> implements ClaimsMutator.AudienceCollection<P> {
private final ClaimsMutator.AudienceCollection<?> delegate;
private final P parent;
public DelegateAudienceCollection(P parent, ClaimsMutator.AudienceCollection<?> delegate) {
this.parent = Assert.notNull(parent, "Parent cannot be null.");
this.delegate = Assert.notNull(delegate, "Delegate cannot be null.");
}
@Override
public P single(String aud) {
delegate.single(aud);
return parent;
}
@Override
public ClaimsMutator.AudienceCollection<P> add(String s) {
delegate.add(s);
return this;
}
@Override
public ClaimsMutator.AudienceCollection<P> add(Collection<? extends String> c) {
delegate.add(c);
return this;
}
@Override
public ClaimsMutator.AudienceCollection<P> clear() {
delegate.clear();
return this;
}
@Override
public ClaimsMutator.AudienceCollection<P> remove(String s) {
delegate.remove(s);
return this;
}
@Override
public P and() {
delegate.and(); // allow any cleanup/finalization
return parent;
}
}

View File

@ -23,9 +23,7 @@ import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.MapMutator; import io.jsonwebtoken.lang.MapMutator;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
/** /**
@ -74,7 +72,8 @@ public class DelegatingClaimsMutator<T extends MapMutator<String, Object, T> & C
@Override @Override
public T setAudience(String aud) { public T setAudience(String aud) {
return audienceSingle(aud); //noinspection deprecation
return audience().single(aud);
} }
private Set<String> getAudience() { private Set<String> getAudience() {
@ -83,51 +82,36 @@ public class DelegatingClaimsMutator<T extends MapMutator<String, Object, T> & C
String existing = get(AUDIENCE_STRING); String existing = get(AUDIENCE_STRING);
remove(AUDIENCE_STRING.getId()); // clear out any canonical/idiomatic values since we're replacing remove(AUDIENCE_STRING.getId()); // clear out any canonical/idiomatic values since we're replacing
setDelegate(this.DELEGATE.replace(DefaultClaims.AUDIENCE)); setDelegate(this.DELEGATE.replace(DefaultClaims.AUDIENCE));
if (Strings.hasText(existing)) { put(DefaultClaims.AUDIENCE, Collections.setOf(existing)); // replace as Set
put(DefaultClaims.AUDIENCE, Collections.setOf(existing)); // replace as Set
}
} }
return get(DefaultClaims.AUDIENCE); return get(DefaultClaims.AUDIENCE);
} }
@Override private T audienceSingle(String aud) {
public T audienceSingle(String aud) {
if (!Strings.hasText(aud)) { if (!Strings.hasText(aud)) {
return put(DefaultClaims.AUDIENCE, null); return put(DefaultClaims.AUDIENCE, null);
} }
// otherwise it's an actual single string, we need to ensure that we can represent it as a single // otherwise it's an actual single string, we need to ensure that we can represent it as a single
// string by swapping out the AUDIENCE param if necessary: // string by swapping out the AUDIENCE param:
if (this.DELEGATE.PARAMS.get(AUDIENCE_STRING.getId()).supports(Collections.emptySet())) { // need to swap: remove(AUDIENCE_STRING.getId()); //remove any existing value, as conversion will throw an exception
remove(AUDIENCE_STRING.getId()); //remove any existing value, as conversion will throw an exception setDelegate(this.DELEGATE.replace(AUDIENCE_STRING));
setDelegate(this.DELEGATE.replace(AUDIENCE_STRING));
}
return put(AUDIENCE_STRING, aud); return put(AUDIENCE_STRING, aud);
} }
@Override @Override
public T audience(String aud) { public AudienceCollection<T> audience() {
aud = Strings.clean(aud); return new AbstractAudienceCollection<T>(self(), getAudience()) {
if (Strings.hasText(aud)) { @Override
audience(java.util.Collections.singleton(aud)); public T single(String audience) {
} return audienceSingle(audience);
return self();
}
@Override
public T audience(Collection<String> aud) {
if (!Collections.isEmpty(aud)) {
Set<String> existing = Collections.nullSafe(getAudience());
Set<String> set = new LinkedHashSet<>(existing.size() + aud.size());
set.addAll(existing);
for (String s : aud) {
s = Strings.clean(s);
if (s != null) {
set.add(s);
}
} }
put(DefaultClaims.AUDIENCE, set);
} @Override
return self(); public T and() {
put(DefaultClaims.AUDIENCE, Collections.asSet(getCollection()));
return super.and();
}
};
} }
@Override @Override

View File

@ -23,16 +23,11 @@ import io.jsonwebtoken.Locator;
import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.lang.Function; import io.jsonwebtoken.impl.lang.Function;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameter;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import java.util.Collection;
import java.util.LinkedHashSet;
public class IdLocator<H extends Header, R extends Identifiable> implements Locator<R>, Function<H, R> { public class IdLocator<H extends Header, R extends Identifiable> implements Locator<R>, Function<H, R> {
private final Parameter<String> param; private final Parameter<String> param;
@ -41,28 +36,12 @@ public class IdLocator<H extends Header, R extends Identifiable> implements Loca
private final Registry<String, R> registry; private final Registry<String, R> registry;
public IdLocator(Parameter<String> param, Registry<String, R> registry, Collection<R> extras, String requiredExceptionMessage) { public IdLocator(Parameter<String> param, Registry<String, R> registry, String requiredExceptionMessage) {
this.param = Assert.notNull(param, "Header param cannot be null."); this.param = Assert.notNull(param, "Header param cannot be null.");
this.requiredMsg = Strings.clean(requiredExceptionMessage); this.requiredMsg = Strings.clean(requiredExceptionMessage);
this.valueRequired = Strings.hasText(this.requiredMsg); this.valueRequired = Strings.hasText(this.requiredMsg);
Assert.notEmpty(registry, "Registry cannot be null or empty."); Assert.notEmpty(registry, "Registry cannot be null or empty.");
Collection<R> all = new LinkedHashSet<>(Collections.size(registry) + Collections.size(extras)); this.registry = registry;
all.addAll(registry.values()); // defaults MUST come before extras to allow extras to override if necessary
all.addAll(extras);
// The registry requires CaSe-SeNsItIvE keys on purpose - all JWA standard algorithm identifiers
// (JWS 'alg', JWE 'enc', JWK 'kty', etc) are all case-sensitive per via the following RFC language:
//
// This name is a case-sensitive ASCII string. Names may not match other registered names in a
// case-insensitive manner unless the Designated Experts state that there is a compelling reason to
// allow an exception.
//
// References:
// - JWS/JWE alg and JWE enc 'Algorithm Name': https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1.1
// - JWE zip 'Compression Algorithm Value': https://www.rfc-editor.org/rfc/rfc7518.html#section-7.3.1
// - JWK '"kty" Parameter Value': https://www.rfc-editor.org/rfc/rfc7518.html#section-7.4.1
this.registry = new IdRegistry<>(param.getName(), all); // do not use the caseSensitive ctor argument - must be false
} }
private static String type(Header header) { private static String type(Header header) {

View File

@ -17,18 +17,19 @@ package io.jsonwebtoken.impl.io;
import io.jsonwebtoken.impl.compression.DeflateCompressionAlgorithm; import io.jsonwebtoken.impl.compression.DeflateCompressionAlgorithm;
import io.jsonwebtoken.impl.compression.GzipCompressionAlgorithm; import io.jsonwebtoken.impl.compression.GzipCompressionAlgorithm;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.CompressionAlgorithm;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.ZIP @SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.ZIP
public final class StandardCompressionAlgorithms extends DelegatingRegistry<String, CompressionAlgorithm> { public final class StandardCompressionAlgorithms extends IdRegistry<CompressionAlgorithm> {
public static final String NAME = "Compression Algorithm";
public StandardCompressionAlgorithms() { public StandardCompressionAlgorithms() {
super(new IdRegistry<>("Compression Algorithm", Collections.<CompressionAlgorithm>of( super(NAME, Collections.<CompressionAlgorithm>of(
new DeflateCompressionAlgorithm(), new DeflateCompressionAlgorithm(),
new GzipCompressionAlgorithm() new GzipCompressionAlgorithm()
), false)); ));
} }
} }

View File

@ -0,0 +1,75 @@
/*
* 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;
import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.lang.CollectionMutator;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Strings;
import java.util.Collection;
import java.util.LinkedHashSet;
public class DefaultCollectionMutator<E, M extends CollectionMutator<E, M>> implements CollectionMutator<E, M> {
private final Collection<E> collection;
public DefaultCollectionMutator(Collection<? extends E> seed) {
this.collection = new LinkedHashSet<>(Collections.nullSafe(seed));
}
@SuppressWarnings("unchecked")
protected final M self() {
return (M) this;
}
@Override
public M add(E e) {
if (Objects.isEmpty(e)) return self();
if (e instanceof Identifiable && !Strings.hasText(((Identifiable) e).getId())) {
String msg = e.getClass() + " getId() value cannot be null or empty.";
throw new IllegalArgumentException(msg);
}
this.collection.remove(e);
this.collection.add(e);
return self();
}
@Override
public M remove(E e) {
this.collection.remove(e);
return self();
}
@Override
public M add(Collection<? extends E> c) {
for (E element : Collections.nullSafe(c)) {
add(element);
}
return self();
}
@Override
public M clear() {
this.collection.clear();
return self();
}
protected Collection<E> getCollection() {
return Collections.immutable(this.collection);
}
}

View File

@ -16,24 +16,22 @@
package io.jsonwebtoken.impl.lang; package io.jsonwebtoken.impl.lang;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.NestedCollection;
/** import java.util.Collection;
* {@code Registry} implementation that delegates all calls to an internal Registry instance.
*
* @param <K> Registry key type
* @param <V> Registry value type
* @since JJWT_RELEASE_VERSION
*/
public class DelegatingRegistry<K, V> extends DelegatingMap<K, V, Registry<K, V>> implements Registry<K, V> {
protected DelegatingRegistry(Registry<K, V> registry) { public class DefaultNestedCollection<E, P> extends DefaultCollectionMutator<E, NestedCollection<E, P>>
super(registry); implements NestedCollection<E, P> {
this.DELEGATE = Assert.notEmpty(registry, "Delegate registry cannot be null or empty.");
private final P parent;
public DefaultNestedCollection(P parent, Collection<? extends E> seed) {
super(seed);
this.parent = Assert.notNull(parent, "Parent cannot be null.");
} }
@Override @Override
public V forKey(K key) throws IllegalArgumentException { public P and() {
return DELEGATE.forKey(key); return this.parent;
} }
} }

View File

@ -84,4 +84,25 @@ public class DefaultRegistry<K, V> extends DelegatingMap<K, V, Map<K, V>> implem
public void clear() { public void clear() {
immutable(); immutable();
} }
@Override
public int hashCode() {
return DELEGATE.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj instanceof DefaultRegistry) {
DefaultRegistry<?, ?> other = (DefaultRegistry<?, ?>) obj;
return this.qualifiedKeyName.equals(other.qualifiedKeyName) &&
this.DELEGATE.equals(other.DELEGATE);
}
return false;
}
@Override
public String toString() {
return DELEGATE.toString();
}
} }

View File

@ -37,7 +37,18 @@ public class IdRegistry<T extends Identifiable> extends StringRegistry<T> {
} }
public IdRegistry(String name, Collection<T> instances) { public IdRegistry(String name, Collection<T> instances) {
this(name, instances, true); // Each registry requires CaSe-SeNsItIvE keys by default purpose - all JWA standard algorithm identifiers
// (JWS 'alg', JWE 'enc', JWK 'kty', etc) are all case-sensitive per via the following RFC language:
//
// This name is a case-sensitive ASCII string. Names may not match other registered names in a
// case-insensitive manner unless the Designated Experts state that there is a compelling reason to
// allow an exception.
//
// References:
// - JWS/JWE alg and JWE enc 'Algorithm Name': https://www.rfc-editor.org/rfc/rfc7518.html#section-7.1.1
// - JWE zip 'Compression Algorithm Value': https://www.rfc-editor.org/rfc/rfc7518.html#section-7.3.1
// - JWK '"kty" Parameter Value': https://www.rfc-editor.org/rfc/rfc7518.html#section-7.4.1
this(name, instances, true); // <---
} }
public IdRegistry(String name, Collection<T> instances, boolean caseSensitive) { public IdRegistry(String name, Collection<T> instances, boolean caseSensitive) {

View File

@ -15,12 +15,13 @@
*/ */
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DefaultNestedCollection;
import io.jsonwebtoken.impl.lang.DelegatingMapMutator; import io.jsonwebtoken.impl.lang.DelegatingMapMutator;
import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.impl.lang.Parameter; import io.jsonwebtoken.impl.lang.Parameter;
import io.jsonwebtoken.impl.lang.Parameters; import io.jsonwebtoken.impl.lang.Parameters;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.NestedCollection;
import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.security.HashAlgorithm; import io.jsonwebtoken.security.HashAlgorithm;
import io.jsonwebtoken.security.Jwk; import io.jsonwebtoken.security.Jwk;
@ -37,7 +38,6 @@ import java.security.Key;
import java.security.Provider; import java.security.Provider;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
abstract class AbstractJwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilder<K, J, T>> abstract class AbstractJwkBuilder<K extends Key, J extends Jwk<K>, T extends JwkBuilder<K, J, T>>
@ -108,18 +108,16 @@ abstract class AbstractJwkBuilder<K extends Key, J extends Jwk<K>, T extends Jwk
} }
@Override @Override
public T operation(KeyOperation operation) throws IllegalArgumentException { public NestedCollection<KeyOperation, T> operations() {
return operation != null ? operations(Collections.setOf(operation)) : self(); return new DefaultNestedCollection<KeyOperation, T>(self(), this.DELEGATE.getOperations()) {
} @Override
public T and() {
@Override Collection<? extends KeyOperation> c = getCollection();
public T operations(Collection<KeyOperation> ops) { opsPolicy.validate(c);
Set<KeyOperation> set = new LinkedHashSet<>(Collections.nullSafe(ops)); // new ones override existing ones DELEGATE.setOperations(c);
Set<KeyOperation> existing = Collections.nullSafe(this.DELEGATE.getOperations()); return super.and();
set.addAll(existing); }
this.opsPolicy.validate(set); };
this.DELEGATE.setOperations(set);
return self();
} }
@Override @Override

View File

@ -195,7 +195,7 @@ public class DefaultJwkContext<K extends Key> extends AbstractX509Context<JwkCon
} }
@Override @Override
public JwkContext<K> setOperations(Collection<KeyOperation> ops) { public JwkContext<K> setOperations(Collection<? extends KeyOperation> ops) {
put(AbstractJwk.KEY_OPS, ops); put(AbstractJwk.KEY_OPS, ops);
return this; return this;
} }

View File

@ -41,7 +41,7 @@ final class DefaultKeyOperationPolicy implements KeyOperationPolicy {
} }
@Override @Override
public void validate(Collection<KeyOperation> ops) { public void validate(Collection<? extends KeyOperation> ops) {
if (allowUnrelated || Collections.isEmpty(ops)) return; if (allowUnrelated || Collections.isEmpty(ops)) return;
for (KeyOperation operation : ops) { for (KeyOperation operation : ops) {
for (KeyOperation inner : ops) { for (KeyOperation inner : ops) {

View File

@ -15,24 +15,20 @@
*/ */
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.impl.lang.DefaultCollectionMutator;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.Jwks; import io.jsonwebtoken.security.Jwks;
import io.jsonwebtoken.security.KeyOperation; import io.jsonwebtoken.security.KeyOperation;
import io.jsonwebtoken.security.KeyOperationPolicy; import io.jsonwebtoken.security.KeyOperationPolicy;
import io.jsonwebtoken.security.KeyOperationPolicyBuilder; import io.jsonwebtoken.security.KeyOperationPolicyBuilder;
import java.util.Collection; public class DefaultKeyOperationPolicyBuilder extends DefaultCollectionMutator<KeyOperation, KeyOperationPolicyBuilder>
import java.util.LinkedHashMap; implements KeyOperationPolicyBuilder {
import java.util.Map;
public class DefaultKeyOperationPolicyBuilder implements KeyOperationPolicyBuilder {
private final Map<String, KeyOperation> ops;
private boolean unrelated = false; private boolean unrelated = false;
public DefaultKeyOperationPolicyBuilder() { public DefaultKeyOperationPolicyBuilder() {
this.ops = new LinkedHashMap<>(Jwks.OP.get()); super(Jwks.OP.get().values());
} }
@Override @Override
@ -41,28 +37,8 @@ public class DefaultKeyOperationPolicyBuilder implements KeyOperationPolicyBuild
return this; return this;
} }
@Override
public KeyOperationPolicyBuilder add(KeyOperation op) {
if (op != null) {
String id = Assert.hasText(op.getId(), "KeyOperation id cannot be null or empty.");
this.ops.remove(id);
this.ops.put(id, op);
}
return this;
}
@Override
public KeyOperationPolicyBuilder add(Collection<KeyOperation> ops) {
if (!Collections.isEmpty(ops)) {
for (KeyOperation op : ops) {
add(op);
}
}
return this;
}
@Override @Override
public KeyOperationPolicy build() { public KeyOperationPolicy build() {
return new DefaultKeyOperationPolicy(Collections.immutable(this.ops.values()), this.unrelated); return new DefaultKeyOperationPolicy(Collections.immutable(getCollection()), this.unrelated);
} }
} }

View File

@ -48,7 +48,7 @@ public interface JwkContext<K extends Key> extends Identifiable, Map<String, Obj
Set<KeyOperation> getOperations(); Set<KeyOperation> getOperations();
JwkContext<K> setOperations(Collection<KeyOperation> operations); JwkContext<K> setOperations(Collection<? extends KeyOperation> operations);
String getAlgorithm(); String getAlgorithm();

View File

@ -15,17 +15,16 @@
*/ */
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.Curve; import io.jsonwebtoken.security.Curve;
import java.security.Key; import java.security.Key;
public final class StandardCurves extends DelegatingRegistry<String, Curve> { public final class StandardCurves extends IdRegistry<Curve> {
public StandardCurves() { public StandardCurves() {
super(new IdRegistry<>("Elliptic Curve", Collections.<Curve>of( super("Elliptic Curve", Collections.<Curve>of(
ECCurve.P256, ECCurve.P256,
ECCurve.P384, ECCurve.P384,
ECCurve.P521, ECCurve.P521,
@ -33,7 +32,7 @@ public final class StandardCurves extends DelegatingRegistry<String, Curve> {
EdwardsCurve.X448, EdwardsCurve.X448,
EdwardsCurve.Ed25519, EdwardsCurve.Ed25519,
EdwardsCurve.Ed448 EdwardsCurve.Ed448
), false)); ));
} }
public static Curve findByKey(Key key) { public static Curve findByKey(Key key) {

View File

@ -15,21 +15,22 @@
*/ */
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.AeadAlgorithm;
@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.ENC @SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.ENC
public final class StandardEncryptionAlgorithms extends DelegatingRegistry<String, AeadAlgorithm> { public final class StandardEncryptionAlgorithms extends IdRegistry<AeadAlgorithm> {
public static final String NAME = "JWE Encryption Algorithm";
public StandardEncryptionAlgorithms() { public StandardEncryptionAlgorithms() {
super(new IdRegistry<>("JWE Encryption Algorithm", Collections.of( super(NAME, Collections.of(
(AeadAlgorithm) new HmacAesAeadAlgorithm(128), (AeadAlgorithm) new HmacAesAeadAlgorithm(128),
new HmacAesAeadAlgorithm(192), new HmacAesAeadAlgorithm(192),
new HmacAesAeadAlgorithm(256), new HmacAesAeadAlgorithm(256),
new GcmAesAeadAlgorithm(128), new GcmAesAeadAlgorithm(128),
new GcmAesAeadAlgorithm(192), new GcmAesAeadAlgorithm(192),
new GcmAesAeadAlgorithm(256)), false)); new GcmAesAeadAlgorithm(256)));
} }
} }

View File

@ -15,7 +15,6 @@
*/ */
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.HashAlgorithm; import io.jsonwebtoken.security.HashAlgorithm;
@ -26,10 +25,10 @@ import io.jsonwebtoken.security.HashAlgorithm;
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.security.Jwks.HASH @SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.security.Jwks.HASH
public class StandardHashAlgorithms extends DelegatingRegistry<String, HashAlgorithm> { public final class StandardHashAlgorithms extends IdRegistry<HashAlgorithm> {
public StandardHashAlgorithms() { public StandardHashAlgorithms() {
super(new IdRegistry<>("IANA Hash Algorithm", Collections.<HashAlgorithm>of( super("IANA Hash Algorithm", Collections.<HashAlgorithm>of(
// We don't include DefaultHashAlgorithm.SHA1 here on purpose because 1) it's not in the JWK IANA // We don't include DefaultHashAlgorithm.SHA1 here on purpose because 1) it's not in the JWK IANA
// registry so we don't need to expose it anyway, and 2) we don't want to expose a less-safe algorithm. // registry so we don't need to expose it anyway, and 2) we don't want to expose a less-safe algorithm.
// The SHA1 instance only exists in JJWT's codebase to support RFC-required `x5t` // The SHA1 instance only exists in JJWT's codebase to support RFC-required `x5t`
@ -40,6 +39,6 @@ public class StandardHashAlgorithms extends DelegatingRegistry<String, HashAlgor
new DefaultHashAlgorithm("sha3-256"), new DefaultHashAlgorithm("sha3-256"),
new DefaultHashAlgorithm("sha3-384"), new DefaultHashAlgorithm("sha3-384"),
new DefaultHashAlgorithm("sha3-512") new DefaultHashAlgorithm("sha3-512")
))); ));
} }
} }

View File

@ -15,7 +15,6 @@
*/ */
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.KeyAlgorithm;
@ -30,7 +29,9 @@ import java.security.spec.MGF1ParameterSpec;
* *
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
public final class StandardKeyAlgorithms extends DelegatingRegistry<String, KeyAlgorithm<?, ?>> { public final class StandardKeyAlgorithms extends IdRegistry<KeyAlgorithm<?, ?>> {
public static final String NAME = "JWE Key Management Algorithm";
private static final String RSA1_5_ID = "RSA1_5"; private static final String RSA1_5_ID = "RSA1_5";
private static final String RSA1_5_TRANSFORMATION = "RSA/ECB/PKCS1Padding"; private static final String RSA1_5_TRANSFORMATION = "RSA/ECB/PKCS1Padding";
@ -42,7 +43,7 @@ public final class StandardKeyAlgorithms extends DelegatingRegistry<String, KeyA
new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT); new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
public StandardKeyAlgorithms() { public StandardKeyAlgorithms() {
super(new IdRegistry<>("JWE Key Management Algorithm", Collections.<KeyAlgorithm<?, ?>>of( super(NAME, Collections.<KeyAlgorithm<?, ?>>of(
new DirectKeyAlgorithm(), new DirectKeyAlgorithm(),
new AesWrapKeyAlgorithm(128), new AesWrapKeyAlgorithm(128),
new AesWrapKeyAlgorithm(192), new AesWrapKeyAlgorithm(192),
@ -60,7 +61,7 @@ public final class StandardKeyAlgorithms extends DelegatingRegistry<String, KeyA
new DefaultRsaKeyAlgorithm(RSA1_5_ID, RSA1_5_TRANSFORMATION), new DefaultRsaKeyAlgorithm(RSA1_5_ID, RSA1_5_TRANSFORMATION),
new DefaultRsaKeyAlgorithm(RSA_OAEP_ID, RSA_OAEP_TRANSFORMATION), new DefaultRsaKeyAlgorithm(RSA_OAEP_ID, RSA_OAEP_TRANSFORMATION),
new DefaultRsaKeyAlgorithm(RSA_OAEP_256_ID, RSA_OAEP_256_TRANSFORMATION, RSA_OAEP_256_SPEC) new DefaultRsaKeyAlgorithm(RSA_OAEP_256_ID, RSA_OAEP_256_TRANSFORMATION, RSA_OAEP_256_SPEC)
), false)); ));
} }
/* /*

View File

@ -15,15 +15,14 @@
*/ */
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.KeyOperation; import io.jsonwebtoken.security.KeyOperation;
public class StandardKeyOperations extends DelegatingRegistry<String, KeyOperation> { public final class StandardKeyOperations extends IdRegistry<KeyOperation> {
public StandardKeyOperations() { public StandardKeyOperations() {
super(new IdRegistry<>("JSON Web Key Operation", Collections.of( super("JSON Web Key Operation", Collections.of(
DefaultKeyOperation.SIGN, DefaultKeyOperation.SIGN,
DefaultKeyOperation.VERIFY, DefaultKeyOperation.VERIFY,
DefaultKeyOperation.ENCRYPT, DefaultKeyOperation.ENCRYPT,
@ -32,6 +31,6 @@ public class StandardKeyOperations extends DelegatingRegistry<String, KeyOperati
DefaultKeyOperation.UNWRAP, DefaultKeyOperation.UNWRAP,
DefaultKeyOperation.DERIVE_KEY, DefaultKeyOperation.DERIVE_KEY,
DefaultKeyOperation.DERIVE_BITS DefaultKeyOperation.DERIVE_BITS
))); ));
} }
} }

View File

@ -15,7 +15,6 @@
*/ */
package io.jsonwebtoken.impl.security; package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry; import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.Password; import io.jsonwebtoken.security.Password;
@ -26,10 +25,12 @@ import java.security.Key;
import java.security.PrivateKey; import java.security.PrivateKey;
@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.SIG @SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.SIG
public final class StandardSecureDigestAlgorithms extends DelegatingRegistry<String, SecureDigestAlgorithm<?, ?>> { public final class StandardSecureDigestAlgorithms extends IdRegistry<SecureDigestAlgorithm<?, ?>> {
public static final String NAME = "JWS Digital Signature or MAC";
public StandardSecureDigestAlgorithms() { public StandardSecureDigestAlgorithms() {
super(new IdRegistry<>("JWS Digital Signature or MAC", Collections.of( super(NAME, Collections.of(
NoneSignatureAlgorithm.INSTANCE, NoneSignatureAlgorithm.INSTANCE,
DefaultMacAlgorithm.HS256, DefaultMacAlgorithm.HS256,
DefaultMacAlgorithm.HS384, DefaultMacAlgorithm.HS384,
@ -44,7 +45,7 @@ public final class StandardSecureDigestAlgorithms extends DelegatingRegistry<Str
EcSignatureAlgorithm.ES384, EcSignatureAlgorithm.ES384,
EcSignatureAlgorithm.ES512, EcSignatureAlgorithm.ES512,
EdSignatureAlgorithm.INSTANCE EdSignatureAlgorithm.INSTANCE
), false)); ));
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@ -1131,7 +1131,7 @@ class JwtParserTest {
def one = 'one' def one = 'one'
def two = 'two' def two = 'two'
def expected = [one, two] def expected = [one, two]
String jwt = Jwts.builder().audience(one).audience(two).compact() String jwt = Jwts.builder().audience().add(one).add(two).and().compact()
def aud = Jwts.parser().unsecured().requireAudience(one).requireAudience(two).build() def aud = Jwts.parser().unsecured().requireAudience(one).requireAudience(two).build()
.parseClaimsJwt(jwt).getPayload().getAudience() .parseClaimsJwt(jwt).getPayload().getAudience()
assertEquals expected.size(), aud.size() assertEquals expected.size(), aud.size()
@ -1142,7 +1142,7 @@ class JwtParserTest {
void testParseAtLeastOneAudiences() { void testParseAtLeastOneAudiences() {
def one = 'one' def one = 'one'
String jwt = Jwts.builder().audience(one).audience('two').compact() // more audiences than required String jwt = Jwts.builder().audience().add(one).add('two').and().compact() // more audiences than required
def aud = Jwts.parser().unsecured().requireAudience(one) // require only one def aud = Jwts.parser().unsecured().requireAudience(one) // require only one
.build().parseClaimsJwt(jwt).getPayload().getAudience() .build().parseClaimsJwt(jwt).getPayload().getAudience()

View File

@ -1055,37 +1055,12 @@ class JwtsTest {
def realAlg = Jwts.SIG.HS256 // any alg will do, we're going to wrap it def realAlg = Jwts.SIG.HS256 // any alg will do, we're going to wrap it
def key = TestKeys.HS256 def key = TestKeys.HS256
def id = realAlg.getId() + 'X' // custom id def id = realAlg.getId() + 'X' // custom id
def alg = new MacAlgorithm() { def alg = new TestMacAlgorithm(id: id, delegate: realAlg)
@Override
SecretKeyBuilder key() {
return realAlg.key()
}
@Override
int getKeyBitLength() {
return realAlg.keyBitLength
}
@Override
byte[] digest(SecureRequest<InputStream, SecretKey> request) {
return realAlg.digest(request)
}
@Override
boolean verify(VerifySecureDigestRequest<SecretKey> request) {
return realAlg.verify(request)
}
@Override
String getId() {
return id
}
}
def jws = Jwts.builder().setSubject("joe").signWith(key, alg).compact() def jws = Jwts.builder().setSubject("joe").signWith(key, alg).compact()
assertEquals 'joe', Jwts.parser() assertEquals 'joe', Jwts.parser()
.addSignatureAlgorithms([alg]) .sig().add(alg).and()
.setSigningKey(key) .setSigningKey(key)
.build() .build()
.parseClaimsJws(jws).payload.getSubject() .parseClaimsJws(jws).payload.getSubject()
@ -1129,7 +1104,7 @@ class JwtsTest {
def jwe = Jwts.builder().setSubject("joe").encryptWith(key, encAlg).compact() def jwe = Jwts.builder().setSubject("joe").encryptWith(key, encAlg).compact()
assertEquals 'joe', Jwts.parser() assertEquals 'joe', Jwts.parser()
.addEncryptionAlgorithms([encAlg]) .enc().add(encAlg).and()
.decryptWith(key) .decryptWith(key)
.build() .build()
.parseClaimsJwe(jwe).payload.getSubject() .parseClaimsJwe(jwe).payload.getSubject()
@ -1168,7 +1143,7 @@ class JwtsTest {
try { try {
Jwts.parser() Jwts.parser()
.keyLocator(new ConstantKeyLocator(TestKeys.HS256, TestKeys.A128GCM)) .keyLocator(new ConstantKeyLocator(TestKeys.HS256, TestKeys.A128GCM))
.addKeyAlgorithms([badKeyAlg]) // <-- add bad alg here .key().add(badKeyAlg).and() // <-- add bad alg here
.build() .build()
.parseClaimsJwe(compact) .parseClaimsJwe(compact)
fail() fail()

View File

@ -73,7 +73,7 @@ class RFC7797Test {
InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content)) InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content))
for (def payload : [content, asByteInputStream, asBufferedInputStream]) { for (def payload : [content, asByteInputStream, asBufferedInputStream]) {
def parser = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build() def parser = Jwts.parser().verifyWith(key).build()
def jws def jws
if (payload instanceof byte[]) { if (payload instanceof byte[]) {
jws = parser.parseContentJws(s, (byte[]) payload) jws = parser.parseContentJws(s, (byte[]) payload)
@ -111,7 +111,7 @@ class RFC7797Test {
InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content)) InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content))
for (def payload : [content, asByteInputStream, asBufferedInputStream]) { for (def payload : [content, asByteInputStream, asBufferedInputStream]) {
def parser = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build() def parser = Jwts.parser().verifyWith(key).build()
def jws def jws
if (payload instanceof byte[]) { if (payload instanceof byte[]) {
jws = parser.parseClaimsJws(s, (byte[]) payload) jws = parser.parseClaimsJws(s, (byte[]) payload)
@ -137,7 +137,7 @@ class RFC7797Test {
InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content)) InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(content))
for (def payload : [content, asByteInputStream, asBufferedInputStream]) { for (def payload : [content, asByteInputStream, asBufferedInputStream]) {
def parser = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build() def parser = Jwts.parser().verifyWith(key).build()
def jws def jws
if (payload instanceof byte[]) { if (payload instanceof byte[]) {
jws = parser.parseContentJws(s, (byte[]) payload) jws = parser.parseContentJws(s, (byte[]) payload)
@ -176,8 +176,7 @@ class RFC7797Test {
String compact = Jwts.builder().content(stream).signWith(key).encodePayload(false).compact() String compact = Jwts.builder().content(stream).signWith(key).encodePayload(false).compact()
// signature still verified: // signature still verified:
def jwt = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id) def jwt = Jwts.parser().verifyWith(key).build().parseContentJws(compact, data)
.build().parseContentJws(compact, data)
assertEquals 'HS256', jwt.header.getAlgorithm() assertEquals 'HS256', jwt.header.getAlgorithm()
assertEquals s, Strings.utf8(jwt.getPayload()) assertEquals s, Strings.utf8(jwt.getPayload())
} }
@ -191,8 +190,7 @@ class RFC7797Test {
String s = Jwts.builder().signWith(key).content(payload).encodePayload(false).compact() String s = Jwts.builder().signWith(key).content(payload).encodePayload(false).compact()
def jws = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id) def jws = Jwts.parser().verifyWith(key).build().parseClaimsJws(s, payload)
.build().parseClaimsJws(s, payload)
assertEquals 'me', jws.getPayload().getSubject() assertEquals 'me', jws.getPayload().getSubject()
} }
@ -232,8 +230,7 @@ class RFC7797Test {
@Test @Test
void testParseContentWithEmptyBytesPayload() { void testParseContentWithEmptyBytesPayload() {
try { try {
Jwts.parser().verifyWith(TestKeys.HS256).critical(DefaultJwsHeader.B64.id).build() Jwts.parser().verifyWith(TestKeys.HS256).build().parseContentJws('whatever', Bytes.EMPTY) // <-- empty
.parseContentJws('whatever', Bytes.EMPTY) // <-- empty
fail() fail()
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
String msg = 'unencodedPayload argument cannot be null or empty.' String msg = 'unencodedPayload argument cannot be null or empty.'
@ -244,8 +241,7 @@ class RFC7797Test {
@Test @Test
void testParseClaimsWithEmptyBytesPayload() { void testParseClaimsWithEmptyBytesPayload() {
try { try {
Jwts.parser().verifyWith(TestKeys.HS256).critical(DefaultJwsHeader.B64.id).build() Jwts.parser().verifyWith(TestKeys.HS256).build().parseClaimsJws('whatever', Bytes.EMPTY) // <-- empty
.parseClaimsJws('whatever', Bytes.EMPTY) // <-- empty
fail() fail()
} catch (IllegalArgumentException expected) { } catch (IllegalArgumentException expected) {
String msg = 'unencodedPayload argument cannot be null or empty.' String msg = 'unencodedPayload argument cannot be null or empty.'
@ -266,7 +262,7 @@ class RFC7797Test {
// try to parse it as a 'normal' JWS (without supplying the payload): // try to parse it as a 'normal' JWS (without supplying the payload):
try { try {
Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build() Jwts.parser().verifyWith(key).critical().add(DefaultJwsHeader.B64.id).and().build()
.parseContentJws(s) // <-- no payload supplied .parseContentJws(s) // <-- no payload supplied
fail() fail()
} catch (io.jsonwebtoken.security.SignatureException expected) { } catch (io.jsonwebtoken.security.SignatureException expected) {
@ -289,7 +285,7 @@ class RFC7797Test {
// try to parse it as a 'normal' JWS (without supplying the payload): // try to parse it as a 'normal' JWS (without supplying the payload):
try { try {
Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build() Jwts.parser().verifyWith(key).critical().add(DefaultJwsHeader.B64.id).and().build()
.parseClaimsJws(s) // <-- no payload supplied .parseClaimsJws(s) // <-- no payload supplied
fail() fail()
} catch (io.jsonwebtoken.security.SignatureException expected) { } catch (io.jsonwebtoken.security.SignatureException expected) {
@ -313,7 +309,7 @@ class RFC7797Test {
// create a non-detached unencoded JWS: // create a non-detached unencoded JWS:
String s = Jwts.builder().signWith(key).content(payload).encodePayload(false).compact() String s = Jwts.builder().signWith(key).content(payload).encodePayload(false).compact()
def jws = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build() def jws = Jwts.parser().verifyWith(key).critical().add(DefaultJwsHeader.B64.id).and().build()
.parseContentJws(s) // <--- parse normally, without calling parseContentJws(s, unencodedPayload) .parseContentJws(s) // <--- parse normally, without calling parseContentJws(s, unencodedPayload)
assertArrayEquals Strings.utf8(payload), jws.getPayload() assertArrayEquals Strings.utf8(payload), jws.getPayload()
@ -327,7 +323,7 @@ class RFC7797Test {
// create a non-detached unencoded JWS: // create a non-detached unencoded JWS:
String s = Jwts.builder().signWith(key).subject('me').encodePayload(false).compact() String s = Jwts.builder().signWith(key).subject('me').encodePayload(false).compact()
def jws = Jwts.parser().verifyWith(key).critical(DefaultJwsHeader.B64.id).build() def jws = Jwts.parser().verifyWith(key).critical().add(DefaultJwsHeader.B64.id).and().build()
.parseClaimsJws(s) // <--- parse normally, without calling parseClaimsJws(s, unencodedPayload) .parseClaimsJws(s) // <--- parse normally, without calling parseClaimsJws(s, unencodedPayload)
assertEquals 'me', jws.getPayload().getSubject() assertEquals 'me', jws.getPayload().getSubject()
@ -356,7 +352,7 @@ class RFC7797Test {
InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(compressed)) InputStream asBufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(compressed))
for (def payload : [compressed, asByteInputStream, asBufferedInputStream]) { for (def payload : [compressed, asByteInputStream, asBufferedInputStream]) {
def parser = Jwts.parser().verifyWith(key).critical('b64').build() def parser = Jwts.parser().verifyWith(key).build()
def jws def jws
if (payload instanceof byte[]) { if (payload instanceof byte[]) {
jws = parser.parseContentJws(s, (byte[]) payload) jws = parser.parseContentJws(s, (byte[]) payload)
@ -396,7 +392,7 @@ class RFC7797Test {
// now try to parse a compressed unencoded using a signing key resolver: // now try to parse a compressed unencoded using a signing key resolver:
try { try {
Jwts.parser().critical(DefaultJwsHeader.B64.id) // enable b64 extension Jwts.parser()
.setSigningKeyResolver(new SigningKeyResolverAdapter() { .setSigningKeyResolver(new SigningKeyResolverAdapter() {
@Override @Override
Key resolveSigningKey(JwsHeader header, byte[] content) { Key resolveSigningKey(JwsHeader header, byte[] content) {

View File

@ -214,7 +214,7 @@ class AbstractProtectedHeaderTest {
void testCritical() { void testCritical() {
Set<String> crits = Collections.setOf('foo', 'bar') Set<String> crits = Collections.setOf('foo', 'bar')
def header = Jwts.header().add('alg', 'HS256').add('foo', 'value1').add('bar', 'value2') def header = Jwts.header().add('alg', 'HS256').add('foo', 'value1').add('bar', 'value2')
.critical(crits).build() as DefaultProtectedHeader .critical().add(crits).and().build() as DefaultProtectedHeader
assertEquals crits, header.getCritical() assertEquals crits, header.getCritical()
} }

View File

@ -590,11 +590,12 @@ class DefaultJwtBuilderTest {
} }
} }
@SuppressWarnings('GrDeprecatedAPIUsage')
@Test @Test
void testAudienceSingle() { void testAudienceSingle() {
def key = TestKeys.HS256 def key = TestKeys.HS256
String audienceSingleString = 'test' String audienceSingleString = 'test'
def jwt = builder.audienceSingle(audienceSingleString).compact() def jwt = builder.audience().single(audienceSingleString).compact()
// can't use the parser here to validate because it coerces the string value into an array automatically, // can't use the parser here to validate because it coerces the string value into an array automatically,
// so we need to check the raw payload: // so we need to check the raw payload:
def encoded = new JwtTokenizer().tokenize(jwt).getPayload() def encoded = new JwtTokenizer().tokenize(jwt).getPayload()
@ -612,7 +613,8 @@ class DefaultJwtBuilderTest {
void testAudienceSingleMultiple() { void testAudienceSingleMultiple() {
def first = 'first' def first = 'first'
def second = 'second' def second = 'second'
def jwt = builder.audienceSingle(first).audienceSingle(second).compact() //noinspection GrDeprecatedAPIUsage
def jwt = builder.audience().single(first).audience().single(second).compact()
// can't use the parser here to validate because it coerces the string value into an array automatically, // can't use the parser here to validate because it coerces the string value into an array automatically,
// so we need to check the raw payload: // so we need to check the raw payload:
def encoded = new JwtTokenizer().tokenize(jwt).getPayload() def encoded = new JwtTokenizer().tokenize(jwt).getPayload()
@ -629,8 +631,8 @@ class DefaultJwtBuilderTest {
@Test @Test
void testAudienceSingleThenNull() { void testAudienceSingleThenNull() {
def jwt = builder.id('test') def jwt = builder.id('test')
.audienceSingle('single') // set one .audience().single('single') // set one
.audienceSingle(null) // remove it entirely .audience().single(null) // remove it entirely
.compact() .compact()
// shouldn't be an audience at all: // shouldn't be an audience at all:
@ -646,9 +648,10 @@ class DefaultJwtBuilderTest {
def first = 'first' def first = 'first'
def second = 'second' def second = 'second'
def expected = [first, second] as Set<String> def expected = [first, second] as Set<String>
def jwt = builder.audienceSingle(first) // sets single value def jwt = builder
.audienceSingle(null) // removes entirely .audience().single(first) // sets single value
.audience([first, second]) // sets collection .audience().single(null) // removes entirely
.audience().add([first, second]).and() // sets collection
.compact() .compact()
def aud = Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience() def aud = Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
@ -659,11 +662,12 @@ class DefaultJwtBuilderTest {
* Test to ensure that if we receive a JWT with a single string value, that the parser coerces it to a String array * Test to ensure that if we receive a JWT with a single string value, that the parser coerces it to a String array
* so we don't have to worry about different data types: * so we don't have to worry about different data types:
*/ */
@SuppressWarnings('GrDeprecatedAPIUsage')
@Test @Test
void testParseAudienceSingle() { void testParseAudienceSingle() {
def key = TestKeys.HS256 def key = TestKeys.HS256
String audienceSingleString = 'test' String audienceSingleString = 'test'
def jwt = builder.audienceSingle(audienceSingleString).compact() def jwt = builder.audience().single(audienceSingleString).compact()
assertEquals audienceSingleString, Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload assertEquals audienceSingleString, Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload
.getAudience().iterator().next() // a collection, not a single string .getAudience().iterator().next() // a collection, not a single string
@ -672,19 +676,19 @@ class DefaultJwtBuilderTest {
@Test @Test
void testAudience() { void testAudience() {
def aud = 'fubar' def aud = 'fubar'
def jwt = Jwts.builder().audience(aud).compact() def jwt = Jwts.builder().audience().add(aud).and().compact()
assertEquals aud, Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience().iterator().next() assertEquals aud, Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience().iterator().next()
} }
@Test @Test
void testAudienceNullString() { void testAudienceNullString() {
def jwt = Jwts.builder().subject('me').audience(null).compact() def jwt = Jwts.builder().subject('me').audience().add(null).and().compact()
assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience() assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
} }
@Test @Test
void testAudienceEmptyString() { void testAudienceEmptyString() {
def jwt = Jwts.builder().subject('me').audience(' ').compact() def jwt = Jwts.builder().subject('me').audience().add(' ').and().compact()
assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience() assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
} }
@ -692,7 +696,7 @@ class DefaultJwtBuilderTest {
void testAudienceMultipleTimes() { void testAudienceMultipleTimes() {
def one = 'one' def one = 'one'
def two = 'two' def two = 'two'
def jwt = Jwts.builder().audience(one).audience(two).compact() def jwt = Jwts.builder().audience().add(one).add(two).and().compact()
def aud = Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience() def aud = Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
assertTrue aud.contains(one) assertTrue aud.contains(one)
assertTrue aud.contains(two) assertTrue aud.contains(two)
@ -701,14 +705,14 @@ class DefaultJwtBuilderTest {
@Test @Test
void testAudienceNullCollection() { void testAudienceNullCollection() {
Collection c = null Collection c = null
def jwt = Jwts.builder().subject('me').audience(c).compact() def jwt = Jwts.builder().subject('me').audience().add(c).and().compact()
assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience() assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
} }
@Test @Test
void testAudienceEmptyCollection() { void testAudienceEmptyCollection() {
Collection c = new ArrayList() Collection c = new ArrayList()
def jwt = Jwts.builder().subject('me').audience(c).compact() def jwt = Jwts.builder().subject('me').audience().add(c).and().compact()
assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience() assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
} }
@ -716,7 +720,7 @@ class DefaultJwtBuilderTest {
void testAudienceCollectionWithNullElement() { void testAudienceCollectionWithNullElement() {
Collection c = new ArrayList() Collection c = new ArrayList()
c.add(null) c.add(null)
def jwt = Jwts.builder().subject('me').audience(c).compact() def jwt = Jwts.builder().subject('me').audience().add(c).and().compact()
assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience() assertNull Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
} }
@ -728,7 +732,8 @@ class DefaultJwtBuilderTest {
void testAudienceSingleThenAudience() { void testAudienceSingleThenAudience() {
def one = 'one' def one = 'one'
def two = 'two' def two = 'two'
def jwt = Jwts.builder().audienceSingle(one).audience(two).compact() //noinspection GrDeprecatedAPIUsage
def jwt = Jwts.builder().audience().single(one).audience().add(two).and().compact()
def aud = Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience() def aud = Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
assertTrue aud.contains(one) assertTrue aud.contains(one)
assertTrue aud.contains(two) assertTrue aud.contains(two)
@ -743,7 +748,8 @@ class DefaultJwtBuilderTest {
void testAudienceThenAudienceSingle() { void testAudienceThenAudienceSingle() {
def one = 'one' def one = 'one'
def two = 'two' def two = 'two'
def jwt = Jwts.builder().audience(one).audienceSingle(two).compact() //noinspection GrDeprecatedAPIUsage
def jwt = Jwts.builder().audience().add(one).and().audience().single(two).compact()
// can't use the parser here to validate because it coerces the string value into an array automatically, // can't use the parser here to validate because it coerces the string value into an array automatically,
// so we need to check the raw payload: // so we need to check the raw payload:
@ -763,7 +769,8 @@ class DefaultJwtBuilderTest {
def single = 'one' def single = 'one'
def collection = ['two', 'three'] as Set<String> def collection = ['two', 'three'] as Set<String>
def expected = ['one', 'two', 'three'] as Set<String> def expected = ['one', 'two', 'three'] as Set<String>
def jwt = Jwts.builder().audienceSingle(single).audience(collection).compact() //noinspection GrDeprecatedAPIUsage
def jwt = Jwts.builder().audience().single(single).audience().add(collection).and().compact()
def aud = Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience() def aud = Jwts.parser().unsecured().build().parseClaimsJwt(jwt).payload.getAudience()
assertEquals expected.size(), aud.size() assertEquals expected.size(), aud.size()
assertTrue aud.contains(single) && aud.containsAll(collection) assertTrue aud.contains(single) && aud.containsAll(collection)
@ -779,7 +786,7 @@ class DefaultJwtBuilderTest {
def one = 'one' def one = 'one'
def two = 'two' def two = 'two'
def three = 'three' def three = 'three'
def jwt = Jwts.builder().audience([one, two]).audienceSingle(three).compact() def jwt = Jwts.builder().audience().add([one, two]).and().audience().single(three).compact()
// can't use the parser here to validate because it coerces the string value into an array automatically, // can't use the parser here to validate because it coerces the string value into an array automatically,
// so we need to check the raw payload: // so we need to check the raw payload:

View File

@ -325,7 +325,7 @@ class DefaultJwtHeaderBuilderTest {
@Test @Test
void testCritical() { void testCritical() {
def crit = ['foo'] as Set<String> def crit = ['foo'] as Set<String>
header = jws().add('foo', 'bar').critical(crit).build() as JwsHeader header = jws().add('foo', 'bar').critical().add(crit).and().build() as JwsHeader
assertTrue header instanceof JwsHeader assertTrue header instanceof JwsHeader
assertFalse header instanceof JweHeader assertFalse header instanceof JweHeader
assertEquals crit, header.getCritical() assertEquals crit, header.getCritical()
@ -502,7 +502,7 @@ class DefaultJwtHeaderBuilderTest {
@Test @Test
void testCritSingle() { void testCritSingle() {
def crit = 'test' def crit = 'test'
def header = jws().add(crit, 'foo').critical(crit).build() as ProtectedHeader def header = jws().add(crit, 'foo').critical().add(crit).and().build() as ProtectedHeader
def expected = [crit] as Set<String> def expected = [crit] as Set<String>
assertEquals expected, header.getCritical() assertEquals expected, header.getCritical()
} }
@ -511,34 +511,34 @@ class DefaultJwtHeaderBuilderTest {
void testCritSingleNullIgnored() { void testCritSingleNullIgnored() {
def crit = 'test' def crit = 'test'
def expected = [crit] as Set<String> def expected = [crit] as Set<String>
def header = jws().add(crit, 'foo').critical(crit).build() as ProtectedHeader def header = jws().add(crit, 'foo').critical().add(crit).and().build() as ProtectedHeader
assertEquals expected, header.getCritical() assertEquals expected, header.getCritical()
header = builder.critical((String) null).build() as ProtectedHeader // ignored header = builder.critical().add((String) null).and().build() as ProtectedHeader // ignored
assertEquals expected, header.getCritical() // nothing changed assertEquals expected, header.getCritical() // nothing changed
} }
@Test @Test
void testCritNullCollectionIgnored() { void testCritNullCollectionIgnored() {
def crit = ['test'] as Set<String> def crit = ['test'] as Set<String>
def header = jws().add('test', 'foo').critical(crit).build() as ProtectedHeader def header = jws().add('test', 'foo').critical().add(crit).and().build() as ProtectedHeader
assertEquals crit, header.getCritical() assertEquals crit, header.getCritical()
header = builder.critical((Collection) null).build() as ProtectedHeader header = builder.critical().add((Collection) null).and().build() as ProtectedHeader
assertEquals crit, header.getCritical() // nothing changed assertEquals crit, header.getCritical() // nothing changed
} }
@Test @Test
void testCritCollectionWithNullElement() { void testCritCollectionWithNullElement() {
def crit = [null] as Set<String> def crit = [null] as Set<String>
def header = jws().add('test', 'foo').critical(crit).build() as ProtectedHeader def header = jws().add('test', 'foo').critical().add(crit).and().build() as ProtectedHeader
assertNull header.getCritical() assertNull header.getCritical()
} }
@Test @Test
void testCritEmptyIgnored() { void testCritEmptyIgnored() {
def crit = ['test'] as Set<String> def crit = ['test'] as Set<String>
def header = jws().add('test', 'foo').critical(crit).build() as ProtectedHeader ProtectedHeader header = jws().add('test', 'foo').critical().add(crit).and().build() as ProtectedHeader
assertEquals crit, header.getCritical() assertEquals crit, header.getCritical()
header = builder.critical([] as Set<String>).build() header = builder.critical().add([] as Set<String>).and().build() as ProtectedHeader
assertEquals crit, header.getCritical() // ignored assertEquals crit, header.getCritical() // ignored
} }
@ -550,7 +550,7 @@ class DefaultJwtHeaderBuilderTest {
void testCritRemovedForUnprotectedHeader() { void testCritRemovedForUnprotectedHeader() {
def crit = Collections.setOf('foo', 'bar') def crit = Collections.setOf('foo', 'bar')
// no JWS or JWE params specified: // no JWS or JWE params specified:
def header = builder.add('test', 'value').critical(crit).build() def header = builder.add('test', 'value').critical().add(crit).and().build()
assertFalse header.containsKey(DefaultProtectedHeader.CRIT.getId()) assertFalse header.containsKey(DefaultProtectedHeader.CRIT.getId())
} }
@ -562,7 +562,7 @@ class DefaultJwtHeaderBuilderTest {
void testCritNamesSanitizedWhenHeaderMissingCorrespondingParameter() { void testCritNamesSanitizedWhenHeaderMissingCorrespondingParameter() {
def critGiven = ['foo', 'bar'] as Set<String> def critGiven = ['foo', 'bar'] as Set<String>
def critExpected = ['foo'] as Set<String> def critExpected = ['foo'] as Set<String>
def header = jws().add('foo', 'fooVal').critical(critGiven).build() as ProtectedHeader def header = jws().add('foo', 'fooVal').critical().add(critGiven).and().build() as ProtectedHeader
// header didn't set the 'bar' parameter, so 'bar' should not be in the crit values: // header didn't set the 'bar' parameter, so 'bar' should not be in the crit values:
assertEquals critExpected, header.getCritical() assertEquals critExpected, header.getCritical()
} }
@ -570,7 +570,7 @@ class DefaultJwtHeaderBuilderTest {
@Test @Test
void testCritNamesRemovedWhenHeaderMissingCorrespondingParameter() { void testCritNamesRemovedWhenHeaderMissingCorrespondingParameter() {
def critGiven = ['foo'] as Set<String> def critGiven = ['foo'] as Set<String>
def header = jws().critical(critGiven).build() as ProtectedHeader ProtectedHeader header = jws().critical().add(critGiven).and().build() as ProtectedHeader
// header didn't set the 'foo' parameter, so crit would have been empty, and then removed from the header: // header didn't set the 'foo' parameter, so crit would have been empty, and then removed from the header:
assertNull header.getCritical() assertNull header.getCritical()
} }

View File

@ -17,15 +17,12 @@ package io.jsonwebtoken.impl
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import io.jsonwebtoken.* import io.jsonwebtoken.*
import io.jsonwebtoken.impl.security.ConstantKeyLocator import io.jsonwebtoken.impl.security.*
import io.jsonwebtoken.impl.security.LocatingKeyResolver
import io.jsonwebtoken.impl.security.TestKeys
import io.jsonwebtoken.io.* import io.jsonwebtoken.io.*
import io.jsonwebtoken.security.* import io.jsonwebtoken.security.InvalidKeyException
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import javax.crypto.SecretKey
import java.security.Provider import java.security.Provider
import static org.easymock.EasyMock.* import static org.easymock.EasyMock.*
@ -47,7 +44,7 @@ class DefaultJwtParserBuilderTest {
@Test @Test
void testCriticalEmtpy() { void testCriticalEmtpy() {
builder.critical(' ') // shouldn't modify the set builder.critical().add(' ').and() // shouldn't modify the set
assertTrue builder.@critical.isEmpty() assertTrue builder.@critical.isEmpty()
} }
@ -165,26 +162,26 @@ class DefaultJwtParserBuilderTest {
} }
} }
def parser = builder.setCompressionCodecResolver(resolver).build() def parser = builder.setCompressionCodecResolver(resolver).build()
assertSame resolver, parser.zipAlgFn.resolver assertSame resolver, parser.zipAlgs.resolver
} }
@Test @Test
void testAddCompressionAlgorithms() { void testAddCompressionAlgorithms() {
def codec = new TestCompressionCodec(id: 'test') def codec = new TestCompressionCodec(id: 'test')
def parser = builder.addCompressionAlgorithms([codec] as Set<CompressionCodec>).build() def parser = builder.zip().add(codec).and().build()
def header = Jwts.header().add('zip', codec.getId()).build() def header = Jwts.header().add('zip', codec.getId()).build()
assertSame codec, parser.zipAlgFn.locate(header) assertSame codec, parser.zipAlgs.locate(header)
} }
@Test @Test
void testAddCompressionAlgorithmsOverrideDefaults() { void testAddCompressionAlgorithmsOverrideDefaults() {
def header = Jwts.header().add('zip', 'DEF').build() def header = Jwts.header().add('zip', 'DEF').build()
def parser = builder.build() def parser = builder.build()
assertSame Jwts.ZIP.DEF, parser.zipAlgFn.apply(header) // standard implementation default assertSame Jwts.ZIP.DEF, parser.zipAlgs.apply(header) // standard implementation default
def alg = new TestCompressionCodec(id: 'DEF') // custom impl with standard identifier def alg = new TestCompressionCodec(id: 'DEF') // custom impl with standard identifier
parser = builder.addCompressionAlgorithms([alg]).build() parser = builder.zip().add(alg).and().build()
assertSame alg, parser.zipAlgFn.apply(header) // custom one, not standard impl assertSame alg, parser.zipAlgs.apply(header) // custom one, not standard impl
} }
@Test @Test
@ -192,9 +189,9 @@ class DefaultJwtParserBuilderTest {
def standard = Jwts.header().add('zip', 'DEF').build() def standard = Jwts.header().add('zip', 'DEF').build()
def nonStandard = Jwts.header().add('zip', 'def').build() def nonStandard = Jwts.header().add('zip', 'def').build()
def parser = builder.build() def parser = builder.build()
assertSame Jwts.ZIP.DEF, parser.zipAlgFn.apply(standard) // standard implementation default assertSame Jwts.ZIP.DEF, parser.zipAlgs.apply(standard) // standard implementation default
try { try {
parser.zipAlgFn.apply(nonStandard) parser.zipAlgs.apply(nonStandard)
fail() fail()
} catch (UnsupportedJwtException e) { } catch (UnsupportedJwtException e) {
String msg = "Unrecognized JWT ${DefaultHeader.COMPRESSION_ALGORITHM} header value: def" String msg = "Unrecognized JWT ${DefaultHeader.COMPRESSION_ALGORITHM} header value: def"
@ -207,11 +204,11 @@ class DefaultJwtParserBuilderTest {
final String standardId = Jwts.ENC.A256GCM.getId() final String standardId = Jwts.ENC.A256GCM.getId()
def header = Jwts.header().add('enc', standardId).build() def header = Jwts.header().add('enc', standardId).build()
def parser = builder.build() def parser = builder.build()
assertSame Jwts.ENC.A256GCM, parser.encAlgFn.apply(header) // standard implementation default assertSame Jwts.ENC.A256GCM, parser.encAlgs.apply(header) // standard implementation default
def custom = new TestAeadAlgorithm(id: standardId) // custom impl with standard identifier def custom = new TestAeadAlgorithm(id: standardId) // custom impl with standard identifier
parser = builder.addEncryptionAlgorithms([custom]).build() parser = builder.enc().add(custom).and().build()
assertSame custom, parser.encAlgFn.apply(header) // custom one, not standard impl assertSame custom, parser.encAlgs.apply(header) // custom one, not standard impl
} }
@Test @Test
@ -220,9 +217,9 @@ class DefaultJwtParserBuilderTest {
def standard = Jwts.header().add('alg', 'foo').add('enc', alg.id).build() def standard = Jwts.header().add('alg', 'foo').add('enc', alg.id).build()
def nonStandard = Jwts.header().add('alg', 'foo').add('enc', alg.id.toLowerCase()).build() def nonStandard = Jwts.header().add('alg', 'foo').add('enc', alg.id.toLowerCase()).build()
def parser = builder.build() def parser = builder.build()
assertSame alg, parser.encAlgFn.apply(standard) // standard id assertSame alg, parser.encAlgs.apply(standard) // standard id
try { try {
parser.encAlgFn.apply(nonStandard) // non-standard id parser.encAlgs.apply(nonStandard) // non-standard id
fail() fail()
} catch (UnsupportedJwtException e) { } catch (UnsupportedJwtException e) {
String msg = "Unrecognized JWE ${DefaultJweHeader.ENCRYPTION_ALGORITHM} header value: ${alg.id.toLowerCase()}" String msg = "Unrecognized JWE ${DefaultJweHeader.ENCRYPTION_ALGORITHM} header value: ${alg.id.toLowerCase()}"
@ -235,11 +232,11 @@ class DefaultJwtParserBuilderTest {
final String standardId = Jwts.KEY.A256GCMKW.id final String standardId = Jwts.KEY.A256GCMKW.id
def header = Jwts.header().add('enc', Jwts.ENC.A256GCM.id).add('alg', standardId).build() def header = Jwts.header().add('enc', Jwts.ENC.A256GCM.id).add('alg', standardId).build()
def parser = builder.build() def parser = builder.build()
assertSame Jwts.KEY.A256GCMKW, parser.keyAlgFn.apply(header) // standard implementation default assertSame Jwts.KEY.A256GCMKW, parser.keyAlgs.apply(header) // standard implementation default
def custom = new TestKeyAlgorithm(id: standardId) // custom impl with standard identifier def custom = new TestKeyAlgorithm(id: standardId) // custom impl with standard identifier
parser = builder.addKeyAlgorithms([custom]).build() parser = builder.key().add(custom).and().build()
assertSame custom, parser.keyAlgFn.apply(header) // custom one, not standard impl assertSame custom, parser.keyAlgs.apply(header) // custom one, not standard impl
} }
@Test @Test
@ -249,9 +246,9 @@ class DefaultJwtParserBuilderTest {
def standard = hb.add('alg', alg.id).build() def standard = hb.add('alg', alg.id).build()
def nonStandard = hb.add('alg', alg.id.toLowerCase()).build() def nonStandard = hb.add('alg', alg.id.toLowerCase()).build()
def parser = builder.build() def parser = builder.build()
assertSame alg, parser.keyAlgFn.apply(standard) // standard id assertSame alg, parser.keyAlgs.apply(standard) // standard id
try { try {
parser.keyAlgFn.apply(nonStandard) // non-standard id parser.keyAlgs.apply(nonStandard) // non-standard id
fail() fail()
} catch (UnsupportedJwtException e) { } catch (UnsupportedJwtException e) {
String msg = "Unrecognized JWE ${DefaultJweHeader.ALGORITHM} header value: ${alg.id.toLowerCase()}" String msg = "Unrecognized JWE ${DefaultJweHeader.ALGORITHM} header value: ${alg.id.toLowerCase()}"
@ -264,11 +261,11 @@ class DefaultJwtParserBuilderTest {
final String standardId = Jwts.SIG.HS256.id final String standardId = Jwts.SIG.HS256.id
def header = Jwts.header().add('alg', standardId).build() def header = Jwts.header().add('alg', standardId).build()
def parser = builder.build() def parser = builder.build()
assertSame Jwts.SIG.HS256, parser.sigAlgFn.apply(header) // standard implementation default assertSame Jwts.SIG.HS256, parser.sigAlgs.apply(header) // standard implementation default
def custom = new TestMacAlgorithm(id: standardId) // custom impl with standard identifier def custom = new TestMacAlgorithm(id: standardId) // custom impl with standard identifier
parser = builder.addSignatureAlgorithms([custom]).build() parser = builder.sig().add(custom).and().build()
assertSame custom, parser.sigAlgFn.apply(header) // custom one, not standard impl assertSame custom, parser.sigAlgs.apply(header) // custom one, not standard impl
} }
@Test @Test
@ -278,9 +275,9 @@ class DefaultJwtParserBuilderTest {
def standard = hb.build() def standard = hb.build()
def nonStandard = hb.add('alg', alg.id.toLowerCase()).build() def nonStandard = hb.add('alg', alg.id.toLowerCase()).build()
def parser = builder.build() def parser = builder.build()
assertSame alg, parser.sigAlgFn.apply(standard) // standard id assertSame alg, parser.sigAlgs.apply(standard) // standard id
try { try {
parser.sigAlgFn.apply(nonStandard) // non-standard id parser.sigAlgs.apply(nonStandard) // non-standard id
fail() fail()
} catch (UnsupportedJwtException e) { } catch (UnsupportedJwtException e) {
String msg = "Unrecognized JWS ${DefaultJwsHeader.ALGORITHM} header value: ${alg.id.toLowerCase()}" String msg = "Unrecognized JWS ${DefaultJwsHeader.ALGORITHM} header value: ${alg.id.toLowerCase()}"
@ -298,10 +295,10 @@ class DefaultJwtParserBuilderTest {
} }
} }
try { try {
builder.setCompressionCodecResolver(resolver).addCompressionAlgorithms([codec] as Set<CompressionCodec>).build() builder.setCompressionCodecResolver(resolver).zip().add(codec).and().build()
fail() fail()
} catch (IllegalStateException expected) { } catch (IllegalStateException expected) {
String msg = "Both 'addCompressionAlgorithms' and 'compressionCodecResolver' cannot be specified. Choose either." String msg = "Both 'zip()' and 'compressionCodecResolver' cannot be configured. Choose either."
assertEquals msg, expected.getMessage() assertEquals msg, expected.getMessage()
} }
} }
@ -410,83 +407,4 @@ class DefaultJwtParserBuilderTest {
} }
} }
static class TestAeadAlgorithm implements AeadAlgorithm {
String id
int keyBitLength = 256
@Override
String getId() {
return id
}
@Override
void encrypt(AeadRequest request, AeadResult result) throws SecurityException {
}
@Override
void decrypt(DecryptAeadRequest request, OutputStream out) throws SecurityException {
}
@Override
SecretKeyBuilder key() {
return null
}
@Override
int getKeyBitLength() {
return keyBitLength
}
}
static class TestKeyAlgorithm implements KeyAlgorithm {
String id
int keyBitLength = 256
@Override
String getId() {
return id
}
@Override
KeyResult getEncryptionKey(KeyRequest request) throws SecurityException {
return null
}
@Override
SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException {
return null
}
}
static class TestMacAlgorithm implements MacAlgorithm {
String id
@Override
String getId() {
return id
}
@Override
byte[] digest(SecureRequest<InputStream, SecretKey> request) throws SecurityException {
return new byte[0]
}
@Override
boolean verify(VerifySecureDigestRequest<SecretKey> request) throws SecurityException {
return false
}
@Override
SecretKeyBuilder key() {
return null
}
@Override
int getKeyBitLength() {
return 0
}
}
} }

View File

@ -245,11 +245,11 @@ class DefaultJwtParserTest {
def key = TestKeys.HS256 def key = TestKeys.HS256
def crit = Collections.setOf('whatever') def crit = Collections.setOf('whatever')
String jws = Jwts.builder() String jws = Jwts.builder()
.header().critical(crit).add('whatever', 42).and() .header().critical().add(crit).and().add('whatever', 42).and()
.subject('me') .subject('me')
.signWith(key).compact() .signWith(key).compact()
def jwt = Jwts.parser().critical(crit).verifyWith(key).build().parseClaimsJws(jws) def jwt = Jwts.parser().critical().add(crit).and().verifyWith(key).build().parseClaimsJws(jws)
// no exception thrown, as expected, check the header values: // no exception thrown, as expected, check the header values:
def parsedCrit = jwt.getHeader().getCritical() def parsedCrit = jwt.getHeader().getCritical()

View File

@ -29,7 +29,7 @@ class DefaultJwtTest {
@Test @Test
void testToString() { void testToString() {
String compact = Jwts.builder().header().add('foo', 'bar').and().audience('jsmith').compact() String compact = Jwts.builder().header().add('foo', 'bar').and().audience().add('jsmith').and().compact()
Jwt jwt = Jwts.parser().unsecured().build().parseClaimsJwt(compact) Jwt jwt = Jwts.parser().unsecured().build().parseClaimsJwt(compact)
assertEquals 'header={foo=bar, alg=none},payload={aud=[jsmith]}', jwt.toString() assertEquals 'header={foo=bar, alg=none},payload={aud=[jsmith]}', jwt.toString()
} }

View File

@ -45,6 +45,7 @@ class DefaultMutableJweHeaderTest {
switch (propName) { switch (propName) {
case 'algorithm': header.add('alg', val); break // no setter case 'algorithm': header.add('alg', val); break // no setter
case 'compressionAlgorithm': header.add('zip', val); break // no setter case 'compressionAlgorithm': header.add('zip', val); break // no setter
case 'critical': header.critical().add(val).and(); break // no setter
default: header."$propName"(val) default: header."$propName"(val)
} }

View File

@ -0,0 +1,46 @@
/*
* 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
import io.jsonwebtoken.ClaimsMutator
import org.junit.Test
import static org.easymock.EasyMock.*
import static org.junit.Assert.assertSame
class DelegateAudienceCollectionTest {
@Test
void clear() {
ClaimsMutator.AudienceCollection delegate = createMock(ClaimsMutator.AudienceCollection)
expect(delegate.clear()).andReturn(delegate)
replay(delegate)
def c = new DelegateAudienceCollection(this, delegate)
assertSame c, c.clear()
verify delegate
}
@Test
void remove() {
String val = 'hello'
ClaimsMutator.AudienceCollection delegate = createMock(ClaimsMutator.AudienceCollection)
expect(delegate.remove(same(val))).andReturn(delegate)
replay(delegate)
def c = new DelegateAudienceCollection(this, delegate)
assertSame c, c.remove(val)
verify delegate
}
}

View File

@ -40,12 +40,12 @@ class IdLocatorTest {
def a = new StringIdentifiable(value: 'A') def a = new StringIdentifiable(value: 'A')
def b = new StringIdentifiable(value: 'B') def b = new StringIdentifiable(value: 'B')
registry = new IdRegistry('Foo', [a, b], false) registry = new IdRegistry('Foo', [a, b], false)
locator = new IdLocator(TEST_PARAM, registry, Collections.emptyList(), exMsg) locator = new IdLocator(TEST_PARAM, registry, exMsg)
} }
@Test @Test
void unrequiredHeaderValueTest() { void unrequiredHeaderValueTest() {
locator = new IdLocator(TEST_PARAM, registry, Collections.emptyList(), null) locator = new IdLocator(TEST_PARAM, registry, null)
def header = Jwts.header().add('a', 'b').build() def header = Jwts.header().add('a', 'b').build()
assertNull locator.apply(header) assertNull locator.apply(header)
} }

View File

@ -0,0 +1,106 @@
/*
* 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
import io.jsonwebtoken.Identifiable
import io.jsonwebtoken.lang.Strings
import org.junit.Before
import org.junit.Test
import static org.junit.Assert.*
/**
* @since JJWT_RELEASE_VERSION
*/
class DefaultCollectionMutatorTest {
private DefaultCollectionMutator m
@Before
void setUp() {
m = new DefaultCollectionMutator(null)
}
@Test
void newInstance() {
def c = m.getCollection()
assertNotNull c
assertTrue c.isEmpty()
}
@Test
void addEmpty() {
m.add(Strings.EMPTY)
assertTrue m.getCollection().isEmpty() // wasn't added
}
@Test
void add() {
def val = 'hello'
m.add(val)
assertEquals Collections.singleton(val), m.getCollection()
}
@Test
void addCollection() {
def vals = ['hello', 'world']
m.add(vals)
assertEquals vals.size(), m.getCollection().size()
assertTrue vals.containsAll(m.getCollection())
assertTrue m.getCollection().containsAll(vals)
def i = m.getCollection().iterator() // order retained
assertEquals vals[0], i.next()
assertEquals vals[1], i.next()
assertFalse i.hasNext()
}
@Test(expected = IllegalArgumentException)
void addIdentifiableWithNullId() {
def e = new Identifiable() {
@Override
String getId() {
return null
}
}
m.add(e)
}
@Test(expected = IllegalArgumentException)
void addIdentifiableWithEmptyId() {
def e = new Identifiable() {
@Override
String getId() {
return ' '
}
}
m.add(e)
}
@Test
void remove() {
m.add('hello').add('world')
m.remove('hello')
assertEquals Collections.singleton('world'), m.getCollection()
}
@Test
void clear() {
m.add('one').add('two').add(['three', 'four'])
assertEquals 4, m.getCollection().size()
m.clear()
assertTrue m.getCollection().isEmpty()
}
}

View File

@ -18,8 +18,7 @@ package io.jsonwebtoken.impl.lang
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import static org.junit.Assert.assertEquals import static org.junit.Assert.*
import static org.junit.Assert.fail
class DefaultRegistryTest { class DefaultRegistryTest {
@ -53,4 +52,30 @@ class DefaultRegistryTest {
def key = 'a' def key = 'a'
assertEquals reg.apply(key), reg.get(key) assertEquals reg.apply(key), reg.get(key)
} }
@Test
void testHashCode() {
assertEquals reg.@DELEGATE.hashCode(), reg.hashCode()
}
@Test
void testEqualsIdentity() {
assertEquals reg, reg
}
@Test
void testEqualsValues() {
def another = new DefaultRegistry<>('test', 'id', ['a', 'b', 'c', 'd'], Functions.identity())
assertEquals another, reg
}
@Test
void testNotEquals() {
assertFalse reg.equals(new Object())
}
@Test
void testToString() {
assertEquals reg.@DELEGATE.toString(), reg.toString()
}
} }

View File

@ -1,47 +0,0 @@
/*
* Copyright (C) 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
import io.jsonwebtoken.lang.Registry
import org.junit.Before
import org.junit.Test
import static org.junit.Assert.assertEquals
class DelegatingRegistryTest {
Registry<String, String> registry
@Before
void setUp() {
def src = new DefaultRegistry('test', 'id', ['a', 'b', 'c'], Functions.identity())
this.registry = new DelegatingRegistry(src)
}
@Test
void testForKey() {
assertEquals 'a', registry.forKey('a')
assertEquals 'b', registry.forKey('b')
assertEquals 'c', registry.forKey('c')
}
@Test(expected = IllegalArgumentException)
void testForKeyInvalid() {
registry.forKey('invalid') // any key value that doesn't exist
}
}

View File

@ -142,7 +142,7 @@ class AbstractJwkBuilderTest {
def op = Jwks.OP.get().get(s) def op = Jwks.OP.get().get(s)
def canonical = Collections.setOf(s) def canonical = Collections.setOf(s)
def idiomatic = Collections.setOf(op) def idiomatic = Collections.setOf(op)
def jwk = builder().operation(op).build() def jwk = builder().operations().add(op).and().build()
assertEquals idiomatic, jwk.getOperations() assertEquals idiomatic, jwk.getOperations()
assertEquals canonical, jwk.key_ops assertEquals canonical, jwk.key_ops
} }
@ -153,7 +153,7 @@ class AbstractJwkBuilderTest {
def op = Jwks.OP.builder().id(s).build() def op = Jwks.OP.builder().id(s).build()
def canonical = Collections.setOf(s) def canonical = Collections.setOf(s)
def idiomatic = Collections.setOf(op) def idiomatic = Collections.setOf(op)
def jwk = builder().operation(op).build() def jwk = builder().operations().add(op).and().build()
assertEquals idiomatic, jwk.getOperations() assertEquals idiomatic, jwk.getOperations()
assertEquals canonical, jwk.key_ops assertEquals canonical, jwk.key_ops
} }
@ -164,7 +164,7 @@ class AbstractJwkBuilderTest {
def op = Jwks.OP.builder().id(s).related('verify').build() def op = Jwks.OP.builder().id(s).related('verify').build()
def canonical = Collections.setOf(s) def canonical = Collections.setOf(s)
def idiomatic = Collections.setOf(op) def idiomatic = Collections.setOf(op)
def jwk = builder().operation(op).build() def jwk = builder().operations().add(op).and().build()
assertEquals idiomatic, jwk.getOperations() assertEquals idiomatic, jwk.getOperations()
assertEquals canonical, jwk.key_ops assertEquals canonical, jwk.key_ops
assertSame op, jwk.getOperations().iterator().next() assertSame op, jwk.getOperations().iterator().next()
@ -172,7 +172,7 @@ class AbstractJwkBuilderTest {
//now assert that the standard VERIFY operation treats this as related since it has the same ID: //now assert that the standard VERIFY operation treats this as related since it has the same ID:
canonical = Collections.setOf(s, 'verify') canonical = Collections.setOf(s, 'verify')
idiomatic = Collections.setOf(op, Jwks.OP.VERIFY) idiomatic = Collections.setOf(op, Jwks.OP.VERIFY)
jwk = builder().operation(op).operation(Jwks.OP.VERIFY).build() as Jwk jwk = builder().operations().add(op).add(Jwks.OP.VERIFY).and().build() as Jwk
assertEquals idiomatic, jwk.getOperations() assertEquals idiomatic, jwk.getOperations()
assertEquals canonical, jwk.key_ops assertEquals canonical, jwk.key_ops
} }
@ -183,7 +183,7 @@ class AbstractJwkBuilderTest {
def b = 'verify' def b = 'verify'
def canonical = Collections.setOf(a, b) def canonical = Collections.setOf(a, b)
def idiomatic = Collections.setOf(Jwks.OP.SIGN, Jwks.OP.VERIFY) def idiomatic = Collections.setOf(Jwks.OP.SIGN, Jwks.OP.VERIFY)
def jwk = builder().operations(idiomatic).build() def jwk = builder().operations().add(idiomatic).and().build()
assertEquals idiomatic, jwk.getOperations() assertEquals idiomatic, jwk.getOperations()
assertEquals canonical, jwk.key_ops assertEquals canonical, jwk.key_ops
} }
@ -192,7 +192,7 @@ class AbstractJwkBuilderTest {
void testOperationsUnrelated() { void testOperationsUnrelated() {
try { try {
// exception thrown on setter, before calling build: // exception thrown on setter, before calling build:
builder().operations(Collections.setOf(Jwks.OP.SIGN, Jwks.OP.ENCRYPT)) builder().operations().add(Collections.setOf(Jwks.OP.SIGN, Jwks.OP.ENCRYPT)).and()
fail() fail()
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
String msg = 'Unrelated key operations are not allowed. KeyOperation [\'encrypt\' (Encrypt content)] is ' + String msg = 'Unrelated key operations are not allowed. KeyOperation [\'encrypt\' (Encrypt content)] is ' +
@ -240,7 +240,7 @@ class AbstractJwkBuilderTest {
def op = Jwks.OP.builder().id('sign').description('Different Description') def op = Jwks.OP.builder().id('sign').description('Different Description')
.related(Jwks.OP.VERIFY.id).build() .related(Jwks.OP.VERIFY.id).build()
def builder = builder().operationPolicy(Jwks.OP.policy().add(op).build()) def builder = builder().operationPolicy(Jwks.OP.policy().add(op).build())
def jwk = builder.operations(Collections.setOf(op, Jwks.OP.VERIFY)).build() as Jwk def jwk = builder.operations().add(Collections.setOf(op, Jwks.OP.VERIFY)).and().build() as Jwk
assertSame op, jwk.getOperations().find({it.id == 'sign'}) assertSame op, jwk.getOperations().find({it.id == 'sign'})
} }

View File

@ -124,7 +124,7 @@ class EdSignatureAlgorithmTest {
} }
static void testSig(PrivateKey signing, PublicKey verification) { static void testSig(PrivateKey signing, PublicKey verification) {
String jwt = Jwts.builder().issuer('me').audience('you').signWith(signing, alg).compact() String jwt = Jwts.builder().issuer('me').audience().add('you').and().signWith(signing, alg).compact()
def token = Jwts.parser().verifyWith(verification).build().parseClaimsJws(jwt) def token = Jwts.parser().verifyWith(verification).build().parseClaimsJws(jwt)
assertEquals([alg: alg.getId()], token.header) assertEquals([alg: alg.getId()], token.header)
assertEquals 'me', token.getPayload().getIssuer() assertEquals 'me', token.getPayload().getIssuer()

View File

@ -147,14 +147,15 @@ class JwksTest {
def op = Jwks.OP.ENCRYPT def op = Jwks.OP.ENCRYPT
def expected = [op] as Set<KeyOperation> def expected = [op] as Set<KeyOperation>
def canonical = [op.getId()] as Set<String> def canonical = [op.getId()] as Set<String>
def jwk = Jwks.builder().key(TestKeys.A128GCM).operation(op).build() def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add(op).and().build()
assertEquals expected, jwk.getOperations() assertEquals expected, jwk.getOperations()
assertEquals canonical, jwk.get(AbstractJwk.KEY_OPS.id) assertEquals canonical, jwk.get(AbstractJwk.KEY_OPS.id)
} }
@Test @Test
void testSingleOperationNull() { void testSingleOperationNull() {
def jwk = Jwks.builder().key(TestKeys.A128GCM).operation(null).build() //ignored null def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add((KeyOperation) null).and().build()
//ignored null
assertNull jwk.getOperations() //nothing added assertNull jwk.getOperations() //nothing added
assertFalse jwk.containsKey(AbstractJwk.KEY_OPS.id) assertFalse jwk.containsKey(AbstractJwk.KEY_OPS.id)
} }
@ -163,7 +164,7 @@ class JwksTest {
void testSingleOperationAppends() { void testSingleOperationAppends() {
def expected = [Jwks.OP.ENCRYPT, Jwks.OP.DECRYPT] as Set<KeyOperation> def expected = [Jwks.OP.ENCRYPT, Jwks.OP.DECRYPT] as Set<KeyOperation>
def jwk = Jwks.builder().key(TestKeys.A128GCM) def jwk = Jwks.builder().key(TestKeys.A128GCM)
.operation(Jwks.OP.ENCRYPT).operation(Jwks.OP.DECRYPT) .operations().add(Jwks.OP.ENCRYPT).add(Jwks.OP.DECRYPT).and()
.build() .build()
assertEquals expected, jwk.getOperations() assertEquals expected, jwk.getOperations()
} }
@ -171,20 +172,20 @@ class JwksTest {
@Test @Test
void testOperations() { void testOperations() {
def val = [Jwks.OP.SIGN, Jwks.OP.VERIFY] as Set<KeyOperation> def val = [Jwks.OP.SIGN, Jwks.OP.VERIFY] as Set<KeyOperation>
def jwk = Jwks.builder().key(TestKeys.A128GCM).operations(val).build() def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add(val).and().build()
assertEquals val, jwk.getOperations() assertEquals val, jwk.getOperations()
} }
@Test @Test
void testOperationsNull() { void testOperationsNull() {
def jwk = Jwks.builder().key(TestKeys.A128GCM).operations(null).build() def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add((Collection) null).and().build()
assertNull jwk.getOperations() assertNull jwk.getOperations()
assertFalse jwk.containsKey(AbstractJwk.KEY_OPS.id) assertFalse jwk.containsKey(AbstractJwk.KEY_OPS.id)
} }
@Test @Test
void testOperationsEmpty() { void testOperationsEmpty() {
def jwk = Jwks.builder().key(TestKeys.A128GCM).operations(Collections.emptyList()).build() def jwk = Jwks.builder().key(TestKeys.A128GCM).operations().add(Collections.emptyList()).and().build()
assertNull jwk.getOperations() assertNull jwk.getOperations()
} }

View File

@ -48,7 +48,7 @@ class SecretJwkFactoryTest {
@Test @Test
void testSignOpSetsKeyHmacSHA256() { void testSignOpSetsKeyHmacSHA256() {
SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).delete('alg').build() SecretJwk jwk = Jwks.builder().key(TestKeys.HS256).delete('alg').build()
SecretJwk result = Jwks.builder().add(jwk).operations([Jwks.OP.SIGN]).build() as SecretJwk SecretJwk result = Jwks.builder().add(jwk).operations().add(Jwks.OP.SIGN).and().build() as SecretJwk
assertNull result.getAlgorithm() assertNull result.getAlgorithm()
assertNull result.get('use') assertNull result.get('use')
assertEquals 'HmacSHA256', result.toKey().getAlgorithm() assertEquals 'HmacSHA256', result.toKey().getAlgorithm()
@ -64,7 +64,7 @@ class SecretJwkFactoryTest {
@Test @Test
void testSignOpSetsKeyHmacSHA384() { void testSignOpSetsKeyHmacSHA384() {
SecretJwk jwk = Jwks.builder().key(TestKeys.HS384).delete('alg').build() SecretJwk jwk = Jwks.builder().key(TestKeys.HS384).delete('alg').build()
SecretJwk result = Jwks.builder().add(jwk).operations([Jwks.OP.SIGN]).build() as SecretJwk SecretJwk result = Jwks.builder().add(jwk).operations().add(Jwks.OP.SIGN).and().build() as SecretJwk
assertNull result.getAlgorithm() assertNull result.getAlgorithm()
assertNull result.get('use') assertNull result.get('use')
assertEquals 'HmacSHA384', result.toKey().getAlgorithm() assertEquals 'HmacSHA384', result.toKey().getAlgorithm()
@ -80,7 +80,7 @@ class SecretJwkFactoryTest {
@Test @Test
void testSignOpSetsKeyHmacSHA512() { void testSignOpSetsKeyHmacSHA512() {
SecretJwk jwk = Jwks.builder().key(TestKeys.HS512).delete('alg').build() SecretJwk jwk = Jwks.builder().key(TestKeys.HS512).delete('alg').build()
SecretJwk result = Jwks.builder().add(jwk).operations([Jwks.OP.SIGN]).build() as SecretJwk SecretJwk result = Jwks.builder().add(jwk).operations().add(Jwks.OP.SIGN).and().build() as SecretJwk
assertNull result.getAlgorithm() assertNull result.getAlgorithm()
assertNull result.get('use') assertNull result.get('use')
assertEquals 'HmacSHA512', result.toKey().getAlgorithm() assertEquals 'HmacSHA512', result.toKey().getAlgorithm()

View File

@ -0,0 +1,47 @@
/*
* 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.security
import io.jsonwebtoken.security.*
class TestAeadAlgorithm implements AeadAlgorithm {
String id
int keyBitLength = 256
@Override
String getId() {
return id
}
@Override
void encrypt(AeadRequest request, AeadResult result) throws SecurityException {
}
@Override
void decrypt(DecryptAeadRequest request, OutputStream out) throws SecurityException {
}
@Override
SecretKeyBuilder key() {
return null
}
@Override
int getKeyBitLength() {
return keyBitLength
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.security
import io.jsonwebtoken.security.*
import javax.crypto.SecretKey
class TestKeyAlgorithm implements KeyAlgorithm {
String id
@Override
String getId() {
return id
}
@Override
KeyResult getEncryptionKey(KeyRequest request) throws SecurityException {
return null
}
@Override
SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException {
return null
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.security
import io.jsonwebtoken.security.*
import javax.crypto.SecretKey
class TestMacAlgorithm implements MacAlgorithm {
String id
MacAlgorithm delegate
@Override
String getId() {
return id
}
@Override
byte[] digest(SecureRequest<InputStream, SecretKey> request) throws SecurityException {
return delegate.digest(request)
}
@Override
boolean verify(VerifySecureDigestRequest<SecretKey> request) throws SecurityException {
return delegate.verify(request)
}
@Override
SecretKeyBuilder key() {
return delegate.key()
}
@Override
int getKeyBitLength() {
return delegate.getKeyBitLength()
}
}

View File

@ -79,13 +79,6 @@ class EncryptionAlgorithmsTest {
} }
} }
@Test
void testGetCaseInsensitive() {
for (AeadAlgorithm alg : registry.values()) {
assertSame alg, registry.get(alg.getId().toLowerCase())
}
}
@Test(expected = IllegalArgumentException) @Test(expected = IllegalArgumentException)
void testForIdWithInvalidId() { void testForIdWithInvalidId() {
//unlike the 'get' paradigm, 'key' requires the value to exist //unlike the 'get' paradigm, 'key' requires the value to exist

View File

@ -23,7 +23,7 @@ import java.security.Key
import static org.junit.Assert.* import static org.junit.Assert.*
/** /**
* Tests {@link Jwts#KEY} values. * Tests {@link Jwts.KEY} values.
* *
* @since JJWT_RELEASE_VERSION * @since JJWT_RELEASE_VERSION
*/ */
@ -63,13 +63,6 @@ class KeyAlgorithmsTest {
} }
} }
@Test
void testForKeyCaseInsensitive() {
for (KeyAlgorithm alg : Jwts.KEY.get().values()) {
assertSame alg, Jwts.KEY.get().forKey(alg.getId().toLowerCase())
}
}
@Test(expected = IllegalArgumentException) @Test(expected = IllegalArgumentException)
void testForKeyWithInvalidId() { void testForKeyWithInvalidId() {
//unlike the 'get' paradigm, 'key' requires the value to exist //unlike the 'get' paradigm, 'key' requires the value to exist
@ -83,13 +76,6 @@ class KeyAlgorithmsTest {
} }
} }
@Test
void testGetCaseInsensitive() {
for (KeyAlgorithm alg : Jwts.KEY.get().values()) {
assertSame alg, Jwts.KEY.get().get(alg.getId().toLowerCase())
}
}
@Test @Test
void testGetWithInvalidId() { void testGetWithInvalidId() {
// 'get' paradigm can return null if not found // 'get' paradigm can return null if not found

View File

@ -45,13 +45,6 @@ class StandardAlgorithmsTest {
} }
} }
@Test
void testForKeyCaseInsensitive() {
eachRegAlg { reg, alg ->
assertSame alg, reg.forKey(alg.getId().toLowerCase())
}
}
@Test @Test
void testForKeyWithInvalidId() { void testForKeyWithInvalidId() {
//unlike the 'get' paradigm, 'forKey' requires the value to exist //unlike the 'get' paradigm, 'forKey' requires the value to exist
@ -72,13 +65,6 @@ class StandardAlgorithmsTest {
} }
} }
@Test
void testGetCaseInsensitive() {
eachRegAlg { reg, alg ->
assertSame alg, reg.get(alg.getId().toLowerCase())
}
}
@Test @Test
void testGetWithInvalidId() { void testGetWithInvalidId() {
// 'get' paradigm can return null if not found // 'get' paradigm can return null if not found

View File

@ -75,9 +75,8 @@ public class JavaReadmeTest {
.compact(); .compact();
Jws<byte[]> parsed = Jwts.parser().verifyWith(testKey) // 1 Jws<byte[]> parsed = Jwts.parser().verifyWith(testKey) // 1
.critical("b64") // 2
.build() .build()
.parseContentJws(jws, content); // 3 .parseContentJws(jws, content); // 2
assertArrayEquals(content, parsed.getPayload()); assertArrayEquals(content, parsed.getPayload());
} }
@ -98,7 +97,7 @@ public class JavaReadmeTest {
.compact(); .compact();
Jws<Claims> parsed = Jwts.parser().verifyWith(testKey) // 1 Jws<Claims> parsed = Jwts.parser().verifyWith(testKey) // 1
.critical("b64") // 2 .critical().add("b64").and() // 2
.build() .build()
.parseClaimsJws(jws); // 3 .parseClaimsJws(jws); // 3
@ -106,7 +105,6 @@ public class JavaReadmeTest {
assert "me".equals(parsed.getPayload().getIssuer()); assert "me".equals(parsed.getPayload().getIssuer());
parsed = Jwts.parser().verifyWith(testKey) parsed = Jwts.parser().verifyWith(testKey)
.critical("b64")
.build() .build()
.parseClaimsJws(jws, claimsString.getBytes(StandardCharsets.UTF_8)); // <--- .parseClaimsJws(jws, claimsString.getBytes(StandardCharsets.UTF_8)); // <---
@ -237,7 +235,7 @@ public class JavaReadmeTest {
AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc... AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc...
// Bob creates the compact JWE with Alice's RSA public key so only she may read it: // Bob creates the compact JWE with Alice's RSA public key so only she may read it:
String jwe = Jwts.builder().audience("Alice") String jwe = Jwts.builder().audience().add("Alice").and()
.encryptWith(pair.getPublic(), alg, enc) // <-- Alice's RSA public key .encryptWith(pair.getPublic(), alg, enc) // <-- Alice's RSA public key
.compact(); .compact();
@ -285,7 +283,7 @@ public class JavaReadmeTest {
AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc... AeadAlgorithm enc = Jwts.ENC.A256GCM; //or A192GCM, A128GCM, A256CBC-HS512, etc...
// Bob creates the compact JWE with Alice's EC public key so only she may read it: // Bob creates the compact JWE with Alice's EC public key so only she may read it:
String jwe = Jwts.builder().audience("Alice") String jwe = Jwts.builder().audience().add("Alice").and()
.encryptWith(pair.getPublic(), alg, enc) // <-- Alice's EC public key .encryptWith(pair.getPublic(), alg, enc) // <-- Alice's EC public key
.compact(); .compact();