Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ClaimJwtException.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ClaimJwtException.java (.../ClaimJwtException.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ClaimJwtException.java (.../ClaimJwtException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,35 +16,82 @@ package io.jsonwebtoken; /** - * ClaimJwtException is a subclass of the {@link JwtException} that is thrown after a validation of an JTW claim failed. + * ClaimJwtException is a subclass of the {@link JwtException} that is thrown after a validation of an JWT claim failed. * * @since 0.5 */ public abstract class ClaimJwtException extends JwtException { + /** + * Deprecated as this is an implementation detail accidentally exposed in the JJWT 0.5 public API. It is no + * longer referenced anywhere in JJWT's implementation and will be removed in a future release. + * + * @deprecated will be removed in a future release. + */ + @Deprecated public static final String INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was: %s."; + + /** + * Deprecated as this is an implementation detail accidentally exposed in the JJWT 0.5 public API. It is no + * longer referenced anywhere in JJWT's implementation and will be removed in a future release. + * + * @deprecated will be removed in a future release. + */ + @Deprecated public static final String MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was not present in the JWT claims."; + /** + * The header associated with the Claims that failed validation. + */ private final Header header; + /** + * The Claims that failed validation. + */ private final Claims claims; + /** + * Creates a new instance with the specified header, claims and exception message. + * + * @param header the header inspected + * @param claims the claims obtained + * @param message the exception message + */ protected ClaimJwtException(Header header, Claims claims, String message) { super(message); this.header = header; this.claims = claims; } + /** + * Creates a new instance with the specified header, claims and exception message as a result of encountering + * the specified {@code cause}. + * + * @param header the header inspected + * @param claims the claims obtained + * @param message the exception message + * @param cause the exception that caused this ClaimJwtException to be thrown. + */ protected ClaimJwtException(Header header, Claims claims, String message, Throwable cause) { super(message, cause); this.header = header; this.claims = claims; } + /** + * Returns the {@link Claims} that failed validation. + * + * @return the {@link Claims} that failed validation. + */ public Claims getClaims() { return claims; } + /** + * Returns the header associated with the {@link #getClaims() claims} that failed validation. + * + * @return the header associated with the {@link #getClaims() claims} that failed validation. + */ public Header getHeader() { return header; } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Claims.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Claims.java (.../Claims.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Claims.java (.../Claims.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -17,94 +17,89 @@ import java.util.Date; import java.util.Map; +import java.util.Set; /** - * A JWT Claims set. + * A JWT Claims set. * - *

This is ultimately a JSON map and any values can be added to it, but JWT standard names are provided as - * type-safe getters and setters for convenience.

+ *

This is an immutable JSON map with convenient type-safe getters for JWT standard claim names.

* - *

Because this interface extends {@code Map<String, Object>}, if you would like to add your own properties, - * you simply use map methods, for example:

+ *

Additionally, this interface also extends Map<String, Object>, so you can use standard + * {@code Map} accessor/iterator methods as desired, for example:

* - *
- * claims.{@link Map#put(Object, Object) put}("someKey", "someValue");
- * 
+ *
+ * claims.get("someKey");
* - *

Creation

+ *

However, because {@code Claims} instances are immutable, calling any of the map mutation methods + * (such as {@code Map.}{@link Map#put(Object, Object) put}, etc) will result in a runtime exception. The + * {@code Map} interface is implemented specifically for the convenience of working with existing Map-based utilities + * and APIs.

* - *

It is easiest to create a {@code Claims} instance by calling one of the - * {@link Jwts#claims() JWTs.claims()} factory methods.

- * * @since 0.1 */ -public interface Claims extends Map, ClaimsMutator { +public interface Claims extends Map, Identifiable { - /** JWT {@code Issuer} claims parameter name: "iss" */ - public static final String ISSUER = "iss"; + /** + * JWT {@code Issuer} claims parameter name: "iss" + */ + String ISSUER = "iss"; - /** JWT {@code Subject} claims parameter name: "sub" */ - public static final String SUBJECT = "sub"; + /** + * JWT {@code Subject} claims parameter name: "sub" + */ + String SUBJECT = "sub"; - /** JWT {@code Audience} claims parameter name: "aud" */ - public static final String AUDIENCE = "aud"; + /** + * JWT {@code Audience} claims parameter name: "aud" + */ + String AUDIENCE = "aud"; - /** JWT {@code Expiration} claims parameter name: "exp" */ - public static final String EXPIRATION = "exp"; + /** + * JWT {@code Expiration} claims parameter name: "exp" + */ + String EXPIRATION = "exp"; - /** JWT {@code Not Before} claims parameter name: "nbf" */ - public static final String NOT_BEFORE = "nbf"; + /** + * JWT {@code Not Before} claims parameter name: "nbf" + */ + String NOT_BEFORE = "nbf"; - /** JWT {@code Issued At} claims parameter name: "iat" */ - public static final String ISSUED_AT = "iat"; + /** + * JWT {@code Issued At} claims parameter name: "iat" + */ + String ISSUED_AT = "iat"; - /** JWT {@code JWT ID} claims parameter name: "jti" */ - public static final String ID = "jti"; + /** + * JWT {@code JWT ID} claims parameter name: "jti" + */ + String ID = "jti"; /** - * Returns the JWT + * Returns the JWT * iss (issuer) value or {@code null} if not present. * * @return the JWT {@code iss} value or {@code null} if not present. */ String getIssuer(); /** - * {@inheritDoc} - */ - @Override //only for better/targeted JavaDoc - Claims setIssuer(String iss); - - /** - * Returns the JWT + * Returns the JWT * sub (subject) value or {@code null} if not present. * * @return the JWT {@code sub} value or {@code null} if not present. */ String getSubject(); /** - * {@inheritDoc} - */ - @Override //only for better/targeted JavaDoc - Claims setSubject(String sub); - - /** - * Returns the JWT + * Returns the JWT * aud (audience) value or {@code null} if not present. * * @return the JWT {@code aud} value or {@code null} if not present. */ - String getAudience(); + Set getAudience(); /** - * {@inheritDoc} - */ - @Override //only for better/targeted JavaDoc - Claims setAudience(String aud); - - /** - * Returns the JWT + * Returns the JWT * exp (expiration) timestamp or {@code null} if not present. * *

A JWT obtained after this timestamp should not be used.

@@ -114,13 +109,7 @@ Date getExpiration(); /** - * {@inheritDoc} - */ - @Override //only for better/targeted JavaDoc - Claims setExpiration(Date exp); - - /** - * Returns the JWT + * Returns the JWT * nbf (not before) timestamp or {@code null} if not present. * *

A JWT obtained before this timestamp should not be used.

@@ -130,13 +119,7 @@ Date getNotBefore(); /** - * {@inheritDoc} - */ - @Override //only for better/targeted JavaDoc - Claims setNotBefore(Date nbf); - - /** - * Returns the JWT + * Returns the JWT * iat (issued at) timestamp or {@code null} if not present. * *

If present, this value is the timestamp when the JWT was created.

@@ -146,13 +129,7 @@ Date getIssuedAt(); /** - * {@inheritDoc} - */ - @Override //only for better/targeted JavaDoc - Claims setIssuedAt(Date iat); - - /** - * Returns the JWTs + * Returns the JWTs * jti (JWT ID) value or {@code null} if not present. * *

This value is a CaSe-SenSiTiVe unique identifier for the JWT. If available, this value is expected to be @@ -162,30 +139,28 @@ * * @return the JWT {@code jti} value or {@code null} if not present. */ + @Override + // just for JavaDoc specific to the JWT spec String getId(); /** - * {@inheritDoc} - */ - @Override //only for better/targeted JavaDoc - Claims setId(String jti); - - /** - * Returns the JWTs claim ({@code claimName}) value as a type {@code requiredType}, or {@code null} if not present. + * Returns the JWTs claim ({@code claimName}) value as a {@code requiredType} instance, or {@code null} if not + * present. * *

JJWT only converts simple String, Date, Long, Integer, Short and Byte types automatically. Anything more - * complex is expected to be already converted to your desired type by the JSON - * {@link io.jsonwebtoken.io.Deserializer Deserializer} implementation. You may specify a custom Deserializer for a - * JwtParser with the desired conversion configuration via the {@link JwtParserBuilder#deserializeJsonWith} method. - * See custom JSON processor for more + * complex is expected to be already converted to your desired type by the JSON parser. You may specify a custom + * JSON processor using the {@code JwtParserBuilder}'s + * {@link JwtParserBuilder#json(io.jsonwebtoken.io.Deserializer) json(Deserializer)} method. See the JJWT + * documentation on custom JSON processors for more * information. If using Jackson, you can specify custom claim POJO types as described in * custom claim types. * - * @param claimName name of claim + * @param claimName name of claim * @param requiredType the type of the value expected to be returned - * @param the type of the value expected to be returned + * @param the type of the value expected to be returned * @return the JWT {@code claimName} value or {@code null} if not present. * @throws RequiredTypeException throw if the claim value is not null and not of type {@code requiredType} + * @see JJWT JSON Support */ T get(String claimName, Class requiredType); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ClaimsBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ClaimsBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ClaimsBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,29 @@ +/* + * 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; + +import io.jsonwebtoken.lang.Builder; +import io.jsonwebtoken.lang.MapMutator; + +/** + * {@link Builder} used to create an immutable {@link Claims} instance. + * + * @see JwtBuilder + * @see Claims + * @since 0.12.0 + */ +public interface ClaimsBuilder extends MapMutator, ClaimsMutator, Builder { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ClaimsMutator.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ClaimsMutator.java (.../ClaimsMutator.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ClaimsMutator.java (.../ClaimsMutator.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,6 +15,9 @@ */ package io.jsonwebtoken; +import io.jsonwebtoken.lang.NestedCollection; + +import java.util.Collection; import java.util.Date; /** @@ -25,78 +28,243 @@ * @see io.jsonwebtoken.Claims * @since 0.2 */ -public interface ClaimsMutator { +public interface ClaimsMutator> { /** - * Sets the JWT - * iss (issuer) value. A {@code null} value will remove the property from the JSON map. + * Sets the JWT + * iss (issuer) claim. A {@code null} value will remove the property from the JSON Claims map. * * @param iss the JWT {@code iss} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #issuer(String)}. This method will be removed before the JJWT 1.0 release. */ + @Deprecated T setIssuer(String iss); /** - * Sets the JWT - * sub (subject) value. A {@code null} value will remove the property from the JSON map. + * Sets the JWT + * iss (issuer) claim. A {@code null} value will remove the property from the JSON Claims map. * + * @param iss the JWT {@code iss} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @since 0.12.0 + */ + T issuer(String iss); + + /** + * Sets the JWT + * sub (subject) claim. A {@code null} value will remove the property from the JSON Claims map. + * * @param sub the JWT {@code sub} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #subject(String)}. This method will be removed before the JJWT 1.0 release. */ + @Deprecated T setSubject(String sub); /** - * Sets the JWT - * aud (audience) value. A {@code null} value will remove the property from the JSON map. + * Sets the JWT + * sub (subject) claim. A {@code null} value will remove the property from the JSON Claims map. * + * @param sub the JWT {@code sub} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @since 0.12.0 + */ + T subject(String sub); + + /** + * Sets the JWT aud (audience) + * claim as a single String, NOT a String array. 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()}.{@link AudienceCollection#add(Object) add(String)} and + * {@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. * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of {@link #audience()}. This method will be removed before + * the JJWT 1.0 release. */ + @Deprecated T setAudience(String aud); /** - * Sets the JWT - * exp (expiration) timestamp. A {@code null} value will remove the property from the JSON map. + * Configures the JWT + * aud (audience) Claim + * set, quietly ignoring any null, empty, whitespace-only, or existing value already in the set. * + *

When finished, the {@code audience} collection's {@link AudienceCollection#and() and()} method may be used + * to continue configuration. For example:

+ *
+     *  Jwts.builder() // or Jwts.claims()
+     *
+     *     .audience().add("anAudience").and() // return parent
+     *
+     *  .subject("Joe") // resume configuration...
+     *  // etc...
+     * 
+ * + * @return the {@link AudienceCollection AudienceCollection} to use for {@code aud} configuration. + * @see AudienceCollection AudienceCollection + * @see AudienceCollection#single(String) AudienceCollection.single(String) + * @since 0.12.0 + */ + AudienceCollection audience(); + + /** + * Sets the JWT + * exp (expiration) timestamp claim. A {@code null} value will remove the property from the + * JSON Claims map. + * *

A JWT obtained after this timestamp should not be used.

* * @param exp the JWT {@code exp} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #expiration(Date)}. This method will be removed before the JJWT 1.0 release. */ + @Deprecated T setExpiration(Date exp); /** - * Sets the JWT - * nbf (not before) timestamp. A {@code null} value will remove the property from the JSON map. + * Sets the JWT + * exp (expiration) timestamp claim. A {@code null} value will remove the property from the + * JSON Claims map. * + *

A JWT obtained after this timestamp should not be used.

+ * + * @param exp the JWT {@code exp} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @since 0.12.0 + */ + T expiration(Date exp); + + /** + * Sets the JWT + * nbf (not before) timestamp claim. A {@code null} value will remove the property from the + * JSON Claims map. + * *

A JWT obtained before this timestamp should not be used.

* * @param nbf the JWT {@code nbf} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #notBefore(Date)}. This method will be removed before the JJWT 1.0 release. */ + @Deprecated T setNotBefore(Date nbf); /** - * Sets the JWT - * iat (issued at) timestamp. A {@code null} value will remove the property from the JSON map. + * Sets the JWT + * nbf (not before) timestamp claim. A {@code null} value will remove the property from the + * JSON Claims map. * + *

A JWT obtained before this timestamp should not be used.

+ * + * @param nbf the JWT {@code nbf} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @since 0.12.0 + */ + T notBefore(Date nbf); + + /** + * Sets the JWT + * iat (issued at) timestamp claim. A {@code null} value will remove the property from the + * JSON Claims map. + * *

The value is the timestamp when the JWT was created.

* * @param iat the JWT {@code iat} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #issuedAt(Date)}. This method will be removed before the JJWT 1.0 release. */ + @Deprecated T setIssuedAt(Date iat); /** - * Sets the JWT - * jti (JWT ID) value. A {@code null} value will remove the property from the JSON map. + * Sets the JWT + * iat (issued at) timestamp claim. A {@code null} value will remove the property from the + * JSON Claims map. * + *

The value is the timestamp when the JWT was created.

+ * + * @param iat the JWT {@code iat} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @since 0.12.0 + */ + T issuedAt(Date iat); + + /** + * Sets the JWT + * jti (JWT ID) claim. A {@code null} value will remove the property from the JSON Claims map. + * *

This value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a * manner that ensures that there is a negligible probability that the same value will be accidentally * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

* * @param jti the JWT {@code jti} value or {@code null} to remove the property from the JSON map. * @return the {@code Claims} instance for method chaining. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #id(String)}. This method will be removed before the JJWT 1.0 release. */ + @Deprecated T setId(String jti); + + /** + * Sets the JWT + * jti (JWT ID) claim. A {@code null} value will remove the property from the JSON Claims map. + * + *

This value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a + * manner that ensures that there is a negligible probability that the same value will be accidentally + * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

+ * + * @param jti the JWT {@code jti} value or {@code null} to remove the property from the JSON map. + * @return the {@code Claims} instance for method chaining. + * @since 0.12.0 + */ + 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. + * + *

Because this interface extends {@link NestedCollection}, the {@link #and()} method may be used to continue + * parent configuration. For example:

+ *
+     *  Jwts.builder() // or Jwts.claims()
+     *
+     *     .audience().add("anAudience").and() // return parent
+     *
+     *  .subject("Joe") // resume parent configuration...
+     *  // etc...
+ * + * @param

the type of ClaimsMutator to return for method chaining. + * @see #single(String) + * @since 0.12.0 + */ + interface AudienceCollection

extends NestedCollection { + + /** + * Sets the JWT aud (audience) + * Claim as a single String, NOT a String array. 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 0.12.0 + * @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); + } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/CompressionCodec.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/CompressionCodec.java (.../CompressionCodec.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/CompressionCodec.java (.../CompressionCodec.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,41 +15,56 @@ */ package io.jsonwebtoken; +import io.jsonwebtoken.io.CompressionAlgorithm; + /** * Compresses and decompresses byte arrays according to a compression algorithm. * - * @see CompressionCodecs#DEFLATE - * @see CompressionCodecs#GZIP + *

"zip" identifier

+ * + *

{@code CompressionCodec} extends {@code Identifiable}; the value returned from + * {@link Identifiable#getId() getId()} will be used as the JWT + * zip header value.

+ * + * @see Jwts.ZIP#DEF + * @see Jwts.ZIP#GZIP * @since 0.6.0 + * @deprecated since 0.12.0 in favor of {@link io.jsonwebtoken.io.CompressionAlgorithm} to equal the RFC name for this concept. */ -public interface CompressionCodec { +@Deprecated +public interface CompressionCodec extends CompressionAlgorithm { /** - * The compression algorithm name to use as the JWT's {@code zip} header value. + * The algorithm name to use as the JWT + * zip header value. * - * @return the compression algorithm name to use as the JWT's {@code zip} header value. + * @return the algorithm name to use as the JWT + * zip header value. + * @deprecated since 0.12.0 in favor of {@link #getId()} to ensure congruence with + * all other identifiable algorithms. */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated String getAlgorithmName(); /** - * Compresses the specified byte array according to the compression {@link #getAlgorithmName() algorithm}. + * Compresses the specified byte array, returning the compressed byte array result. * - * @param payload bytes to compress + * @param content bytes to compress * @return compressed bytes - * @throws CompressionException if the specified byte array cannot be compressed according to the compression - * {@link #getAlgorithmName() algorithm}. + * @throws CompressionException if the specified byte array cannot be compressed. */ - byte[] compress(byte[] payload) throws CompressionException; + @Deprecated + byte[] compress(byte[] content) throws CompressionException; /** - * Decompresses the specified compressed byte array according to the compression - * {@link #getAlgorithmName() algorithm}. The specified byte array must already be in compressed form - * according to the {@link #getAlgorithmName() algorithm}. + * Decompresses the specified compressed byte array, returning the decompressed byte array result. The + * specified byte array must already be in compressed form. * * @param compressed compressed bytes * @return decompressed bytes - * @throws CompressionException if the specified byte array cannot be decompressed according to the compression - * {@link #getAlgorithmName() algorithm}. + * @throws CompressionException if the specified byte array cannot be decompressed. */ + @Deprecated byte[] decompress(byte[] compressed) throws CompressionException; -} \ No newline at end of file +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/CompressionCodecResolver.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/CompressionCodecResolver.java (.../CompressionCodecResolver.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/CompressionCodecResolver.java (.../CompressionCodecResolver.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -20,17 +20,21 @@ * can use to decompress the JWT body. * *

JJWT's default {@link JwtParser} implementation supports both the - * {@link CompressionCodecs#DEFLATE DEFLATE} - * and {@link CompressionCodecs#GZIP GZIP} algorithms by default - you do not need to + * {@link Jwts.ZIP#DEF DEFLATE} and {@link Jwts.ZIP#GZIP GZIP} algorithms by default - you do not need to * specify a {@code CompressionCodecResolver} in these cases.

* - *

However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must implement + *

However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you can implement * your own {@link CompressionCodecResolver} and specify that when - * {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} and - * {@link io.jsonwebtoken.JwtParser#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.

+ * {@link io.jsonwebtoken.JwtBuilder#compressWith(io.jsonwebtoken.io.CompressionAlgorithm) building} and + * {@link io.jsonwebtoken.JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.

* + * @see JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver) + * @see JwtParserBuilder#zip() * @since 0.6.0 + * @deprecated in favor of {@link JwtParserBuilder#zip()} */ +@SuppressWarnings("DeprecatedIsStillUsed") +@Deprecated public interface CompressionCodecResolver { /** Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/CompressionCodecs.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/CompressionCodecs.java (.../CompressionCodecs.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/CompressionCodecs.java (.../CompressionCodecs.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,15 +15,15 @@ */ package io.jsonwebtoken; -import io.jsonwebtoken.lang.Classes; - /** * Provides default implementations of the {@link CompressionCodec} interface. * - * @see #DEFLATE - * @see #GZIP + * @see Jwts.ZIP#DEF + * @see Jwts.ZIP#GZIP * @since 0.7.0 + * @deprecated in favor of {@link Jwts.ZIP}. */ +@Deprecated //TODO: delete for 1.0 public final class CompressionCodecs { private CompressionCodecs() { @@ -32,18 +32,25 @@ /** * Codec implementing the JWA standard * deflate compression algorithm + * + * @deprecated in favor of {@link Jwts.ZIP#DEF}. */ - public static final CompressionCodec DEFLATE = - Classes.newInstance("io.jsonwebtoken.impl.compression.DeflateCompressionCodec"); + @Deprecated + public static final CompressionCodec DEFLATE = (CompressionCodec) Jwts.ZIP.DEF; /** * Codec implementing the gzip compression algorithm. - *

Compatibility Warning

+ * + *

Compatibility Warning

+ * *

This is not a standard JWA compression algorithm. Be sure to use this only when you are confident * that all parties accessing the token support the gzip algorithm.

- *

If you're concerned about compatibility, the {@link #DEFLATE DEFLATE} code is JWA standards-compliant.

+ * + *

If you're concerned about compatibility, the {@link Jwts.ZIP#DEF DEF} code is JWA standards-compliant.

+ * + * @deprecated in favor of {@link Jwts.ZIP#GZIP} */ - public static final CompressionCodec GZIP = - Classes.newInstance("io.jsonwebtoken.impl.compression.GzipCompressionCodec"); + @Deprecated + public static final CompressionCodec GZIP = (CompressionCodec) Jwts.ZIP.GZIP; } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/CompressionException.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/CompressionException.java (.../CompressionException.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/CompressionException.java (.../CompressionException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,17 +15,30 @@ */ package io.jsonwebtoken; +import io.jsonwebtoken.io.IOException; + /** - * Exception indicating that either compressing or decompressing an JWT body failed. + * Exception indicating that either compressing or decompressing a JWT body failed. * * @since 0.6.0 */ -public class CompressionException extends JwtException { +public class CompressionException extends IOException { + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ public CompressionException(String message) { super(message); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public CompressionException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ExpiredJwtException.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ExpiredJwtException.java (.../ExpiredJwtException.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ExpiredJwtException.java (.../ExpiredJwtException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -22,15 +22,24 @@ */ public class ExpiredJwtException extends ClaimJwtException { + /** + * Creates a new instance with the specified header, claims, and explanation message. + * + * @param header jwt header + * @param claims jwt claims (body) + * @param message the message explaining why the exception is thrown. + */ public ExpiredJwtException(Header header, Claims claims, String message) { super(header, claims, message); } /** - * @param header jwt header - * @param claims jwt claims (body) - * @param message exception message - * @param cause cause + * Creates a new instance with the specified header, claims, explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + * @param header jwt header + * @param claims jwt claims (body) * @since 0.5 */ public ExpiredJwtException(Header header, Claims claims, String message, Throwable cause) { Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Header.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Header.java (.../Header.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Header.java (.../Header.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -18,115 +18,149 @@ import java.util.Map; /** - * A JWT JOSE header. + * A JWT JOSE header. * - *

This is ultimately a JSON map and any values can be added to it, but JWT JOSE standard names are provided as - * type-safe getters and setters for convenience.

+ *

This is an immutable JSON map with convenient type-safe getters for JWT standard header parameter names.

* - *

Because this interface extends {@code Map<String, Object>}, if you would like to add your own properties, - * you simply use map methods, for example:

+ *

Because this interface extends Map<String, Object>, you can use standard {@code Map} + * accessor/iterator methods as desired, for example:

* - *
- * header.{@link Map#put(Object, Object) put}("headerParamName", "headerParamValue");
- * 
+ *
+ * header.get("someKey");
* - *

Creation

+ *

However, because {@code Header} instances are immutable, calling any of the map mutation methods + * (such as {@code Map.}{@link Map#put(Object, Object) put}, etc) will result in a runtime exception.

* - *

It is easiest to create a {@code Header} instance by calling one of the - * {@link Jwts#header() JWTs.header()} factory methods.

+ *

Security

* + *

The {@code Header} interface itself makes no implications of integrity protection via either digital signatures or + * encryption. Instead, {@link JwsHeader} and {@link JweHeader} represent this information for respective + * {@link Jws} and {@link Jwe} instances.

+ * + * @see ProtectedHeader + * @see JwsHeader + * @see JweHeader * @since 0.1 */ -public interface Header> extends Map { +public interface Header extends Map { - /** JWT {@code Type} (typ) value: "JWT" */ - public static final String JWT_TYPE = "JWT"; + /** + * JWT {@code Type} (typ) value: "JWT" + * + * @deprecated since 0.12.0 - this constant is never used within the JJWT codebase. + */ + @Deprecated + String JWT_TYPE = "JWT"; - /** JWT {@code Type} header parameter name: "typ" */ - public static final String TYPE = "typ"; + /** + * JWT {@code Type} header parameter name: "typ" + * @deprecated since 0.12.0 in favor of {@link #getType()}. + */ + @Deprecated + String TYPE = "typ"; - /** JWT {@code Content Type} header parameter name: "cty" */ - public static final String CONTENT_TYPE = "cty"; + /** + * JWT {@code Content Type} header parameter name: "cty" + * @deprecated since 0.12.0 in favor of {@link #getContentType()}. + */ + @Deprecated + String CONTENT_TYPE = "cty"; - /** JWT {@code Compression Algorithm} header parameter name: "zip" */ - public static final String COMPRESSION_ALGORITHM = "zip"; + /** + * JWT {@code Algorithm} header parameter name: "alg". + * + * @see JWS Algorithm Header + * @see JWE Algorithm Header + * @deprecated since 0.12.0 in favor of {@link #getAlgorithm()}. + */ + @Deprecated + String ALGORITHM = "alg"; - /** JJWT legacy/deprecated compression algorithm header parameter name: "calg" - * @deprecated use {@link #COMPRESSION_ALGORITHM} instead. */ + /** + * JWT {@code Compression Algorithm} header parameter name: "zip" + * @deprecated since 0.12.0 in favor of {@link #getCompressionAlgorithm()} + */ @Deprecated - public static final String DEPRECATED_COMPRESSION_ALGORITHM = "calg"; + String COMPRESSION_ALGORITHM = "zip"; /** - * Returns the - * typ (type) header value or {@code null} if not present. + * JJWT legacy/deprecated compression algorithm header parameter name: "calg" * - * @return the {@code typ} header value or {@code null} if not present. + * @deprecated use {@link #COMPRESSION_ALGORITHM} instead. */ - String getType(); + @Deprecated + String DEPRECATED_COMPRESSION_ALGORITHM = "calg"; /** - * Sets the JWT - * typ (Type) header value. A {@code null} value will remove the property from the JSON map. + * Returns the + * typ (Type) header value or {@code null} if not present. * - * @param typ the JWT JOSE {@code typ} header value or {@code null} to remove the property from the JSON map. - * @return the {@code Header} instance for method chaining. + * @return the {@code typ} header value or {@code null} if not present. */ - T setType(String typ); + String getType(); /** - * Returns the - * cty (Content Type) header value or {@code null} if not present. + * Returns the + * cty (Content Type) header value or {@code null} if not present. * - *

In the normal case where nested signing or encryption operations are not employed (i.e. a compact - * serialization JWT), the use of this header parameter is NOT RECOMMENDED. In the case that nested - * signing or encryption is employed, this Header Parameter MUST be present; in this case, the value MUST be - * {@code JWT}, to indicate that a Nested JWT is carried in this JWT. While media type names are not - * case-sensitive, it is RECOMMENDED that {@code JWT} always be spelled using uppercase characters for - * compatibility with legacy implementations. See - * JWT Appendix A.2 for - * an example of a Nested JWT.

+ *

The cty (Content Type) Header Parameter is used by applications to declare the + * IANA MediaType of the content + * (the payload). This is intended for use by the application when more than + * one kind of object could be present in the Payload; the application can use this value to disambiguate among + * the different kinds of objects that might be present. It will typically not be used by applications when + * the kind of object is already known. This parameter is ignored by JWT implementations (like JJWT); any + * processing of this parameter is performed by the JWS application. Use of this Header Parameter is OPTIONAL.

* + *

To keep messages compact in common situations, it is RECOMMENDED that producers omit an + * application/ prefix of a media type value in a {@code cty} Header Parameter when + * no other '/' appears in the media type value. A recipient using the media type value MUST + * treat it as if application/ were prepended to any {@code cty} value not containing a + * '/'. For instance, a {@code cty} value of example SHOULD be used to + * represent the application/example media type, whereas the media type + * application/example;part="1/2" cannot be shortened to + * example;part="1/2".

+ * * @return the {@code typ} header parameter value or {@code null} if not present. */ String getContentType(); /** - * Sets the JWT - * cty (Content Type) header parameter value. A {@code null} value will remove the property from - * the JSON map. + * Returns the JWT {@code alg} (Algorithm) header value or {@code null} if not present. * - *

In the normal case where nested signing or encryption operations are not employed (i.e. a compact - * serialization JWT), the use of this header parameter is NOT RECOMMENDED. In the case that nested - * signing or encryption is employed, this Header Parameter MUST be present; in this case, the value MUST be - * {@code JWT}, to indicate that a Nested JWT is carried in this JWT. While media type names are not - * case-sensitive, it is RECOMMENDED that {@code JWT} always be spelled using uppercase characters for - * compatibility with legacy implementations. See - * JWT Appendix A.2 for - * an example of a Nested JWT.

+ *
    + *
  • If the JWT is a Signed JWT (a JWS), the + * alg (Algorithm) header parameter identifies the cryptographic algorithm used to secure the + * JWS. Consider using {@link Jwts.SIG}.{@link io.jsonwebtoken.lang.Registry#get(Object) get(id)} + * to convert this string value to a type-safe {@code SecureDigestAlgorithm} instance.
  • + *
  • If the JWT is an Encrypted JWT (a JWE), the + * alg (Algorithm) header parameter + * identifies the cryptographic key management algorithm used to encrypt or determine the value of the Content + * Encryption Key (CEK). The encrypted content is not usable if the alg value does not represent a + * supported algorithm, or if the recipient does not have a key that can be used with that algorithm. Consider + * using {@link Jwts.KEY}.{@link io.jsonwebtoken.lang.Registry#get(Object) get(id)} to convert this string value + * to a type-safe {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm} instance.
  • + *
* - * @param cty the JWT JOSE {@code cty} header value or {@code null} to remove the property from the JSON map. + * @return the {@code alg} header value or {@code null} if not present. This will always be + * {@code non-null} on validly constructed JWT instances, but could be {@code null} during construction. + * @since 0.12.0 */ - T setContentType(String cty); + String getAlgorithm(); /** - * Returns the JWT zip (Compression Algorithm) header value or {@code null} if not present. + * Returns the JWT zip + * (Compression Algorithm) header parameter value or {@code null} if not present. * + *

Compatibility Note

+ * + *

While the JWT family of specifications only defines the zip header in the JWE + * (JSON Web Encryption) specification, JJWT will also support compression for JWS as well if you choose to use it. + * However, be aware that if you use compression when creating a JWS token, other libraries may not be able to + * parse the JWS. However, compression when creating JWE tokens should be universally accepted for any library + * that supports JWE.

+ * * @return the {@code zip} header parameter value or {@code null} if not present. * @since 0.6.0 */ String getCompressionAlgorithm(); - - /** - * Sets the JWT zip (Compression Algorithm) header parameter value. A {@code null} value will remove - * the property from the JSON map. - *

- *

The compression algorithm is NOT part of the JWT specification - * and must be used carefully since, is not expected that other libraries (including previous versions of this one) - * be able to deserialize a compressed JTW body correctly.

- * - * @param zip the JWT compression algorithm {@code zip} value or {@code null} to remove the property from the JSON map. - * @since 0.6.0 - */ - T setCompressionAlgorithm(String zip); - } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/HeaderMutator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/HeaderMutator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/HeaderMutator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2021 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; + +import io.jsonwebtoken.lang.MapMutator; + +/** + * Mutation (modifications) to a {@link Header Header} instance. + * + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface HeaderMutator> extends MapMutator { + + //IMPLEMENTOR NOTE: if this `algorithm` method ever needs to be exposed in the public API, it might be better to + // have it in the Jwts.HeaderBuilder interface and NOT this one: in the context of + // JwtBuilder.Header, there is never a reason for an application developer to call algorithm(id) + // directly because the KeyAlgorithm or SecureDigestAlgorithm instance must always be provided + // via the signWith or encryptWith methods. The JwtBuilder will always set the algorithm + // header based on these two instances, so there is no need for an app dev to do so. + /* + * Sets the JWT {@code alg} (Algorithm) header value. A {@code null} value will remove the property + * from the JSON map. + *
    + *
  • If the JWT is a Signed JWT (a JWS), the + * {@code alg} (Algorithm) header + * parameter identifies the cryptographic algorithm used to secure the JWS.
  • + *
  • If the JWT is an Encrypted JWT (a JWE), the + * alg (Algorithm) header parameter + * identifies the cryptographic key management algorithm used to encrypt or determine the value of the Content + * Encryption Key (CEK). The encrypted content is not usable if the alg value does not represent a + * supported algorithm, or if the recipient does not have a key that can be used with that algorithm.
  • + *
+ * + * @param alg the {@code alg} header value + * @return this header for method chaining + * @since 0.12.0 + * + T algorithm(String alg); + */ + + /** + * Sets the JWT + * typ (Type) header value. A {@code null} value will remove the property from the JSON map. + * + * @param typ the JWT JOSE {@code typ} header value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + */ + T type(String typ); + + /** + * Sets the compact + * cty (Content Type) header parameter value, used by applications to declare the + * IANA MediaType of the JWT + * payload. A {@code null} value will remove the property from the JSON map. + * + *

Compact Media Type Identifier

+ * + *

This method will automatically remove any application/ prefix from the + * {@code cty} string if possible according to the rules defined in the last paragraph of + * RFC 7517, Section 4.1.10:

+ *
+     *     To keep messages compact in common situations, it is RECOMMENDED that
+     *     producers omit an "application/" prefix of a media type value in a
+     *     "cty" Header Parameter when no other '/' appears in the media type
+     *     value.  A recipient using the media type value MUST treat it as if
+     *     "application/" were prepended to any "cty" value not containing a
+     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
+     *     represent the "application/example" media type, whereas the media
+     *     type "application/example;part="1/2"" cannot be shortened to
+     *     "example;part="1/2"".
+ * + *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the + * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as + * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media + * Type identifiers without needing JWT-specific prefix conditional logic in application code. + *

+ * + * @param cty the JWT {@code cty} header value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + */ + T contentType(String cty); + + /** + * Deprecated since of 0.12.0, delegates to {@link #type(String)}. + * + * @param typ the JWT JOSE {@code typ} header value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + * @see #type(String) + * @deprecated since 0.12.0 in favor of the more modern builder-style {@link #type(String)} method. + * This method will be removed before the 1.0 release. + */ + @Deprecated + T setType(String typ); + + /** + * Deprecated as of 0.12.0, delegates to {@link #contentType(String)}. + * + * @param cty the JWT JOSE {@code cty} header value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + * @see #contentType(String) + * @deprecated since 0.12.0 in favor of the more modern builder-style {@link #contentType(String)}. + */ + @Deprecated + T setContentType(String cty); + + /** + * Deprecated as of 0.12.0, there is no need to set this any longer as the {@code JwtBuilder} will + * always set the {@code zip} header as necessary. + * + * @param zip the JWT compression algorithm {@code zip} value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + * @since 0.6.0 + * @deprecated since 0.12.0 and will be removed before the 1.0 release. + */ + @Deprecated + T setCompressionAlgorithm(String zip); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Identifiable.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Identifiable.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Identifiable.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2021 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; + +/** + * An object that may be uniquely identified by an {@link #getId() id} relative to other instances of the same type. + * + *

The following table indicates how various JWT or JWK {@link #getId() getId()} values are used.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
JWA Identifiable Concepts
JJWT TypeHow {@link #getId()} is Used
{@link io.jsonwebtoken.Claims Claims}JWT's {@code jti} (JWT ID) + * claim.
{@link io.jsonwebtoken.security.Jwk Jwk}JWK's {@code kid} (Key ID) + * parameter value.
{@link io.jsonwebtoken.security.Curve Curve}JWK's {@code crv} (Curve) + * parameter value.
{@link io.jsonwebtoken.io.CompressionAlgorithm CompressionAlgorithm}JWE protected header's + * {@code zip} (Compression Algorithm) + * parameter value.
{@link io.jsonwebtoken.security.HashAlgorithm HashAlgorithm}Within a {@link io.jsonwebtoken.security.JwkThumbprint JwkThumbprint}'s URI value.
{@link io.jsonwebtoken.security.MacAlgorithm MacAlgorithm}JWS protected header's + * {@code alg} (Algorithm) parameter value.
{@link io.jsonwebtoken.security.SignatureAlgorithm SignatureAlgorithm}JWS protected header's + * {@code alg} (Algorithm) parameter value.
{@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm}JWE protected header's + * {@code alg} (Key Management Algorithm) + * parameter value.
{@link io.jsonwebtoken.security.AeadAlgorithm AeadAlgorithm}JWE protected header's + * {@code enc} (Encryption Algorithm) + * parameter value.
+ * + * @since 0.12.0 + */ +public interface Identifiable { + + /** + * Returns the unique string identifier of the associated object. + * + * @return the unique string identifier of the associated object. + */ + String getId(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/IncorrectClaimException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/IncorrectClaimException.java (.../IncorrectClaimException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/IncorrectClaimException.java (.../IncorrectClaimException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -23,11 +23,30 @@ */ public class IncorrectClaimException extends InvalidClaimException { - public IncorrectClaimException(Header header, Claims claims, String message) { - super(header, claims, message); + /** + * Creates a new instance with the specified header, claims and explanation message. + * + * @param header the header inspected + * @param claims the claims with the incorrect claim value + * @param claimName the name of the claim that could not be validated + * @param claimValue the value of the claim that could not be validated + * @param message the exception message + */ + public IncorrectClaimException(Header header, Claims claims, String claimName, Object claimValue, String message) { + super(header, claims, claimName, claimValue, message); } - public IncorrectClaimException(Header header, Claims claims, String message, Throwable cause) { - super(header, claims, message, cause); + /** + * Creates a new instance with the specified header, claims, explanation message and underlying cause. + * + * @param header the header inspected + * @param claims the claims with the incorrect claim value + * @param claimName the name of the claim that could not be validated + * @param claimValue the value of the claim that could not be validated + * @param message the exception message + * @param cause the underlying cause that resulted in this exception being thrown + */ + public IncorrectClaimException(Header header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) { + super(header, claims, claimName, claimValue, message, cause); } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/InvalidClaimException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/InvalidClaimException.java (.../InvalidClaimException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/InvalidClaimException.java (.../InvalidClaimException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -21,35 +21,66 @@ * * @see IncorrectClaimException * @see MissingClaimException - * * @since 0.6 */ public class InvalidClaimException extends ClaimJwtException { - private String claimName; - private Object claimValue; + /** + * The name of the invalid claim. + */ + private final String claimName; - protected InvalidClaimException(Header header, Claims claims, String message) { + /** + * The claim value that could not be validated. + */ + private final Object claimValue; + + /** + * Creates a new instance with the specified header, claims and explanation message. + * + * @param header the header inspected + * @param claims the claims obtained + * @param claimName the name of the claim that could not be validated + * @param claimValue the value of the claim that could not be validated + * @param message the exception message + */ + protected InvalidClaimException(Header header, Claims claims, String claimName, Object claimValue, String message) { super(header, claims, message); + this.claimName = claimName; + this.claimValue = claimValue; } - protected InvalidClaimException(Header header, Claims claims, String message, Throwable cause) { + /** + * Creates a new instance with the specified header, claims, explanation message and underlying cause. + * + * @param header the header inspected + * @param claims the claims obtained + * @param claimName the name of the claim that could not be validated + * @param claimValue the value of the claim that could not be validated + * @param message the exception message + * @param cause the underlying cause that resulted in this exception being thrown + */ + protected InvalidClaimException(Header header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) { super(header, claims, message, cause); + this.claimName = claimName; + this.claimValue = claimValue; } + /** + * Returns the name of the invalid claim. + * + * @return the name of the invalid claim. + */ public String getClaimName() { return claimName; } - public void setClaimName(String claimName) { - this.claimName = claimName; - } - + /** + * Returns the claim value that could not be validated. + * + * @return the claim value that could not be validated. + */ public Object getClaimValue() { return claimValue; } - - public void setClaimValue(Object claimValue) { - this.claimValue = claimValue; - } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Jwe.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Jwe.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Jwe.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 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; + +/** + * An encrypted JWT, called a "JWE", per the + * JWE (RFC 7516) Specification. + * + * @param payload type, either {@link Claims} or {@code byte[]} content. + * @since 0.12.0 + */ +public interface Jwe extends ProtectedJwt { + + /** + * Visitor implementation that ensures the visited JWT is a JSON Web Encryption ('JWE') message with an + * authenticated and decrypted {@code byte[]} array payload, and rejects all others with an + * {@link UnsupportedJwtException}. + * + * @see SupportedJwtVisitor#onDecryptedContent(Jwe) + * @since 0.12.0 + */ + @SuppressWarnings("UnnecessaryModifier") + public static final JwtVisitor> CONTENT = new SupportedJwtVisitor>() { + @Override + public Jwe onDecryptedContent(Jwe jwe) { + return jwe; + } + }; + + /** + * Visitor implementation that ensures the visited JWT is a JSON Web Encryption ('JWE') message with an + * authenticated and decrypted {@link Claims} payload, and rejects all others with an + * {@link UnsupportedJwtException}. + * + * @see SupportedJwtVisitor#onDecryptedClaims(Jwe) + * @since 0.12.0 + */ + @SuppressWarnings("UnnecessaryModifier") + public static final JwtVisitor> CLAIMS = new SupportedJwtVisitor>() { + @Override + public Jwe onDecryptedClaims(Jwe jwe) { + return jwe; + } + }; + + /** + * Returns the Initialization Vector used during JWE encryption and decryption. + * + * @return the Initialization Vector used during JWE encryption and decryption. + */ + byte[] getInitializationVector(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JweHeader.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JweHeader.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JweHeader.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2021 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; + +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.PublicJwk; + +import javax.crypto.SecretKey; +import java.security.Key; + +/** + * A JWE header. + * + * @since 0.12.0 + */ +public interface JweHeader extends ProtectedHeader { + + /** + * Returns the JWE {@code enc} (Encryption + * Algorithm) header value or {@code null} if not present. + * + *

The JWE {@code enc} (encryption algorithm) Header Parameter identifies the content encryption algorithm + * used to perform authenticated encryption on the plaintext to produce the ciphertext and the JWE + * {@code Authentication Tag}.

+ * + *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by + * supplying an {@link AeadAlgorithm} to a {@link JwtBuilder} via one of its + * {@link JwtBuilder#encryptWith(SecretKey, AeadAlgorithm) encryptWith(SecretKey, AeadAlgorithm)} or + * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} + * methods. JJWT will then set this {@code enc} header value automatically to the {@code AeadAlgorithm}'s + * {@link AeadAlgorithm#getId() getId()} value during encryption.

+ * + * @return the JWE {@code enc} (Encryption Algorithm) header value or {@code null} if not present. This will + * always be {@code non-null} on validly-constructed JWE instances, but could be {@code null} during construction. + * @see JwtBuilder#encryptWith(SecretKey, AeadAlgorithm) + * @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) + */ + String getEncryptionAlgorithm(); + + /** + * Returns the {@code epk} (Ephemeral + * Public Key) header value created by the JWE originator for use with key agreement algorithms, or + * {@code null} if not present. + * + *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by + * supplying an ECDH-ES {@link KeyAlgorithm} to a {@link JwtBuilder} via its + * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} + * method. The ECDH-ES {@code KeyAlgorithm} implementation will then set this {@code epk} header value + * automatically when producing the encryption key.

+ * + * @return the {@code epk} (Ephemeral + * Public Key) header value created by the JWE originator for use with key agreement algorithms, or + * {@code null} if not present. + * @see Jwts.KEY + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + PublicJwk getEphemeralPublicKey(); + + /** + * Returns any information about the JWE producer for use with key agreement algorithms, or {@code null} if not + * present. + * + * @return any information about the JWE producer for use with key agreement algorithms, or {@code null} if not + * present. + * @see JWE apu (Agreement PartyUInfo) Header Parameter + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + byte[] getAgreementPartyUInfo(); + + /** + * Returns any information about the JWE recipient for use with key agreement algorithms, or {@code null} if not + * present. + * + * @return any information about the JWE recipient for use with key agreement algorithms, or {@code null} if not + * present. + * @see JWE apv (Agreement PartyVInfo) Header Parameter + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + byte[] getAgreementPartyVInfo(); + + /** + * Returns the 96-bit "iv" + * (Initialization Vector) generated during key encryption, or {@code null} if not present. + * Set by AES GCM {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm} implementations. + * + *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by + * supplying an AES GCM Wrap {@link KeyAlgorithm} to a {@link JwtBuilder} via its + * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} + * method. The AES GCM Wrap {@code KeyAlgorithm} implementation will then set this {@code iv} header value + * automatically when producing the encryption key.

+ * + * @return the 96-bit initialization vector generated during key encryption, or {@code null} if not present. + * @see Jwts.KEY#A128GCMKW + * @see Jwts.KEY#A192GCMKW + * @see Jwts.KEY#A256GCMKW + */ + byte[] getInitializationVector(); + + /** + * Returns the 128-bit "tag" + * (Authentication Tag) resulting from key encryption, or {@code null} if not present. + * + *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by + * supplying an AES GCM Wrap {@link KeyAlgorithm} to a {@link JwtBuilder} via its + * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} + * method. The AES GCM Wrap {@code KeyAlgorithm} implementation will then set this {@code tag} header value + * automatically when producing the encryption key.

+ * + * @return the 128-bit authentication tag resulting from key encryption, or {@code null} if not present. + * @see Jwts.KEY#A128GCMKW + * @see Jwts.KEY#A192GCMKW + * @see Jwts.KEY#A256GCMKW + */ + byte[] getAuthenticationTag(); + + /** + * Returns the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, or {@code null} + * if not present. Used with password-based {@link io.jsonwebtoken.security.KeyAlgorithm KeyAlgorithm}s. + * + * @return the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, or {@code null} + * if not present. + * @see JWE p2c (PBES2 Count) Header Parameter + * @see Jwts.KEY#PBES2_HS256_A128KW + * @see Jwts.KEY#PBES2_HS384_A192KW + * @see Jwts.KEY#PBES2_HS512_A256KW + */ + Integer getPbes2Count(); + + /** + * Returns the PBKDF2 {@code Salt Input} value necessary to derive the key used during JWE encryption, or + * {@code null} if not present. + * + *

Note that there is no corresponding 'setter' method for this 'getter' because JJWT users set this value by + * supplying a password-based {@link KeyAlgorithm} to a {@link JwtBuilder} via its + * {@link JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} + * method. The password-based {@code KeyAlgorithm} implementation will then set this {@code p2s} header value + * automatically when producing the encryption key.

+ * + * @return the PBKDF2 {@code Salt Input} value necessary to derive the key used during JWE encryption, or + * {@code null} if not present. + * @see JWE p2s (PBES2 Salt Input) Header Parameter + * @see Jwts.KEY#PBES2_HS256_A128KW + * @see Jwts.KEY#PBES2_HS384_A192KW + * @see Jwts.KEY#PBES2_HS512_A256KW + */ + byte[] getPbes2Salt(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JweHeaderMutator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JweHeaderMutator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JweHeaderMutator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2021 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; + +import io.jsonwebtoken.security.KeyAlgorithm; + +/** + * Mutation (modifications) to a {@link JweHeader} instance. + * + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface JweHeaderMutator> extends ProtectedHeaderMutator { + + /** + * Sets any information about the JWE producer for use with key agreement algorithms. A {@code null} or empty value + * removes the property from the JSON map. + * + * @param info information about the JWE producer to use with key agreement algorithms. + * @return the header for method chaining. + * @see JWE apu (Agreement PartyUInfo) Header Parameter + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + T agreementPartyUInfo(byte[] info); + + /** + * Sets any information about the JWE producer for use with key agreement algorithms. A {@code null} value removes + * the property from the JSON map. + * + *

If not {@code null}, this is a convenience method that calls the equivalent of the following:

+ *
+     * {@link #agreementPartyUInfo(byte[]) agreementPartyUInfo}(info.getBytes(StandardCharsets.UTF_8))
+ * + * @param info information about the JWE producer to use with key agreement algorithms. + * @return the header for method chaining. + * @see JWE apu (Agreement PartyUInfo) Header Parameter + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + T agreementPartyUInfo(String info); + + /** + * Sets any information about the JWE recipient for use with key agreement algorithms. A {@code null} value removes + * the property from the JSON map. + * + * @param info information about the JWE recipient to use with key agreement algorithms. + * @return the header for method chaining. + * @see JWE apv (Agreement PartyVInfo) Header Parameter + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + T agreementPartyVInfo(byte[] info); + + /** + * Sets any information about the JWE recipient for use with key agreement algorithms. A {@code null} value removes + * the property from the JSON map. + * + *

If not {@code null}, this is a convenience method that calls the equivalent of the following:

+ *
+     * {@link #agreementPartyVInfo(byte[]) setAgreementPartVUInfo}(info.getBytes(StandardCharsets.UTF_8))
+ * + * @param info information about the JWE recipient to use with key agreement algorithms. + * @return the header for method chaining. + * @see JWE apv (Agreement PartyVInfo) Header Parameter + * @see Jwts.KEY#ECDH_ES + * @see Jwts.KEY#ECDH_ES_A128KW + * @see Jwts.KEY#ECDH_ES_A192KW + * @see Jwts.KEY#ECDH_ES_A256KW + */ + T agreementPartyVInfo(String info); + + /** + * Sets the number of PBKDF2 iterations necessary to derive the key used during JWE encryption. If this value + * is not set when a password-based {@link KeyAlgorithm} is used, JJWT will automatically choose a suitable + * number of iterations based on + * OWASP PBKDF2 Iteration Recommendations. + * + *

Minimum Count

+ * + *

{@code IllegalArgumentException} will be thrown during encryption if a specified {@code count} is + * less than 1000 (one thousand), which is the + * minimum number recommended by the + * JWA specification. Anything less is susceptible to security attacks so the default PBKDF2 + * {@code KeyAlgorithm} implementations reject such values.

+ * + * @param count the number of PBKDF2 iterations necessary to derive the key used during JWE encryption, must be + * greater than or equal to 1000 (one thousand). + * @return the header for method chaining + * @see JWE p2c (PBES2 Count) Header Parameter + * @see Jwts.KEY#PBES2_HS256_A128KW + * @see Jwts.KEY#PBES2_HS384_A192KW + * @see Jwts.KEY#PBES2_HS512_A256KW + * @see OWASP PBKDF2 Iteration Recommendations + */ + T pbes2Count(int count); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Jws.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Jws.java (.../Jws.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Jws.java (.../Jws.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -18,11 +18,49 @@ /** * An expanded (not compact/serialized) Signed JSON Web Token. * - * @param the type of the JWS body contents, either a String or a {@link Claims} instance. - * + * @param

the type of the JWS payload, either a byte[] or a {@link Claims} instance. * @since 0.1 */ -public interface Jws extends Jwt { +public interface Jws

extends ProtectedJwt { - String getSignature(); + /** + * Visitor implementation that ensures the visited JWT is a JSON Web Signature ('JWS') message with a + * cryptographically authenticated/verified {@code byte[]} array payload, and rejects all others with an + * {@link UnsupportedJwtException}. + * + * @see SupportedJwtVisitor#onVerifiedContent(Jws) + * @since 0.12.0 + */ + @SuppressWarnings("UnnecessaryModifier") + public static final JwtVisitor> CONTENT = new SupportedJwtVisitor>() { + @Override + public Jws onVerifiedContent(Jws jws) { + return jws; + } + }; + + /** + * Visitor implementation that ensures the visited JWT is a JSON Web Signature ('JWS') message with a + * cryptographically authenticated/verified {@link Claims} payload, and rejects all others with an + * {@link UnsupportedJwtException}. + * + * @see SupportedJwtVisitor#onVerifiedClaims(Jws) + * @since 0.12.0 + */ + @SuppressWarnings("UnnecessaryModifier") + public static final JwtVisitor> CLAIMS = new SupportedJwtVisitor>() { + @Override + public Jws onVerifiedClaims(Jws jws) { + return jws; + } + }; + + /** + * Returns the verified JWS signature as a Base64Url string. + * + * @return the verified JWS signature as a Base64Url string. + * @deprecated since 0.12.0 in favor of {@link #getDigest() getDigest()}. + */ + @Deprecated + String getSignature(); //TODO for 1.0: return a byte[] } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwsHeader.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwsHeader.java (.../JwsHeader.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwsHeader.java (.../JwsHeader.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,92 +16,92 @@ package io.jsonwebtoken; /** - * A JWS header. + * A JWS header. * - * @param header type * @since 0.1 */ -public interface JwsHeader> extends Header { +public interface JwsHeader extends ProtectedHeader { - /** JWS {@code Algorithm} header parameter name: "alg" */ - public static final String ALGORITHM = "alg"; + /** + * JWS Algorithm Header name: the string literal alg + * + * @deprecated since 0.12.0 in favor of {@link #getAlgorithm()} + */ + @Deprecated + String ALGORITHM = "alg"; - /** JWS {@code JWT Set URL} header parameter name: "jku" */ - public static final String JWK_SET_URL = "jku"; + /** + * JWS JWK Set URL Header name: the string literal jku + * + * @deprecated since 0.12.0 in favor of {@link #getJwkSetUrl()} + */ + @Deprecated + String JWK_SET_URL = "jku"; - /** JWS {@code JSON Web Key} header parameter name: "jwk" */ - public static final String JSON_WEB_KEY = "jwk"; + /** + * JWS JSON Web Key Header name: the string literal jwk + * + * @deprecated since 0.12.0 in favor of {@link #getJwk()} + */ + @Deprecated + String JSON_WEB_KEY = "jwk"; - /** JWS {@code Key ID} header parameter name: "kid" */ - public static final String KEY_ID = "kid"; + /** + * JWS Key ID Header name: the string literal kid + * + * @deprecated since 0.12.0 in favor of {@link #getKeyId()} + */ + @Deprecated + String KEY_ID = "kid"; - /** JWS {@code X.509 URL} header parameter name: "x5u" */ - public static final String X509_URL = "x5u"; + /** + * JWS X.509 URL Header name: the string literal x5u + * + * @deprecated since 0.12.0 in favor of {@link #getX509Url()} + */ + @Deprecated + String X509_URL = "x5u"; - /** JWS {@code X.509 Certificate Chain} header parameter name: "x5c" */ - public static final String X509_CERT_CHAIN = "x5c"; + /** + * JWS X.509 Certificate Chain Header name: the string literal x5c + * + * @deprecated since 0.12.0 in favor of {@link #getX509Chain()} + */ + @Deprecated + String X509_CERT_CHAIN = "x5c"; - /** JWS {@code X.509 Certificate SHA-1 Thumbprint} header parameter name: "x5t" */ - public static final String X509_CERT_SHA1_THUMBPRINT = "x5t"; - - /** JWS {@code X.509 Certificate SHA-256 Thumbprint} header parameter name: "x5t#S256" */ - public static final String X509_CERT_SHA256_THUMBPRINT = "x5t#S256"; - - /** JWS {@code Critical} header parameter name: "crit" */ - public static final String CRITICAL = "crit"; - /** - * Returns the JWS - * alg (algorithm) header value or {@code null} if not present. + * JWS X.509 Certificate SHA-1 Thumbprint Header name: the string literal x5t * - *

The algorithm header parameter identifies the cryptographic algorithm used to secure the JWS. Consider - * using {@link io.jsonwebtoken.SignatureAlgorithm#forName(String) SignatureAlgorithm.forName} to convert this - * string value to a type-safe enum instance.

- * - * @return the JWS {@code alg} header value or {@code null} if not present. This will always be - * {@code non-null} on validly constructed JWS instances, but could be {@code null} during construction. + * @deprecated since 0.12.0 in favor of {@link #getX509Sha1Thumbprint()} */ - String getAlgorithm(); + @Deprecated + String X509_CERT_SHA1_THUMBPRINT = "x5t"; /** - * Sets the JWT - * alg (Algorithm) header value. A {@code null} value will remove the property from the JSON map. + * JWS X.509 Certificate SHA-256 Thumbprint Header name: the string literal x5t#S256 * - *

The algorithm header parameter identifies the cryptographic algorithm used to secure the JWS. Consider - * using a type-safe {@link io.jsonwebtoken.SignatureAlgorithm SignatureAlgorithm} instance and using its - * {@link io.jsonwebtoken.SignatureAlgorithm#getValue() value} as the argument to this method.

- * - * @param alg the JWS {@code alg} header value or {@code null} to remove the property from the JSON map. - * @return the {@code Header} instance for method chaining. + * @deprecated since 0.12.0 in favor of {@link #getX509Sha256Thumbprint()} */ - T setAlgorithm(String alg); + @Deprecated + String X509_CERT_SHA256_THUMBPRINT = "x5t#S256"; /** - * Returns the JWS - * kid (Key ID) header value or {@code null} if not present. + * JWS Critical Header name: the string literal crit * - *

The keyId header parameter is a hint indicating which key was used to secure the JWS. This parameter allows - * originators to explicitly signal a change of key to recipients. The structure of the keyId value is - * unspecified.

- * - *

When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.

- * - * @return the JWS {@code kid} header value or {@code null} if not present. + * @deprecated since 0.12.0 in favor of {@link #getCritical()} */ - String getKeyId(); + @Deprecated + String CRITICAL = "crit"; /** - * Sets the JWT - * kid (Key ID) header value. A {@code null} value will remove the property from the JSON map. + * Returns {@code true} if the payload is Base64Url-encoded per standard JWS rules, or {@code false} if the + * RFC 7797: JSON Web Signature (JWS) Unencoded Payload + * Option has been specified. * - *

The keyId header parameter is a hint indicating which key was used to secure the JWS. This parameter allows - * originators to explicitly signal a change of key to recipients. The structure of the keyId value is - * unspecified.

- * - *

When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.

- * - * @param kid the JWS {@code kid} header value or {@code null} to remove the property from the JSON map. - * @return the {@code Header} instance for method chaining. + * @return {@code true} if the payload is Base64Url-encoded per standard JWS rules, or {@code false} if the + * RFC 7797: JSON Web Signature (JWS) Unencoded Payload + * Option has been specified. */ - T setKeyId(String kid); + boolean isPayloadEncoded(); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Jwt.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Jwt.java (.../Jwt.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Jwt.java (.../Jwt.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -18,23 +18,79 @@ /** * An expanded (not compact/serialized) JSON Web Token. * - * @param the type of the JWT body contents, either a String or a {@link Claims} instance. - * + * @param the type of the JWT header + * @param

the type of the JWT payload, either a content byte array or a {@link Claims} instance. * @since 0.1 */ -public interface Jwt { +public interface Jwt { /** + * Visitor implementation that ensures the visited JWT is an unsecured content JWT (one not cryptographically + * signed or encrypted) and rejects all others with an {@link UnsupportedJwtException}. + * + * @see SupportedJwtVisitor#onUnsecuredContent(Jwt) + * @since 0.12.0 + */ + @SuppressWarnings("UnnecessaryModifier") + public static final JwtVisitor> UNSECURED_CONTENT = new SupportedJwtVisitor>() { + @Override + public Jwt onUnsecuredContent(Jwt jwt) { + return jwt; + } + }; + + /** + * Visitor implementation that ensures the visited JWT is an unsecured {@link Claims} JWT (one not + * cryptographically signed or encrypted) and rejects all others with an {@link UnsupportedJwtException}. + * + * @see SupportedJwtVisitor#onUnsecuredClaims(Jwt) + * @since 0.12.0 + */ + @SuppressWarnings("UnnecessaryModifier") + public static final JwtVisitor> UNSECURED_CLAIMS = new SupportedJwtVisitor>() { + @Override + public Jwt onUnsecuredClaims(Jwt jwt) { + return jwt; + } + }; + + /** * Returns the JWT {@link Header} or {@code null} if not present. * * @return the JWT {@link Header} or {@code null} if not present. */ H getHeader(); /** - * Returns the JWT body, either a {@code String} or a {@code Claims} instance. + * Returns the JWT payload, either a {@code byte[]} or a {@code Claims} instance. Use + * {@link #getPayload()} instead, as this method will be removed prior to the 1.0 release. * - * @return the JWT body, either a {@code String} or a {@code Claims} instance. + * @return the JWT payload, either a {@code byte[]} or a {@code Claims} instance. + * @deprecated since 0.12.0 because it has been renamed to {@link #getPayload()}. 'Payload' (not + * body) is what the JWT specifications call this property, so it has been renamed to reflect the correct JWT + * nomenclature/taxonomy. */ - B getBody(); + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + P getBody(); // TODO: remove for 1.0 + + /** + * Returns the JWT payload, either a {@code byte[]} or a {@code Claims} instance. If the payload is a byte + * array, and if the JWT creator set the (optional) {@link Header#getContentType() contentType} header + * value, the application may inspect the {@code contentType} value to determine how to convert the byte array to + * the final content type as desired. + * + * @return the JWT payload, either a {@code byte[]} or a {@code Claims} instance. + * @since 0.12.0 + */ + P getPayload(); + + /** + * Invokes the specified {@code visitor}'s appropriate type-specific {@code visit} method based on this JWT's type. + * + * @param visitor the visitor to invoke. + * @param the value type returned from the {@code visit} method. + * @return the value returned from visitor's {@code visit} method implementation. + */ + T accept(JwtVisitor visitor); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtBuilder.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtBuilder.java (.../JwtBuilder.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtBuilder.java (.../JwtBuilder.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,370 +15,732 @@ */ package io.jsonwebtoken; +import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Encoder; import io.jsonwebtoken.io.Serializer; +import io.jsonwebtoken.lang.Conjunctor; +import io.jsonwebtoken.lang.MapMutator; +import io.jsonwebtoken.security.AeadAlgorithm; import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.KeyAlgorithm; import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.Password; +import io.jsonwebtoken.security.SecureDigestAlgorithm; +import io.jsonwebtoken.security.WeakKeyException; +import io.jsonwebtoken.security.X509Builder; + +import javax.crypto.SecretKey; +import java.io.InputStream; +import java.io.OutputStream; import java.security.Key; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.interfaces.ECKey; +import java.security.interfaces.RSAKey; import java.util.Date; import java.util.Map; /** - * A builder for constructing JWTs. + * A builder for constructing Unprotected JWTs, Signed JWTs (aka 'JWS's) and Encrypted JWTs (aka 'JWE's). * * @since 0.1 */ public interface JwtBuilder extends ClaimsMutator { - //replaces any existing header with the specified header. + /** + * Sets the JCA Provider to use during cryptographic signing or encryption operations, or {@code null} if the + * JCA subsystem preferred provider should be used. + * + * @param provider the JCA Provider to use during cryptographic signing or encryption operations, or {@code null} if the + * JCA subsystem preferred provider should be used. + * @return the builder for method chaining. + * @since 0.12.0 + */ + JwtBuilder provider(Provider provider); /** - * Sets (and replaces) any existing header with the specified header. If you do not want to replace the existing - * header and only want to append to it, use the {@link #setHeaderParams(java.util.Map)} method instead. + * Sets the {@link SecureRandom} to use during cryptographic signing or encryption operations, or {@code null} if + * a default {@link SecureRandom} should be used. * - * @param header the header to set (and potentially replace any existing header). + * @param secureRandom the {@link SecureRandom} to use during cryptographic signing or encryption operations, or + * {@code null} if a default {@link SecureRandom} should be used. * @return the builder for method chaining. + * @since 0.12.0 */ - JwtBuilder setHeader(Header header); + JwtBuilder random(SecureRandom secureRandom); /** - * Sets (and replaces) any existing header with the specified header. If you do not want to replace the existing - * header and only want to append to it, use the {@link #setHeaderParams(java.util.Map)} method instead. + * Returns the {@code Header} to use to modify the constructed JWT's header name/value pairs as desired. + * When finished, callers may return to JWT construction via the {@link BuilderHeader#and() and()} method. + * For example: * - * @param header the header to set (and potentially replace any existing header). + *

+     * String jwt = Jwts.builder()
+     *
+     *     .header()
+     *         .keyId("keyId")
+     *         .add("aName", aValue)
+     *         .add(myHeaderMap)
+     *         // ... etc ...
+     *         .{@link BuilderHeader#and() and()} //return back to the JwtBuilder
+     *
+     *     .subject("Joe") // resume JwtBuilder calls
+     *     // ... etc ...
+     *     .compact();
+ * + * @return the {@link BuilderHeader} to use for header construction. + * @since 0.12.0 + */ + BuilderHeader header(); + + /** + * Per standard Java idiom 'setter' conventions, this method sets (and fully replaces) any existing header with the + * specified name/value pairs. This is a wrapper method for: + * + *
+     * {@link #header()}.{@link MapMutator#empty() empty()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()}
+ * + *

If you do not want to replace the existing header and only want to append to it, + * call {@link #header()}.{@link io.jsonwebtoken.lang.MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()} instead.

+ * + * @param map the name/value pairs to set as (and potentially replace) the constructed JWT header. * @return the builder for method chaining. + * @deprecated since 0.12.0 in favor of + * {@link #header()}.{@link MapMutator#empty() empty()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()} + * (to replace all header parameters) or + * {@link #header()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()} + * to only append the {@code map} entries. This method will be removed before the 1.0 release. */ - JwtBuilder setHeader(Map header); + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + JwtBuilder setHeader(Map map); /** - * Applies the specified name/value pairs to the header. If a header does not yet exist at the time this method - * is called, one will be created automatically before applying the name/value pairs. + * Adds the specified name/value pairs to the header. Any parameter with an empty or null value will remove the + * entry from the header. This is a wrapper method for: + *
+     * {@link #header()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()}
* * @param params the header name/value pairs to append to the header. * @return the builder for method chaining. + * @deprecated since 0.12.0 in favor of + * {@link #header()}.{@link MapMutator#add(Map) add(map)}.{@link BuilderHeader#and() and()}. + * This method will be removed before the 1.0 release. */ - JwtBuilder setHeaderParams(Map params); + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + JwtBuilder setHeaderParams(Map params); - //sets the specified header parameter, overwriting any previous value under the same name. - /** - * Applies the specified name/value pair to the header. If a header does not yet exist at the time this method - * is called, one will be created automatically before applying the name/value pair. + * Adds the specified name/value pair to the header. If the value is {@code null} or empty, the parameter will + * be removed from the header entirely. This is a wrapper method for: + *
+     * {@link #header()}.{@link MapMutator#add(Object, Object) add(name, value)}.{@link BuilderHeader#and() and()}
* * @param name the header parameter name * @param value the header parameter value * @return the builder for method chaining. + * @deprecated since 0.12.0 in favor of + * {@link #header()}.{@link MapMutator#add(Object, Object) add(name, value)}.{@link BuilderHeader#and() and()}. + * This method will be removed before the 1.0 release. */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated JwtBuilder setHeaderParam(String name, Object value); /** - * Sets the JWT's payload to be a plaintext (non-JSON) string. If you want the JWT body to be JSON, use the - * {@link #setClaims(Claims)} or {@link #setClaims(java.util.Map)} methods instead. + * Since JJWT 0.12.0, this is an alias for {@link #content(String)}. This method will be removed + * before the 1.0 release. * - *

The payload and claims properties are mutually exclusive - only one of the two may be used.

- * - * @param payload the plaintext (non-JSON) string that will be the body of the JWT. + * @param payload the string used to set UTF-8-encoded bytes as the JWT payload. * @return the builder for method chaining. + * @see #content(String) + * @deprecated since 0.12.0 in favor of {@link #content(String)} + * because both Claims and Content are technically 'payloads', so this method name is misleading. This method will + * be removed before the 1.0 release. */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated JwtBuilder setPayload(String payload); /** - * Sets the JWT payload to be a JSON Claims instance. If you do not want the JWT body to be JSON and instead want - * it to be a plaintext string, use the {@link #setPayload(String)} method instead. + * Sets the JWT payload to be the specified string's UTF-8 bytes. This is a convenience method semantically + * equivalent to calling: * - *

The payload and claims properties are mutually exclusive - only one of the two may be used.

+ *
+     * {@link #content(byte[]) content}(payload.getBytes(StandardCharsets.UTF_8))
* - * @param claims the JWT claims to be set as the JWT body. + *

Content Type Recommendation

+ * + *

Unless you are confident that the JWT recipient will always know to convert the payload bytes + * to a UTF-8 string without additional metadata, it is strongly recommended to use the + * {@link #content(String, String)} method instead of this one. That method ensures that a JWT recipient can + * inspect the {@code cty} header to know how to handle the payload bytes without ambiguity.

+ * + *

Mutually Exclusive Claims and Content

+ * + *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} + * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the + * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

+ * + * @param content the content string to use for the JWT payload * @return the builder for method chaining. + * @see #content(String, String) + * @see #content(byte[], String) + * @see #content(InputStream, String) + * @since 0.12.0 */ - JwtBuilder setClaims(Claims claims); + JwtBuilder content(String content); /** - * Sets the JWT payload to be a JSON Claims instance populated by the specified name/value pairs. If you do not - * want the JWT body to be JSON and instead want it to be a plaintext string, use the {@link #setPayload(String)} - * method instead. + * Sets the JWT payload to be the specified content byte array. This is a convenience method semantically + * equivalent to calling: + *
+     * {@link #content(InputStream) content}(new ByteArrayInputStream(content))
* - *

The payload* and claims* properties are mutually exclusive - only one of the two may be used.

+ *

Content Type Recommendation

* - * @param claims the JWT claims to be set as the JWT body. + *

Unless you are confident that the JWT recipient will always know how to use the payload bytes + * without additional metadata, it is strongly recommended to also set the + * {@link Header#getContentType() contentType} header. For example:

+ * + *
+     * content(bytes).{@link #header() header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
+ * + *

This ensures a JWT recipient can inspect the {@code cty} header to know how to handle the payload bytes + * without ambiguity.

+ * + *

Mutually Exclusive Claims and Content

+ * + *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} + * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the + * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

+ * + * @param content the content byte array to use as the JWT payload * @return the builder for method chaining. + * @see #content(byte[], String) + * @since 0.12.0 */ - JwtBuilder setClaims(Map claims); + JwtBuilder content(byte[] content); /** - * Adds all given name/value pairs to the JSON Claims in the payload. If a Claims instance does not yet exist at the - * time this method is called, one will be created automatically before applying the name/value pairs. + * Sets the JWT payload to be the bytes in the specified content stream. * - *

The payload and claims properties are mutually exclusive - only one of the two may be used.

+ *

Content Type Recommendation

* - * @param claims the JWT claims to be added to the JWT body. + *

Unless you are confident that the JWT recipient will always know how to use the payload bytes + * without additional metadata, it is strongly recommended to also set the + * {@link HeaderMutator#contentType(String) contentType} header. For example:

+ * + *
+     * content(in).{@link #header() header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
+ * + *

This ensures a JWT recipient can inspect the {@code cty} header to know how to handle the payload bytes + * without ambiguity.

+ * + *

Mutually Exclusive Claims and Content

+ * + *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} + * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the + * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

+ * + * @param in the input stream containing the bytes to use as the JWT payload * @return the builder for method chaining. - * @since 0.8 + * @see #content(byte[], String) + * @since 0.12.0 */ - JwtBuilder addClaims(Map claims); + JwtBuilder content(InputStream in); /** - * Sets the JWT Claims - * iss (issuer) value. A {@code null} value will remove the property from the Claims. + * Sets the JWT payload to be the specified String's UTF-8 bytes, and also sets the + * {@link HeaderMutator#contentType(String) contentType} header value to a compact {@code cty} IANA Media Type + * identifier to indicate the data format of the resulting byte array. The JWT recipient can inspect the + * {@code cty} value to determine how to convert the byte array to the final content type as desired. This is a + * convenience method semantically equivalent to: * - *

This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set - * the Claims {@link Claims#setIssuer(String) issuer} field with the specified value. This allows you to write - * code like this:

+ *
+     * {@link #content(String) content(content)}.{@link #header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
* - *
-     * String jwt = Jwts.builder().setIssuer("Joe").compact();
-     * 
+ *

Compact Media Type Identifier

* - *

instead of this:

- *
-     * Claims claims = Jwts.claims().setIssuer("Joe");
-     * String jwt = Jwts.builder().setClaims(claims).compact();
-     * 
- *

if desired.

+ *

This method will automatically remove any application/ prefix from the + * {@code cty} string if possible according to the rules defined in the last paragraph of + * RFC 7517, Section 4.1.10:

* - * @param iss the JWT {@code iss} value or {@code null} to remove the property from the Claims map. - * @return the builder instance for method chaining. - * @since 0.2 + *
+     *     To keep messages compact in common situations, it is RECOMMENDED that
+     *     producers omit an "application/" prefix of a media type value in a
+     *     "cty" Header Parameter when no other '/' appears in the media type
+     *     value.  A recipient using the media type value MUST treat it as if
+     *     "application/" were prepended to any "cty" value not containing a
+     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
+     *     represent the "application/example" media type, whereas the media
+     *     type "application/example;part="1/2"" cannot be shortened to
+     *     "example;part="1/2"".
+ * + *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the + * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as + * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media + * Type identifiers without needing JWT-specific prefix conditional logic in application code. + *

+ * + *

Mutually Exclusive Claims and Content

+ * + *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} + * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the + * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

+ * + * @param content the content byte array that will be the JWT payload. Cannot be null or empty. + * @param cty the content type (media type) identifier attributed to the byte array. Cannot be null or empty. + * @return the builder for method chaining. + * @throws IllegalArgumentException if either {@code content} or {@code cty} are null or empty. + * @since 0.12.0 */ - @Override - //only for better/targeted JavaDoc - JwtBuilder setIssuer(String iss); + JwtBuilder content(String content, String cty) throws IllegalArgumentException; /** - * Sets the JWT Claims - * sub (subject) value. A {@code null} value will remove the property from the Claims. + * Sets the JWT payload to be the specified byte array, and also sets the + * {@link HeaderMutator#contentType(String) contentType} header value to a compact {@code cty} IANA Media Type + * identifier to indicate the data format of the byte array. The JWT recipient can inspect the + * {@code cty} value to determine how to convert the byte array to the final content type as desired. This is a + * convenience method semantically equivalent to: * - *

This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set - * the Claims {@link Claims#setSubject(String) subject} field with the specified value. This allows you to write - * code like this:

+ *
+     * {@link #content(byte[]) content(content)}.{@link #header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
* - *
-     * String jwt = Jwts.builder().setSubject("Me").compact();
-     * 
+ *

Compact Media Type Identifier

* - *

instead of this:

- *
-     * Claims claims = Jwts.claims().setSubject("Me");
-     * String jwt = Jwts.builder().setClaims(claims).compact();
-     * 
- *

if desired.

+ *

This method will automatically remove any application/ prefix from the + * {@code cty} string if possible according to the rules defined in the last paragraph of + * RFC 7517, Section 4.1.10:

+ *
+     *     To keep messages compact in common situations, it is RECOMMENDED that
+     *     producers omit an "application/" prefix of a media type value in a
+     *     "cty" Header Parameter when no other '/' appears in the media type
+     *     value.  A recipient using the media type value MUST treat it as if
+     *     "application/" were prepended to any "cty" value not containing a
+     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
+     *     represent the "application/example" media type, whereas the media
+     *     type "application/example;part="1/2"" cannot be shortened to
+     *     "example;part="1/2"".
* - * @param sub the JWT {@code sub} value or {@code null} to remove the property from the Claims map. + *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the + * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as + * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media + * Type identifiers without needing JWT-specific prefix conditional logic in application code. + *

+ * + *

Mutually Exclusive Claims and Content

+ * + *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} + * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the + * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

+ * + * @param content the content byte array that will be the JWT payload. Cannot be null or empty. + * @param cty the content type (media type) identifier attributed to the byte array. Cannot be null or empty. + * @return the builder for method chaining. + * @throws IllegalArgumentException if either {@code content} or {@code cty} are null or empty. + * @since 0.12.0 + */ + JwtBuilder content(byte[] content, String cty) throws IllegalArgumentException; + + /** + * Sets the JWT payload to be the specified content byte stream and also sets the + * {@link BuilderHeader#contentType(String) contentType} header value to a compact {@code cty} IANA Media Type + * identifier to indicate the data format of the byte array. The JWT recipient can inspect the + * {@code cty} value to determine how to convert the byte array to the final content type as desired. This is a + * convenience method semantically equivalent to: + * + *
+     * {@link #content(InputStream) content(content)}.{@link #header()}.{@link HeaderMutator#contentType(String) contentType(cty)}.{@link BuilderHeader#and() and()}
+ * + *

Compact Media Type Identifier

+ * + *

This method will automatically remove any application/ prefix from the + * {@code cty} string if possible according to the rules defined in the last paragraph of + * RFC 7517, Section 4.1.10:

+ * + *
+     *     To keep messages compact in common situations, it is RECOMMENDED that
+     *     producers omit an "application/" prefix of a media type value in a
+     *     "cty" Header Parameter when no other '/' appears in the media type
+     *     value.  A recipient using the media type value MUST treat it as if
+     *     "application/" were prepended to any "cty" value not containing a
+     *     '/'.  For instance, a "cty" value of "example" SHOULD be used to
+     *     represent the "application/example" media type, whereas the media
+     *     type "application/example;part="1/2"" cannot be shortened to
+     *     "example;part="1/2"".
+ * + *

JJWT performs the reverse during JWT parsing: {@link Header#getContentType()} will automatically prepend the + * {@code application/} prefix if the parsed {@code cty} value does not contain a '/' character (as + * mandated by the RFC language above). This ensures application developers can use and read standard IANA Media + * Type identifiers without needing JWT-specific prefix conditional logic in application code. + *

+ * + *

Mutually Exclusive Claims and Content

+ * + *

This method is mutually exclusive of the {@link #claim(String, Object)} and {@link #claims()} + * methods. Either {@code claims} or {@code content} method variants may be used, but not both. If you want the + * JWT payload to be JSON claims, use the {@link #claim(String, Object)} or {@link #claims()} methods instead.

+ * + * @param content the content byte array that will be the JWT payload. Cannot be null. + * @param cty the content type (media type) identifier attributed to the byte array. Cannot be null or empty. + * @return the builder for method chaining. + * @throws IllegalArgumentException if either {@code content} or {@code cty} are null or empty. + * @since 0.12.0 + */ + JwtBuilder content(InputStream content, String cty) throws IllegalArgumentException; + + /** + * Returns the JWT {@code Claims} payload to modify as desired. When finished, callers may + * return to {@code JwtBuilder} configuration via the {@link BuilderClaims#and() and()} method. + * For example: + * + *
+     * String jwt = Jwts.builder()
+     *
+     *     .claims()
+     *         .issuer("me")
+     *         .subject("Joe")
+     *         .audience().add("you").and()
+     *         .add("customClaim", customValue)
+     *         .add(myClaimsMap)
+     *         // ... etc ...
+     *         .{@link BuilderClaims#and() and()} //return back to the JwtBuilder
+     *
+     *     .signWith(key) // resume JwtBuilder calls
+     *     // ... etc ...
+     *     .compact();
+ * + * @return the {@link BuilderClaims} to use for Claims construction. + * @since 0.12.0 + */ + BuilderClaims claims(); + + /** + * Replaces the JWT Claims payload with the specified name/value pairs. This is an alias for: + *
+     * {@link #claims()}.{@link MapMutator#empty() empty()}.{@link MapMutator#add(Map) add(claims)}.{@link BuilderClaims#and() and()}
+ * + *

The {@code content} and {@code claims} properties are mutually exclusive - only one of the two variants + * may be used.

+ * + * @param claims the JWT Claims to be set as the JWT payload. + * @return the builder for method chaining. + * @see #claims() + * @see #content(String) + * @see #content(byte[]) + * @see #content(InputStream) + * @deprecated since 0.12.0 in favor of using the {@link #claims()} builder. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + JwtBuilder setClaims(Map claims); + + /** + * Adds/appends all given name/value pairs to the JSON Claims in the payload. This is an alias for: + * + *
+     * {@link #claims()}.{@link MapMutator#add(Map) add(claims)}.{@link BuilderClaims#and() and()}
+ * + *

The content and claims properties are mutually exclusive - only one of the two may be used.

+ * + * @param claims the JWT Claims to be added to the JWT payload. + * @return the builder for method chaining. + * @since 0.8 + * @deprecated since 0.12.0 in favor of + * {@link #claims()}.{@link BuilderClaims#add(Map) add(Map)}.{@link BuilderClaims#and() and()}. + * This method will be removed before the 1.0 release. + */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + JwtBuilder addClaims(Map claims); + + /** + * Sets a JWT claim, overwriting any existing claim with the same name. A {@code null} or empty + * value will remove the claim entirely. This is a convenience alias for: + *
+     * {@link #claims()}.{@link MapMutator#add(Object, Object) add(name, value)}.{@link BuilderClaims#and() and()}
+ * + * @param name the JWT Claims property name + * @param value the value to set for the specified Claims property name * @return the builder instance for method chaining. * @since 0.2 */ - @Override - //only for better/targeted JavaDoc - JwtBuilder setSubject(String sub); + JwtBuilder claim(String name, Object value); /** - * Sets the JWT Claims - * aud (audience) value. A {@code null} value will remove the property from the Claims. + * Adds all given name/value pairs to the JSON Claims in the payload, overwriting any existing claims + * with the same names. If any name has a {@code null} or empty value, that claim will be removed from the + * Claims. This is a convenience alias for: + *
+     * {@link #claims()}.{@link MapMutator#add(Map) add(claims)}.{@link BuilderClaims#and() and()}
* - *

This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set - * the Claims {@link Claims#setAudience(String) audience} field with the specified value. This allows you to write - * code like this:

+ *

The content and claims properties are mutually exclusive - only one of the two may be used.

* - *
-     * String jwt = Jwts.builder().setAudience("You").compact();
-     * 
+ * @param claims the JWT Claims to be added to the JWT payload. + * @return the builder instance for method chaining + * @since 0.12.0 + */ + JwtBuilder claims(Map claims); + + /** + * Sets the JWT Claims + * iss (issuer) claim. A {@code null} value will remove the property from the Claims. + * This is a convenience wrapper for: + *
+     * {@link #claims()}.{@link ClaimsMutator#issuer(String) issuer(iss)}.{@link BuilderClaims#and() and()}
* - *

instead of this:

- *
-     * Claims claims = Jwts.claims().setAudience("You");
-     * String jwt = Jwts.builder().setClaims(claims).compact();
-     * 
- *

if desired.

+ * @param iss the JWT {@code iss} 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 issuer(String iss); + + /** + * Sets the JWT Claims + * sub (subject) claim. A {@code null} value will remove the property from the Claims. + * This is a convenience wrapper for: + *
+     * {@link #claims()}.{@link ClaimsMutator#subject(String) subject(sub)}.{@link BuilderClaims#and() and()}
* - * @param aud the JWT {@code aud} value or {@code null} to remove the property from the Claims map. + * @param sub the JWT {@code sub} value or {@code null} to remove the property from the Claims map. * @return the builder instance for method chaining. - * @since 0.2 */ @Override - //only for better/targeted JavaDoc - JwtBuilder setAudience(String aud); + // for better/targeted JavaDoc + JwtBuilder subject(String sub); /** - * Sets the JWT Claims - * exp (expiration) value. A {@code null} value will remove the property from the Claims. + * Sets the JWT Claims + * exp (expiration) claim. A {@code null} value will remove the property from the Claims. * *

A JWT obtained after this timestamp should not be used.

* - *

This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set - * the Claims {@link Claims#setExpiration(java.util.Date) expiration} field with the specified value. This allows - * you to write code like this:

+ *

This is a convenience wrapper for:

+ *
+     * {@link #claims()}.{@link ClaimsMutator#expiration(Date) expiration(exp)}.{@link BuilderClaims#and() and()}
* - *
-     * String jwt = Jwts.builder().setExpiration(new Date(System.currentTimeMillis() + 3600000)).compact();
-     * 
- * - *

instead of this:

- *
-     * Claims claims = Jwts.claims().setExpiration(new Date(System.currentTimeMillis() + 3600000));
-     * String jwt = Jwts.builder().setClaims(claims).compact();
-     * 
- *

if desired.

- * * @param exp the JWT {@code exp} value or {@code null} to remove the property from the Claims map. * @return the builder instance for method chaining. - * @since 0.2 */ @Override - //only for better/targeted JavaDoc - JwtBuilder setExpiration(Date exp); + // for better/targeted JavaDoc + JwtBuilder expiration(Date exp); /** - * Sets the JWT Claims - * nbf (not before) value. A {@code null} value will remove the property from the Claims. + * Sets the JWT Claims + * nbf (not before) claim. A {@code null} value will remove the property from the Claims. * *

A JWT obtained before this timestamp should not be used.

* - *

This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set - * the Claims {@link Claims#setNotBefore(java.util.Date) notBefore} field with the specified value. This allows - * you to write code like this:

+ *

This is a convenience wrapper for:

+ *
+     * {@link #claims()}.{@link ClaimsMutator#notBefore(Date) notBefore(nbf)}.{@link BuilderClaims#and() and()}
* - *
-     * String jwt = Jwts.builder().setNotBefore(new Date()).compact();
-     * 
- * - *

instead of this:

- *
-     * Claims claims = Jwts.claims().setNotBefore(new Date());
-     * String jwt = Jwts.builder().setClaims(claims).compact();
-     * 
- *

if desired.

- * * @param nbf the JWT {@code nbf} value or {@code null} to remove the property from the Claims map. * @return the builder instance for method chaining. - * @since 0.2 */ @Override - //only for better/targeted JavaDoc - JwtBuilder setNotBefore(Date nbf); + // for better/targeted JavaDoc + JwtBuilder notBefore(Date nbf); /** - * Sets the JWT Claims - * iat (issued at) value. A {@code null} value will remove the property from the Claims. + * Sets the JWT Claims + * iat (issued at) claim. A {@code null} value will remove the property from the Claims. * *

The value is the timestamp when the JWT was created.

* - *

This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set - * the Claims {@link Claims#setIssuedAt(java.util.Date) issuedAt} field with the specified value. This allows - * you to write code like this:

+ *

This is a convenience wrapper for:

+ *
+     * {@link #claims()}.{@link ClaimsMutator#issuedAt(Date) issuedAt(iat)}.{@link BuilderClaims#and() and()}
* - *
-     * String jwt = Jwts.builder().setIssuedAt(new Date()).compact();
-     * 
- * - *

instead of this:

- *
-     * Claims claims = Jwts.claims().setIssuedAt(new Date());
-     * String jwt = Jwts.builder().setClaims(claims).compact();
-     * 
- *

if desired.

- * * @param iat the JWT {@code iat} value or {@code null} to remove the property from the Claims map. * @return the builder instance for method chaining. - * @since 0.2 */ @Override - //only for better/targeted JavaDoc - JwtBuilder setIssuedAt(Date iat); + // for better/targeted JavaDoc + JwtBuilder issuedAt(Date iat); /** - * Sets the JWT Claims - * jti (JWT ID) value. A {@code null} value will remove the property from the Claims. + * Sets the JWT Claims + * jti (JWT ID) claim. A {@code null} value will remove the property from the Claims. * *

The value is a CaSe-SenSiTiVe unique identifier for the JWT. If specified, this value MUST be assigned in a * manner that ensures that there is a negligible probability that the same value will be accidentally * assigned to a different data object. The ID can be used to prevent the JWT from being replayed.

* - *

This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set - * the Claims {@link Claims#setId(String) id} field with the specified value. This allows - * you to write code like this:

+ *

This is a convenience wrapper for:

+ *
+     * {@link #claims()}.{@link ClaimsMutator#id(String) id(jti)}.{@link BuilderClaims#and() and()}
* - *
-     * String jwt = Jwts.builder().setId(UUID.randomUUID().toString()).compact();
-     * 
- * - *

instead of this:

- *
-     * Claims claims = Jwts.claims().setId(UUID.randomUUID().toString());
-     * String jwt = Jwts.builder().setClaims(claims).compact();
-     * 
- *

if desired.

- * * @param jti the JWT {@code jti} (id) value or {@code null} to remove the property from the Claims map. * @return the builder instance for method chaining. - * @since 0.2 */ @Override - //only for better/targeted JavaDoc - JwtBuilder setId(String jti); + // for better/targeted JavaDoc + JwtBuilder id(String jti); /** - * Sets a custom JWT Claims parameter value. A {@code null} value will remove the property from the Claims. + * Signs the constructed JWT with the specified key using the key's recommended signature algorithm + * as defined below, producing a JWS. If the recommended signature algorithm isn't sufficient for your needs, + * consider using {@link #signWith(Key, SecureDigestAlgorithm)} instead. * - *

This is a convenience method. It will first ensure a Claims instance exists as the JWT body and then set the - * named property on the Claims instance using the Claims {@link Claims#put(Object, Object) put} method. This allows - * you to write code like this:

+ *

If you are looking to invoke this method with a byte array that you are confident may be used for HMAC-SHA + * algorithms, consider using {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to + * convert the byte array into a valid {@code Key}.

* - *
-     * String jwt = Jwts.builder().claim("aName", "aValue").compact();
-     * 
+ *

Recommended Signature Algorithm

* - *

instead of this:

- *
-     * Claims claims = Jwts.claims().put("aName", "aValue");
-     * String jwt = Jwts.builder().setClaims(claims).compact();
-     * 
- *

if desired.

+ *

The recommended signature algorithm used with a given key is chosen based on the following:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Key Recommended Signature Algorithm
If the Key is a:And:With a key size of:The SignatureAlgorithm used will be:
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA256")1256 <= size <= 383 2{@link Jwts.SIG#HS256 HS256}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA384")1384 <= size <= 511{@link Jwts.SIG#HS384 HS384}
{@link SecretKey}{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA512")1512 <= size{@link Jwts.SIG#HS512 HS512}
{@link ECKey}instanceof {@link PrivateKey}256 <= size <= 383 3{@link Jwts.SIG#ES256 ES256}
{@link ECKey}instanceof {@link PrivateKey}384 <= size <= 520 4{@link Jwts.SIG#ES384 ES384}
{@link ECKey}instanceof {@link PrivateKey}521 <= size 4{@link Jwts.SIG#ES512 ES512}
{@link RSAKey}instanceof {@link PrivateKey}2048 <= size <= 3071 5,6{@link Jwts.SIG#RS256 RS256}
{@link RSAKey}instanceof {@link PrivateKey}3072 <= size <= 4095 6{@link Jwts.SIG#RS384 RS384}
{@link RSAKey}instanceof {@link PrivateKey}4096 <= size 5{@link Jwts.SIG#RS512 RS512}
EdECKey7instanceof {@link PrivateKey}256 || 456{@link Jwts.SIG#EdDSA EdDSA}
+ *

Notes:

+ *
    + *
  1. {@code SecretKey} instances must have an {@link Key#getAlgorithm() algorithm} name equal + * to {@code HmacSHA256}, {@code HmacSHA384} or {@code HmacSHA512}. If not, the key bytes might not be + * suitable for HMAC signatures will be rejected with a {@link InvalidKeyException}.
  2. + *
  3. The JWT JWA Specification (RFC 7518, + * Section 3.2) mandates that HMAC-SHA-* signing keys MUST be 256 bits or greater. + * {@code SecretKey}s with key lengths less than 256 bits will be rejected with an + * {@link WeakKeyException}.
  4. + *
  5. The JWT JWA Specification (RFC 7518, + * Section 3.4) mandates that ECDSA signing key lengths MUST be 256 bits or greater. + * {@code ECKey}s with key lengths less than 256 bits will be rejected with a + * {@link WeakKeyException}.
  6. + *
  7. The ECDSA {@code P-521} curve does indeed use keys of 521 bits, not 512 as might be expected. ECDSA + * keys of 384 < size <= 520 are suitable for ES384, while ES512 requires keys >= 521 bits. The '512' part of the + * ES512 name reflects the usage of the SHA-512 algorithm, not the ECDSA key length. ES512 with ECDSA keys less + * than 521 bits will be rejected with a {@link WeakKeyException}.
  8. + *
  9. The JWT JWA Specification (RFC 7518, + * Section 3.3) mandates that RSA signing key lengths MUST be 2048 bits or greater. + * {@code RSAKey}s with key lengths less than 2048 bits will be rejected with a + * {@link WeakKeyException}.
  10. + *
  11. Technically any RSA key of length >= 2048 bits may be used with the + * {@link Jwts.SIG#RS256 RS256}, {@link Jwts.SIG#RS384 RS384}, and + * {@link Jwts.SIG#RS512 RS512} algorithms, so we assume an RSA signature algorithm based on the key + * length to parallel similar decisions in the JWT specification for HMAC and ECDSA signature algorithms. + * This is not required - just a convenience.
  12. + *
  13. EdECKeys + * require JDK >= 15 or BouncyCastle in the runtime classpath.
  14. + *
* - * @param name the JWT Claims property name - * @param value the value to set for the specified Claims property name - * @return the builder instance for method chaining. - * @since 0.2 - */ - JwtBuilder claim(String name, Object value); - - /** - * Signs the constructed JWT with the specified key using the key's - * {@link SignatureAlgorithm#forSigningKey(Key) recommended signature algorithm}, producing a JWS. If the - * recommended signature algorithm isn't sufficient for your needs, consider using - * {@link #signWith(Key, SignatureAlgorithm)} instead. + *

This implementation does not use the {@link Jwts.SIG#PS256 PS256}, + * {@link Jwts.SIG#PS384 PS384}, or {@link Jwts.SIG#PS512 PS512} RSA variants for any + * specified {@link RSAKey} because the the {@link Jwts.SIG#RS256 RS256}, + * {@link Jwts.SIG#RS384 RS384}, and {@link Jwts.SIG#RS512 RS512} algorithms are + * available in the JDK by default while the {@code PS}* variants require either JDK 11 or an additional JCA + * Provider (like BouncyCastle). If you wish to use a {@code PS}* variant with your key, use the + * {@link #signWith(Key, SecureDigestAlgorithm)} method instead.

* - *

If you are looking to invoke this method with a byte array that you are confident may be used for HMAC-SHA - * algorithms, consider using {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to - * convert the byte array into a valid {@code Key}.

+ *

Finally, this method will throw an {@link InvalidKeyException} for any key that does not match the + * heuristics and requirements documented above, since that inevitably means the Key is either insufficient, + * unsupported, or explicitly disallowed by the JWT specification.

* * @param key the key to use for signing * @return the builder instance for method chaining. - * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as - * described by {@link SignatureAlgorithm#forSigningKey(Key)}. - * @see #signWith(Key, SignatureAlgorithm) + * @throws InvalidKeyException if the Key is insufficient, unsupported, or explicitly disallowed by the JWT + * specification as described above in recommended signature algorithms. + * @see Jwts.SIG + * @see #signWith(Key, SecureDigestAlgorithm) * @since 0.10.0 */ JwtBuilder signWith(Key key) throws InvalidKeyException; /** * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. * - *

Deprecation Notice: Deprecated as of 0.10.0

+ *

Deprecation Notice: Deprecated as of 0.10.0

* *

Use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to - * obtain the {@code Key} and then invoke {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)}.

+ * obtain the {@code Key} and then invoke {@link #signWith(Key)} or + * {@link #signWith(Key, SecureDigestAlgorithm)}.

* *

This method will be removed in the 1.0 release.

* * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. * @param secretKey the algorithm-specific signing key to use to digitally sign the JWT. * @return the builder for method chaining. - * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as - * described by {@link SignatureAlgorithm#forSigningKey(Key)}. + * @throws InvalidKeyException if the Key is insufficient for the specified algorithm or explicitly disallowed by + * the JWT specification. * @deprecated as of 0.10.0: use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to - * obtain the {@code Key} and then invoke {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)}. + * obtain the {@code Key} and then invoke {@link #signWith(Key)} or + * {@link #signWith(Key, SecureDigestAlgorithm)}. * This method will be removed in the 1.0 release. */ @Deprecated @@ -390,7 +752,7 @@ *

This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting * byte array is used to invoke {@link #signWith(SignatureAlgorithm, byte[])}.

* - *

Deprecation Notice: Deprecated as of 0.10.0, will be removed in the 1.0 release.

+ *

Deprecation Notice: Deprecated as of 0.10.0, will be removed in the 1.0 release.

* *

This method has been deprecated because the {@code key} argument for this method can be confusing: keys for * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were @@ -402,21 +764,20 @@ *

{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}

* *

However, a non-trivial number of JJWT users were confused by the method signature and attempted to - * use raw password strings as the key argument - for example {@code signWith(HS256, myPassword)} - which is + * use raw password strings as the key argument - for example {@code with(HS256, myPassword)} - which is * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.

* *

See this * * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for * signature operations.

* - *

To perform the correct logic with base64EncodedSecretKey strings with JJWT >= 0.10.0, you may do this: + *

To perform the correct logic with base64EncodedSecretKey strings with JJWT >= 0.10.0, you may do this:

*

      * byte[] keyBytes = {@link Decoders Decoders}.{@link Decoders#BASE64 BASE64}.{@link Decoder#decode(Object) decode(base64EncodedSecretKey)};
      * Key key = {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(keyBytes)};
-     * jwtBuilder.signWith(key); //or {@link #signWith(Key, SignatureAlgorithm)}
+     * jwtBuilder.with(key); //or {@link #signWith(Key, SignatureAlgorithm)}
      * 
- *

* *

This method will be removed in the 1.0 release.

* @@ -445,15 +806,22 @@ * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for * the specified algorithm. * @see #signWith(Key) - * @deprecated since 0.10.0: use {@link #signWith(Key, SignatureAlgorithm)} instead. This method will be removed - * in the 1.0 release. + * @deprecated since 0.10.0. Use {@link #signWith(Key, SecureDigestAlgorithm)} instead. + * This method will be removed before the 1.0 release. */ @Deprecated JwtBuilder signWith(SignatureAlgorithm alg, Key key) throws InvalidKeyException; /** - * Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS. + *

Deprecation Notice

* + *

This has been deprecated since 0.12.0. Use + * {@link #signWith(Key, SecureDigestAlgorithm)} instead. Standard JWA algorithms + * are represented as instances of this new interface in the {@link Jwts.SIG} + * algorithm registry.

+ * + *

Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS.

+ * *

It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if * you want explicit control over the signature algorithm used with the specified key.

@@ -465,66 +833,224 @@ * the specified algorithm. * @see #signWith(Key) * @since 0.10.0 + * @deprecated since 0.12.0 to use the more flexible {@link #signWith(Key, SecureDigestAlgorithm)}. */ + @Deprecated JwtBuilder signWith(Key key, SignatureAlgorithm alg) throws InvalidKeyException; /** - * Compresses the JWT body using the specified {@link CompressionCodec}. + * Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS. * + *

The {@link Jwts.SIG} registry makes available all standard signature + * algorithms defined in the JWA specification.

+ * + *

It is typically recommended to call the {@link #signWith(Key)} instead for simplicity. + * However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if + * you want explicit control over the signature algorithm used with the specified key.

+ * + * @param key the signing key to use to digitally sign the JWT. + * @param The type of key accepted by the {@code SignatureAlgorithm}. + * @param alg the JWS algorithm to use with the key to digitally sign the JWT, thereby producing a JWS. + * @return the builder for method chaining. + * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for + * the specified algorithm. + * @see #signWith(Key) + * @see Jwts.SIG + * @since 0.12.0 + */ + JwtBuilder signWith(K key, SecureDigestAlgorithm alg) throws InvalidKeyException; + + /** + * Encrypts the constructed JWT with the specified symmetric {@code key} using the provided {@code enc}ryption + * algorithm, producing a JWE. Because it is a symmetric key, the JWE recipient + * must also have access to the same key to decrypt. + * + *

This method is a convenience method that delegates to + * {@link #encryptWith(Key, KeyAlgorithm, AeadAlgorithm) encryptWith(Key, KeyAlgorithm, AeadAlgorithm)} + * based on the {@code key} argument:

+ *
    + *
  • If the provided {@code key} is a {@link Password Password} instance, + * the {@code KeyAlgorithm} used will be one of the three JWA-standard password-based key algorithms + * ({@link Jwts.KEY#PBES2_HS256_A128KW PBES2_HS256_A128KW}, + * {@link Jwts.KEY#PBES2_HS384_A192KW PBES2_HS384_A192KW}, or + * {@link Jwts.KEY#PBES2_HS512_A256KW PBES2_HS512_A256KW}) as determined by the {@code enc} algorithm's + * {@link AeadAlgorithm#getKeyBitLength() key length} requirement.
  • + *
  • If the {@code key} is otherwise a standard {@code SecretKey}, the {@code KeyAlgorithm} will be + * {@link Jwts.KEY#DIRECT DIRECT}, indicating that {@code key} should be used directly with the + * {@code enc} algorithm. In this case, the {@code key} argument MUST be of sufficient strength to + * use with the specified {@code enc} algorithm, otherwise an exception will be thrown during encryption. If + * desired, secure-random keys suitable for an {@link AeadAlgorithm} may be generated using the algorithm's + * {@link AeadAlgorithm#key() key()} builder.
  • + *
+ * + * @param key the symmetric encryption key to use with the {@code enc} algorithm. + * @param enc the {@link AeadAlgorithm} algorithm used to encrypt the JWE, usually one of the JWA-standard + * algorithms accessible via {@link Jwts.ENC}. + * @return the JWE builder for method chaining. + * @see Jwts.ENC + */ + JwtBuilder encryptWith(SecretKey key, AeadAlgorithm enc); + + /** + * Encrypts the constructed JWT using the specified {@code enc} algorithm with the symmetric key produced by the + * {@code keyAlg} when invoked with the given {@code key}, producing a JWE. + * + *

This behavior can be illustrated by the following pseudocode, a rough example of what happens during + * {@link #compact() compact}ion:

+ *
+     *     SecretKey encryptionKey = keyAlg.getEncryptionKey(key);           // (1)
+     *     byte[] jweCiphertext = enc.encrypt(payloadBytes, encryptionKey);  // (2)
+ *
    + *
  1. The {@code keyAlg} argument is first invoked with the provided {@code key} argument, resulting in a + * {@link SecretKey}.
  2. + *
  3. This {@code SecretKey} result is used to call the provided {@code enc} encryption algorithm argument, + * resulting in the final JWE ciphertext.
  4. + *
+ * + *

Most application developers will reference one of the JWA + * {@link Jwts.KEY standard key algorithms} and {@link Jwts.ENC standard encryption algorithms} + * when invoking this method, but custom implementations are also supported.

+ * + * @param the type of key that must be used with the specified {@code keyAlg} instance. + * @param key the key used to invoke the provided {@code keyAlg} instance. + * @param keyAlg the key management algorithm that will produce the symmetric {@code SecretKey} to use with the + * {@code enc} algorithm + * @param enc the {@link AeadAlgorithm} algorithm used to encrypt the JWE + * @return the JWE builder for method chaining. + * @see Jwts.ENC + * @see Jwts.KEY + */ + JwtBuilder encryptWith(K key, KeyAlgorithm keyAlg, AeadAlgorithm enc); + + /** + * Compresses the JWT payload using the specified {@link CompressionAlgorithm}. + * *

If your compact JWTs are large, and you want to reduce their total size during network transmission, this * can be useful. For example, when embedding JWTs in URLs, some browsers may not support URLs longer than a * certain length. Using compression can help ensure the compact JWT fits within that length. However, NOTE:

* - *

Compatibility Warning

+ *

Compatibility Warning

* - *

The JWT family of specifications defines compression only for JWE (Json Web Encryption) + *

The JWT family of specifications defines compression only for JWE (JSON Web Encryption) * tokens. Even so, JJWT will also support compression for JWS tokens as well if you choose to use it. * However, be aware that if you use compression when creating a JWS token, other libraries may not be able to - * parse that JWS token. When using compression for JWS tokens, be sure that that all parties accessing the + * parse that JWS token. When using compression for JWS tokens, be sure that all parties accessing the * JWS token support compression for JWS.

* *

Compression when creating JWE tokens however should be universally accepted for any * library that supports JWE.

* - * @param codec implementation of the {@link CompressionCodec} to be used. + * @param alg implementation of the {@link CompressionAlgorithm} to be used. * @return the builder for method chaining. - * @see io.jsonwebtoken.CompressionCodecs - * @since 0.6.0 + * @see Jwts.ZIP + * @since 0.12.0 */ - JwtBuilder compressWith(CompressionCodec codec); + JwtBuilder compressWith(CompressionAlgorithm alg); /** - * Perform Base64Url encoding with the specified Encoder. + * Perform Base64Url encoding during {@link #compact() compaction} with the specified Encoder. * *

JJWT uses a spec-compliant encoder that works on all supported JDK versions, but you may call this method * to specify a different encoder if you desire.

* * @param base64UrlEncoder the encoder to use when Base64Url-encoding * @return the builder for method chaining. + * @see #b64Url(Encoder) * @since 0.10.0 + * @deprecated since 0.12.0 in favor of {@link #b64Url(Encoder)}. */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated JwtBuilder base64UrlEncodeWith(Encoder base64UrlEncoder); /** - * Performs object-to-JSON serialization with the specified Serializer. This is used by the builder to convert - * JWT/JWS/JWT headers and claims Maps to JSON strings as required by the JWT specification. + * Perform Base64Url encoding during {@link #compact() compaction} with the specified {@code OutputStream} Encoder. + * The Encoder's {@link Encoder#encode(Object) encode} method will be given a target {@code OutputStream} to + * wrap, and the resulting (wrapping) {@code OutputStream} will be used for writing, ensuring automatic + * Base64URL-encoding during write operations. * + *

JJWT uses a spec-compliant encoder that works on all supported JDK versions, but you may call this method + * to specify a different stream encoder if desired.

+ * + * @param encoder the encoder to use when Base64Url-encoding + * @return the builder for method chaining. + * @since 0.12.0 + */ + JwtBuilder b64Url(Encoder encoder); + + /** + * Enables RFC 7797: JSON Web Signature (JWS) + * Unencoded Payload Option if {@code false}, or standard JWT/JWS/JWE payload encoding otherwise. The default + * value is {@code true} per standard RFC behavior rules. + * + *

This value may only be {@code false} for JWSs (signed JWTs). It may not be used for standard + * (unprotected) JWTs or encrypted JWTs (JWEs). The builder will throw an exception during {@link #compact()} if + * {@code false} and a JWS is not being created.

+ * + * @param b64 whether to Base64URL-encode the JWS payload + * @return the builder for method chaining. + */ + JwtBuilder encodePayload(boolean b64); + + /** + * Performs Map-to-JSON serialization with the specified Serializer. This is used by the builder to convert + * JWT/JWS/JWE headers and claims Maps to JSON strings as required by the JWT specification. + * *

If this method is not called, JJWT will use whatever serializer it can find at runtime, checking for the * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found * in the runtime classpath, an exception will be thrown when the {@link #compact()} method is invoked.

* * @param serializer the serializer to use when converting Map objects to JSON strings. * @return the builder for method chaining. * @since 0.10.0 + * @deprecated since 0.12.0 in favor of {@link #json(Serializer)} */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated JwtBuilder serializeToJsonWith(Serializer> serializer); /** + * Perform Map-to-JSON serialization with the specified Serializer. This is used by the builder to convert + * JWT/JWS/JWE headers and Claims Maps to JSON strings as required by the JWT specification. + * + *

If this method is not called, JJWT will use whatever Serializer it can find at runtime, checking for the + * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found + * in the runtime classpath, an exception will be thrown when the {@link #compact()} method is invoked.

+ * + * @param serializer the Serializer to use when converting Map objects to JSON strings. + * @return the builder for method chaining. + * @since 0.12.0 + */ + JwtBuilder json(Serializer> serializer); + + /** * Actually builds the JWT and serializes it to a compact, URL-safe string according to the - * JWT Compact Serialization + * JWT Compact Serialization * rules. * * @return A compact URL-safe JWT string. */ String compact(); + + /** + * Claims for use with a {@link JwtBuilder} that supports method chaining for standard JWT Claims parameters. + * Once claims are configured, the associated {@link JwtBuilder} may be obtained with the {@link #and() and()} + * method for continued configuration. + * + * @since 0.12.0 + */ + interface BuilderClaims extends MapMutator, ClaimsMutator, + Conjunctor { + } + + /** + * Header for use with a {@link JwtBuilder} that supports method chaining for + * standard JWT, JWS and JWE header parameters. Once header parameters are configured, the associated + * {@link JwtBuilder} may be obtained with the {@link #and() and()} method for continued configuration. + * + * @since 0.12.0 + */ + interface BuilderHeader extends JweHeaderMutator, X509Builder, + Conjunctor { + } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtException.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtException.java (.../JwtException.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtException.java (.../JwtException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -22,10 +22,21 @@ */ public class JwtException extends RuntimeException { + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ public JwtException(String message) { super(message); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public JwtException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtHandler.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtHandler.java (.../JwtHandler.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtHandler.java (.../JwtHandler.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -17,25 +17,31 @@ /** * A JwtHandler is invoked by a {@link io.jsonwebtoken.JwtParser JwtParser} after parsing a JWT to indicate the exact - * type of JWT or JWS parsed. + * type of JWT, JWS or JWE parsed. * * @param the type of object to return to the parser caller after handling the parsed JWT. * @since 0.2 + * @deprecated since 0.12.0 in favor of calling {@link Jwt#accept(JwtVisitor)}. */ -public interface JwtHandler { +@SuppressWarnings("DeprecatedIsStillUsed") +@Deprecated +public interface JwtHandler extends JwtVisitor { /** * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is - * a plaintext JWT. A plaintext JWT has a String (non-JSON) body payload and it is not cryptographically signed. + * an unsecured content JWT. An unsecured content JWT has a byte array payload that is not + * cryptographically signed or encrypted. If the JWT creator set the (optional) + * {@link Header#getContentType() contentType} header value, the application may inspect that value to determine + * how to convert the byte array to the final content type as desired. * - * @param jwt the parsed plaintext JWT + * @param jwt the parsed unsecured content JWT * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. */ - T onPlaintextJwt(Jwt jwt); + T onContentJwt(Jwt jwt); /** * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is - * a Claims JWT. A Claims JWT has a {@link Claims} body and it is not cryptographically signed. + * a Claims JWT. A Claims JWT has a {@link Claims} payload that is not cryptographically signed or encrypted. * * @param jwt the parsed claims JWT * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. @@ -44,19 +50,21 @@ /** * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is - * a plaintext JWS. A plaintext JWS is a JWT with a String (non-JSON) body (payload) that has been - * cryptographically signed. + * a content JWS. A content JWS is a JWT with a byte array payload that has been cryptographically signed. + * If the JWT creator set the (optional) {@link Header#getContentType() contentType} header value, the + * application may inspect that value to determine how to convert the byte array to the final content type + * as desired. * *

This method will only be invoked if the cryptographic signature can be successfully verified.

* - * @param jws the parsed plaintext JWS + * @param jws the parsed content JWS * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. */ - T onPlaintextJws(Jws jws); + T onContentJws(Jws jws); /** * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is - * a valid Claims JWS. A Claims JWS is a JWT with a {@link Claims} body that has been cryptographically signed. + * a valid Claims JWS. A Claims JWS is a JWT with a {@link Claims} payload that has been cryptographically signed. * *

This method will only be invoked if the cryptographic signature can be successfully verified.

* @@ -65,4 +73,30 @@ */ T onClaimsJws(Jws jws); + /** + * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is + * a content JWE. A content JWE is a JWE with a byte array payload that has been encrypted. If the JWT creator set + * the (optional) {@link Header#getContentType() contentType} header value, the application may inspect that + * value to determine how to convert the byte array to the final content type as desired. + * + *

This method will only be invoked if the content JWE can be successfully decrypted.

+ * + * @param jwe the parsed content jwe + * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. + * @since 0.12.0 + */ + T onContentJwe(Jwe jwe); + + /** + * This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is + * a valid Claims JWE. A Claims JWE is a JWT with a {@link Claims} payload that has been encrypted. + * + *

This method will only be invoked if the Claims JWE can be successfully decrypted.

+ * + * @param jwe the parsed claims jwe + * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. + * @since 0.12.0 + */ + T onClaimsJwe(Jwe jwe); + } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtHandlerAdapter.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtHandlerAdapter.java (.../JwtHandlerAdapter.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtHandlerAdapter.java (.../JwtHandlerAdapter.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -21,32 +21,78 @@ * known/expected for a particular use case. * *

All of the methods in this implementation throw exceptions: overridden methods represent - * scenarios expected by calling code in known situations. It would be unexpected to receive a JWS or JWT that did + * scenarios expected by calling code in known situations. It would be unexpected to receive a JWT that did * not match parsing expectations, so all non-overridden methods throw exceptions to indicate that the JWT * input was unexpected.

* * @param the type of object to return to the parser caller after handling the parsed JWT. * @since 0.2 */ -public class JwtHandlerAdapter implements JwtHandler { +public abstract class JwtHandlerAdapter extends SupportedJwtVisitor implements JwtHandler { + /** + * Default constructor, does not initialize any internal state. + */ + public JwtHandlerAdapter() { + } + @Override - public T onPlaintextJwt(Jwt jwt) { - throw new UnsupportedJwtException("Unsigned plaintext JWTs are not supported."); + public T onUnsecuredContent(Jwt jwt) { + return onContentJwt(jwt); // bridge for existing implementations } @Override + public T onUnsecuredClaims(Jwt jwt) { + return onClaimsJwt(jwt); + } + + @Override + public T onVerifiedContent(Jws jws) { + return onContentJws(jws); + } + + @Override + public T onVerifiedClaims(Jws jws) { + return onClaimsJws(jws); + } + + @Override + public T onDecryptedContent(Jwe jwe) { + return onContentJwe(jwe); + } + + @Override + public T onDecryptedClaims(Jwe jwe) { + return onClaimsJwe(jwe); + } + + @Override + public T onContentJwt(Jwt jwt) { + return super.onUnsecuredContent(jwt); + } + + @Override public T onClaimsJwt(Jwt jwt) { - throw new UnsupportedJwtException("Unsigned Claims JWTs are not supported."); + return super.onUnsecuredClaims(jwt); } @Override - public T onPlaintextJws(Jws jws) { - throw new UnsupportedJwtException("Signed plaintext JWSs are not supported."); + public T onContentJws(Jws jws) { + return super.onVerifiedContent(jws); } @Override public T onClaimsJws(Jws jws) { - throw new UnsupportedJwtException("Signed Claims JWSs are not supported."); + return super.onVerifiedClaims(jws); } + + @Override + public T onContentJwe(Jwe jwe) { + return super.onDecryptedContent(jwe); + } + + @Override + public T onClaimsJwe(Jwe jwe) { + return super.onDecryptedClaims(jwe); + } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtParser.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtParser.java (.../JwtParser.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtParser.java (.../JwtParser.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,582 +15,408 @@ */ package io.jsonwebtoken; -import io.jsonwebtoken.io.Decoder; -import io.jsonwebtoken.io.Deserializer; +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.security.SecurityException; import io.jsonwebtoken.security.SignatureException; -import java.security.Key; -import java.util.Date; -import java.util.Map; +import java.io.InputStream; /** * A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT. + * A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT. * * @since 0.1 */ -public interface JwtParser { +public interface JwtParser extends Parser> { - public static final char SEPARATOR_CHAR = '.'; - /** - * Ensures that the specified {@code jti} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. + * Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} + * otherwise. * - * @param id - * @return the parser method for chaining. - * @see MissingClaimException - * @see IncorrectClaimException - * @deprecated see {@link JwtParserBuilder#requireId(String)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + *

Note that if you are reasonably sure that the token is signed, it is more efficient to attempt to + * parse the token (and catching exceptions if necessary) instead of calling this method first before parsing.

+ * + * @param compact the compact serialized JWT to check + * @return {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} + * otherwise. */ - @Deprecated - JwtParser requireId(String id); + boolean isSigned(CharSequence compact); /** - * Ensures that the specified {@code sub} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. + * Parses the specified compact serialized JWT string based on the builder's current configuration state and + * returns the resulting JWT, JWS, or JWE instance. * - * @param subject - * @return the parser for method chaining. - * @see MissingClaimException - * @see IncorrectClaimException - * @deprecated see {@link JwtParserBuilder#requireSubject(String)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + *

Because it is often cumbersome to determine if the result is a JWT, JWS or JWE, or if the payload is a Claims + * or {@code byte[]} array with {@code instanceof} checks, it may be useful to call the result's + * {@link Jwt#accept(JwtVisitor) accept(JwtVisitor)} method for a type-safe callback approach instead of using if-then-else + * {@code instanceof} conditionals. For example, instead of:

+ * + *
+     * // NOT RECOMMENDED:
+     * Jwt<?,?> jwt = parser.parse(input);
+     * if (jwt instanceof Jwe<?>) {
+     *     Jwe<?> jwe = (Jwe<?>)jwt;
+     *     if (jwe.getPayload() instanceof Claims) {
+     *         Jwe<Claims> claimsJwe = (Jwe<Claims>)jwe;
+     *         // do something with claimsJwe
+     *     }
+     * }
+ * + *

the following alternative is usually preferred:

+ * + *
+     * Jwe<Claims> jwe = parser.parse(input).accept({@link Jwe#CLAIMS});
+ * + * @param jwt the compact serialized JWT to parse + * @return the parsed JWT instance + * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). + * Invalid JWTs should not be trusted and should be discarded. + * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail + * signature validation should not be trusted and should be discarded. + * @throws SecurityException if the specified JWT string is a JWE and decryption fails + * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time + * before the time this method is invoked. + * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace. + * @see Jwt#accept(JwtVisitor) */ - @Deprecated - JwtParser requireSubject(String subject); + Jwt parse(CharSequence jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException, + SecurityException, IllegalArgumentException; /** - * Ensures that the specified {@code aud} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. + * Deprecated since 0.12.0 in favor of calling any {@code parse*} method immediately + * followed by invoking the parsed JWT's {@link Jwt#accept(JwtVisitor) accept} method with your preferred visitor. For + * example: * - * @param audience - * @return the parser for method chaining. - * @see MissingClaimException - * @see IncorrectClaimException - * @deprecated see {@link JwtParserBuilder#requireAudience(String)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + *

+     * {@link #parse(CharSequence) parse}(jwt).{@link Jwt#accept(JwtVisitor) accept}({@link JwtVisitor visitor});
+ * + *

This method will be removed before the 1.0 release.

+ * + * @param jwt the compact serialized JWT to parse + * @param handler the handler to invoke when encountering a specific type of JWT + * @param the type of object returned from the {@code handler} + * @return the result returned by the {@code JwtHandler} + * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). + * Invalid JWTs should not be trusted and should be discarded. + * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail + * signature validation should not be trusted and should be discarded. + * @throws SecurityException if the specified JWT string is a JWE and decryption fails + * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time + * before the time this method is invoked. + * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace, or if the + * {@code handler} is {@code null}. + * @see Jwt#accept(JwtVisitor) + * @since 0.2 + * @deprecated since 0.12.0 in favor of + * {@link #parse(CharSequence)}.{@link Jwt#accept(JwtVisitor) accept}({@link JwtVisitor visitor}); */ @Deprecated - JwtParser requireAudience(String audience); + T parse(CharSequence jwt, JwtHandler handler) throws ExpiredJwtException, UnsupportedJwtException, + MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException; /** - * Ensures that the specified {@code iss} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. + * Deprecated since 0.12.0 in favor of {@link #parseUnsecuredContent(CharSequence)}. * - * @param issuer - * @return the parser for method chaining. - * @see MissingClaimException - * @see IncorrectClaimException - * @deprecated see {@link JwtParserBuilder#requireIssuer(String)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + *

This method will be removed before the 1.0 release.

+ * + * @param jwt a compact serialized unsecured content JWT string. + * @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string. + * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured content JWT + * @throws MalformedJwtException if the {@code jwt} string is not a valid JWT + * @throws SignatureException if the {@code jwt} string is actually a JWS and signature validation fails + * @throws SecurityException if the {@code jwt} string is actually a JWE and decryption fails + * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace + * @see #parseUnsecuredContent(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.2 + * @deprecated since 0.12.0 in favor of {@link #parseUnsecuredContent(CharSequence)}. */ + @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated - JwtParser requireIssuer(String issuer); + Jwt parseContentJwt(CharSequence jwt) throws UnsupportedJwtException, MalformedJwtException, + SignatureException, SecurityException, IllegalArgumentException; /** - * Ensures that the specified {@code iat} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. + * Deprecated since 0.12.0 in favor of {@link #parseUnsecuredClaims(CharSequence)}. * - * @param issuedAt - * @return the parser for method chaining. - * @see MissingClaimException - * @see IncorrectClaimException - * @deprecated see {@link JwtParserBuilder#requireIssuedAt(Date)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + *

This method will be removed before the 1.0 release.

+ * + * @param jwt a compact serialized unsecured Claims JWT string. + * @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string. + * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured Claims JWT + * @throws MalformedJwtException if the {@code jwt} string is not a valid JWT + * @throws SignatureException if the {@code jwt} string is actually a JWS and signature validation fails + * @throws SecurityException if the {@code jwt} string is actually a JWE and decryption fails + * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace + * @see #parseUnsecuredClaims(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.2 + * @deprecated since 0.12.0 in favor of {@link #parseUnsecuredClaims(CharSequence)}. */ + @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated - JwtParser requireIssuedAt(Date issuedAt); + Jwt parseClaimsJwt(CharSequence jwt) throws ExpiredJwtException, UnsupportedJwtException, + MalformedJwtException, SignatureException, SecurityException, IllegalArgumentException; /** - * Ensures that the specified {@code exp} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. + * Deprecated since 0.12.0 in favor of {@link #parseSignedContent(CharSequence)}. * - * @param expiration - * @return the parser for method chaining. - * @see MissingClaimException - * @see IncorrectClaimException - * @deprecated see {@link JwtParserBuilder#requireExpiration(Date)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + *

This method will be removed before the 1.0 release.

+ * + * @param jws a compact content JWS string + * @return the parsed and validated content JWS + * @throws UnsupportedJwtException if the {@code jws} argument does not represent a content JWS + * @throws MalformedJwtException if the {@code jws} string is not a valid JWS + * @throws SignatureException if the {@code jws} JWS signature validation fails + * @throws SecurityException if the {@code jws} string is actually a JWE and decryption fails + * @throws IllegalArgumentException if the {@code jws} string is {@code null} or empty or only whitespace + * @see #parseSignedContent(CharSequence) + * @see #parseEncryptedContent(CharSequence) + * @see #parse(CharSequence) + * @since 0.2 + * @deprecated since 0.12.0 in favor of {@link #parseSignedContent(CharSequence)}. */ + @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated - JwtParser requireExpiration(Date expiration); + Jws parseContentJws(CharSequence jws) throws UnsupportedJwtException, MalformedJwtException, SignatureException, + SecurityException, IllegalArgumentException; /** - * Ensures that the specified {@code nbf} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. + * Deprecated since 0.12.0 in favor of {@link #parseSignedClaims(CharSequence)}. * - * @param notBefore - * @return the parser for method chaining - * @see MissingClaimException - * @see IncorrectClaimException - * @deprecated see {@link JwtParserBuilder#requireNotBefore(Date)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + * @param jws a compact Claims JWS string. + * @return the parsed and validated Claims JWS + * @throws UnsupportedJwtException if the {@code claimsJws} argument does not represent an Claims JWS + * @throws MalformedJwtException if the {@code claimsJws} string is not a valid JWS + * @throws SignatureException if the {@code claimsJws} JWS signature validation fails + * @throws SecurityException if the {@code jws} string is actually a JWE and decryption fails + * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time + * before the time this method is invoked. + * @throws IllegalArgumentException if the {@code claimsJws} string is {@code null} or empty or only whitespace + * @see #parseSignedClaims(CharSequence) + * @see #parseEncryptedClaims(CharSequence) + * @see #parse(CharSequence) + * @since 0.2 + * @deprecated since 0.12.0 in favor of {@link #parseSignedClaims(CharSequence)}. */ + @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated - JwtParser requireNotBefore(Date notBefore); + Jws parseClaimsJws(CharSequence jws) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, + SignatureException, SecurityException, IllegalArgumentException; /** - * Ensures that the specified {@code claimName} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the - * JWT is invalid and may not be used. + * Parses the {@code jwt} argument, expected to be an unsecured content JWT. If the JWT creator set + * the (optional) {@link Header#getContentType() contentType} header value, the application may inspect that + * value to determine how to convert the byte array to the final content type as desired. * - * @param claimName - * @param value - * @return the parser for method chaining. - * @see MissingClaimException - * @see IncorrectClaimException - * @deprecated see {@link JwtParserBuilder#require(String, Object)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + *

This is a convenience method logically equivalent to the following:

+ * + *
+     * {@link #parse(CharSequence) parse}(jwt).{@link Jwt#accept(JwtVisitor) accept}({@link
+     * Jwt#UNSECURED_CONTENT});
+ * + * @param jwt a compact unsecured content JWT. + * @return the parsed unsecured content JWT. + * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured content JWT + * @throws JwtException if the {@code jwt} string cannot be parsed or validated as required. + * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace + * @see #parse(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.12.0 */ - @Deprecated - JwtParser require(String claimName, Object value); + Jwt parseUnsecuredContent(CharSequence jwt) throws JwtException, IllegalArgumentException; /** - * Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT. - * The parser uses a default Clock implementation that simply returns {@code new Date()} when called. + * Parses the {@code jwt} argument, expected to be an unsecured {@code Claims} JWT. This is a + * convenience method logically equivalent to the following: * - * @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT. - * @return the parser for method chaining. - * @since 0.7.0 - * @deprecated see {@link JwtParserBuilder#setClock(Clock)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + *

+     * {@link #parse(CharSequence) parse}(jwt).{@link Jwt#accept(JwtVisitor) accept}({@link
+     * Jwt#UNSECURED_CLAIMS});
+ * + * @param jwt a compact unsecured Claims JWT. + * @return the parsed unsecured Claims JWT. + * @throws UnsupportedJwtException if the {@code jwt} argument does not represent an unsecured Claims JWT + * @throws JwtException if the {@code jwt} string cannot be parsed or validated as required. + * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace + * @see #parse(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.12.0 */ - @Deprecated - JwtParser setClock(Clock clock); + Jwt parseUnsecuredClaims(CharSequence jwt) throws JwtException, IllegalArgumentException; /** - * Sets the amount of clock skew in seconds to tolerate when verifying the local time against the {@code exp} - * and {@code nbf} claims. + * Parses the {@code jws} argument, expected to be a cryptographically-signed content JWS. If the JWS + * creator set the (optional) {@link Header#getContentType() contentType} header value, the application may + * inspect that value to determine how to convert the byte array to the final content type as desired. * - * @param seconds the number of seconds to tolerate for clock skew when verifying {@code exp} or {@code nbf} claims. - * @return the parser for method chaining. - * @since 0.7.0 - * @throws IllegalArgumentException if {@code seconds} is a value greater than {@code Long.MAX_VALUE / 1000} as - * any such value would cause numeric overflow when multiplying by 1000 to obtain a millisecond value. - * @deprecated see {@link JwtParserBuilder#setAllowedClockSkewSeconds(long)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + *

This is a convenience method logically equivalent to the following:

+ * + *
+     * {@link #parse(CharSequence) parse}(jws).{@link Jwt#accept(JwtVisitor) accept}({@link
+     * Jws#CONTENT});
+ * + * @param jws a compact cryptographically-signed content JWS. + * @return the parsed cryptographically-verified content JWS. + * @throws UnsupportedJwtException if the {@code jws} argument does not represent a signed content JWS + * @throws JwtException if the {@code jws} string cannot be parsed or validated as required. + * @throws IllegalArgumentException if the {@code jws} string is {@code null} or empty or only whitespace + * @see #parse(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.12.0 */ - @Deprecated - JwtParser setAllowedClockSkewSeconds(long seconds) throws IllegalArgumentException; + Jws parseSignedContent(CharSequence jws) throws JwtException, IllegalArgumentException; /** - * Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not - * a JWS (no signature), this key is not used. - *

- *

Note that this key MUST be a valid key for the signature algorithm found in the JWT header - * (as the {@code alg} header parameter).

- *

- *

This method overwrites any previously set key.

+ * Parses a JWS known to use the + * RFC 7797: JSON Web Signature (JWS) Unencoded Payload + * Option, using the specified {@code unencodedPayload} for signature verification. * - * @param key the algorithm-specific signature verification key used to validate any discovered JWS digital - * signature. - * @return the parser for method chaining. - * @deprecated see {@link JwtParserBuilder#setSigningKey(byte[])}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + *

Unencoded Non-Detached Payload

+ * + *

Note that if the JWS contains a valid unencoded Payload string (what RFC 7797 calls an + * "unencoded non-detached + * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes + * the payload content necessary for signature verification.

+ * + * @param jws the Unencoded Payload JWS to parse. + * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. + * @return the parsed Unencoded Payload. + * @since 0.12.0 */ - @Deprecated - JwtParser setSigningKey(byte[] key); + Jws parseSignedContent(CharSequence jws, byte[] unencodedPayload); /** - * Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not - * a JWS (no signature), this key is not used. + * Parses a JWS known to use the + * RFC 7797: JSON Web Signature (JWS) Unencoded Payload + * Option, using the bytes from the specified {@code unencodedPayload} stream for signature verification. * - *

Note that this key MUST be a valid key for the signature algorithm found in the JWT header - * (as the {@code alg} header parameter).

+ *

Because it is not possible to know how large the {@code unencodedPayload} stream will be, the stream bytes + * will not be buffered in memory, ensuring the resulting {@link Jws} return value's {@link Jws#getPayload()} + * is always empty. This is generally not a concern since the caller already has access to the stream bytes and + * may obtain them independently before or after calling this method if they are needed otherwise.

* - *

This method overwrites any previously set key.

+ *

Unencoded Non-Detached Payload

* - *

This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting - * byte array is used to invoke {@link #setSigningKey(byte[])}.

+ *

Note that if the JWS contains a valid unencoded payload String (what RFC 7797 calls an + * "unencoded non-detached + * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes + * the payload content necessary for signature verification. In this case the resulting {@link Jws} return + * value's {@link Jws#getPayload()} will contain the embedded payload String's UTF-8 bytes.

* - *

Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0

- * - *

This method has been deprecated because the {@code key} argument for this method can be confusing: keys for - * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were - * obtained from the String argument.

- * - *

This method always expected a String argument that was effectively the same as the result of the following - * (pseudocode):

- * - *

{@code String base64EncodedSecretKey = base64Encode(secretKeyBytes);}

- * - *

However, a non-trivial number of JJWT users were confused by the method signature and attempted to - * use raw password strings as the key argument - for example {@code setSigningKey(myPassword)} - which is - * almost always incorrect for cryptographic hashes and can produce erroneous or insecure results.

- * - *

See this - * - * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for - * signature operations.

- * - *

Finally, please use the {@link #setSigningKey(Key) setSigningKey(Key)} instead, as this method and the - * {@code byte[]} variant will be removed before the 1.0.0 release.

- * - * @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signature verification key to use to validate - * any discovered JWS digital signature. - * @return the parser for method chaining. - * @deprecated see {@link JwtParserBuilder#setSigningKey(String)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + * @param jws the Unencoded Payload JWS to parse. + * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. + * @return the parsed Unencoded Payload. + * @since 0.12.0 */ - @Deprecated - JwtParser setSigningKey(String base64EncodedSecretKey); + Jws parseSignedContent(CharSequence jws, InputStream unencodedPayload); /** - * Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not - * a JWS (no signature), this key is not used. - *

- *

Note that this key MUST be a valid key for the signature algorithm found in the JWT header - * (as the {@code alg} header parameter).

- *

- *

This method overwrites any previously set key.

+ * Parses the {@code jws} argument, expected to be a cryptographically-signed {@code Claims} JWS. This is a + * convenience method logically equivalent to the following: * - * @param key the algorithm-specific signature verification key to use to validate any discovered JWS digital - * signature. - * @return the parser for method chaining. - * @deprecated see {@link JwtParserBuilder#setSigningKey(Key)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 - */ - @Deprecated - JwtParser setSigningKey(Key key); - - /** - * Sets the {@link SigningKeyResolver} used to acquire the signing key that should be used to verify - * a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used. - *

- *

Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing - * the JWT and the JWT header or payload (plaintext body or Claims) must be inspected first to determine how to - * look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the - * returned key. For example:

- *

- *

-     * Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
-     *         @Override
-     *         public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
-     *             //inspect the header or claims, lookup and return the signing key
-     *             return getSigningKey(header, claims); //implement me
-     *         }})
-     *     .parseClaimsJws(compact);
-     * 
- *

- *

A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.

- *

- *

This method should only be used if a signing key is not provided by the other {@code setSigningKey*} builder - * methods.

+ *
+     * {@link #parse(CharSequence) parse}(jws).{@link Jwt#accept(JwtVisitor) accept}({@link
+     * Jws#CLAIMS});
* - * @param signingKeyResolver the signing key resolver used to retrieve the signing key. - * @return the parser for method chaining. - * @since 0.4 - * @deprecated see {@link JwtParserBuilder#setSigningKeyResolver(SigningKeyResolver)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + * @param jws a compact cryptographically-signed Claims JWS. + * @return the parsed cryptographically-verified Claims JWS. + * @throws UnsupportedJwtException if the {@code jwt} argument does not represent a signed Claims JWT + * @throws JwtException if the {@code jwt} string cannot be parsed or validated as required. + * @throws IllegalArgumentException if the {@code jwt} string is {@code null} or empty or only whitespace + * @see #parse(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.12.0 */ - @Deprecated - JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver); + Jws parseSignedClaims(CharSequence jws) throws JwtException, IllegalArgumentException; /** - * Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to - * decompress the JWT body. If the parsed JWT is not compressed, this resolver is not used. - *

NOTE: Compression is not defined by the JWT Specification, and it is not expected that other libraries - * (including JJWT versions < 0.6.0) are able to consume a compressed JWT body correctly. This method is only - * useful if the compact JWT was compressed with JJWT >= 0.6.0 or another library that you know implements - * the same behavior.

- *

Default Support

- *

JJWT's default {@link JwtParser} implementation supports both the - * {@link CompressionCodecs#DEFLATE DEFLATE} - * and {@link CompressionCodecs#GZIP GZIP} algorithms by default - you do not need to - * specify a {@code CompressionCodecResolver} in these cases.

- *

However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must implement - * your own {@link CompressionCodecResolver} and specify that via this method and also when - * {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} JWTs.

+ * Parses a JWS known to use the + * RFC 7797: JSON Web Signature (JWS) Unencoded Payload + * Option, using the specified {@code unencodedPayload} for signature verification. * - * @param compressionCodecResolver the compression codec resolver used to decompress the JWT body. - * @return the parser for method chaining. - * @since 0.6.0 - * @deprecated see {@link JwtParserBuilder#setCompressionCodecResolver(CompressionCodecResolver)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 - */ - @Deprecated - JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver); - - /** - * Perform Base64Url decoding with the specified Decoder + *

Unencoded Non-Detached Payload

* - *

JJWT uses a spec-compliant decoder that works on all supported JDK versions, but you may call this method - * to specify a different decoder if you desire.

+ *

Note that if the JWS contains a valid unencoded payload String (what RFC 7797 calls an + * "unencoded non-detached + * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes + * the payload content necessary for signature verification and claims creation.

* - * @param base64UrlDecoder the decoder to use when Base64Url-decoding - * @return the parser for method chaining. - * @since 0.10.0 - * @deprecated see {@link JwtParserBuilder#base64UrlDecodeWith(Decoder)}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 + * @param jws the Unencoded Payload JWS to parse. + * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. + * @return the parsed and validated Claims JWS. + * @throws JwtException if parsing, signature verification, or JWT validation fails. + * @throws IllegalArgumentException if either the {@code jws} or {@code unencodedPayload} are null or empty. + * @since 0.12.0 */ - @Deprecated - JwtParser base64UrlDecodeWith(Decoder base64UrlDecoder); + Jws parseSignedClaims(CharSequence jws, byte[] unencodedPayload) throws JwtException, IllegalArgumentException; /** - * Uses the specified deserializer to convert JSON Strings (UTF-8 byte arrays) into Java Map objects. This is - * used by the parser after Base64Url-decoding to convert JWT/JWS/JWT JSON headers and claims into Java Map - * objects. + * Parses a JWS known to use the + * RFC 7797: JSON Web Signature (JWS) Unencoded Payload + * Option, using the bytes from the specified {@code unencodedPayload} stream for signature verification and + * {@link Claims} creation. * - *

If this method is not called, JJWT will use whatever deserializer it can find at runtime, checking for the - * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found - * in the runtime classpath, an exception will be thrown when one of the various {@code parse}* methods is - * invoked.

+ *

NOTE: however, because calling this method indicates a completed + * {@link Claims} instance is desired, the specified {@code unencodedPayload} JSON stream will be fully + * read into a Claims instance. If this will be problematic for your application (perhaps if you expect extremely + * large Claims), it is recommended to use the {@link #parseSignedContent(CharSequence, InputStream)} method + * instead.

* - * @param deserializer the deserializer to use when converting JSON Strings (UTF-8 byte arrays) into Map objects. - * @return the parser for method chaining. - * @since 0.10.0 - * @deprecated see {@link JwtParserBuilder#deserializeJsonWith(Deserializer)} )}. - * To construct a JwtParser use the corresponding builder via {@link Jwts#parserBuilder()}. This will construct an - * immutable JwtParser. - *

NOTE: this method will be removed before version 1.0 - */ - @Deprecated - JwtParser deserializeJsonWith(Deserializer> deserializer); - - /** - * Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} - * otherwise. - *

- *

Note that if you are reasonably sure that the token is signed, it is more efficient to attempt to - * parse the token (and catching exceptions if necessary) instead of calling this method first before parsing.

+ *

Unencoded Non-Detached Payload

* - * @param jwt the compact serialized JWT to check - * @return {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false} - * otherwise. - */ - boolean isSigned(String jwt); - - /** - * Parses the specified compact serialized JWT string based on the builder's current configuration state and - * returns the resulting JWT or JWS instance. - *

- *

This method returns a JWT or JWS based on the parsed string. Because it may be cumbersome to determine if it - * is a JWT or JWS, or if the body/payload is a Claims or String with {@code instanceof} checks, the - * {@link #parse(String, JwtHandler) parse(String,JwtHandler)} method allows for a type-safe callback approach that - * may help reduce code or instanceof checks.

+ *

Note that if the JWS contains a valid unencoded Payload string (what RFC 7797 calls an + * "unencoded non-detached + * payload", the {@code unencodedPayload} method argument will be ignored, as the JWS already includes + * the payload content necessary for signature verification and Claims creation.

* - * @param jwt the compact serialized JWT to parse - * @return the specified compact serialized JWT string based on the builder's current configuration state. - * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). - * Invalid - * JWTs should not be trusted and should be discarded. - * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail - * signature validation should not be trusted and should be discarded. - * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time - * before the time this method is invoked. - * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace. - * @see #parse(String, JwtHandler) - * @see #parsePlaintextJwt(String) - * @see #parseClaimsJwt(String) - * @see #parsePlaintextJws(String) - * @see #parseClaimsJws(String) + * @param jws the Unencoded Payload JWS to parse. + * @param unencodedPayload the JWS's associated required unencoded payload used for signature verification. + * @return the parsed and validated Claims JWS. + * @throws JwtException if parsing, signature verification, or JWT validation fails. + * @throws IllegalArgumentException if either the {@code jws} or {@code unencodedPayload} are null or empty. + * @since 0.12.0 */ - Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; + Jws parseSignedClaims(CharSequence jws, InputStream unencodedPayload) throws JwtException, IllegalArgumentException; /** - * Parses the specified compact serialized JWT string based on the builder's current configuration state and - * invokes the specified {@code handler} with the resulting JWT or JWS instance. - *

- *

If you are confident of the format of the JWT before parsing, you can create an anonymous subclass using the - * {@link io.jsonwebtoken.JwtHandlerAdapter JwtHandlerAdapter} and override only the methods you know are relevant - * for your use case(s), for example:

- *

- *

-     * String compactJwt = request.getParameter("jwt"); //we are confident this is a signed JWS
+     * Parses the {@code jwe} argument, expected to be an encrypted content JWE. If the JWE
+     * creator set the (optional) {@link Header#getContentType() contentType} header value, the application may
+     * inspect that value to determine how to convert the byte array to the final content type as desired.
      *
-     * String subject = Jwts.parser().setSigningKey(key).parse(compactJwt, new JwtHandlerAdapter<String>() {
-     *     @Override
-     *     public String onClaimsJws(Jws<Claims> jws) {
-     *         return jws.getBody().getSubject();
-     *     }
-     * });
-     * 
- *

- *

If you know the JWT string can be only one type of JWT, then it is even easier to invoke one of the - * following convenience methods instead of this one:

- *

- *

    - *
  • {@link #parsePlaintextJwt(String)}
  • - *
  • {@link #parseClaimsJwt(String)}
  • - *
  • {@link #parsePlaintextJws(String)}
  • - *
  • {@link #parseClaimsJws(String)}
  • - *
+ *

This is a convenience method logically equivalent to the following:

* - * @param jwt the compact serialized JWT to parse - * @return the result returned by the {@code JwtHandler} - * @throws MalformedJwtException if the specified JWT was incorrectly constructed (and therefore invalid). - * Invalid JWTs should not be trusted and should be discarded. - * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail - * signature validation should not be trusted and should be discarded. - * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time - * before the time this method is invoked. - * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace, or if the - * {@code handler} is {@code null}. - * @see #parsePlaintextJwt(String) - * @see #parseClaimsJwt(String) - * @see #parsePlaintextJws(String) - * @see #parseClaimsJws(String) - * @see #parse(String) - * @since 0.2 - */ - T parse(String jwt, JwtHandler handler) - throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; - - /** - * Parses the specified compact serialized JWT string based on the builder's current configuration state and - * returns - * the resulting unsigned plaintext JWT instance. - *

- *

This is a convenience method that is usable if you are confident that the compact string argument reflects an - * unsigned plaintext JWT. An unsigned plaintext JWT has a String (non-JSON) body payload and it is not - * cryptographically signed.

- *

- *

If the compact string presented does not reflect an unsigned plaintext JWT with non-JSON string body, - * an {@link UnsupportedJwtException} will be thrown.

+ *
+     * {@link #parse(CharSequence) parse}(jwe).{@link Jwt#accept(JwtVisitor) accept}({@link
+     * Jwe#CONTENT});
* - * @param plaintextJwt a compact serialized unsigned plaintext JWT string. - * @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string. - * @throws UnsupportedJwtException if the {@code plaintextJwt} argument does not represent an unsigned plaintext - * JWT - * @throws MalformedJwtException if the {@code plaintextJwt} string is not a valid JWT - * @throws SignatureException if the {@code plaintextJwt} string is actually a JWS and signature validation - * fails - * @throws IllegalArgumentException if the {@code plaintextJwt} string is {@code null} or empty or only whitespace - * @see #parseClaimsJwt(String) - * @see #parsePlaintextJws(String) - * @see #parseClaimsJws(String) - * @see #parse(String, JwtHandler) - * @see #parse(String) - * @since 0.2 + * @param jwe a compact encrypted content JWE. + * @return the parsed decrypted content JWE. + * @throws UnsupportedJwtException if the {@code jwe} argument does not represent an encrypted content JWE + * @throws JwtException if the {@code jwe} string cannot be parsed or validated as required. + * @throws IllegalArgumentException if the {@code jwe} string is {@code null} or empty or only whitespace + * @see #parse(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.12.0 */ - Jwt parsePlaintextJwt(String plaintextJwt) - throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; + Jwe parseEncryptedContent(CharSequence jwe) throws JwtException, IllegalArgumentException; /** - * Parses the specified compact serialized JWT string based on the builder's current configuration state and - * returns - * the resulting unsigned plaintext JWT instance. - *

- *

This is a convenience method that is usable if you are confident that the compact string argument reflects an - * unsigned Claims JWT. An unsigned Claims JWT has a {@link Claims} body and it is not cryptographically - * signed.

- *

- *

If the compact string presented does not reflect an unsigned Claims JWT, an - * {@link UnsupportedJwtException} will be thrown.

+ * Parses the {@code jwe} argument, expected to be an encrypted {@code Claims} JWE. This is a + * convenience method logically equivalent to the following: * - * @param claimsJwt a compact serialized unsigned Claims JWT string. - * @return the {@link Jwt Jwt} instance that reflects the specified compact JWT string. - * @throws UnsupportedJwtException if the {@code claimsJwt} argument does not represent an unsigned Claims JWT - * @throws MalformedJwtException if the {@code claimsJwt} string is not a valid JWT - * @throws SignatureException if the {@code claimsJwt} string is actually a JWS and signature validation - * fails - * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time - * before the time this method is invoked. - * @throws IllegalArgumentException if the {@code claimsJwt} string is {@code null} or empty or only whitespace - * @see #parsePlaintextJwt(String) - * @see #parsePlaintextJws(String) - * @see #parseClaimsJws(String) - * @see #parse(String, JwtHandler) - * @see #parse(String) - * @since 0.2 - */ - Jwt parseClaimsJwt(String claimsJwt) - throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; - - /** - * Parses the specified compact serialized JWS string based on the builder's current configuration state and - * returns - * the resulting plaintext JWS instance. - *

- *

This is a convenience method that is usable if you are confident that the compact string argument reflects a - * plaintext JWS. A plaintext JWS is a JWT with a String (non-JSON) body (payload) that has been - * cryptographically signed.

- *

- *

If the compact string presented does not reflect a plaintext JWS, an {@link UnsupportedJwtException} - * will be thrown.

+ *
+     * {@link #parse(CharSequence) parse}(jwe).{@link Jwt#accept(JwtVisitor) accept}({@link
+     * Jwe#CLAIMS});
* - * @param plaintextJws a compact serialized JWS string. - * @return the {@link Jws Jws} instance that reflects the specified compact JWS string. - * @throws UnsupportedJwtException if the {@code plaintextJws} argument does not represent an plaintext JWS - * @throws MalformedJwtException if the {@code plaintextJws} string is not a valid JWS - * @throws SignatureException if the {@code plaintextJws} JWS signature validation fails - * @throws IllegalArgumentException if the {@code plaintextJws} string is {@code null} or empty or only whitespace - * @see #parsePlaintextJwt(String) - * @see #parseClaimsJwt(String) - * @see #parseClaimsJws(String) - * @see #parse(String, JwtHandler) - * @see #parse(String) - * @since 0.2 + * @param jwe a compact encrypted Claims JWE. + * @return the parsed decrypted Claims JWE. + * @throws UnsupportedJwtException if the {@code jwe} argument does not represent an encrypted Claims JWE. + * @throws JwtException if the {@code jwe} string cannot be parsed or validated as required. + * @throws IllegalArgumentException if the {@code jwe} string is {@code null} or empty or only whitespace + * @see #parse(CharSequence) + * @see Jwt#accept(JwtVisitor) + * @since 0.12.0 */ - Jws parsePlaintextJws(String plaintextJws) - throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; - - /** - * Parses the specified compact serialized JWS string based on the builder's current configuration state and - * returns - * the resulting Claims JWS instance. - *

- *

This is a convenience method that is usable if you are confident that the compact string argument reflects a - * Claims JWS. A Claims JWS is a JWT with a {@link Claims} body that has been cryptographically signed.

- *

- *

If the compact string presented does not reflect a Claims JWS, an {@link UnsupportedJwtException} will be - * thrown.

- * - * @param claimsJws a compact serialized Claims JWS string. - * @return the {@link Jws Jws} instance that reflects the specified compact Claims JWS string. - * @throws UnsupportedJwtException if the {@code claimsJws} argument does not represent an Claims JWS - * @throws MalformedJwtException if the {@code claimsJws} string is not a valid JWS - * @throws SignatureException if the {@code claimsJws} JWS signature validation fails - * @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time - * before the time this method is invoked. - * @throws IllegalArgumentException if the {@code claimsJws} string is {@code null} or empty or only whitespace - * @see #parsePlaintextJwt(String) - * @see #parseClaimsJwt(String) - * @see #parsePlaintextJws(String) - * @see #parse(String, JwtHandler) - * @see #parse(String) - * @since 0.2 - */ - Jws parseClaimsJws(String claimsJws) - throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; + Jwe parseEncryptedClaims(CharSequence jwe) throws JwtException, IllegalArgumentException; } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtParserBuilder.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtParserBuilder.java (.../JwtParserBuilder.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtParserBuilder.java (.../JwtParserBuilder.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,32 +15,134 @@ */ package io.jsonwebtoken; +import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.Decoder; import io.jsonwebtoken.io.Deserializer; +import io.jsonwebtoken.lang.Builder; +import io.jsonwebtoken.lang.Conjunctor; +import io.jsonwebtoken.lang.NestedCollection; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.SecureDigestAlgorithm; +import javax.crypto.SecretKey; +import java.io.InputStream; import java.security.Key; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; import java.util.Date; import java.util.Map; /** * A builder to construct a {@link JwtParser}. Example usage: *
{@code
- *     Jwts.parserBuilder()
- *         .setSigningKey(...)
+ *     Jwts.parser()
  *         .requireIssuer("https://issuer.example.com")
+ *         .verifyWith(...)
  *         .build()
  *         .parse(jwtString)
  * }
+ * * @since 0.11.0 */ -public interface JwtParserBuilder { +@SuppressWarnings("JavadocLinkAsPlainText") +public interface JwtParserBuilder extends Builder { /** + * Enables parsing of Unsecured JWTs (JWTs with an 'alg' (Algorithm) header value of + * 'none' or missing the 'alg' header entirely). Be careful when calling this method - one should fully understand + * Unsecured JWS Security Considerations + * before enabling this feature. + *

If this method is not called, Unsecured JWTs are disabled by default as mandated by + * RFC 7518, Section + * 3.6.

+ * + * @return the builder for method chaining. + * @see Unsecured JWS Security Considerations + * @see Using the Algorithm "none" + * @see Jwts.SIG#NONE + * @see #unsecuredDecompression() + * @since 0.12.0 + */ + JwtParserBuilder unsecured(); + + /** + * If the parser is {@link #unsecured()}, calling this method additionally enables + * payload decompression of Unsecured JWTs (JWTs with an 'alg' (Algorithm) header value of 'none') that also have + * a 'zip' (Compression) header. This behavior is disabled by default because using compression + * algorithms with data from unverified (unauthenticated) parties can be susceptible to Denial of Service attacks + * and other data integrity problems as described in + * In the + * Compression Hornet’s Nest: A Security Study of Data Compression in Network Services. + * + *

Because this behavior is only relevant if the parser is unsecured, + * calling this method without also calling {@link #unsecured()} will result in a build exception, as the + * incongruent state could reflect a misunderstanding of both behaviors which should be remedied by the + * application developer.

+ * + * As is the case for {@link #unsecured()}, be careful when calling this method - one should fully + * understand + * Unsecured JWS Security Considerations + * before enabling this feature. + * + * @return the builder for method chaining. + * @see Unsecured JWS Security Considerations + * @see In the + * Compression Hornet’s Nest: A Security Study of Data Compression in Network Services + * @see Jwts.SIG#NONE + * @see #unsecured() + * @since 0.12.0 + */ + JwtParserBuilder unsecuredDecompression(); + + /** + * 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 + * those extensions' header names are not specified via this method, the parser will reject that JWT. + * + *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser + * configuration, for example:

+ *
+     * parserBuilder.critical().add("headerName").{@link Conjunctor#and() and()} // etc...
+ * + *

Extension Behavior

+ * + *

The {@code critical} collection only identifies header parameter names that are used in extensions supported + * by the application. Application developers, not JJWT, MUST perform the associated extension behavior + * using the parsed JWT.

+ * + *

Continued Parser Configuration

+ *

When finished, use the collection's + * {@link Conjunctor#and() and()} method to continue parser configuration, for example: + *

+     * Jwts.parser()
+     *     .critical().add("headerName").{@link Conjunctor#and() and()} // return parent
+     * // resume parser configuration...
+ * + * @return the {@link NestedCollection} to use for {@code crit} configuration. + * @see ProtectedHeader#getCritical() + * @since 0.12.0 + */ + NestedCollection critical(); + + /** + * Sets the JCA Provider to use during cryptographic signature and key decryption operations, or {@code null} if the + * JCA subsystem preferred provider should be used. + * + * @param provider the JCA Provider to use during cryptographic signature and decryption operations, or {@code null} + * if the JCA subsystem preferred provider should be used. + * @return the builder for method chaining. + * @since 0.12.0 + */ + JwtParserBuilder provider(Provider provider); + + /** * Ensures that the specified {@code jti} exists in the parsed JWT. If missing or if the parsed * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * - * @param id + * @param id the required value of the {@code jti} header parameter. * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException @@ -52,7 +154,7 @@ * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * - * @param subject + * @param subject the required value of the {@code sub} header parameter. * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException @@ -61,10 +163,10 @@ /** * Ensures that the specified {@code aud} exists in the parsed JWT. If missing or if the parsed - * value does not equal the specified value, an exception will be thrown indicating that the + * value does not contain the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * - * @param audience + * @param audience the required value of the {@code aud} header parameter. * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException @@ -76,7 +178,7 @@ * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * - * @param issuer + * @param issuer the required value of the {@code iss} header parameter. * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException @@ -88,7 +190,7 @@ * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * - * @param issuedAt + * @param issuedAt the required value of the {@code iat} header parameter. * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException @@ -100,7 +202,7 @@ * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * - * @param expiration + * @param expiration the required value of the {@code exp} header parameter. * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException @@ -112,7 +214,7 @@ * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * - * @param notBefore + * @param notBefore the required value of the {@code npf} header parameter. * @return the parser builder for method chaining * @see MissingClaimException * @see IncorrectClaimException @@ -124,8 +226,8 @@ * value does not equal the specified value, an exception will be thrown indicating that the * JWT is invalid and may not be used. * - * @param claimName - * @param value + * @param claimName the name of a claim that must exist + * @param value the required value of the specified {@code claimName} * @return the parser builder for method chaining. * @see MissingClaimException * @see IncorrectClaimException @@ -138,49 +240,78 @@ * * @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT. * @return the parser builder for method chaining. + * @deprecated since 0.12.0 for the more modern builder-style named {@link #clock(Clock)} method. + * This method will be removed before the JJWT 1.0 release. */ + @Deprecated JwtParserBuilder setClock(Clock clock); /** + * Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT. + * The parser uses a default Clock implementation that simply returns {@code new Date()} when called. + * + * @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT. + * @return the parser builder for method chaining. + */ + JwtParserBuilder clock(Clock clock); + + /** * Sets the amount of clock skew in seconds to tolerate when verifying the local time against the {@code exp} * and {@code nbf} claims. * * @param seconds the number of seconds to tolerate for clock skew when verifying {@code exp} or {@code nbf} claims. * @return the parser builder for method chaining. * @throws IllegalArgumentException if {@code seconds} is a value greater than {@code Long.MAX_VALUE / 1000} as - * any such value would cause numeric overflow when multiplying by 1000 to obtain a millisecond value. + * any such value would cause numeric overflow when multiplying by 1000 to obtain + * a millisecond value. + * @deprecated since 0.12.0 in favor of the shorter and more modern builder-style named + * {@link #clockSkewSeconds(long)}. This method will be removed before the JJWT 1.0 release. */ + @Deprecated JwtParserBuilder setAllowedClockSkewSeconds(long seconds) throws IllegalArgumentException; /** - * Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not - * a JWS (no signature), this key is not used. - *

+ * Sets the amount of clock skew in seconds to tolerate when verifying the local time against the {@code exp} + * and {@code nbf} claims. + * + * @param seconds the number of seconds to tolerate for clock skew when verifying {@code exp} or {@code nbf} claims. + * @return the parser builder for method chaining. + * @throws IllegalArgumentException if {@code seconds} is a value greater than {@code Long.MAX_VALUE / 1000} as + * any such value would cause numeric overflow when multiplying by 1000 to obtain + * a millisecond value. + */ + JwtParserBuilder clockSkewSeconds(long seconds) throws IllegalArgumentException; + + /** + *

Deprecation Notice

+ * + *

This method has been deprecated since 0.12.0 and will be removed before 1.0. It was not + * readily obvious to many JJWT users that this method was for bytes that pertained only to HMAC + * {@code SecretKey}s, and could be confused with keys of other types. It is better to obtain a type-safe + * {@link SecretKey} instance and call {@link #verifyWith(SecretKey)} instead.

+ * + *

Previous Documentation

+ * + *

Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not + * a JWS (no signature), this key is not used.

+ * *

Note that this key MUST be a valid key for the signature algorithm found in the JWT header * (as the {@code alg} header parameter).

- *

+ * *

This method overwrites any previously set key.

* * @param key the algorithm-specific signature verification key used to validate any discovered JWS digital * signature. * @return the parser builder for method chaining. + * @deprecated since 0.12.0 in favor of {@link #verifyWith(SecretKey)} for type safety and name + * congruence with the {@link #decryptWith(SecretKey)} method. */ + @Deprecated JwtParserBuilder setSigningKey(byte[] key); /** - * Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not - * a JWS (no signature), this key is not used. + *

Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0

* - *

Note that this key MUST be a valid key for the signature algorithm found in the JWT header - * (as the {@code alg} header parameter).

- * - *

This method overwrites any previously set key.

- * - *

This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting - * byte array is used to invoke {@link #setSigningKey(byte[])}.

- * - *

Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0

- * *

This method has been deprecated because the {@code key} argument for this method can be confusing: keys for * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were * obtained from the String argument.

@@ -199,78 +330,429 @@ * StackOverflow answer explaining why raw (non-base64-encoded) strings are almost always incorrect for * signature operations.

* - *

Finally, please use the {@link #setSigningKey(Key) setSigningKey(Key)} instead, as this method and the - * {@code byte[]} variant will be removed before the 1.0.0 release.

+ *

Finally, please use the {@link #verifyWith(SecretKey)} method instead, as this method (and likely + * {@link #setSigningKey(byte[])}) will be removed before the 1.0.0 release.

* - * @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signature verification key to use to validate - * any discovered JWS digital signature. + *

Previous JavaDoc

+ * + *

This is a convenience method that equates to the following:

+ * + *
+     * byte[] bytes = Decoders.{@link io.jsonwebtoken.io.Decoders#BASE64 BASE64}.decode(base64EncodedSecretKey);
+     * Key key = Keys.{@link io.jsonwebtoken.security.Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor}(bytes);
+     * return {@link #verifyWith(SecretKey) verifyWith}(key);
+ * + * @param base64EncodedSecretKey BASE64-encoded HMAC-SHA key bytes used to create a Key which will be used to + * verify all encountered JWS digital signatures. * @return the parser builder for method chaining. + * @deprecated in favor of {@link #verifyWith(SecretKey)} as explained in the above Deprecation Notice, + * and will be removed in 1.0.0. */ + @Deprecated JwtParserBuilder setSigningKey(String base64EncodedSecretKey); /** - * Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not - * a JWS (no signature), this key is not used. - *

- *

Note that this key MUST be a valid key for the signature algorithm found in the JWT header - * (as the {@code alg} header parameter).

- *

- *

This method overwrites any previously set key.

+ *

Deprecation Notice

* - * @param key the algorithm-specific signature verification key to use to validate any discovered JWS digital - * signature. + *

This method is being renamed to accurately reflect its purpose - the key is not technically a signing key, + * it is a signature verification key, and the two concepts can be different, especially with asymmetric key + * cryptography. The method has been deprecated since 0.12.0 in favor of + * {@link #verifyWith(SecretKey)} for type safety, to reflect accurate naming of the concept, and for name + * congruence with the {@link #decryptWith(SecretKey)} method.

+ * + *

This method merely delegates directly to {@link #verifyWith(SecretKey)} or {@link #verifyWith(PublicKey)}}.

+ * + * @param key the algorithm-specific signature verification key to use to verify all encountered JWS digital + * signatures. * @return the parser builder for method chaining. + * @deprecated since 0.12.0 in favor of {@link #verifyWith(SecretKey)} for naming congruence with the + * {@link #decryptWith(SecretKey)} method. */ + @Deprecated JwtParserBuilder setSigningKey(Key key); /** - * Sets the {@link SigningKeyResolver} used to acquire the signing key that should be used to verify - * a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used. - *

+ * Sets the signature verification SecretKey used to verify all encountered JWS signatures. If the encountered JWT + * string is not a JWS (e.g. unsigned or a JWE), this key is not used. + * + *

This is a convenience method to use in a specific scenario: when the parser will only ever encounter + * JWSs with signatures that can always be verified by a single SecretKey. This also implies that this key + * MUST be a valid key for the signature algorithm ({@code alg} header) used for the JWS.

+ * + *

If there is any chance that the parser will also encounter JWEs, or JWSs that need different signature + * verification keys based on the JWS being parsed, it is strongly recommended to configure your own + * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

+ * + *

Calling this method overrides any previously set signature verification key.

+ * + * @param key the signature verification key to use to verify all encountered JWS digital signatures. + * @return the parser builder for method chaining. + * @see #verifyWith(PublicKey) + * @since 0.12.0 + */ + JwtParserBuilder verifyWith(SecretKey key); + + /** + * Sets the signature verification PublicKey used to verify all encountered JWS signatures. If the encountered JWT + * string is not a JWS (e.g. unsigned or a JWE), this key is not used. + * + *

This is a convenience method to use in a specific scenario: when the parser will only ever encounter + * JWSs with signatures that can always be verified by a single PublicKey. This also implies that this key + * MUST be a valid key for the signature algorithm ({@code alg} header) used for the JWS.

+ * + *

If there is any chance that the parser will also encounter JWEs, or JWSs that need different signature + * verification keys based on the JWS being parsed, it is strongly recommended to configure your own + * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

+ * + *

Calling this method overrides any previously set signature verification key.

+ * + * @param key the signature verification key to use to verify all encountered JWS digital signatures. + * @return the parser builder for method chaining. + * @see #verifyWith(SecretKey) + * @since 0.12.0 + */ + JwtParserBuilder verifyWith(PublicKey key); + + /** + * Sets the decryption SecretKey used to decrypt all encountered JWEs. If the encountered JWT string is not a + * JWE (e.g. a JWS), this key is not used. + * + *

This is a convenience method to use in specific circumstances: when the parser will only ever encounter + * JWEs that can always be decrypted by a single SecretKey. This also implies that this key MUST be a valid + * key for both the key management algorithm ({@code alg} header) and the content encryption algorithm + * ({@code enc} header) used for the JWE.

+ * + *

If there is any chance that the parser will also encounter JWSs, or JWEs that need different decryption + * keys based on the JWE being parsed, it is strongly recommended to configure your own + * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

+ * + *

Calling this method overrides any previously set decryption key.

+ * + * @param key the algorithm-specific decryption key to use to decrypt all encountered JWEs. + * @return the parser builder for method chaining. + * @see #decryptWith(PrivateKey) + * @since 0.12.0 + */ + JwtParserBuilder decryptWith(SecretKey key); + + /** + * Sets the decryption PrivateKey used to decrypt all encountered JWEs. If the encountered JWT string is not a + * JWE (e.g. a JWS), this key is not used. + * + *

This is a convenience method to use in specific circumstances: when the parser will only ever encounter JWEs + * that can always be decrypted by a single PrivateKey. This also implies that this key MUST be a valid + * key for the JWE's key management algorithm ({@code alg} header).

+ * + *

If there is any chance that the parser will also encounter JWSs, or JWEs that need different decryption + * keys based on the JWE being parsed, it is strongly recommended to configure your own + * {@link #keyLocator(Locator) keyLocator} instead of calling this method.

+ * + *

Calling this method overrides any previously set decryption key.

+ * + * @param key the algorithm-specific decryption key to use to decrypt all encountered JWEs. + * @return the parser builder for method chaining. + * @see #decryptWith(SecretKey) + * @since 0.12.0 + */ + JwtParserBuilder decryptWith(PrivateKey key); + + /** + * Sets the {@link Locator} used to acquire any signature verification or decryption key needed during parsing. + *
    + *
  • If the parsed String is a JWS, the {@code Locator} will be called to find the appropriate key + * necessary to verify the JWS signature.
  • + *
  • If the parsed String is a JWE, it will be called to find the appropriate decryption key.
  • + *
+ * + *

A key {@code Locator} is necessary when the signature verification or decryption key is not + * already known before parsing the JWT and the JWT header must be inspected first to determine how to + * look up the verification or decryption key. Once returned by the locator, the JwtParser will then either + * verify the JWS signature or decrypt the JWE payload with the returned key. For example:

+ * + *
+     * Jws<Claims> jws = Jwts.parser().keyLocator(new Locator<Key>() {
+     *         @Override
+     *         public Key locate(Header<?> header) {
+     *             if (header instanceof JwsHeader) {
+     *                 return getSignatureVerificationKey((JwsHeader)header); // implement me
+     *             } else {
+     *                 return getDecryptionKey((JweHeader)header); // implement me
+     *             }
+     *         }})
+     *     .build()
+     *     .parseSignedClaims(compact);
+     * 
+ * + *

A Key {@code Locator} is invoked once during parsing before performing decryption or signature verification.

+ * + *

Provider-constrained Keys

+ * + *

If any verification or decryption key returned from a Key {@code Locator} must be used with a specific + * security {@link Provider} (such as for PKCS11 or Hardware Security Module (HSM) keys), you must make that + * Provider available for JWT parsing in one of 3 ways, listed in order of recommendation and simplicity:

+ * + *
    + *
  1. + * Configure the Provider in the JVM, either by modifying the {@code java.security} file or by + * registering the Provider dynamically via + * {@link java.security.Security#addProvider(Provider) Security.addProvider(Provider)}. This is the + * recommended approach so you do not need to modify code anywhere that may need to parse JWTs.
  2. + *
  3. Specify the {@code Provider} as the {@code JwtParser} default via {@link #provider(Provider)}. This will + * ensure the provider is used by default with all located keys unless overridden by a + * key-specific Provider. This is only recommended when you are confident that all JWTs encountered by the + * parser instance will use keys attributed to the same {@code Provider}, unless overridden by a specific + * key.
  4. + *
  5. Associate the {@code Provider} with a specific key so it is used for that key only. This option + * is useful if some located keys require a specific provider, while other located keys can assume a + * default provider.
  6. + *
+ * + *

If you need to use option #3, you associate a key for the {@code JwtParser}'s needs by using a + * key builder before returning the key as the {@code Locator} return value. For example:

+ *
+     *     public Key locate(Header<?> header) {
+     *         PrivateKey key = findKey(header); // or SecretKey
+     *         Provider keySpecificProvider = getKeyProvider(key); // implement me
+     *         // associate the key with its required provider:
+     *         return Keys.builder(key).provider(keySpecificProvider).build();
+     *     }
+ * + * @param keyLocator the locator used to retrieve decryption or signature verification keys. + * @return the parser builder for method chaining. + * @since 0.12.0 + */ + JwtParserBuilder keyLocator(Locator keyLocator); + + /** + *

Deprecation Notice

+ * + *

This method has been deprecated as of JJWT version 0.12.0 because it only supports key location + * for JWSs (signed JWTs) instead of both signed (JWS) and encrypted (JWE) scenarios. Use the + * {@link #keyLocator(Locator) keyLocator} method instead to ensure a locator that can work for both JWS and + * JWE inputs. This method will be removed for the 1.0 release.

+ * + *

Previous Documentation

+ * + *

Sets the {@link SigningKeyResolver} used to acquire the signing key that should be used to verify + * a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used.

+ * *

Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing - * the JWT and the JWT header or payload (plaintext body or Claims) must be inspected first to determine how to + * the JWT and the JWT header or payload (content byte array or Claims) must be inspected first to determine how to * look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the * returned key. For example:

- *

+ * *

      * Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
      *         @Override
      *         public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {
      *             //inspect the header or claims, lookup and return the signing key
      *             return getSigningKey(header, claims); //implement me
      *         }})
-     *     .parseClaimsJws(compact);
+     *     .build().parseSignedClaims(compact);
      * 
- *

+ * *

A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.

- *

- *

This method should only be used if a signing key is not provided by the other {@code setSigningKey*} builder - * methods.

* * @param signingKeyResolver the signing key resolver used to retrieve the signing key. * @return the parser builder for method chaining. + * @deprecated since 0.12.0 in favor of {@link #keyLocator(Locator)} */ + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated JwtParserBuilder setSigningKeyResolver(SigningKeyResolver signingKeyResolver); /** + * Configures the parser's supported {@link AeadAlgorithm}s used to decrypt JWE payloads. If the parser + * encounters a JWE {@link JweHeader#getEncryptionAlgorithm() enc} header value that equals an + * AEAD algorithm's {@link Identifiable#getId() id}, that algorithm will be used to decrypt the JWT + * payload. + * + *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser + * configuration, for example:

+ *
+     * parserBuilder.enc().add(anAeadAlgorithm).{@link Conjunctor#and() and()} // etc...
+ * + *

Standard Algorithms and Overrides

+ * + *

All JWA-standard AEAD encryption algorithms in the {@link Jwts.ENC} registry are supported by default and + * do not need to be added. The collection may be useful however for removing some algorithms (for example, + * any algorithms not used by the application, or those not compatible with application security requirements), + * or for adding custom implementations.

+ * + *

Custom Implementations

+ * + *

There may be only one registered {@code AeadAlgorithm} per algorithm {@code id}, and any algorithm + * 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}. But beware: + * + *

+ * Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will + * replace (override) the JJWT standard algorithm implementation.
+ * + *

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).

+ * + * @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 "enc" (Encryption Algorithm) Header Parameter + * @see Encryption Algorithm Name (id) requirements + * @since 0.12.0 + */ + NestedCollection enc(); + + /** + * Configures the parser's supported {@link KeyAlgorithm}s used to obtain a JWE's decryption key. If the + * parser encounters a JWE {@link JweHeader#getAlgorithm()} alg} header value that equals a {@code KeyAlgorithm}'s + * {@link Identifiable#getId() id}, that key algorithm will be used to obtain the JWE's decryption key. + * + *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser + * configuration, for example:

+ *
+     * parserBuilder.key().add(aKeyAlgorithm).{@link Conjunctor#and() and()} // etc...
+ * + *

Standard Algorithms and Overrides

+ * + *

All JWA-standard key encryption algorithms in the {@link Jwts.KEY} registry are supported by default and + * do not need to be added. The collection may be useful however for removing some algorithms (for example, + * any algorithms not used by the application, or those not compatible with application security requirements), + * or for adding custom implementations.

+ * + *

Custom Implementations

+ * + *

There may be only one registered {@code KeyAlgorithm} per algorithm {@code id}, and any algorithm + * 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}. But beware: + * + *

+ * Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will + * replace (override) the JJWT standard algorithm implementation.
+ * + *

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).

+ * + * @return the {@link NestedCollection} to use to configure the key algorithms available when parsing. + * @see JwtBuilder#encryptWith(Key, KeyAlgorithm, AeadAlgorithm) + * @see Jwts.KEY + * @see JWE "alg" (Algorithm) Header Parameter + * @see Key Algorithm Name (id) requirements + * @since 0.12.0 + */ + NestedCollection, JwtParserBuilder> key(); + + /** + * Configures the parser's supported + * {@link io.jsonwebtoken.security.SignatureAlgorithm SignatureAlgorithm} and + * {@link io.jsonwebtoken.security.MacAlgorithm MacAlgorithm}s used to verify JWS signatures. If the parser + * 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. + * + *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser + * configuration, for example:

+ *
+     * parserBuilder.sig().add(aSignatureAlgorithm).{@link Conjunctor#and() and()} // etc...
+ * + *

Standard Algorithms and Overrides

+ * + *

All JWA-standard signature and MAC algorithms in the {@link Jwts.SIG} registry are supported by default and + * do not need to be added. The collection may be useful however for removing some algorithms (for example, + * any algorithms not used by the application, or those not compatible with application security requirements), or + * for adding custom implementations.

+ * + *

Custom Implementations

+ * + *

There may be only one registered {@code SecureDigestAlgorithm} per algorithm {@code id}, and any algorithm + * 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}. But beware: + * + *

+ * Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will + * replace (override) the JJWT standard algorithm implementation.
+ * + *

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).

+ * + * @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 JWS "alg" (Algorithm) Header Parameter + * @see Algorithm Name (id) requirements + * @since 0.12.0 + */ + NestedCollection, JwtParserBuilder> sig(); + + /** + * Configures the parser's supported {@link CompressionAlgorithm}s used to decompress JWT payloads. If the parser + * encounters a JWT {@link ProtectedHeader#getCompressionAlgorithm() zip} header value that equals a + * compression algorithm's {@link Identifiable#getId() id}, that algorithm will be used to decompress the JWT + * payload. + * + *

The collection's {@link Conjunctor#and() and()} method returns to the builder for continued parser + * configuration, for example:

+ *
+     * parserBuilder.zip().add(aCompressionAlgorithm).{@link Conjunctor#and() and()} // etc...
+ * + *

Standard Algorithms and Overrides

+ * + *

All JWA-standard compression algorithms in the {@link Jwts.ZIP} registry are supported by default and + * do not need to be added. The collection may be useful however for removing some algorithms (for example, + * any algorithms not used by the application), or for adding custom implementations.

+ * + *

Custom Implementations

+ * + *

There may be only one registered {@code CompressionAlgorithm} per algorithm {@code id}, and any algorithm + * 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}. But beware: + * + *

+ * Any algorithm instance added to this collection with a JWA-standard {@link Identifiable#getId() id} will + * replace (override) the JJWT standard algorithm implementation.
+ * + *

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).

+ * + * @return the {@link NestedCollection} to use to configure the compression algorithms available when parsing. + * @see JwtBuilder#compressWith(CompressionAlgorithm) + * @see Jwts.ZIP + * @see "zip" (Compression Algorithm) Header Parameter + * @see Compression Algorithm Name (id) requirements + * @since 0.12.0 + */ + NestedCollection zip(); + + /** + *

Deprecated as of JJWT 0.12.0. This method will be removed before the 1.0 release.

+ * + *

This method has been deprecated as of JJWT version 0.12.0 because it imposed unnecessary + * implementation requirements on application developers when simply adding to a compression algorithm collection + * would suffice. Use the {@link #zip()} method instead to add + * any custom algorithm implementations without needing to also implement a Locator implementation.

+ * + *

Previous Documentation

+ *

* Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to * decompress the JWT body. If the parsed JWT is not compressed, this resolver is not used. - *

NOTE: Compression is not defined by the JWT Specification, and it is not expected that other libraries - * (including JJWT versions < 0.6.0) are able to consume a compressed JWT body correctly. This method is only - * useful if the compact JWT was compressed with JJWT >= 0.6.0 or another library that you know implements - * the same behavior.

- *

Default Support

- *

JJWT's default {@link JwtParser} implementation supports both the - * {@link CompressionCodecs#DEFLATE DEFLATE} - * and {@link CompressionCodecs#GZIP GZIP} algorithms by default - you do not need to + * + *

WARNING: Compression is not defined by the JWS Specification - only the JWE Specification - and it is + * not expected that other libraries (including JJWT versions < 0.6.0) are able to consume a compressed JWS + * body correctly.

+ * + *

Default Support

+ * + *

JJWT's default {@link JwtParser} implementation supports both the {@link Jwts.ZIP#DEF DEF} + * and {@link Jwts.ZIP#GZIP GZIP} algorithms by default - you do not need to * specify a {@code CompressionCodecResolver} in these cases.

- *

However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must implement - * your own {@link CompressionCodecResolver} and specify that via this method and also when - * {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} JWTs.

* * @param compressionCodecResolver the compression codec resolver used to decompress the JWT body. * @return the parser builder for method chaining. + * @deprecated since 0.12.0 in favor of {@link #zip()}. This method will be removed before the + * 1.0 release. */ + @Deprecated JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver); /** @@ -281,10 +763,27 @@ * * @param base64UrlDecoder the decoder to use when Base64Url-decoding * @return the parser builder for method chaining. + * @deprecated since 0.12.0 in favor of {@link #b64Url(Decoder)}. This method will be removed + * before the JJWT 1.0 release. */ - JwtParserBuilder base64UrlDecodeWith(Decoder base64UrlDecoder); + @Deprecated + JwtParserBuilder base64UrlDecodeWith(Decoder base64UrlDecoder); /** + * Perform Base64Url decoding during parsing with the specified {@code InputStream} Decoder. + * The Decoder's {@link Decoder#decode(Object) decode} method will be given a source {@code InputStream} to + * wrap, and the resulting (wrapping) {@code InputStream} will be used for reading , ensuring automatic + * Base64URL-decoding during read operations. + * + *

JJWT uses a spec-compliant decoder that works on all supported JDK versions, but you may call this method + * to specify a different stream decoder if desired.

+ * + * @param base64UrlDecoder the stream decoder to use when Base64Url-decoding + * @return the parser builder for method chaining. + */ + JwtParserBuilder b64Url(Decoder base64UrlDecoder); + + /** * Uses the specified deserializer to convert JSON Strings (UTF-8 byte arrays) into Java Map objects. This is * used by the parser after Base64Url-decoding to convert JWT/JWS/JWT JSON headers and claims into Java Map * objects. @@ -296,11 +795,31 @@ * * @param deserializer the deserializer to use when converting JSON Strings (UTF-8 byte arrays) into Map objects. * @return the builder for method chaining. + * @deprecated since 0.12.0 in favor of {@link #json(Deserializer)}. + * This method will be removed before the JJWT 1.0 release. */ - JwtParserBuilder deserializeJsonWith(Deserializer> deserializer); + @Deprecated + JwtParserBuilder deserializeJsonWith(Deserializer> deserializer); /** + * Uses the specified JSON {@link Deserializer} to deserialize JSON (UTF-8 byte streams) into Java Map objects. + * This is used by the parser after Base64Url-decoding to convert JWT/JWS/JWT headers and Claims into Java Map + * instances. + * + *

If this method is not called, JJWT will use whatever Deserializer it can find at runtime, checking for the + * presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found + * in the runtime classpath, an exception will be thrown when one of the various {@code parse}* methods is + * invoked.

+ * + * @param deserializer the deserializer to use to deserialize JSON (UTF-8 byte streams) into Map instances. + * @return the builder for method chaining. + * @since 0.12.0 + */ + JwtParserBuilder json(Deserializer> deserializer); + + /** * Returns an immutable/thread-safe {@link JwtParser} created from the configuration from this JwtParserBuilder. + * * @return an immutable/thread-safe JwtParser created from the configuration from this JwtParserBuilder. */ JwtParser build(); Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtVisitor.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtVisitor.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/JwtVisitor.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,68 @@ +/* + * 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; + +/** + * A JwtVisitor supports the Visitor design pattern for + * {@link Jwt} instances. Visitor implementations define logic for a specific JWT subtype or payload subtype + * avoiding type-checking if-then-else conditionals in favor of type-safe method dispatch when encountering a JWT. + * + * @param the type of object to return after invoking the {@link Jwt#accept(JwtVisitor)} method. + * @since 0.12.0 + */ +public interface JwtVisitor { + + /** + * Handles an encountered Unsecured JWT that has not been cryptographically secured at all. Implementations can + * check the {@link Jwt#getPayload()} to determine if it is a {@link Claims} instance or a {@code byte[]} array. + * + *

If the payload is a {@code byte[]} array, and the JWT creator has set the (optional) + * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert + * the byte array to the final type as desired.

+ * + * @param jwt the parsed Unsecured JWT. + * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. + */ + T visit(Jwt jwt); + + /** + * Handles an encountered JSON Web Signature (aka 'JWS') message that has been cryptographically + * verified/authenticated. Implementations can check the {@link Jwt#getPayload()} determine if it is a + * {@link Claims} instance or a {@code byte[]} array. + * + *

If the payload is a {@code byte[]} array, and the JWS creator has set the (optional) + * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert + * the byte array to the final type as desired.

+ * + * @param jws the parsed verified/authenticated JWS. + * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. + */ + T visit(Jws jws); + + /** + * Handles an encountered JSON Web Encryption (aka 'JWE') message that has been authenticated and decrypted. + * Implementations can check the (decrypted) {@link Jwt#getPayload()} to determine if it is a {@link Claims} + * instance or a {@code byte[]} array. + * + *

If the payload is a {@code byte[]} array, and the JWE creator has set the (optional) + * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert + * the byte array to the final type as desired.

+ * + * @param jwe the parsed authenticated and decrypted JWE. + * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. + */ + T visit(Jwe jwe); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Jwts.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Jwts.java (.../Jwts.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Jwts.java (.../Jwts.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,119 +15,1038 @@ */ package io.jsonwebtoken; +import io.jsonwebtoken.io.CompressionAlgorithm; +import io.jsonwebtoken.lang.Builder; import io.jsonwebtoken.lang.Classes; +import io.jsonwebtoken.lang.Registry; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.KeyPairBuilderSupplier; +import io.jsonwebtoken.security.MacAlgorithm; +import io.jsonwebtoken.security.Password; +import io.jsonwebtoken.security.SecretKeyAlgorithm; +import io.jsonwebtoken.security.SecureDigestAlgorithm; +import io.jsonwebtoken.security.SignatureAlgorithm; +import io.jsonwebtoken.security.X509Builder; +import javax.crypto.SecretKey; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; import java.util.Map; /** * Factory class useful for creating instances of JWT interfaces. Using this factory class can be a good * alternative to tightly coupling your code to implementation classes. * + *

Standard Algorithm References

+ *

Standard JSON Web Token algorithms used during JWS or JWE building or parsing are available organized by + * algorithm type. Each organized collection of algorithms is available via a constant to allow + * for easy code-completion in IDEs, showing available algorithm instances. For example, when typing:

+ *
+ * Jwts.// press code-completion hotkeys to suggest available algorithm registry fields
+ * Jwts.{@link SIG SIG}.// press hotkeys to suggest individual Digital Signature or MAC algorithms or utility methods
+ * Jwts.{@link ENC ENC}.// press hotkeys to suggest individual encryption algorithms or utility methods
+ * Jwts.{@link KEY KEY}.// press hotkeys to suggest individual key algorithms or utility methods
+ * * @since 0.1 */ public final class Jwts { - private static final Class[] MAP_ARG = new Class[]{Map.class}; - private Jwts() { + // do not change this visibility. Raw type method signature not be publicly exposed: + @SuppressWarnings("unchecked") + private static T get(Registry registry, String id) { + return (T) registry.forKey(id); } /** - * Creates a new {@link Header} instance suitable for plaintext (not digitally signed) JWTs. As this - * is a less common use of JWTs, consider using the {@link #jwsHeader()} factory method instead if you will later - * digitally sign the JWT. + * Constants for all standard JWA + * Cryptographic Algorithms for Content + * Encryption defined in the JSON + * Web Signature and Encryption Algorithms Registry. Each standard algorithm is available as a + * ({@code public static final}) constant for direct type-safe reference in application code. For example: + *
+     * Jwts.builder()
+     *    // ... etc ...
+     *    .encryptWith(aKey, Jwts.ENC.A256GCM) // or A128GCM, A192GCM, etc...
+     *    .build();
+ *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

* - * @return a new {@link Header} instance suitable for plaintext (not digitally signed) JWTs. + * @see #get() + * @since 0.12.0 */ - public static Header header() { - return Classes.newInstance("io.jsonwebtoken.impl.DefaultHeader"); + public static final class ENC { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardEncryptionAlgorithms"; + private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + /** + * Returns all standard JWA Cryptographic + * Algorithms for Content Encryption defined in the + * JSON Web Signature and Encryption + * Algorithms Registry. + * + * @return all standard JWA content encryption algorithms. + */ + public static Registry get() { + return REGISTRY; + } + + // prevent instantiation + private ENC() { + } + + /** + * {@code AES_128_CBC_HMAC_SHA_256} authenticated encryption algorithm as defined by + * RFC 7518, Section 5.2.3. This algorithm + * requires a 256-bit (32 byte) key. + */ + public static final AeadAlgorithm A128CBC_HS256 = get().forKey("A128CBC-HS256"); + + /** + * {@code AES_192_CBC_HMAC_SHA_384} authenticated encryption algorithm, as defined by + * RFC 7518, Section 5.2.4. This algorithm + * requires a 384-bit (48 byte) key. + */ + public static final AeadAlgorithm A192CBC_HS384 = get().forKey("A192CBC-HS384"); + + /** + * {@code AES_256_CBC_HMAC_SHA_512} authenticated encryption algorithm, as defined by + * RFC 7518, Section 5.2.5. This algorithm + * requires a 512-bit (64 byte) key. + */ + public static final AeadAlgorithm A256CBC_HS512 = get().forKey("A256CBC-HS512"); + + /** + * "AES GCM using 128-bit key" as defined by + * RFC 7518, Section 5.31. This + * algorithm requires a 128-bit (16 byte) key. + * + *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ */ + public static final AeadAlgorithm A128GCM = get().forKey("A128GCM"); + + /** + * "AES GCM using 192-bit key" as defined by + * RFC 7518, Section 5.31. This + * algorithm requires a 192-bit (24 byte) key. + * + *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ */ + public static final AeadAlgorithm A192GCM = get().forKey("A192GCM"); + + /** + * "AES GCM using 256-bit key" as defined by + * RFC 7518, Section 5.31. This + * algorithm requires a 256-bit (32 byte) key. + * + *

1 Requires Java 8 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 7 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ */ + public static final AeadAlgorithm A256GCM = get().forKey("A256GCM"); } /** - * Creates a new {@link Header} instance suitable for plaintext (not digitally signed) JWTs, populated - * with the specified name/value pairs. As this is a less common use of JWTs, consider using the - * {@link #jwsHeader(java.util.Map)} factory method instead if you will later digitally sign the JWT. + * Constants for all JWA (RFC 7518) standard + * Cryptographic Algorithms for Digital Signatures and MACs defined in the + * JSON Web Signature and Encryption Algorithms + * Registry. Each standard algorithm is available as a ({@code public static final}) constant for + * direct type-safe reference in application code. For example: + *
+     * Jwts.builder()
+     *    // ... etc ...
+     *    .signWith(aKey, Jwts.SIG.HS512) // or RS512, PS256, EdDSA, etc...
+     *    .build();
+ *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

* - * @return a new {@link Header} instance suitable for plaintext (not digitally signed) JWTs. + * @see #get() + * @since 0.12.0 */ - public static Header header(Map header) { - return Classes.newInstance("io.jsonwebtoken.impl.DefaultHeader", MAP_ARG, header); + public static final class SIG { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms"; + private static final Registry> REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + //prevent instantiation + private SIG() { + } + + /** + * Returns all standard JWA Cryptographic + * Algorithms for Digital Signatures and MACs defined in the + * JSON Web Signature and Encryption + * Algorithms Registry. + * + * @return all standard JWA digital signature and MAC algorithms. + */ + public static Registry> get() { + return REGISTRY; + } + + /** + * The "none" signature algorithm as defined by + * RFC 7518, Section 3.6. This algorithm + * is used only when creating unsecured (not integrity protected) JWSs and is not usable in any other scenario. + * Any attempt to call its methods will result in an exception being thrown. + */ + public static final SecureDigestAlgorithm NONE = Jwts.get(REGISTRY, "none"); + + /** + * {@code HMAC using SHA-256} message authentication algorithm as defined by + * RFC 7518, Section 3.2. This algorithm + * requires a 256-bit (32 byte) key. + */ + public static final MacAlgorithm HS256 = Jwts.get(REGISTRY, "HS256"); + + /** + * {@code HMAC using SHA-384} message authentication algorithm as defined by + * RFC 7518, Section 3.2. This algorithm + * requires a 384-bit (48 byte) key. + */ + public static final MacAlgorithm HS384 = Jwts.get(REGISTRY, "HS384"); + + /** + * {@code HMAC using SHA-512} message authentication algorithm as defined by + * RFC 7518, Section 3.2. This algorithm + * requires a 512-bit (64 byte) key. + */ + public static final MacAlgorithm HS512 = Jwts.get(REGISTRY, "HS512"); + + /** + * {@code RSASSA-PKCS1-v1_5 using SHA-256} signature algorithm as defined by + * RFC 7518, Section 3.3. This algorithm + * requires a 2048-bit key. + */ + public static final SignatureAlgorithm RS256 = Jwts.get(REGISTRY, "RS256"); + + /** + * {@code RSASSA-PKCS1-v1_5 using SHA-384} signature algorithm as defined by + * RFC 7518, Section 3.3. This algorithm + * requires a 2048-bit key, but the JJWT team recommends a 3072-bit key. + */ + public static final SignatureAlgorithm RS384 = Jwts.get(REGISTRY, "RS384"); + + /** + * {@code RSASSA-PKCS1-v1_5 using SHA-512} signature algorithm as defined by + * RFC 7518, Section 3.3. This algorithm + * requires a 2048-bit key, but the JJWT team recommends a 4096-bit key. + */ + public static final SignatureAlgorithm RS512 = Jwts.get(REGISTRY, "RS512"); + + /** + * {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256} signature algorithm as defined by + * RFC 7518, Section 3.51. + * This algorithm requires a 2048-bit key. + * + *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ */ + public static final SignatureAlgorithm PS256 = Jwts.get(REGISTRY, "PS256"); + + /** + * {@code RSASSA-PSS using SHA-384 and MGF1 with SHA-384} signature algorithm as defined by + * RFC 7518, Section 3.51. + * This algorithm requires a 2048-bit key, but the JJWT team recommends a 3072-bit key. + * + *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ */ + public static final SignatureAlgorithm PS384 = Jwts.get(REGISTRY, "PS384"); + + /** + * {@code RSASSA-PSS using SHA-512 and MGF1 with SHA-512} signature algorithm as defined by + * RFC 7518, Section 3.51. + * This algorithm requires a 2048-bit key, but the JJWT team recommends a 4096-bit key. + * + *

1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

+ */ + public static final SignatureAlgorithm PS512 = Jwts.get(REGISTRY, "PS512"); + + /** + * {@code ECDSA using P-256 and SHA-256} signature algorithm as defined by + * RFC 7518, Section 3.4. This algorithm + * requires a 256-bit key. + */ + public static final SignatureAlgorithm ES256 = Jwts.get(REGISTRY, "ES256"); + + /** + * {@code ECDSA using P-384 and SHA-384} signature algorithm as defined by + * RFC 7518, Section 3.4. This algorithm + * requires a 384-bit key. + */ + public static final SignatureAlgorithm ES384 = Jwts.get(REGISTRY, "ES384"); + + /** + * {@code ECDSA using P-521 and SHA-512} signature algorithm as defined by + * RFC 7518, Section 3.4. This algorithm + * requires a 521-bit key. + */ + public static final SignatureAlgorithm ES512 = Jwts.get(REGISTRY, "ES512"); + + /** + * {@code EdDSA} signature algorithm defined by + * RFC 8037, Section 3.1 that requires + * either {@code Ed25519} or {@code Ed448} Edwards Elliptic Curve1 keys. + * + *

KeyPair Generation

+ * + *

This instance's {@link KeyPairBuilderSupplier#keyPair() keyPair()} builder creates {@code Ed448} keys, + * and is essentially an alias for + * {@link io.jsonwebtoken.security.Jwks.CRV Jwks.CRV}.{@link io.jsonwebtoken.security.Jwks.CRV#Ed448 Ed448}.{@link KeyPairBuilderSupplier#keyPair() keyPair()}.

+ * + *

If you would like to generate an {@code Ed25519} {@code KeyPair} for use with the {@code EdDSA} algorithm, + * you may use the + * {@link io.jsonwebtoken.security.Jwks.CRV Jwks.CRV}.{@link io.jsonwebtoken.security.Jwks.CRV#Ed25519 Ed25519}.{@link KeyPairBuilderSupplier#keyPair() keyPair()} + * builder instead.

+ * + *

1This algorithm requires at least JDK 15 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath.

+ */ + public static final SignatureAlgorithm EdDSA = Jwts.get(REGISTRY, "EdDSA"); } /** - * Returns a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's). + * Constants for all standard JWA (RFC 7518) + * Cryptographic Algorithms for Key Management. Each standard algorithm is available as a + * ({@code public static final}) constant for direct type-safe reference in application code. For example: + *
+     * Jwts.builder()
+     *    // ... etc ...
+     *    .encryptWith(aKey, Jwts.KEY.ECDH_ES_A256KW, Jwts.ENC.A256GCM)
+     *    .build();
+ *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

* - * @return a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's). - * @see JwtBuilder#setHeader(Header) + * @see #get() + * @since 0.12.0 */ - public static JwsHeader jwsHeader() { - return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwsHeader"); + public static final class KEY { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardKeyAlgorithms"; + private static final Registry> REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + /** + * Returns all standard JWA standard Cryptographic + * Algorithms for Key Management.. + * + * @return all standard JWA Key Management algorithms. + */ + public static Registry> get() { + return REGISTRY; + } + + /** + * Key algorithm reflecting direct use of a shared symmetric key as the JWE AEAD encryption key, as defined + * by RFC 7518 (JWA), Section 4.5. This + * algorithm does not produce encrypted key ciphertext. + */ + public static final KeyAlgorithm DIRECT = Jwts.get(REGISTRY, "dir"); + + /** + * AES Key Wrap algorithm with default initial value using a 128-bit key, as defined by + * RFC 7518 (JWA), Section 4.4. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Encrypts this newly-generated {@code SecretKey} with a 128-bit shared symmetric key using the + * AES Key Wrap algorithm, producing encrypted key ciphertext.
  4. + *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Decrypts the encrypted key ciphertext with the 128-bit shared symmetric key, + * using the AES Key Unwrap algorithm, producing the decryption key plaintext.
  4. + *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. + *
+ */ + public static final SecretKeyAlgorithm A128KW = Jwts.get(REGISTRY, "A128KW"); + + /** + * AES Key Wrap algorithm with default initial value using a 192-bit key, as defined by + * RFC 7518 (JWA), Section 4.4. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Encrypts this newly-generated {@code SecretKey} with a 192-bit shared symmetric key using the + * AES Key Wrap algorithm, producing encrypted key ciphertext.
  4. + *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Decrypts the encrypted key ciphertext with the 192-bit shared symmetric key, + * using the AES Key Unwrap algorithm, producing the decryption key plaintext.
  4. + *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. + *
+ */ + public static final SecretKeyAlgorithm A192KW = Jwts.get(REGISTRY, "A192KW"); + + /** + * AES Key Wrap algorithm with default initial value using a 256-bit key, as defined by + * RFC 7518 (JWA), Section 4.4. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Encrypts this newly-generated {@code SecretKey} with a 256-bit shared symmetric key using the + * AES Key Wrap algorithm, producing encrypted key ciphertext.
  4. + *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Decrypts the encrypted key ciphertext with the 256-bit shared symmetric key, + * using the AES Key Unwrap algorithm, producing the decryption key plaintext.
  4. + *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. + *
+ */ + public static final SecretKeyAlgorithm A256KW = Jwts.get(REGISTRY, "A256KW"); + + /** + * Key wrap algorithm with AES GCM using a 128-bit key, as defined by + * RFC 7518 (JWA), Section 4.7. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Generates a new secure-random 96-bit Initialization Vector to use during key wrap/encryption.
  4. + *
  5. Encrypts this newly-generated {@code SecretKey} with a 128-bit shared symmetric key using the + * AES GCM Key Wrap algorithm with the generated Initialization Vector, producing encrypted key ciphertext + * and GCM authentication tag.
  6. + *
  7. Sets the generated initialization vector as the required + * "iv" + * (Initialization Vector) Header Parameter
  8. + *
  9. Sets the resulting GCM authentication tag as the required + * "tag" + * (Authentication Tag) Header Parameter
  10. + *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  12. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Obtains the required initialization vector from the + * "iv" + * (Initialization Vector) Header Parameter
  4. + *
  5. Obtains the required GCM authentication tag from the + * "tag" + * (Authentication Tag) Header Parameter
  6. + *
  7. Decrypts the encrypted key ciphertext with the 128-bit shared symmetric key, the initialization vector + * and GCM authentication tag using the AES GCM Key Unwrap algorithm, producing the decryption key + * plaintext.
  8. + *
  9. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. + *
+ */ + public static final SecretKeyAlgorithm A128GCMKW = Jwts.get(REGISTRY, "A128GCMKW"); + + /** + * Key wrap algorithm with AES GCM using a 192-bit key, as defined by + * RFC 7518 (JWA), Section 4.7. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Generates a new secure-random 96-bit Initialization Vector to use during key wrap/encryption.
  4. + *
  5. Encrypts this newly-generated {@code SecretKey} with a 192-bit shared symmetric key using the + * AES GCM Key Wrap algorithm with the generated Initialization Vector, producing encrypted key ciphertext + * and GCM authentication tag.
  6. + *
  7. Sets the generated initialization vector as the required + * "iv" + * (Initialization Vector) Header Parameter
  8. + *
  9. Sets the resulting GCM authentication tag as the required + * "tag" + * (Authentication Tag) Header Parameter
  10. + *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  12. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Obtains the required initialization vector from the + * "iv" + * (Initialization Vector) Header Parameter
  4. + *
  5. Obtains the required GCM authentication tag from the + * "tag" + * (Authentication Tag) Header Parameter
  6. + *
  7. Decrypts the encrypted key ciphertext with the 192-bit shared symmetric key, the initialization vector + * and GCM authentication tag using the AES GCM Key Unwrap algorithm, producing the decryption key \ + * plaintext.
  8. + *
  9. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. + *
+ */ + public static final SecretKeyAlgorithm A192GCMKW = Jwts.get(REGISTRY, "A192GCMKW"); + + /** + * Key wrap algorithm with AES GCM using a 256-bit key, as defined by + * RFC 7518 (JWA), Section 4.7. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Generates a new secure-random 96-bit Initialization Vector to use during key wrap/encryption.
  4. + *
  5. Encrypts this newly-generated {@code SecretKey} with a 256-bit shared symmetric key using the + * AES GCM Key Wrap algorithm with the generated Initialization Vector, producing encrypted key ciphertext + * and GCM authentication tag.
  6. + *
  7. Sets the generated initialization vector as the required + * "iv" + * (Initialization Vector) Header Parameter
  8. + *
  9. Sets the resulting GCM authentication tag as the required + * "tag" + * (Authentication Tag) Header Parameter
  10. + *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  12. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Obtains the required initialization vector from the + * "iv" + * (Initialization Vector) Header Parameter
  4. + *
  5. Obtains the required GCM authentication tag from the + * "tag" + * (Authentication Tag) Header Parameter
  6. + *
  7. Decrypts the encrypted key ciphertext with the 256-bit shared symmetric key, the initialization vector + * and GCM authentication tag using the AES GCM Key Unwrap algorithm, producing the decryption key \ + * plaintext.
  8. + *
  9. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. + *
+ */ + public static final SecretKeyAlgorithm A256GCMKW = Jwts.get(REGISTRY, "A256GCMKW"); + + /** + * Key encryption algorithm using PBES2 with HMAC SHA-256 and "A128KW" wrapping + * as defined by + * RFC 7518 (JWA), Section 4.8. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Determines the number of PBDKF2 iterations via the JWE header's + * {@link JweHeader#getPbes2Count() pbes2Count} value. If that value is not set, a suitable number of + * iterations will be chosen based on + * OWASP + * PBKDF2 recommendations and then that value is set as the JWE header {@code pbes2Count} value.
  2. + *
  3. Generates a new secure-random salt input and sets it as the JWE header + * {@link JweHeader#getPbes2Salt() pbes2Salt} value.
  4. + *
  5. Derives a 128-bit Key Encryption Key with the PBES2-HS256 password-based key derivation algorithm, + * using the provided password, iteration count, and input salt as arguments.
  6. + *
  7. Generates a new secure-random Content Encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  8. + *
  9. Encrypts this newly-generated Content Encryption {@code SecretKey} with the {@code A128KW} key wrap + * algorithm using the 128-bit derived password-based Key Encryption Key from step {@code #3}, + * producing encrypted key ciphertext.
  10. + *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * Content Encryption {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated + * {@link AeadAlgorithm}.
  12. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required PBKDF2 input salt from the + * "p2s" + * (PBES2 Salt Input) Header Parameter
  2. + *
  3. Obtains the required PBKDF2 iteration count from the + * "p2c" + * (PBES2 Count) Header Parameter
  4. + *
  5. Derives the 128-bit Key Encryption Key with the PBES2-HS256 password-based key derivation algorithm, + * using the provided password, obtained salt input, and obtained iteration count as arguments.
  6. + *
  7. Obtains the encrypted key ciphertext embedded in the received JWE.
  8. + *
  9. Decrypts the encrypted key ciphertext with with the {@code A128KW} key unwrap + * algorithm using the 128-bit derived password-based Key Encryption Key from step {@code #3}, + * producing the decryption key plaintext.
  10. + *
  11. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  12. + *
+ */ + public static final KeyAlgorithm PBES2_HS256_A128KW = Jwts.get(REGISTRY, "PBES2-HS256+A128KW"); + + /** + * Key encryption algorithm using PBES2 with HMAC SHA-384 and "A192KW" wrapping + * as defined by + * RFC 7518 (JWA), Section 4.8. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Determines the number of PBDKF2 iterations via the JWE header's + * {@link JweHeader#getPbes2Count() pbes2Count} value. If that value is not set, a suitable number of + * iterations will be chosen based on + * OWASP + * PBKDF2 recommendations and then that value is set as the JWE header {@code pbes2Count} value.
  2. + *
  3. Generates a new secure-random salt input and sets it as the JWE header + * {@link JweHeader#getPbes2Salt() pbes2Salt} value.
  4. + *
  5. Derives a 192-bit Key Encryption Key with the PBES2-HS384 password-based key derivation algorithm, + * using the provided password, iteration count, and input salt as arguments.
  6. + *
  7. Generates a new secure-random Content Encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  8. + *
  9. Encrypts this newly-generated Content Encryption {@code SecretKey} with the {@code A192KW} key wrap + * algorithm using the 192-bit derived password-based Key Encryption Key from step {@code #3}, + * producing encrypted key ciphertext.
  10. + *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * Content Encryption {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated + * {@link AeadAlgorithm}.
  12. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required PBKDF2 input salt from the + * "p2s" + * (PBES2 Salt Input) Header Parameter
  2. + *
  3. Obtains the required PBKDF2 iteration count from the + * "p2c" + * (PBES2 Count) Header Parameter
  4. + *
  5. Derives the 192-bit Key Encryption Key with the PBES2-HS384 password-based key derivation algorithm, + * using the provided password, obtained salt input, and obtained iteration count as arguments.
  6. + *
  7. Obtains the encrypted key ciphertext embedded in the received JWE.
  8. + *
  9. Decrypts the encrypted key ciphertext with with the {@code A192KW} key unwrap + * algorithm using the 192-bit derived password-based Key Encryption Key from step {@code #3}, + * producing the decryption key plaintext.
  10. + *
  11. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  12. + *
+ */ + public static final KeyAlgorithm PBES2_HS384_A192KW = Jwts.get(REGISTRY, "PBES2-HS384+A192KW"); + + /** + * Key encryption algorithm using PBES2 with HMAC SHA-512 and "A256KW" wrapping + * as defined by + * RFC 7518 (JWA), Section 4.8. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Determines the number of PBDKF2 iterations via the JWE header's + * {@link JweHeader#getPbes2Count() pbes2Count} value. If that value is not set, a suitable number of + * iterations will be chosen based on + * OWASP + * PBKDF2 recommendations and then that value is set as the JWE header {@code pbes2Count} value.
  2. + *
  3. Generates a new secure-random salt input and sets it as the JWE header + * {@link JweHeader#getPbes2Salt() pbes2Salt} value.
  4. + *
  5. Derives a 256-bit Key Encryption Key with the PBES2-HS512 password-based key derivation algorithm, + * using the provided password, iteration count, and input salt as arguments.
  6. + *
  7. Generates a new secure-random Content Encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  8. + *
  9. Encrypts this newly-generated Content Encryption {@code SecretKey} with the {@code A256KW} key wrap + * algorithm using the 256-bit derived password-based Key Encryption Key from step {@code #3}, + * producing encrypted key ciphertext.
  10. + *
  11. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * Content Encryption {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated + * {@link AeadAlgorithm}.
  12. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required PBKDF2 input salt from the + * "p2s" + * (PBES2 Salt Input) Header Parameter
  2. + *
  3. Obtains the required PBKDF2 iteration count from the + * "p2c" + * (PBES2 Count) Header Parameter
  4. + *
  5. Derives the 256-bit Key Encryption Key with the PBES2-HS512 password-based key derivation algorithm, + * using the provided password, obtained salt input, and obtained iteration count as arguments.
  6. + *
  7. Obtains the encrypted key ciphertext embedded in the received JWE.
  8. + *
  9. Decrypts the encrypted key ciphertext with with the {@code A256KW} key unwrap + * algorithm using the 256-bit derived password-based Key Encryption Key from step {@code #3}, + * producing the decryption key plaintext.
  10. + *
  11. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  12. + *
+ */ + public static final KeyAlgorithm PBES2_HS512_A256KW = Jwts.get(REGISTRY, "PBES2-HS512+A256KW"); + + /** + * Key Encryption with {@code RSAES-PKCS1-v1_5}, as defined by + * RFC 7518 (JWA), Section 4.2. + * This algorithm requires a key size of 2048 bits or larger. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Encrypts this newly-generated {@code SecretKey} with the RSA key wrap algorithm, using the JWE + * recipient's RSA Public Key, producing encrypted key ciphertext.
  4. + *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Receives the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Decrypts the encrypted key ciphertext with the RSA key unwrap algorithm, using the JWE recipient's + * RSA Private Key, producing the decryption key plaintext.
  4. + *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. + *
+ */ + public static final KeyAlgorithm RSA1_5 = Jwts.get(REGISTRY, "RSA1_5"); + + /** + * Key Encryption with {@code RSAES OAEP using default parameters}, as defined by + * RFC 7518 (JWA), Section 4.3. + * This algorithm requires a key size of 2048 bits or larger. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Encrypts this newly-generated {@code SecretKey} with the RSA OAEP with SHA-1 and MGF1 key wrap algorithm, + * using the JWE recipient's RSA Public Key, producing encrypted key ciphertext.
  4. + *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Receives the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Decrypts the encrypted key ciphertext with the RSA OAEP with SHA-1 and MGF1 key unwrap algorithm, + * using the JWE recipient's RSA Private Key, producing the decryption key plaintext.
  4. + *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. + *
+ */ + public static final KeyAlgorithm RSA_OAEP = Jwts.get(REGISTRY, "RSA-OAEP"); + + /** + * Key Encryption with {@code RSAES OAEP using SHA-256 and MGF1 with SHA-256}, as defined by + * RFC 7518 (JWA), Section 4.3. + * This algorithm requires a key size of 2048 bits or larger. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  2. + *
  3. Encrypts this newly-generated {@code SecretKey} with the RSA OAEP with SHA-256 and MGF1 key wrap + * algorithm, using the JWE recipient's RSA Public Key, producing encrypted key ciphertext.
  4. + *
  5. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  6. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Receives the encrypted key ciphertext embedded in the received JWE.
  2. + *
  3. Decrypts the encrypted key ciphertext with the RSA OAEP with SHA-256 and MGF1 key unwrap algorithm, + * using the JWE recipient's RSA Private Key, producing the decryption key plaintext.
  4. + *
  5. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  6. + *
+ */ + public static final KeyAlgorithm RSA_OAEP_256 = Jwts.get(REGISTRY, "RSA-OAEP-256"); + + /** + * Key Agreement with {@code ECDH-ES using Concat KDF} as defined by + * RFC 7518 (JWA), Section 4.6. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the + * JWE recipient's EC Public Key.
  2. + *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key + * and the JWE recipient's EC Public Key.
  4. + *
  5. Derives a symmetric Content + * Encryption {@code SecretKey} with the Concat KDF algorithm using the + * generated shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. + *
  7. Sets the generated EC key pair's Public Key as the required + * "epk" + * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. + *
  9. Returns the derived symmetric {@code SecretKey} for JJWT to use to encrypt the entire JWE with the + * associated {@link AeadAlgorithm}. Encrypted key ciphertext is not produced with this algorithm, so + * the resulting JWE will not contain any embedded key ciphertext.
  10. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the + * "epk" + * (Ephemeral Public Key) Header Parameter.
  2. + *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. + *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key + * and the JWE recipient's EC Private Key.
  6. + *
  7. Derives the symmetric Content + * Encryption {@code SecretKey} with the Concat KDF algorithm using the + * obtained shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. + *
  9. Returns the derived symmetric {@code SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  10. + *
+ */ + public static final KeyAlgorithm ECDH_ES = Jwts.get(REGISTRY, "ECDH-ES"); + + /** + * Key Agreement with Key Wrapping via + * ECDH-ES using Concat KDF and CEK wrapped with "A128KW" as defined by + * RFC 7518 (JWA), Section 4.6. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the + * JWE recipient's EC Public Key.
  2. + *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key + * and the JWE recipient's EC Public Key.
  4. + *
  5. Derives a 128-bit symmetric Key + * Encryption {@code SecretKey} with the Concat KDF algorithm using the + * generated shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. + *
  7. Sets the generated EC key pair's Public Key as the required + * "epk" + * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. + *
  9. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  10. + *
  11. Encrypts this newly-generated {@code SecretKey} with the {@code A128KW} key wrap + * algorithm using the derived symmetric Key Encryption Key from step {@code #3}, producing encrypted key ciphertext.
  12. + *
  13. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  14. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the + * "epk" + * (Ephemeral Public Key) Header Parameter.
  2. + *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. + *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key + * and the JWE recipient's EC Private Key.
  6. + *
  7. Derives the symmetric Key + * Encryption {@code SecretKey} with the Concat KDF algorithm using the + * obtained shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. + *
  9. Obtains the encrypted key ciphertext embedded in the received JWE.
  10. + *
  11. Decrypts the encrypted key ciphertext with the AES Key Unwrap algorithm using the + * 128-bit derived symmetric key from step {@code #4}, producing the decryption key plaintext.
  12. + *
  13. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  14. + *
+ */ + public static final KeyAlgorithm ECDH_ES_A128KW = Jwts.get(REGISTRY, "ECDH-ES+A128KW"); + + /** + * Key Agreement with Key Wrapping via + * ECDH-ES using Concat KDF and CEK wrapped with "A192KW" as defined by + * RFC 7518 (JWA), Section 4.6. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the + * JWE recipient's EC Public Key.
  2. + *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key + * and the JWE recipient's EC Public Key.
  4. + *
  5. Derives a 192-bit symmetric Key + * Encryption {@code SecretKey} with the Concat KDF algorithm using the + * generated shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. + *
  7. Sets the generated EC key pair's Public Key as the required + * "epk" + * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. + *
  9. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  10. + *
  11. Encrypts this newly-generated {@code SecretKey} with the {@code A192KW} key wrap + * algorithm using the derived symmetric Key Encryption Key from step {@code #3}, producing encrypted key + * ciphertext.
  12. + *
  13. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  14. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the + * "epk" + * (Ephemeral Public Key) Header Parameter.
  2. + *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. + *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key + * and the JWE recipient's EC Private Key.
  6. + *
  7. Derives the 192-bit symmetric + * Key Encryption {@code SecretKey} with the Concat KDF algorithm using the + * obtained shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. + *
  9. Obtains the encrypted key ciphertext embedded in the received JWE.
  10. + *
  11. Decrypts the encrypted key ciphertext with the AES Key Unwrap algorithm using the + * 192-bit derived symmetric key from step {@code #4}, producing the decryption key plaintext.
  12. + *
  13. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  14. + *
+ */ + public static final KeyAlgorithm ECDH_ES_A192KW = Jwts.get(REGISTRY, "ECDH-ES+A192KW"); + + /** + * Key Agreement with Key Wrapping via + * ECDH-ES using Concat KDF and CEK wrapped with "A256KW" as defined by + * RFC 7518 (JWA), Section 4.6. + * + *

During JWE creation, this algorithm:

+ *
    + *
  1. Generates a new secure-random Elliptic Curve public/private key pair on the same curve as the + * JWE recipient's EC Public Key.
  2. + *
  3. Generates a shared secret with the ECDH key agreement algorithm using the generated EC Private Key + * and the JWE recipient's EC Public Key.
  4. + *
  5. Derives a 256-bit symmetric Key + * Encryption {@code SecretKey} with the Concat KDF algorithm using the + * generated shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  6. + *
  7. Sets the generated EC key pair's Public Key as the required + * "epk" + * (Ephemeral Public Key) Header Parameter to be transmitted in the JWE.
  8. + *
  9. Generates a new secure-random content encryption {@link SecretKey} suitable for use with a + * specified {@link AeadAlgorithm} (using {@link AeadAlgorithm#key()}).
  10. + *
  11. Encrypts this newly-generated {@code SecretKey} with the {@code A256KW} key wrap + * algorithm using the derived symmetric Key Encryption Key from step {@code #3}, producing encrypted key + * ciphertext.
  12. + *
  13. Returns the encrypted key ciphertext for inclusion in the final JWE as well as the newly-generated + * {@code SecretKey} for JJWT to use to encrypt the entire JWE with associated {@link AeadAlgorithm}.
  14. + *
+ *

For JWE decryption, this algorithm:

+ *
    + *
  1. Obtains the required ephemeral Elliptic Curve Public Key from the + * "epk" + * (Ephemeral Public Key) Header Parameter.
  2. + *
  3. Validates that the ephemeral Public Key is on the same curve as the recipient's EC Private Key.
  4. + *
  5. Obtains the shared secret with the ECDH key agreement algorithm using the obtained EC Public Key + * and the JWE recipient's EC Private Key.
  6. + *
  7. Derives the 256-bit symmetric + * Key Encryption {@code SecretKey} with the Concat KDF algorithm using the + * obtained shared secret and any available + * {@link JweHeader#getAgreementPartyUInfo() PartyUInfo} and + * {@link JweHeader#getAgreementPartyVInfo() PartyVInfo}.
  8. + *
  9. Obtains the encrypted key ciphertext embedded in the received JWE.
  10. + *
  11. Decrypts the encrypted key ciphertext with the AES Key Unwrap algorithm using the + * 256-bit derived symmetric key from step {@code #4}, producing the decryption key plaintext.
  12. + *
  13. Returns the decryption key plaintext as a {@link SecretKey} for JJWT to use to decrypt the entire + * JWE using the JWE's identified "enc" {@link AeadAlgorithm}.
  14. + *
+ */ + public static final KeyAlgorithm ECDH_ES_A256KW = Jwts.get(REGISTRY, "ECDH-ES+A256KW"); + + //prevent instantiation + private KEY() { + } } /** - * Returns a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's), populated with the - * specified name/value pairs. + * Constants for JWA (RFC 7518) compression algorithms referenced in the {@code zip} header defined in the + * JSON Web Encryption Compression Algorithms + * Registry. Each algorithm is available as a ({@code public static final}) constant for + * direct type-safe reference in application code. For example: + *
+     * Jwts.builder()
+     *    // ... etc ...
+     *    .compressWith(Jwts.ZIP.DEF)
+     *    .build();
+ *

They are also available together as a {@link Registry} instance via the {@link #get()} method.

* - * @return a new {@link JwsHeader} instance suitable for digitally signed JWTs (aka 'JWS's), populated with the - * specified name/value pairs. - * @see JwtBuilder#setHeader(Header) + * @see #get() + * @since 0.12.0 */ - public static JwsHeader jwsHeader(Map header) { - return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwsHeader", MAP_ARG, header); + public static final class ZIP { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.io.StandardCompressionAlgorithms"; + private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + /** + * Returns various useful + * Compression Algorithms. + * + * @return various standard and non-standard useful compression algorithms. + */ + public static Registry get() { + return REGISTRY; + } + + /** + * The JWE-standard DEFLATE + * compression algorithm with a {@code zip} header value of {@code "DEF"}. + * + * @see JWE RFC 7516, Section 4.1.3 + */ + public static final CompressionAlgorithm DEF = get().forKey("DEF"); + + /** + * A commonly used, but NOT JWA-STANDARD + * gzip compression algorithm with a {@code zip} header value + * of {@code "GZIP"}. + * + *

Compatibility Warning

+ * + *

This is not a standard JWE compression algorithm. Be sure to use this only when you are confident + * that all parties accessing the token support the "GZIP" identifier and associated algorithm.

+ * + *

If you're concerned about compatibility, {@link #DEF DEF} is the only JWA standards-compliant algorithm.

+ * + * @see #DEF + */ + public static final CompressionAlgorithm GZIP = get().forKey("GZIP"); + + //prevent instantiation + private ZIP() { + } } /** - * Returns a new {@link Claims} instance to be used as a JWT body. + * A {@link Builder} that dynamically determines the type of {@link Header} to create based on builder state. * - * @return a new {@link Claims} instance to be used as a JWT body. + * @since 0.12.0 */ - public static Claims claims() { - return Classes.newInstance("io.jsonwebtoken.impl.DefaultClaims"); + public interface HeaderBuilder extends JweHeaderMutator, X509Builder, Builder
{ } /** - * Returns a new {@link Claims} instance populated with the specified name/value pairs. + * Returns a new {@link HeaderBuilder} that can build any type of {@link Header} instance depending on + * which builder properties are set. * - * @param claims the name/value pairs to populate the new Claims instance. - * @return a new {@link Claims} instance populated with the specified name/value pairs. + * @return a new {@link HeaderBuilder} that can build any type of {@link Header} instance depending on + * which builder properties are set. + * @since 0.12.0 */ - public static Claims claims(Map claims) { - return Classes.newInstance("io.jsonwebtoken.impl.DefaultClaims", MAP_ARG, claims); + public static HeaderBuilder header() { + return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtHeaderBuilder"); } /** - * Returns a new {@link JwtParser} instance that can be configured and then used to parse JWT strings. + * Returns a new {@link Claims} builder instance to be used to populate JWT claims, which in aggregate will be + * the JWT payload. * - * @return a new {@link JwtParser} instance that can be configured and then used to parse JWT strings. - * @deprecated use {@link Jwts#parserBuilder()} instead. See {@link JwtParserBuilder} for usage details. - *

Migration to new method structure is minimal, for example: - *

Old code: - *

{@code
-     *     Jwts.parser()
-     *         .requireAudience("string")
-     *         .parse(jwtString)
-     * }
- *

New code: - *

{@code
-     *     Jwts.parserBuilder()
-     *         .requireAudience("string")
-     *         .build()
-     *         .parse(jwtString)
-     * }
- *

NOTE: this method will be removed before version 1.0 + * @return a new {@link Claims} builder instance to be used to populate JWT claims, which in aggregate will be + * the JWT payload. */ - @Deprecated - public static JwtParser parser() { - return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtParser"); + public static ClaimsBuilder claims() { + return Classes.newInstance("io.jsonwebtoken.impl.DefaultClaimsBuilder"); } /** - * Returns a new {@link JwtParserBuilder} instance that can be configured to create an immutable/thread-safe {@link JwtParser). + *

Deprecated since 0.12.0 in favor of + * {@code Jwts.}{@link #claims()}{@code .add(map).build()}. + * This method will be removed before 1.0.

* - * @return a new {@link JwtParser} instance that can be configured create an immutable/thread-safe {@link JwtParser). + *

Returns a new {@link Claims} instance populated with the specified name/value pairs.

+ * + * @param claims the name/value pairs to populate the new Claims instance. + * @return a new {@link Claims} instance populated with the specified name/value pairs. + * @deprecated since 0.12.0 in favor of {@code Jwts.}{@link #claims()}{@code .putAll(map).build()}. + * This method will be removed before 1.0. */ - public static JwtParserBuilder parserBuilder() { - return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtParserBuilder"); + @Deprecated + public static Claims claims(Map claims) { + return claims().add(claims).build(); } /** @@ -140,4 +1059,19 @@ public static JwtBuilder builder() { return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtBuilder"); } + + /** + * Returns a new {@link JwtParserBuilder} instance that can be configured to create an immutable/thread-safe {@link JwtParser}. + * + * @return a new {@link JwtParser} instance that can be configured create an immutable/thread-safe {@link JwtParser}. + */ + public static JwtParserBuilder parser() { + return Classes.newInstance("io.jsonwebtoken.impl.DefaultJwtParserBuilder"); + } + + /** + * Private constructor, prevent instantiation. + */ + private Jwts() { + } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Locator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Locator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/Locator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 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; + +import java.security.Key; + +/** + * A {@link Locator} can return an object referenced in a JWT {@link Header} that is necessary to process + * the associated JWT. + * + *

For example, a {@code Locator} implementation can inspect a header's {@code kid} (Key ID) parameter, and use the + * discovered {@code kid} value to lookup and return the associated {@link Key} instance. JJWT could then use this + * {@code key} to decrypt a JWE or verify a JWS signature.

+ * + * @param the type of object that may be returned from the {@link #locate(Header)} method + * @since 0.12.0 + */ +public interface Locator { + + /** + * Returns an object referenced in the specified {@code header}, or {@code null} if the object couldn't be found. + * + * @param header the JWT header to inspect; may be an instance of {@link Header}, {@link JwsHeader} or + * {@link JweHeader} depending on if the respective JWT is an unprotected JWT, JWS or JWE. + * @return an object referenced in the specified {@code header}, or {@code null} if the object couldn't be found. + */ + T locate(Header header); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/LocatorAdapter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/LocatorAdapter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/LocatorAdapter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2021 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; + +import io.jsonwebtoken.lang.Assert; + +/** + * Adapter pattern implementation for the {@link Locator} interface. Subclasses can override any of the + * {@link #doLocate(Header)}, {@link #locate(ProtectedHeader)}, {@link #locate(JwsHeader)}, or + * {@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). + * + * @param the type of object to locate + * @since 0.12.0 + */ +public abstract class LocatorAdapter implements Locator { + + /** + * Constructs a new instance, where all default method implementations return {@code null}. + */ + public LocatorAdapter() { + } + + /** + * Inspects the specified header, and delegates to the {@link #locate(ProtectedHeader)} method if the header + * is protected (either a {@link JwsHeader} or {@link JweHeader}), or the {@link #doLocate(Header)} method + * if the header is not integrity protected. + * + * @param header the JWT header to inspect; may be an instance of {@link Header}, {@link JwsHeader}, or + * {@link JweHeader} depending on if the respective JWT is an unprotected JWT, JWS or JWE. + * @return an object referenced in the specified header, or {@code null} if the referenced object cannot be found + * or does not exist. + */ + @Override + public final T locate(Header header) { + Assert.notNull(header, "Header cannot be null."); + if (header instanceof ProtectedHeader) { + ProtectedHeader protectedHeader = (ProtectedHeader) header; + return locate(protectedHeader); + } + return doLocate(header); + } + + /** + * Returns an object referenced in the specified {@link ProtectedHeader}, or {@code null} if the referenced + * object cannot be found or does not exist. This is a convenience method that delegates to + * {@link #locate(JwsHeader)} if the {@code header} is a {@link JwsHeader} or {@link #locate(JweHeader)} if the + * {@code header} is a {@link JweHeader}. + * + * @param header the protected header of an encountered JWS or JWE. + * @return an object referenced in the specified {@link ProtectedHeader}, or {@code null} if the referenced + * object cannot be found or does not exist. + */ + protected T locate(ProtectedHeader header) { + if (header instanceof JwsHeader) { + return locate((JwsHeader) header); + } else { + Assert.isInstanceOf(JweHeader.class, header, "Unrecognized ProtectedHeader type."); + return locate((JweHeader) header); + } + } + + /** + * Returns an object referenced in the specified JWE header, or {@code null} if the referenced + * object cannot be found or does not exist. Default implementation simply returns {@code null}. + * + * @param header the header of an encountered JWE. + * @return an object referenced in the specified JWE header, or {@code null} if the referenced + * object cannot be found or does not exist. + */ + protected T locate(JweHeader header) { + return null; + } + + /** + * Returns an object referenced in the specified JWS header, or {@code null} if the referenced + * object cannot be found or does not exist. Default implementation simply returns {@code null}. + * + * @param header the header of an encountered JWS. + * @return an object referenced in the specified JWS header, or {@code null} if the referenced + * object cannot be found or does not exist. + */ + protected T locate(JwsHeader header) { + return null; + } + + /** + * Returns an object referenced in the specified unprotected JWT header, or {@code null} if the referenced + * object cannot be found or does not exist. Default implementation simply returns {@code null}. + * + * @param header the header of an encountered JWT. + * @return an object referenced in the specified unprotected JWT header, or {@code null} if the referenced + * object cannot be found or does not exist. + */ + @SuppressWarnings("unused") + protected T doLocate(Header header) { + return null; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/MalformedJwtException.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/MalformedJwtException.java (.../MalformedJwtException.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/MalformedJwtException.java (.../MalformedJwtException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -22,10 +22,21 @@ */ public class MalformedJwtException extends JwtException { + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ public MalformedJwtException(String message) { super(message); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public MalformedJwtException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/MissingClaimException.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/MissingClaimException.java (.../MissingClaimException.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/MissingClaimException.java (.../MissingClaimException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -22,11 +22,34 @@ * @since 0.6 */ public class MissingClaimException extends InvalidClaimException { - public MissingClaimException(Header header, Claims claims, String message) { - super(header, claims, message); + + /** + * Creates a new instance with the specified explanation message. + * + * @param header the header associated with the claims that did not contain the required claim + * @param claims the claims that did not contain the required claim + * @param claimName the name of the claim that could not be validated + * @param claimValue the value of the claim that could not be validated + * @param message the message explaining why the exception is thrown. + */ + public MissingClaimException(Header header, Claims claims, String claimName, Object claimValue, String message) { + super(header, claims, claimName, claimValue, message); } - public MissingClaimException(Header header, Claims claims, String message, Throwable cause) { - super(header, claims, message, cause); + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param header the header associated with the claims that did not contain the required claim + * @param claims the claims that did not contain the required claim + * @param claimName the name of the claim that could not be validated + * @param claimValue the value of the claim that could not be validated + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + * @deprecated since 0.12.0 since it is not used in JJWT's codebase + */ + @Deprecated + public MissingClaimException(Header header, Claims claims, String claimName, Object claimValue, String message, Throwable cause) { + super(header, claims, claimName, claimValue, message, cause); } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/PrematureJwtException.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/PrematureJwtException.java (.../PrematureJwtException.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/PrematureJwtException.java (.../PrematureJwtException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -22,17 +22,28 @@ */ public class PrematureJwtException extends ClaimJwtException { + /** + * Creates a new instance with the specified explanation message. + * + * @param header jwt header + * @param claims jwt claims (body) + * @param message the message explaining why the exception is thrown. + */ public PrematureJwtException(Header header, Claims claims, String message) { super(header, claims, message); } /** - * @param header jwt header - * @param claims jwt claims (body) + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param header jwt header + * @param claims jwt claims (body) * @param message exception message - * @param cause cause + * @param cause cause * @since 0.5 + * @deprecated since 0.12.0 since it is not used in JJWT's codebase */ + @Deprecated public PrematureJwtException(Header header, Claims claims, String message, Throwable cause) { super(header, claims, message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ProtectedHeader.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ProtectedHeader.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ProtectedHeader.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 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; + +import io.jsonwebtoken.security.PublicJwk; +import io.jsonwebtoken.security.X509Accessor; + +import java.net.URI; +import java.util.Set; + +/** + * A JWT header that is integrity protected, either by JWS digital signature or JWE AEAD encryption. + * + * @see JwsHeader + * @see JweHeader + * @since 0.12.0 + */ +public interface ProtectedHeader extends Header, X509Accessor { + + /** + * Returns the {@code jku} (JWK Set URL) value that refers to a + * JWK Set + * resource containing JSON-encoded Public Keys, or {@code null} if not present. When present in a + * {@link JwsHeader}, the first public key in the JWK Set must be the public key complement of the private + * key used to sign the JWS. When present in a {@link JweHeader}, the first public key in the JWK Set must + * be the public key used during encryption. + * + * @return a URI that refers to a JWK Set + * resource for a set of JSON-encoded Public Keys, or {@code null} if not present. + * @see JWS JWK Set URL + * @see JWE JWK Set URL + */ + URI getJwkSetUrl(); + + /** + * Returns the {@code jwk} (JSON Web Key) associated with the JWT. When present in a {@link JwsHeader}, the + * {@code jwk} is the public key complement of the private key used to digitally sign the JWS. When present in a + * {@link JweHeader}, the {@code jwk} is the public key to which the JWE was encrypted, and may be used to + * determine the private key needed to decrypt the JWE. + * + * @return the {@code jwk} (JSON Web Key) associated with the header. + * @see JWS {@code jwk} (JSON Web Key) Header Parameter + * @see JWE {@code jwk} (JSON Web Key) Header Parameter + */ + PublicJwk getJwk(); + + /** + * Returns the JWT case-sensitive {@code kid} (Key ID) header value or {@code null} if not present. + * + *

The keyId header parameter is a hint indicating which key was used to secure a JWS or JWE. This + * parameter allows originators to explicitly signal a change of key to recipients. The structure of the keyId + * value is unspecified. Its value is a CaSe-SeNsItIvE string.

+ * + *

When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.

+ * + * @return the case-sensitive {@code kid} header value or {@code null} if not present. + * @see JWS Key ID + * @see JWE Key ID + */ + String getKeyId(); + + /** + * Returns the header parameter names that use extensions to the JWT or JWA specification(s) that MUST + * be understood and supported by the JWT recipient, or {@code null} if not present. + * + * @return the header parameter names that use extensions to the JWT or JWA specification(s) that MUST + * be understood and supported by the JWT recipient, or {@code null} if not present. + * @see JWS {@code crit} (Critical) Header Parameter + * @see JWS {@code crit} (Critical) Header Parameter + */ + Set getCritical(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ProtectedHeaderMutator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ProtectedHeaderMutator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ProtectedHeaderMutator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2021 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; + +import io.jsonwebtoken.lang.Conjunctor; +import io.jsonwebtoken.lang.NestedCollection; +import io.jsonwebtoken.security.PublicJwk; +import io.jsonwebtoken.security.X509Mutator; + +import java.net.URI; + +/** + * Mutation (modifications) to a {@link ProtectedHeader Header} instance. + * + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface ProtectedHeaderMutator> extends HeaderMutator, X509Mutator { + + /** + * Configures names of header parameters used by JWT or JWA specification extensions that MUST be + * understood and supported by the JWT recipient. When finished, use the collection's + * {@link Conjunctor#and() and()} method to continue header configuration, for example: + *
+     * headerBuilder
+     *     .critical().add("headerName").{@link Conjunctor#and() and()} // return parent
+     * // resume header configuration...
+ * + * @return the {@link NestedCollection} to use for {@code crit} configuration. + * @see JWS crit (Critical) Header Parameter + * @see JWS crit (Critical) Header Parameter + */ + NestedCollection critical(); + + /** + * Sets the {@code jwk} (JSON Web Key) associated with the JWT. When set for a {@link JwsHeader}, the + * {@code jwk} is the public key complement of the private key used to digitally sign the JWS. When set for a + * {@link JweHeader}, the {@code jwk} is the public key to which the JWE was encrypted, and may be used to + * determine the private key needed to decrypt the JWE. + * + * @param jwk the {@code jwk} (JSON Web Key) associated with the header. + * @return the header for method chaining + * @see JWS jwk (JSON Web Key) Header Parameter + * @see JWE jwk (JSON Web Key) Header Parameter + */ + T jwk(PublicJwk jwk); + + /** + * Sets the {@code jku} (JWK Set URL) value that refers to a + * JWK Set + * resource containing JSON-encoded Public Keys, or {@code null} if not present. When set for a + * {@link JwsHeader}, the first public key in the JWK Set must be the public key complement of the + * private key used to sign the JWS. When set for a {@link JweHeader}, the first public key in the JWK Set + * must be the public key used during encryption. + * + * @param uri a URI that refers to a JWK Set + * resource containing JSON-encoded Public Keys + * @return the header for method chaining + * @see JWS JWK Set URL + * @see JWE JWK Set URL + */ + T jwkSetUrl(URI uri); + + /** + * Sets the JWT case-sensitive {@code kid} (Key ID) header value. A {@code null} value will remove the property + * from the JSON map. + * + *

The keyId header parameter is a hint indicating which key was used to secure a JWS or JWE. This parameter + * allows originators to explicitly signal a change of key to recipients. The structure of the keyId value is + * unspecified. Its value MUST be a case-sensitive string.

+ * + *

When used with a JWK, the keyId value is used to match a JWK {@code keyId} parameter value.

+ * + * @param kid the case-sensitive JWS {@code kid} header value or {@code null} to remove the property from the JSON map. + * @return the header instance for method chaining. + * @see JWS Key ID + * @see JWE Key ID + */ + T keyId(String kid); + + /** + * Deprecated since 0.12.0, delegates to {@link #keyId(String)}. + * + * @param kid the case-sensitive JWS {@code kid} header value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + * @see JWS Key ID + * @see JWE Key ID + * @deprecated since 0.12.0 in favor of the more modern builder-style {@link #keyId(String)} method. + */ + @Deprecated + T setKeyId(String kid); + + /** + * Deprecated as of 0.12.0, there is no need to set this any longer as the {@code JwtBuilder} will + * always set the {@code alg} header as necessary. + * + * @param alg the JWS or JWE algorithm {@code alg} value or {@code null} to remove the property from the JSON map. + * @return the instance for method chaining. + * @since 0.1 + * @deprecated since 0.12.0 and will be removed before the 1.0 release. + */ + @Deprecated + T setAlgorithm(String alg); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ProtectedJwt.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ProtectedJwt.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/ProtectedJwt.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,37 @@ +/* + * 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; + +import io.jsonwebtoken.security.DigestSupplier; + +/** + * A {@code ProtectedJwt} is a {@link Jwt} that is integrity protected via a cryptographic algorithm that produces + * a cryptographic digest, such as a MAC, Digital Signature or Authentication Tag. + * + *

Cryptographic Digest

+ *

This interface extends DigestSupplier to make available the {@code ProtectedJwt}'s associated cryptographic + * digest:

+ *
    + *
  • If the JWT is a {@link Jws}, {@link #getDigest() getDigest() } returns the JWS signature.
  • + *
  • If the JWT is a {@link Jwe}, {@link #getDigest() getDigest() } returns the AAD Authentication Tag.
  • + *
+ * + * @param the type of the JWT protected header + * @param

the type of the JWT payload, either a content byte array or a {@link Claims} instance. + * @since 0.12.0 + */ +public interface ProtectedJwt extends Jwt, DigestSupplier { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/RequiredTypeException.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/RequiredTypeException.java (.../RequiredTypeException.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/RequiredTypeException.java (.../RequiredTypeException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,16 +16,28 @@ package io.jsonwebtoken; /** - * Exception thrown when {@link Claims#get(String, Class)} is called and the value does not match the type of the - * {@code Class} argument. + * Exception thrown when attempting to obtain a value from a JWT or JWK and the existing value does not match the + * expected type. * * @since 0.6 */ public class RequiredTypeException extends JwtException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ public RequiredTypeException(String message) { super(message); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public RequiredTypeException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SignatureAlgorithm.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SignatureAlgorithm.java (.../SignatureAlgorithm.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SignatureAlgorithm.java (.../SignatureAlgorithm.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -34,7 +34,9 @@ * JSON Web Algorithms specification. * * @since 0.1 + * @deprecated since 0.12.0; use {@link Jwts.SIG} instead. */ +@Deprecated public enum SignatureAlgorithm { /** @@ -110,10 +112,10 @@ //purposefully ordered higher to lower: private static final List PREFERRED_HMAC_ALGS = Collections.unmodifiableList(Arrays.asList( - SignatureAlgorithm.HS512, SignatureAlgorithm.HS384, SignatureAlgorithm.HS256)); + SignatureAlgorithm.HS512, SignatureAlgorithm.HS384, SignatureAlgorithm.HS256)); //purposefully ordered higher to lower: private static final List PREFERRED_EC_ALGS = Collections.unmodifiableList(Arrays.asList( - SignatureAlgorithm.ES512, SignatureAlgorithm.ES384, SignatureAlgorithm.ES256)); + SignatureAlgorithm.ES512, SignatureAlgorithm.ES384, SignatureAlgorithm.ES256)); private final String value; private final String description; @@ -132,7 +134,7 @@ SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard, int digestLength, int minKeyLength) { - this(value, description,familyName, jcaName, jdkStandard, digestLength, minKeyLength, jcaName); + this(value, description, familyName, jcaName, jdkStandard, digestLength, minKeyLength, jcaName); } SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard, @@ -365,25 +367,25 @@ // These next checks use equalsIgnoreCase per https://github.com/jwtk/jjwt/issues/381#issuecomment-412912272 if (!HS256.jcaName.equalsIgnoreCase(alg) && - !HS384.jcaName.equalsIgnoreCase(alg) && - !HS512.jcaName.equalsIgnoreCase(alg) && - !HS256.pkcs12Name.equals(alg) && - !HS384.pkcs12Name.equals(alg) && - !HS512.pkcs12Name.equals(alg)) { + !HS384.jcaName.equalsIgnoreCase(alg) && + !HS512.jcaName.equalsIgnoreCase(alg) && + !HS256.pkcs12Name.equals(alg) && + !HS384.pkcs12Name.equals(alg) && + !HS512.pkcs12Name.equals(alg)) { throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm '" + alg + - "' does not equal a valid HmacSHA* algorithm name and cannot be used with " + name() + "."); + "' does not equal a valid HmacSHA* algorithm name and cannot be used with " + name() + "."); } int size = encoded.length * 8; //size in bits if (size < this.minKeyLength) { String msg = "The " + keyType(signing) + " key's size is " + size + " bits which " + - "is not secure enough for the " + name() + " algorithm. The JWT " + - "JWA Specification (RFC 7518, Section 3.2) states that keys used with " + name() + " MUST have a " + - "size >= " + minKeyLength + " bits (the key size must be greater than or equal to the hash " + - "output size). Consider using the " + Keys.class.getName() + " class's " + - "'secretKeyFor(SignatureAlgorithm." + name() + ")' method to create a key guaranteed to be " + - "secure enough for " + name() + ". See " + - "https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; + "is not secure enough for the " + name() + " algorithm. The JWT " + + "JWA Specification (RFC 7518, Section 3.2) states that keys used with " + name() + " MUST have a " + + "size >= " + minKeyLength + " bits (the key size must be greater than or equal to the hash " + + "output size). Consider using the " + Keys.class.getName() + " class's " + + "'secretKeyFor(SignatureAlgorithm." + name() + ")' method to create a key guaranteed to be " + + "secure enough for " + name() + ". See " + + "https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; throw new WeakKeyException(msg); } @@ -407,13 +409,13 @@ int size = ecKey.getParams().getOrder().bitLength(); if (size < this.minKeyLength) { String msg = "The " + keyType(signing) + " key's size (ECParameterSpec order) is " + size + - " bits which is not secure enough for the " + name() + " algorithm. The JWT " + - "JWA Specification (RFC 7518, Section 3.4) states that keys used with " + - name() + " MUST have a size >= " + this.minKeyLength + - " bits. Consider using the " + Keys.class.getName() + " class's " + - "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " + - "to be secure enough for " + name() + ". See " + - "https://tools.ietf.org/html/rfc7518#section-3.4 for more information."; + " bits which is not secure enough for the " + name() + " algorithm. The JWT " + + "JWA Specification (RFC 7518, Section 3.4) states that keys used with " + + name() + " MUST have a size >= " + this.minKeyLength + + " bits. Consider using the " + Keys.class.getName() + " class's " + + "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " + + "to be secure enough for " + name() + ". See " + + "https://tools.ietf.org/html/rfc7518#section-3.4 for more information."; throw new WeakKeyException(msg); } @@ -431,12 +433,12 @@ String section = name().startsWith("P") ? "3.5" : "3.3"; String msg = "The " + keyType(signing) + " key's size is " + size + " bits which is not secure " + - "enough for the " + name() + " algorithm. The JWT JWA Specification (RFC 7518, Section " + - section + ") states that keys used with " + name() + " MUST have a size >= " + - this.minKeyLength + " bits. Consider using the " + Keys.class.getName() + " class's " + - "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " + - "to be secure enough for " + name() + ". See " + - "https://tools.ietf.org/html/rfc7518#section-" + section + " for more information."; + "enough for the " + name() + " algorithm. The JWT JWA Specification (RFC 7518, Section " + + section + ") states that keys used with " + name() + " MUST have a size >= " + + this.minKeyLength + " bits. Consider using the " + Keys.class.getName() + " class's " + + "'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " + + "to be secure enough for " + name() + ". See " + + "https://tools.ietf.org/html/rfc7518#section-" + section + " for more information."; throw new WeakKeyException(msg); } } @@ -531,7 +533,7 @@ * Section 3.3) mandates that RSA signing key lengths MUST be 2048 bits or greater. * {@code RSAKey}s with key lengths less than 2048 bits will be rejected with a * {@link WeakKeyException}. - *

  • Technically any RSA key of length >= 2048 bits may be used with the {@link #RS256}, {@link #RS384}, and + *
  • Technically any RSA key of length >= 2048 bits may be used with the {@link #RS256}, {@link #RS384}, and * {@link #RS512} algorithms, so we assume an RSA signature algorithm based on the key length to * parallel similar decisions in the JWT specification for HMAC and ECDSA signature algorithms. * This is not required - just a convenience.
  • @@ -545,7 +547,6 @@ *
  • The {@link #RS256}, {@link #RS384}, and {@link #RS512} algorithms are available in the JDK by default * while the {@code PS}* variants require an additional JCA Provider (like BouncyCastle).
  • * - *

    * *

    Finally, this method will throw an {@link InvalidKeyException} for any key that does not match the * heuristics and requirements documented above, since that inevitably means the Key is either insufficient or @@ -564,29 +565,29 @@ } if (!(key instanceof SecretKey || - (key instanceof PrivateKey && (key instanceof ECKey || key instanceof RSAKey)))) { + (key instanceof PrivateKey && (key instanceof ECKey || key instanceof RSAKey)))) { String msg = "JWT standard signing algorithms require either 1) a SecretKey for HMAC-SHA algorithms or " + - "2) a private RSAKey for RSA algorithms or 3) a private ECKey for Elliptic Curve algorithms. " + - "The specified key is of type " + key.getClass().getName(); + "2) a private RSAKey for RSA algorithms or 3) a private ECKey for Elliptic Curve algorithms. " + + "The specified key is of type " + key.getClass().getName(); throw new InvalidKeyException(msg); } if (key instanceof SecretKey) { - SecretKey secretKey = (SecretKey)key; + SecretKey secretKey = (SecretKey) key; int bitLength = io.jsonwebtoken.lang.Arrays.length(secretKey.getEncoded()) * Byte.SIZE; - for(SignatureAlgorithm alg : PREFERRED_HMAC_ALGS) { + for (SignatureAlgorithm alg : PREFERRED_HMAC_ALGS) { // ensure compatibility check is based on key length. See https://github.com/jwtk/jjwt/issues/381 if (bitLength >= alg.minKeyLength) { return alg; } } String msg = "The specified SecretKey is not strong enough to be used with JWT HMAC signature " + - "algorithms. The JWT specification requires HMAC keys to be >= 256 bits long. The specified " + - "key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.2 for more " + - "information."; + "algorithms. The JWT specification requires HMAC keys to be >= 256 bits long. The specified " + + "key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.2 for more " + + "information."; throw new WeakKeyException(msg); } @@ -607,9 +608,9 @@ } String msg = "The specified RSA signing key is not strong enough to be used with JWT RSA signature " + - "algorithms. The JWT specification requires RSA keys to be >= 2048 bits long. The specified RSA " + - "key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.3 for more " + - "information."; + "algorithms. The JWT specification requires RSA keys to be >= 2048 bits long. The specified RSA " + + "key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.3 for more " + + "information."; throw new WeakKeyException(msg); } @@ -627,9 +628,9 @@ } String msg = "The specified Elliptic Curve signing key is not strong enough to be used with JWT ECDSA " + - "signature algorithms. The JWT specification requires ECDSA keys to be >= 256 bits long. " + - "The specified ECDSA key is " + bitLength + " bits. See " + - "https://tools.ietf.org/html/rfc7518#section-3.4 for more information."; + "signature algorithms. The JWT specification requires ECDSA keys to be >= 256 bits long. " + + "The specified ECDSA key is " + bitLength + " bits. See " + + "https://tools.ietf.org/html/rfc7518#section-3.4 for more information."; throw new WeakKeyException(msg); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SignatureException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SignatureException.java (.../SignatureException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SignatureException.java (.../SignatureException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -21,15 +21,26 @@ * Exception indicating that either calculating a signature or verifying an existing signature of a JWT failed. * * @since 0.1 - * @deprecated in favor of {@link io.jsonwebtoken.security.SecurityException}; this class will be removed before 1.0 + * @deprecated in favor of {@link io.jsonwebtoken.security.SignatureException}; this class will be removed before 1.0 */ @Deprecated public class SignatureException extends SecurityException { + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ public SignatureException(String message) { super(message); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public SignatureException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SigningKeyResolver.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SigningKeyResolver.java (.../SigningKeyResolver.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SigningKeyResolver.java (.../SigningKeyResolver.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -22,7 +22,7 @@ * should be used to verify a JWS signature. * *

    A {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing the JWT and the - * JWT header or payload (plaintext body or Claims) must be inspected first to determine how to look up the signing key. + * JWT header or payload (byte array or Claims) must be inspected first to determine how to look up the signing key. * Once returned by the resolver, the JwtParser will then verify the JWS signature with the returned key. For * example:

    * @@ -33,41 +33,43 @@ * //inspect the header or claims, lookup and return the signing key * return getSigningKeyBytes(header, claims); //implement me * }}) - * .parseClaimsJws(compact); + * .build().parseSignedClaims(compact); * * *

    A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.

    * - *

    SigningKeyResolverAdapter

    + *

    Using an Adapter

    * - *

    If you only need to resolve a signing key for a particular JWS (either a plaintext or Claims JWS), consider using + *

    If you only need to resolve a signing key for a particular JWS (either a content or Claims JWS), consider using * the {@link io.jsonwebtoken.SigningKeyResolverAdapter} and overriding only the method you need to support instead of * implementing this interface directly.

    * - * @see io.jsonwebtoken.SigningKeyResolverAdapter + * @see io.jsonwebtoken.JwtParserBuilder#keyLocator(Locator) * @since 0.4 + * @deprecated since 0.12.0. Implement {@link Locator} instead. */ +@Deprecated public interface SigningKeyResolver { /** * Returns the signing key that should be used to validate a digital signature for the Claims JWS with the specified * header and claims. * * @param header the header of the JWS to validate - * @param claims the claims (body) of the JWS to validate + * @param claims the Claims payload of the JWS to validate * @return the signing key that should be used to validate a digital signature for the Claims JWS with the specified * header and claims. */ Key resolveSigningKey(JwsHeader header, Claims claims); /** - * Returns the signing key that should be used to validate a digital signature for the Plaintext JWS with the - * specified header and plaintext payload. + * Returns the signing key that should be used to validate a digital signature for the content JWS with the + * specified header and byte array payload. * - * @param header the header of the JWS to validate - * @param plaintext the plaintext body of the JWS to validate - * @return the signing key that should be used to validate a digital signature for the Plaintext JWS with the - * specified header and plaintext payload. + * @param header the header of the JWS to validate + * @param content the byte array payload of the JWS to validate + * @return the signing key that should be used to validate a digital signature for the content JWS with the + * specified header and byte array payload. */ - Key resolveSigningKey(JwsHeader header, String plaintext); + Key resolveSigningKey(JwsHeader header, byte[] content); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SigningKeyResolverAdapter.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SigningKeyResolverAdapter.java (.../SigningKeyResolverAdapter.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SigningKeyResolverAdapter.java (.../SigningKeyResolverAdapter.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -21,44 +21,67 @@ import java.security.Key; /** - * An Adapter implementation of the + *

    Deprecation Notice

    + * + *

    As of JJWT 0.12.0, various Resolver concepts (including the {@code SigningKeyResolver}) have been + * unified into a single {@link Locator} interface. For key location, (for both signing and encryption keys), + * use the {@link JwtParserBuilder#keyLocator(Locator)} to configure a parser with your desired Key locator instead + * of using a {@code SigningKeyResolver}. Also see {@link LocatorAdapter} for the Adapter pattern parallel of this + * class. This {@code SigningKeyResolverAdapter} class will be removed before the 1.0 release.

    + * + *

    Previous Documentation

    + * + *

    An Adapter implementation of the * {@link SigningKeyResolver} interface that allows subclasses to process only the type of JWS body that - * is known/expected for a particular case. + * is known/expected for a particular case.

    * - *

    The {@link #resolveSigningKey(JwsHeader, Claims)} and {@link #resolveSigningKey(JwsHeader, String)} method + *

    The {@link #resolveSigningKey(JwsHeader, Claims)} and {@link #resolveSigningKey(JwsHeader, byte[])} method * implementations delegate to the - * {@link #resolveSigningKeyBytes(JwsHeader, Claims)} and {@link #resolveSigningKeyBytes(JwsHeader, String)} methods + * {@link #resolveSigningKeyBytes(JwsHeader, Claims)} and {@link #resolveSigningKeyBytes(JwsHeader, byte[])} methods * respectively. The latter two methods simply throw exceptions: they represent scenarios expected by * calling code in known situations, and it is expected that you override the implementation in those known situations; * non-overridden *KeyBytes methods indicates that the JWS input was unexpected.

    * - *

    If either {@link #resolveSigningKey(JwsHeader, String)} or {@link #resolveSigningKey(JwsHeader, Claims)} + *

    If either {@link #resolveSigningKey(JwsHeader, byte[])} or {@link #resolveSigningKey(JwsHeader, Claims)} * are not overridden, one (or both) of the *KeyBytes variants must be overridden depending on your expected * use case. You do not have to override any method that does not represent an expected condition.

    * + * @see io.jsonwebtoken.JwtParserBuilder#keyLocator(Locator) + * @see LocatorAdapter * @since 0.4 + * @deprecated since 0.12.0. Use {@link LocatorAdapter LocatorAdapter} with + * {@link JwtParserBuilder#keyLocator(Locator)} */ +@SuppressWarnings("DeprecatedIsStillUsed") +@Deprecated public class SigningKeyResolverAdapter implements SigningKeyResolver { + /** + * Default constructor. + */ + public SigningKeyResolverAdapter() { + + } + @Override public Key resolveSigningKey(JwsHeader header, Claims claims) { SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm()); - Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, Claims) implementation cannot be " + - "used for asymmetric key algorithms (RSA, Elliptic Curve). " + - "Override the resolveSigningKey(JwsHeader, Claims) method instead and return a " + - "Key instance appropriate for the " + alg.name() + " algorithm."); + Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, Claims) implementation cannot " + + "be used for asymmetric key algorithms (RSA, Elliptic Curve). " + + "Override the resolveSigningKey(JwsHeader, Claims) method instead and return a " + + "Key instance appropriate for the " + alg.name() + " algorithm."); byte[] keyBytes = resolveSigningKeyBytes(header, claims); return new SecretKeySpec(keyBytes, alg.getJcaName()); } @Override - public Key resolveSigningKey(JwsHeader header, String plaintext) { + public Key resolveSigningKey(JwsHeader header, byte[] content) { SignatureAlgorithm alg = SignatureAlgorithm.forName(header.getAlgorithm()); - Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, String) implementation cannot be " + - "used for asymmetric key algorithms (RSA, Elliptic Curve). " + - "Override the resolveSigningKey(JwsHeader, String) method instead and return a " + - "Key instance appropriate for the " + alg.name() + " algorithm."); - byte[] keyBytes = resolveSigningKeyBytes(header, plaintext); + Assert.isTrue(alg.isHmac(), "The default resolveSigningKey(JwsHeader, byte[]) implementation cannot " + + "be used for asymmetric key algorithms (RSA, Elliptic Curve). " + + "Override the resolveSigningKey(JwsHeader, byte[]) method instead and return a " + + "Key instance appropriate for the " + alg.name() + " algorithm."); + byte[] keyBytes = resolveSigningKeyBytes(header, content); return new SecretKeySpec(keyBytes, alg.getJcaName()); } @@ -76,24 +99,25 @@ */ public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) { throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " + - "Claims JWS signing key resolution. Consider overriding either the " + - "resolveSigningKey(JwsHeader, Claims) method or, for HMAC algorithms, the " + - "resolveSigningKeyBytes(JwsHeader, Claims) method."); + "Claims JWS signing key resolution. Consider overriding either the " + + "resolveSigningKey(JwsHeader, Claims) method or, for HMAC algorithms, the " + + "resolveSigningKeyBytes(JwsHeader, Claims) method."); } /** - * Convenience method invoked by {@link #resolveSigningKey(JwsHeader, String)} that obtains the necessary signing - * key bytes. This implementation simply throws an exception: if the JWS parsed is a plaintext JWS, you must - * override this method or the {@link #resolveSigningKey(JwsHeader, String)} method instead. + * Convenience method invoked by {@link #resolveSigningKey(JwsHeader, byte[])} that obtains the necessary signing + * key bytes. This implementation simply throws an exception: if the JWS parsed is a content JWS, you must + * override this method or the {@link #resolveSigningKey(JwsHeader, byte[])} method instead. * - * @param header the parsed {@link JwsHeader} - * @param payload the parsed String plaintext payload + * @param header the parsed {@link JwsHeader} + * @param content the byte array payload * @return the signing key bytes to use to verify the JWS signature. */ - public byte[] resolveSigningKeyBytes(JwsHeader header, String payload) { + @SuppressWarnings("unused") + public byte[] resolveSigningKeyBytes(JwsHeader header, byte[] content) { throw new UnsupportedJwtException("The specified SigningKeyResolver implementation does not support " + - "plaintext JWS signing key resolution. Consider overriding either the " + - "resolveSigningKey(JwsHeader, String) method or, for HMAC algorithms, the " + - "resolveSigningKeyBytes(JwsHeader, String) method."); + "content JWS signing key resolution. Consider overriding either the " + + "resolveSigningKey(JwsHeader, byte[]) method or, for HMAC algorithms, the " + + "resolveSigningKeyBytes(JwsHeader, byte[]) method."); } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SupportedJwtVisitor.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SupportedJwtVisitor.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/SupportedJwtVisitor.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,200 @@ +/* + * 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; + +import io.jsonwebtoken.lang.Assert; + +/** + * A {@code JwtVisitor} that guarantees only supported JWT instances are handled, rejecting + * all other (unsupported) JWTs with {@link UnsupportedJwtException}s. A JWT is considered supported + * only if the type-specific handler method is overridden by a subclass. + * + * @param the type of value returned from the subclass handler method implementation. + * @since 0.12.0 + */ +public class SupportedJwtVisitor implements JwtVisitor { + + /** + * Default constructor, does not initialize any internal state. + */ + public SupportedJwtVisitor() { + } + + /** + * Handles an encountered unsecured JWT by delegating to either {@link #onUnsecuredContent(Jwt)} or + * {@link #onUnsecuredClaims(Jwt)} depending on the payload type. + * + * @param jwt the parsed unsecured JWT + * @return the value returned by either {@link #onUnsecuredContent(Jwt)} or {@link #onUnsecuredClaims(Jwt)} + * depending on the payload type. + * @throws UnsupportedJwtException if the payload is neither a {@code byte[]} nor {@code Claims}, or either + * delegate method throws the same. + */ + @SuppressWarnings("unchecked") + @Override + public T visit(Jwt jwt) { + Assert.notNull(jwt, "JWT cannot be null."); + Object payload = jwt.getPayload(); + if (payload instanceof byte[]) { + return onUnsecuredContent((Jwt) jwt); + } else { + // only other type we support: + Assert.stateIsInstance(Claims.class, payload, "Unexpected payload data type: "); + return onUnsecuredClaims((Jwt) jwt); + } + } + + /** + * Handles an encountered unsecured content JWT - one that is not cryptographically signed nor + * encrypted, and has a byte[] array payload. If the JWT creator has set the (optional) + * {@link Header#getContentType()} value, the application may inspect that value to determine how to convert + * the byte array to the final type as desired. + * + *

    The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that + * subclasses will override this method if the application needs to support this type of JWT.

    + * + * @param jwt the parsed unsecured content JWT + * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. + * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. + */ + public T onUnsecuredContent(Jwt jwt) throws UnsupportedJwtException { + throw new UnsupportedJwtException("Unexpected unsecured content JWT."); + } + + /** + * Handles an encountered unsecured Claims JWT - one that is not cryptographically signed nor + * encrypted, and has a {@link Claims} payload. + * + *

    The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that + * subclasses will override this method if the application needs to support this type of JWT.

    + * + * @param jwt the parsed unsecured content JWT + * @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary. + * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. + */ + public T onUnsecuredClaims(Jwt jwt) { + throw new UnsupportedJwtException("Unexpected unsecured Claims JWT."); + } + + /** + * Handles an encountered JSON Web Token (aka 'JWS') message that has been cryptographically verified/authenticated + * by delegating to either {@link #onVerifiedContent(Jws)} or {@link #onVerifiedClaims(Jws)} depending on the payload + * type. + * + * @param jws the parsed verified/authenticated JWS. + * @return the value returned by either {@link #onVerifiedContent(Jws)} or {@link #onVerifiedClaims(Jws)} + * depending on the payload type. + * @throws UnsupportedJwtException if the payload is neither a {@code byte[]} nor {@code Claims}, or either + * delegate method throws the same. + */ + @SuppressWarnings("unchecked") + @Override + public T visit(Jws jws) { + Assert.notNull(jws, "JWS cannot be null."); + Object payload = jws.getPayload(); + if (payload instanceof byte[]) { + return onVerifiedContent((Jws) jws); + } else { + Assert.stateIsInstance(Claims.class, payload, "Unexpected payload data type: "); + return onVerifiedClaims((Jws) jws); + } + } + + /** + * Handles an encountered JWS message that has been cryptographically verified/authenticated and has + * a byte[] array payload. If the JWT creator has set the (optional) {@link Header#getContentType()} value, the + * application may inspect that value to determine how to convert the byte array to the final type as desired. + * + *

    The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that + * subclasses will override this method if the application needs to support this type of JWT.

    + * + * @param jws the parsed verified/authenticated JWS. + * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. + * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. + */ + public T onVerifiedContent(Jws jws) { + throw new UnsupportedJwtException("Unexpected content JWS."); + } + + /** + * Handles an encountered JWS message that has been cryptographically verified/authenticated and has a + * {@link Claims} payload. + * + *

    The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that + * subclasses will override this method if the application needs to support this type of JWT.

    + * + * @param jws the parsed signed (and verified) Claims JWS + * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. + * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. + */ + public T onVerifiedClaims(Jws jws) { + throw new UnsupportedJwtException("Unexpected Claims JWS."); + } + + /** + * Handles an encountered JSON Web Encryption (aka 'JWE') message that has been authenticated and decrypted by + * delegating to either {@link #onDecryptedContent(Jwe)} or {@link #onDecryptedClaims(Jwe)} depending on the + * payload type. + * + * @param jwe the parsed authenticated and decrypted JWE. + * @return the value returned by either {@link #onDecryptedContent(Jwe)} or {@link #onDecryptedClaims(Jwe)} + * depending on the payload type. + * @throws UnsupportedJwtException if the payload is neither a {@code byte[]} nor {@code Claims}, or either + * delegate method throws the same. + */ + @SuppressWarnings("unchecked") + @Override + public T visit(Jwe jwe) { + Assert.notNull(jwe, "JWE cannot be null."); + Object payload = jwe.getPayload(); + if (payload instanceof byte[]) { + return onDecryptedContent((Jwe) jwe); + } else { + Assert.stateIsInstance(Claims.class, payload, "Unexpected payload data type: "); + return onDecryptedClaims((Jwe) jwe); + } + } + + /** + * Handles an encountered JWE message that has been authenticated and decrypted, and has byte[] array payload. If + * the JWT creator has set the (optional) {@link Header#getContentType()} value, the application may inspect that + * value to determine how to convert the byte array to the final type as desired. + * + *

    The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that + * subclasses will override this method if the application needs to support this type of JWT.

    + * + * @param jwe the parsed authenticated and decrypted content JWE. + * @return any object to be used after inspecting the JWS, or {@code null} if no return value is necessary. + * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. + */ + public T onDecryptedContent(Jwe jwe) { + throw new UnsupportedJwtException("Unexpected content JWE."); + } + + /** + * Handles an encountered JWE message that has been authenticated and decrypted, and has a {@link Claims} payload. + * + *

    The default implementation immediately throws an {@link UnsupportedJwtException}; it is expected that + * subclasses will override this method if the application needs to support this type of JWT.

    + * + * @param jwe the parsed authenticated and decrypted content JWE. + * @return any object to be used after inspecting the JWE, or {@code null} if no return value is necessary. + * @throws UnsupportedJwtException by default, expecting the subclass implementation to override as necessary. + */ + public T onDecryptedClaims(Jwe jwe) { + throw new UnsupportedJwtException("Unexpected Claims JWE."); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/UnsupportedJwtException.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/UnsupportedJwtException.java (.../UnsupportedJwtException.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/UnsupportedJwtException.java (.../UnsupportedJwtException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -19,17 +19,28 @@ * Exception thrown when receiving a JWT in a particular format/configuration that does not match the format expected * by the application. * - *

    For example, this exception would be thrown if parsing an unsigned plaintext JWT when the application + *

    For example, this exception would be thrown if parsing an unprotected content JWT when the application * requires a cryptographically signed Claims JWS instead.

    * * @since 0.2 */ public class UnsupportedJwtException extends JwtException { + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ public UnsupportedJwtException(String message) { super(message); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public UnsupportedJwtException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/AbstractAudienceCollection.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/AbstractAudienceCollection.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/AbstractAudienceCollection.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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

    type of parent to return + * @since 0.12.0 + */ +abstract class AbstractAudienceCollection

    extends DefaultNestedCollection + implements ClaimsMutator.AudienceCollection

    { + protected AbstractAudienceCollection(P parent, Collection seed) { + super(parent, seed); + } +} \ No newline at end of file Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/AbstractX509Context.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/AbstractX509Context.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/AbstractX509Context.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,81 @@ +/* + * 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; + +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.security.AbstractAsymmetricJwk; +import io.jsonwebtoken.security.X509Mutator; + +import java.net.URI; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Set; + +public class AbstractX509Context> extends ParameterMap implements X509Context { + + public AbstractX509Context(Set> params) { + super(params); + } + + @SuppressWarnings("unchecked") + protected T self() { + return (T) this; + } + + @Override + public URI getX509Url() { + return get(AbstractAsymmetricJwk.X5U); + } + + @Override + public T x509Url(URI uri) { + put(AbstractAsymmetricJwk.X5U, uri); + return self(); + } + + @Override + public List getX509Chain() { + return get(AbstractAsymmetricJwk.X5C); + } + + @Override + public T x509Chain(List chain) { + put(AbstractAsymmetricJwk.X5C, chain); + return self(); + } + + @Override + public byte[] getX509Sha1Thumbprint() { + return get(AbstractAsymmetricJwk.X5T); + } + + @Override + public T x509Sha1Thumbprint(byte[] thumbprint) { + put(AbstractAsymmetricJwk.X5T, thumbprint); + return self(); + } + + @Override + public byte[] getX509Sha256Thumbprint() { + return get(AbstractAsymmetricJwk.X5T_S256); + } + + @Override + public T x509Sha256Thumbprint(byte[] thumbprint) { + put(AbstractAsymmetricJwk.X5T_S256, thumbprint); + return self(); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/CompressionCodecLocator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/CompressionCodecLocator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/CompressionCodecLocator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 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.CompressionCodecResolver; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Locator; +import io.jsonwebtoken.impl.lang.Function; +import io.jsonwebtoken.io.CompressionAlgorithm; +import io.jsonwebtoken.lang.Assert; + +//TODO: delete when deleting CompressionCodecResolver +public class CompressionCodecLocator implements Function, Locator { + + private final CompressionCodecResolver resolver; + + public CompressionCodecLocator(CompressionCodecResolver resolver) { + this.resolver = Assert.notNull(resolver, "CompressionCodecResolver cannot be null."); + } + + @Override + public CompressionAlgorithm apply(Header header) { + return locate(header); + } + + @Override + public CompressionAlgorithm locate(Header header) { + return resolver.resolveCompressionCodec(header); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultClaims.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultClaims.java (.../DefaultClaims.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultClaims.java (.../DefaultClaims.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -17,154 +17,137 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.RequiredTypeException; +import io.jsonwebtoken.impl.lang.JwtDateConverter; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Registry; + import java.util.Date; import java.util.Map; +import java.util.Set; -public class DefaultClaims extends JwtMap implements Claims { +public class DefaultClaims extends ParameterMap implements Claims { private static final String CONVERSION_ERROR_MSG = "Cannot convert existing claim value of type '%s' to desired type " + "'%s'. JJWT only converts simple String, Date, Long, Integer, Short and Byte types automatically. " + "Anything more complex is expected to be already converted to your desired type by the JSON Deserializer " + "implementation. You may specify a custom Deserializer for a JwtParser with the desired conversion " + - "configuration via the JwtParserBuilder.deserializeJsonWith() method. " + + "configuration via the JwtParserBuilder.deserializer() method. " + "See https://github.com/jwtk/jjwt#custom-json-processor for more information. If using Jackson, you can " + "specify custom claim POJO types as described in https://github.com/jwtk/jjwt#json-jackson-custom-types"; - public DefaultClaims() { - super(); - } + static final Parameter ISSUER = Parameters.string(Claims.ISSUER, "Issuer"); + static final Parameter SUBJECT = Parameters.string(Claims.SUBJECT, "Subject"); + static final Parameter> AUDIENCE = Parameters.stringSet(Claims.AUDIENCE, "Audience"); + static final Parameter EXPIRATION = Parameters.rfcDate(Claims.EXPIRATION, "Expiration Time"); + static final Parameter NOT_BEFORE = Parameters.rfcDate(Claims.NOT_BEFORE, "Not Before"); + static final Parameter ISSUED_AT = Parameters.rfcDate(Claims.ISSUED_AT, "Issued At"); + static final Parameter JTI = Parameters.string(Claims.ID, "JWT ID"); - public DefaultClaims(Map map) { - super(map); + static final Registry> PARAMS = + Parameters.registry(ISSUER, SUBJECT, AUDIENCE, EXPIRATION, NOT_BEFORE, ISSUED_AT, JTI); + + protected DefaultClaims() { // visibility for testing + super(PARAMS); } - @Override - public String getIssuer() { - return getString(ISSUER); + public DefaultClaims(ParameterMap m) { + super(m.PARAMS, m); } - @Override - public Claims setIssuer(String iss) { - setValue(ISSUER, iss); - return this; + public DefaultClaims(Map map) { + super(PARAMS, map); } @Override - public String getSubject() { - return getString(SUBJECT); + public String getName() { + return "JWT Claims"; } @Override - public Claims setSubject(String sub) { - setValue(SUBJECT, sub); - return this; + public String getIssuer() { + return get(ISSUER); } @Override - public String getAudience() { - return getString(AUDIENCE); + public String getSubject() { + return get(SUBJECT); } @Override - public Claims setAudience(String aud) { - setValue(AUDIENCE, aud); - return this; + public Set getAudience() { + return get(AUDIENCE); } @Override public Date getExpiration() { - return get(Claims.EXPIRATION, Date.class); + return get(EXPIRATION); } @Override - public Claims setExpiration(Date exp) { - setDateAsSeconds(Claims.EXPIRATION, exp); - return this; - } - - @Override public Date getNotBefore() { - return get(Claims.NOT_BEFORE, Date.class); + return get(NOT_BEFORE); } @Override - public Claims setNotBefore(Date nbf) { - setDateAsSeconds(Claims.NOT_BEFORE, nbf); - return this; - } - - @Override public Date getIssuedAt() { - return get(Claims.ISSUED_AT, Date.class); + return get(ISSUED_AT); } @Override - public Claims setIssuedAt(Date iat) { - setDateAsSeconds(Claims.ISSUED_AT, iat); - return this; - } - - @Override public String getId() { - return getString(ID); + return get(JTI); } @Override - public Claims setId(String jti) { - setValue(Claims.ID, jti); - return this; - } + public T get(String claimName, Class requiredType) { + Assert.notNull(requiredType, "requiredType argument cannot be null."); - /** - * @since 0.10.0 - */ - private static boolean isSpecDate(String claimName) { - return Claims.EXPIRATION.equals(claimName) || - Claims.ISSUED_AT.equals(claimName) || - Claims.NOT_BEFORE.equals(claimName); - } - - @Override - public Object put(String s, Object o) { - if (o instanceof Date && isSpecDate(s)) { //since 0.10.0 - Date date = (Date)o; - return setDateAsSeconds(s, date); + Object value = this.idiomaticValues.get(claimName); + if (requiredType.isInstance(value)) { + return requiredType.cast(value); } - return super.put(s, o); - } - @Override - public T get(String claimName, Class requiredType) { - - Object value = get(claimName); + value = get(claimName); if (value == null) { return null; } if (Date.class.equals(requiredType)) { - if (isSpecDate(claimName)) { - value = toSpecDate(value, claimName); - } else { - value = toDate(value, claimName); + try { + value = JwtDateConverter.toDate(value); // NOT specDate logic + } catch (Exception e) { + String msg = "Cannot create Date from '" + claimName + "' value '" + value + "'. Cause: " + e.getMessage(); + throw new IllegalArgumentException(msg, e); } } - return castClaimValue(value, requiredType); + return castClaimValue(claimName, value, requiredType); } - private T castClaimValue(Object value, Class requiredType) { + private T castClaimValue(String name, Object value, Class requiredType) { - if (value instanceof Integer) { - int intValue = (Integer) value; - if (requiredType == Long.class) { - value = (long) intValue; - } else if (requiredType == Short.class && Short.MIN_VALUE <= intValue && intValue <= Short.MAX_VALUE) { - value = (short) intValue; - } else if (requiredType == Byte.class && Byte.MIN_VALUE <= intValue && intValue <= Byte.MAX_VALUE) { - value = (byte) intValue; + if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte) { + long longValue = ((Number) value).longValue(); + if (Long.class.equals(requiredType)) { + value = longValue; + } else if (Integer.class.equals(requiredType) && Integer.MIN_VALUE <= longValue && longValue <= Integer.MAX_VALUE) { + value = (int) longValue; + } else if (requiredType == Short.class && Short.MIN_VALUE <= longValue && longValue <= Short.MAX_VALUE) { + value = (short) longValue; + } else if (requiredType == Byte.class && Byte.MIN_VALUE <= longValue && longValue <= Byte.MAX_VALUE) { + value = (byte) longValue; } } + if (value instanceof Long && + (requiredType.equals(Integer.class) || requiredType.equals(Short.class) || requiredType.equals(Byte.class))) { + String msg = "Claim '" + name + "' value is too large or too small to be represented as a " + + requiredType.getName() + " instance (would cause numeric overflow)."; + throw new RequiredTypeException(msg); + } + if (!requiredType.isInstance(value)) { throw new RequiredTypeException(String.format(CONVERSION_ERROR_MSG, value.getClass(), requiredType)); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultClaimsBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultClaimsBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultClaimsBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,37 @@ +/* + * 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.Claims; +import io.jsonwebtoken.ClaimsBuilder; + +/** + * @since 0.12.0 + */ +@SuppressWarnings("unused") // used via reflection via Jwts.claims() +public final class DefaultClaimsBuilder extends DelegatingClaimsMutator + implements ClaimsBuilder { + + public DefaultClaimsBuilder() { + super(); + } + + @Override + public Claims build() { + // ensure a new instance is returned so that the builder may be re-used: + return new DefaultClaims(this.DELEGATE); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultHeader.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultHeader.java (.../DefaultHeader.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultHeader.java (.../DefaultHeader.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,57 +16,65 @@ package io.jsonwebtoken.impl; import io.jsonwebtoken.Header; +import io.jsonwebtoken.impl.lang.CompactMediaTypeIdConverter; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Strings; import java.util.Map; -@SuppressWarnings("unchecked") -public class DefaultHeader> extends JwtMap implements Header { +public class DefaultHeader extends ParameterMap implements Header { - public DefaultHeader() { - super(); + static final Parameter TYPE = Parameters.string(Header.TYPE, "Type"); + static final Parameter CONTENT_TYPE = Parameters.builder(String.class) + .setId(Header.CONTENT_TYPE).setName("Content Type") + .setConverter(CompactMediaTypeIdConverter.INSTANCE).build(); + static final Parameter ALGORITHM = Parameters.string(Header.ALGORITHM, "Algorithm"); + static final Parameter COMPRESSION_ALGORITHM = + Parameters.string(Header.COMPRESSION_ALGORITHM, "Compression Algorithm"); + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated // TODO: remove for 1.0.0: + static final Parameter DEPRECATED_COMPRESSION_ALGORITHM = + Parameters.string(Header.DEPRECATED_COMPRESSION_ALGORITHM, "Deprecated Compression Algorithm"); + + static final Registry> PARAMS = + Parameters.registry(TYPE, CONTENT_TYPE, ALGORITHM, COMPRESSION_ALGORITHM, DEPRECATED_COMPRESSION_ALGORITHM); + + public DefaultHeader(Map values) { + super(PARAMS, values); } - public DefaultHeader(Map map) { - super(map); + protected DefaultHeader(Registry> registry, Map values) { + super(registry, values); } @Override - public String getType() { - return getString(TYPE); + public String getName() { + return "JWT header"; } @Override - public T setType(String typ) { - setValue(TYPE, typ); - return (T)this; + public String getType() { + return get(TYPE); } @Override public String getContentType() { - return getString(CONTENT_TYPE); + return get(CONTENT_TYPE); } @Override - public T setContentType(String cty) { - setValue(CONTENT_TYPE, cty); - return (T)this; + public String getAlgorithm() { + return get(ALGORITHM); } - @SuppressWarnings("deprecation") @Override public String getCompressionAlgorithm() { - String alg = getString(COMPRESSION_ALGORITHM); - if (!Strings.hasText(alg)) { - alg = getString(DEPRECATED_COMPRESSION_ALGORITHM); + String s = get(COMPRESSION_ALGORITHM); + if (!Strings.hasText(s)) { + s = get(DEPRECATED_COMPRESSION_ALGORITHM); } - return alg; + return s; } - - @Override - public T setCompressionAlgorithm(String compressionAlgorithm) { - setValue(COMPRESSION_ALGORITHM, compressionAlgorithm); - return (T) this; - } - } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwe.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwe.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwe.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2021 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.Jwe; +import io.jsonwebtoken.JweHeader; +import io.jsonwebtoken.JwtVisitor; +import io.jsonwebtoken.io.Encoders; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Objects; + +import java.security.MessageDigest; + +public class DefaultJwe

    extends DefaultProtectedJwt implements Jwe

    { + + private static final String DIGEST_NAME = "tag"; + + private final byte[] iv; + + public DefaultJwe(JweHeader header, P payload, byte[] iv, byte[] aadTag) { + super(header, payload, aadTag, DIGEST_NAME); + this.iv = Assert.notEmpty(iv, "Initialization vector cannot be null or empty."); + } + + @Override + public byte[] getInitializationVector() { + return this.iv.clone(); + } + + @Override + protected StringBuilder toStringBuilder() { + return super.toStringBuilder().append(",iv=").append(Encoders.BASE64URL.encode(this.iv)); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Jwe) { + Jwe jwe = (Jwe) obj; + return super.equals(jwe) && MessageDigest.isEqual(this.iv, jwe.getInitializationVector()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.nullSafeHashCode(getHeader(), getPayload(), this.iv, this.digest); + } + + @Override + public T accept(JwtVisitor v) { + return v.visit(this); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJweHeader.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJweHeader.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJweHeader.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021 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.JweHeader; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.impl.lang.Converters; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.impl.lang.PositiveIntegerConverter; +import io.jsonwebtoken.impl.lang.RequiredBitLengthConverter; +import io.jsonwebtoken.impl.security.JwkConverter; +import io.jsonwebtoken.lang.Registry; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.PublicJwk; + +import java.util.Map; + +/** + * Header implementation satisfying JWE header parameter requirements. + * + * @since 0.12.0 + */ +public class DefaultJweHeader extends DefaultProtectedHeader implements JweHeader { + + static final Parameter ENCRYPTION_ALGORITHM = Parameters.string("enc", "Encryption Algorithm"); + + public static final Parameter> EPK = Parameters.builder(JwkConverter.PUBLIC_JWK_CLASS) + .setId("epk").setName("Ephemeral Public Key") + .setConverter(JwkConverter.PUBLIC_JWK).build(); + static final Parameter APU = Parameters.bytes("apu", "Agreement PartyUInfo").build(); + static final Parameter APV = Parameters.bytes("apv", "Agreement PartyVInfo").build(); + + // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.7.1.1 says 96 bits required: + public static final Parameter IV = Parameters.bytes("iv", "Initialization Vector") + .setConverter(new RequiredBitLengthConverter(Converters.BASE64URL_BYTES, 96)).build(); + + // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.7.1.2 says 128 bits required: + public static final Parameter TAG = Parameters.bytes("tag", "Authentication Tag") + .setConverter(new RequiredBitLengthConverter(Converters.BASE64URL_BYTES, 128)).build(); + + // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.1 says at least 64 bits (8 bytes) is required: + public static final Parameter P2S = Parameters.bytes("p2s", "PBES2 Salt Input") + .setConverter(new RequiredBitLengthConverter(Converters.BASE64URL_BYTES, 64, false)).build(); + public static final Parameter P2C = Parameters.builder(Integer.class) + .setConverter(PositiveIntegerConverter.INSTANCE).setId("p2c").setName("PBES2 Count").build(); + + static final Registry> PARAMS = + Parameters.registry(DefaultProtectedHeader.PARAMS, ENCRYPTION_ALGORITHM, EPK, APU, APV, IV, TAG, P2S, P2C); + + static boolean isCandidate(ParameterMap map) { + String id = map.get(DefaultHeader.ALGORITHM); + return Strings.hasText(id) && !id.equalsIgnoreCase(Jwts.SIG.NONE.getId()) && // alg cannot be empty or 'none' + Strings.hasText(map.get(ENCRYPTION_ALGORITHM)); // enc cannot be empty +// return Strings.hasText(map.get(ENCRYPTION_ALGORITHM)) || // MUST have at least an `enc` header +// !Collections.isEmpty(map.get(EPK)) || +// !Bytes.isEmpty(map.get(APU)) || +// !Bytes.isEmpty(map.get(APV)) || +// !Bytes.isEmpty(map.get(IV)) || +// !Bytes.isEmpty(map.get(TAG)) || +// !Bytes.isEmpty(map.get(P2S)) || +// (map.get(P2C) != null && map.get(P2C) > 0); + + } + + public DefaultJweHeader(Map map) { + super(PARAMS, map); + } + + @Override + public String getName() { + return "JWE header"; + } + + @Override + public String getEncryptionAlgorithm() { + return get(ENCRYPTION_ALGORITHM); + } + + @Override + public PublicJwk getEphemeralPublicKey() { + return get(EPK); + } + + @Override + public byte[] getAgreementPartyUInfo() { + return get(APU); + } + + @Override + public byte[] getAgreementPartyVInfo() { + return get(APV); + } + + @Override + public byte[] getInitializationVector() { + return get(IV); + } + + @Override + public byte[] getAuthenticationTag() { + return get(TAG); + } + + public byte[] getPbes2Salt() { + return get(P2S); + } + + @Override + public Integer getPbes2Count() { + return get(P2C); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJweHeaderBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJweHeaderBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJweHeaderBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,47 @@ +/* + * 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; + +import io.jsonwebtoken.JweHeaderMutator; +import io.jsonwebtoken.security.X509Builder; + +/** + * @param return type for method chaining + * @since 0.12.0 + */ +public class DefaultJweHeaderBuilder & X509Builder> + extends DefaultJweHeaderMutator implements X509Builder { + + protected DefaultJweHeaderBuilder() { + super(); + } + + protected DefaultJweHeaderBuilder(DefaultJweHeaderMutator src) { + super(src); + } + + @Override + public T x509Sha1Thumbprint(boolean enable) { + this.x509.x509Sha1Thumbprint(enable); + return self(); + } + + @Override + public T x509Sha256Thumbprint(boolean enable) { + this.x509.x509Sha256Thumbprint(enable); + return self(); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJweHeaderMutator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJweHeaderMutator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJweHeaderMutator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,197 @@ +/* + * 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; + +import io.jsonwebtoken.JweHeaderMutator; +import io.jsonwebtoken.impl.lang.DefaultNestedCollection; +import io.jsonwebtoken.impl.lang.DelegatingMapMutator; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.security.X509BuilderSupport; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.NestedCollection; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.PublicJwk; + +import java.net.URI; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * @param return type for method chaining + * @since 0.12.0 + */ +public class DefaultJweHeaderMutator> + extends DelegatingMapMutator implements JweHeaderMutator { + + protected X509BuilderSupport x509; + + public DefaultJweHeaderMutator() { + // Any type of header can be created, but JWE parameters reflect all potential standard ones, so we use those + // params to catch any value being set, especially through generic 'put' or 'putAll' methods: + super(new ParameterMap(DefaultJweHeader.PARAMS)); + clear(); // initialize new X509Builder + } + + public DefaultJweHeaderMutator(DefaultJweHeaderMutator src) { + super(src.DELEGATE); + this.x509 = src.x509; + } + + // ============================================================= + // MapMutator methods + // ============================================================= + + private T put(Parameter param, F value) { + this.DELEGATE.put(param, value); + return self(); + } + + @Override + public void clear() { + super.clear(); + this.x509 = new X509BuilderSupport(this.DELEGATE, IllegalStateException.class); + } + + // ============================================================= + // JWT Header methods + // ============================================================= + +// @Override +// public T algorithm(String alg) { +// return put(DefaultHeader.ALGORITHM, alg); +// } + + @Override + public T contentType(String cty) { + return put(DefaultHeader.CONTENT_TYPE, cty); + } + + @Override + public T type(String typ) { + return put(DefaultHeader.TYPE, typ); + } + + @Override + public T setType(String typ) { + return type(typ); + } + + @Override + public T setContentType(String cty) { + return contentType(cty); + } + + @Override + public T setCompressionAlgorithm(String zip) { + return put(DefaultHeader.COMPRESSION_ALGORITHM, zip); + } + + // ============================================================= + // Protected Header methods + // ============================================================= + + @Override + public NestedCollection critical() { + return new DefaultNestedCollection(self(), this.DELEGATE.get(DefaultProtectedHeader.CRIT)) { + @Override + protected void changed() { + put(DefaultProtectedHeader.CRIT, Collections.asSet(getCollection())); + } + }; + } + + @Override + public T jwk(PublicJwk jwk) { + return put(DefaultProtectedHeader.JWK, jwk); + } + + @Override + public T jwkSetUrl(URI uri) { + return put(DefaultProtectedHeader.JKU, uri); + } + + @Override + public T keyId(String kid) { + return put(DefaultProtectedHeader.KID, kid); + } + + @Override + public T setKeyId(String kid) { + return keyId(kid); + } + + @Override + public T setAlgorithm(String alg) { + return put(DefaultHeader.ALGORITHM, alg); + } + + // ============================================================= + // X.509 methods + // ============================================================= + + @Override + public T x509Url(URI uri) { + this.x509.x509Url(uri); + return self(); + } + + @Override + public T x509Chain(List chain) { + this.x509.x509Chain(chain); + return self(); + } + + @Override + public T x509Sha1Thumbprint(byte[] thumbprint) { + this.x509.x509Sha1Thumbprint(thumbprint); + return self(); + } + + @Override + public T x509Sha256Thumbprint(byte[] thumbprint) { + this.x509.x509Sha256Thumbprint(thumbprint); + return self(); + } + + // ============================================================= + // JWE Header methods + // ============================================================= + + @Override + public T agreementPartyUInfo(byte[] info) { + return put(DefaultJweHeader.APU, info); + } + + @Override + public T agreementPartyUInfo(String info) { + return agreementPartyUInfo(Strings.utf8(Strings.clean(info))); + } + + @Override + public T agreementPartyVInfo(byte[] info) { + return put(DefaultJweHeader.APV, info); + } + + @Override + public T agreementPartyVInfo(String info) { + return agreementPartyVInfo(Strings.utf8(Strings.clean(info))); + } + + @Override + public T pbes2Count(int count) { + return put(DefaultJweHeader.P2C, count); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJws.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJws.java (.../DefaultJws.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJws.java (.../DefaultJws.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -17,36 +17,27 @@ import io.jsonwebtoken.Jws; import io.jsonwebtoken.JwsHeader; +import io.jsonwebtoken.JwtVisitor; +import io.jsonwebtoken.io.Decoders; -public class DefaultJws implements Jws { +public class DefaultJws

    extends DefaultProtectedJwt implements Jws

    { - private final JwsHeader header; - private final B body; + private static final String DIGEST_NAME = "signature"; + private final String signature; - public DefaultJws(JwsHeader header, B body, String signature) { - this.header = header; - this.body = body; + public DefaultJws(JwsHeader header, P payload, String signature) { + super(header, payload, Decoders.BASE64URL.decode(signature), DIGEST_NAME); this.signature = signature; } @Override - public JwsHeader getHeader() { - return this.header; - } - - @Override - public B getBody() { - return this.body; - } - - @Override public String getSignature() { return this.signature; } @Override - public String toString() { - return "header=" + header + ",body=" + body + ",signature=" + signature; + public T accept(JwtVisitor v) { + return v.visit(this); } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwsHeader.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwsHeader.java (.../DefaultJwsHeader.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwsHeader.java (.../DefaultJwsHeader.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,39 +16,35 @@ package io.jsonwebtoken.impl; import io.jsonwebtoken.JwsHeader; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Registry; import java.util.Map; +import java.util.Set; -public class DefaultJwsHeader extends DefaultHeader implements JwsHeader { +public class DefaultJwsHeader extends DefaultProtectedHeader implements JwsHeader { - public DefaultJwsHeader() { - super(); - } + // https://datatracker.ietf.org/doc/html/rfc7797#section-3 : + static final Parameter B64 = Parameters.builder(Boolean.class) + .setId("b64").setName("Base64url-Encode Payload").build(); - public DefaultJwsHeader(Map map) { - super(map); - } + static final Registry> PARAMS = Parameters.registry(DefaultProtectedHeader.PARAMS, B64); - @Override - public String getAlgorithm() { - return getString(ALGORITHM); + public DefaultJwsHeader(Map map) { + super(PARAMS, map); } @Override - public JwsHeader setAlgorithm(String alg) { - setValue(ALGORITHM, alg); - return this; + public String getName() { + return "JWS header"; } @Override - public String getKeyId() { - return getString(KEY_ID); + public boolean isPayloadEncoded() { + Set crit = Collections.nullSafe(getCritical()); + Boolean b64 = get(B64); + return b64 == null || b64 || !crit.contains(B64.getId()); } - - @Override - public JwsHeader setKeyId(String kid) { - setValue(KEY_ID, kid); - return this; - } - } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwt.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwt.java (.../DefaultJwt.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwt.java (.../DefaultJwt.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -17,29 +17,73 @@ import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwt; +import io.jsonwebtoken.JwtVisitor; +import io.jsonwebtoken.io.Encoders; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Objects; -public class DefaultJwt implements Jwt { +public class DefaultJwt implements Jwt { - private final Header header; - private final B body; + private final H header; + private final P payload; - public DefaultJwt(Header header, B body) { - this.header = header; - this.body = body; + public DefaultJwt(H header, P payload) { + this.header = Assert.notNull(header, "header cannot be null."); + this.payload = Assert.notNull(payload, "payload cannot be null."); } @Override - public Header getHeader() { + public H getHeader() { return header; } @Override - public B getBody() { - return body; + public P getBody() { + return getPayload(); } @Override - public String toString() { - return "header=" + header + ",body=" + body; + public P getPayload() { + return this.payload; } + + protected StringBuilder toStringBuilder() { + StringBuilder sb = new StringBuilder(100); + sb.append("header=").append(header).append(",payload="); + if (payload instanceof byte[]) { + String encoded = Encoders.BASE64URL.encode((byte[]) payload); + sb.append(encoded); + } else { + sb.append(payload); + } + return sb; + } + + @Override + public final String toString() { + return toStringBuilder().toString(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Jwt) { + Jwt jwt = (Jwt) obj; + return Objects.nullSafeEquals(header, jwt.getHeader()) && + Objects.nullSafeEquals(payload, jwt.getPayload()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.nullSafeHashCode(header, payload); + } + + @Override + public T accept(JwtVisitor v) { + return v.visit(this); + } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwtBuilder.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwtBuilder.java (.../DefaultJwtBuilder.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwtBuilder.java (.../DefaultJwtBuilder.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,378 +16,808 @@ package io.jsonwebtoken.impl; import io.jsonwebtoken.Claims; -import io.jsonwebtoken.CompressionCodec; import io.jsonwebtoken.Header; +import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.JwtBuilder; -import io.jsonwebtoken.JwtParser; -import io.jsonwebtoken.SignatureAlgorithm; -import io.jsonwebtoken.impl.crypto.DefaultJwtSigner; -import io.jsonwebtoken.impl.crypto.JwtSigner; -import io.jsonwebtoken.impl.lang.LegacyServices; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.impl.io.Base64UrlStreamEncoder; +import io.jsonwebtoken.impl.io.ByteBase64UrlStreamEncoder; +import io.jsonwebtoken.impl.io.CountingInputStream; +import io.jsonwebtoken.impl.io.EncodingOutputStream; +import io.jsonwebtoken.impl.io.NamedSerializer; +import io.jsonwebtoken.impl.io.Streams; +import io.jsonwebtoken.impl.io.UncloseableInputStream; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.Function; +import io.jsonwebtoken.impl.lang.Functions; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Services; +import io.jsonwebtoken.impl.security.DefaultAeadRequest; +import io.jsonwebtoken.impl.security.DefaultAeadResult; +import io.jsonwebtoken.impl.security.DefaultKeyRequest; +import io.jsonwebtoken.impl.security.DefaultSecureRequest; +import io.jsonwebtoken.impl.security.Pbes2HsAkwAlgorithm; +import io.jsonwebtoken.impl.security.ProviderKey; +import io.jsonwebtoken.impl.security.StandardSecureDigestAlgorithms; +import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Encoder; -import io.jsonwebtoken.io.Encoders; -import io.jsonwebtoken.io.SerializationException; import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Objects; import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.AeadRequest; +import io.jsonwebtoken.security.AeadResult; import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.KeyRequest; +import io.jsonwebtoken.security.KeyResult; +import io.jsonwebtoken.security.Password; +import io.jsonwebtoken.security.SecureDigestAlgorithm; +import io.jsonwebtoken.security.SecureRequest; +import io.jsonwebtoken.security.SecurityException; +import io.jsonwebtoken.security.SignatureException; +import io.jsonwebtoken.security.UnsupportedKeyException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; import java.security.Key; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; import java.util.Date; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; public class DefaultJwtBuilder implements JwtBuilder { - private Header header; - private Claims claims; - private String payload; + private static final String PUB_KEY_SIGN_MSG = "PublicKeys may not be used to create digital signatures. " + + "PrivateKeys are used to sign, and PublicKeys are used to verify."; - private SignatureAlgorithm algorithm; + private static final String PRIV_KEY_ENC_MSG = "PrivateKeys may not be used to encrypt data. PublicKeys are " + + "used to encrypt, and PrivateKeys are used to decrypt."; + + protected Provider provider; + protected SecureRandom secureRandom; + + private final DefaultBuilderHeader headerBuilder; + private final DefaultBuilderClaims claimsBuilder; + + private Payload payload = Payload.EMPTY; + + private SecureDigestAlgorithm sigAlg = Jwts.SIG.NONE; + private Function, byte[]> signFunction; + + private AeadAlgorithm enc; // MUST be Symmetric AEAD per https://tools.ietf.org/html/rfc7516#section-4.1.2 + + private KeyAlgorithm keyAlg; + private Function, KeyResult> keyAlgFunction; + private Key key; - private Serializer> serializer; + private Serializer> serializer; - private Encoder base64UrlEncoder = Encoders.BASE64URL; + protected Encoder encoder = Base64UrlStreamEncoder.INSTANCE; + private boolean encodePayload = true; + protected CompressionAlgorithm compressionAlgorithm; - private CompressionCodec compressionCodec; + public DefaultJwtBuilder() { + this.headerBuilder = new DefaultBuilderHeader(this); + this.claimsBuilder = new DefaultBuilderClaims(this); + } @Override - public JwtBuilder serializeToJsonWith(Serializer> serializer) { - Assert.notNull(serializer, "Serializer cannot be null."); - this.serializer = serializer; - return this; + public BuilderHeader header() { + return this.headerBuilder; } @Override - public JwtBuilder base64UrlEncodeWith(Encoder base64UrlEncoder) { - Assert.notNull(base64UrlEncoder, "base64UrlEncoder cannot be null."); - this.base64UrlEncoder = base64UrlEncoder; - return this; + public BuilderClaims claims() { + return this.claimsBuilder; } @Override - public JwtBuilder setHeader(Header header) { - this.header = header; + public JwtBuilder provider(Provider provider) { + this.provider = provider; return this; } @Override - public JwtBuilder setHeader(Map header) { - this.header = new DefaultHeader(header); + public JwtBuilder random(SecureRandom secureRandom) { + this.secureRandom = secureRandom; return this; } @Override - public JwtBuilder setHeaderParams(Map params) { - if (!Collections.isEmpty(params)) { + public JwtBuilder serializeToJsonWith(final Serializer> serializer) { + return json(serializer); + } - Header header = ensureHeader(); + @Override + public JwtBuilder json(Serializer> serializer) { + this.serializer = Assert.notNull(serializer, "JSON Serializer cannot be null."); + return this; + } - for (Map.Entry entry : params.entrySet()) { - header.put(entry.getKey(), entry.getValue()); - } - } + @Override + public JwtBuilder base64UrlEncodeWith(Encoder encoder) { + return b64Url(new ByteBase64UrlStreamEncoder(encoder)); + } + + @Override + public JwtBuilder b64Url(Encoder encoder) { + Assert.notNull(encoder, "encoder cannot be null."); + this.encoder = encoder; return this; } - protected Header ensureHeader() { - if (this.header == null) { - this.header = new DefaultHeader(); - } - return this.header; + @Override + public JwtBuilder encodePayload(boolean b64) { + this.encodePayload = b64; + // clear out any previous values. They will be applied appropriately during compact() + String critParamId = DefaultProtectedHeader.CRIT.getId(); + String b64Id = DefaultJwsHeader.B64.getId(); + Set crit = this.headerBuilder.get(DefaultProtectedHeader.CRIT); + crit = new LinkedHashSet<>(Collections.nullSafe(crit)); + crit.remove(b64Id); + return header().delete(b64Id).add(critParamId, crit).and(); } @Override + public JwtBuilder setHeader(Map map) { + return header().empty().add(map).and(); + } + + @Override + public JwtBuilder setHeaderParams(Map params) { + return header().add(params).and(); + } + + @Override public JwtBuilder setHeaderParam(String name, Object value) { - ensureHeader().put(name, value); - return this; + return header().add(name, value).and(); } + protected static SecureDigestAlgorithm forSigningKey(K key) { + Assert.notNull(key, "Key cannot be null."); + SecureDigestAlgorithm alg = StandardSecureDigestAlgorithms.findBySigningKey(key); + if (alg == null) { + String msg = "Unable to determine a suitable MAC or Signature algorithm for the specified key using " + + "available heuristics: either the key size is too weak be used with available algorithms, or the " + + "key size is unavailable (e.g. if using a PKCS11 or HSM (Hardware Security Module) key store). " + + "If you are using a PKCS11 or HSM keystore, consider using the " + + "JwtBuilder.signWith(Key, SecureDigestAlgorithm) method instead."; + throw new UnsupportedKeyException(msg); + } + return alg; + } + @Override public JwtBuilder signWith(Key key) throws InvalidKeyException { Assert.notNull(key, "Key argument cannot be null."); - SignatureAlgorithm alg = SignatureAlgorithm.forSigningKey(key); + SecureDigestAlgorithm alg = forSigningKey(key); // https://github.com/jwtk/jjwt/issues/381 return signWith(key, alg); } @Override - public JwtBuilder signWith(Key key, SignatureAlgorithm alg) throws InvalidKeyException { + public JwtBuilder signWith(K key, final SecureDigestAlgorithm alg) + throws InvalidKeyException { + Assert.notNull(key, "Key argument cannot be null."); + if (key instanceof PublicKey) { // it's always wrong/insecure to try to create signatures with PublicKeys: + throw new IllegalArgumentException(PUB_KEY_SIGN_MSG); + } + // Implementation note: Ordinarily Passwords should not be used to create secure digests because they usually + // lack the length or entropy necessary for secure cryptographic operations, and are prone to misuse. + // However, we DO NOT prevent them as arguments here (like the above PublicKey check) because + // it is conceivable that a custom SecureDigestAlgorithm implementation would allow Password instances + // so that it might perform its own internal key-derivation logic producing a key that is then used to create a + // secure hash. + // + // Even so, a fallback safety check is that JJWT's only out-of-the-box Password implementation + // (io.jsonwebtoken.impl.security.PasswordSpec) explicitly forbids calls to password.getEncoded() in all + // scenarios to avoid potential misuse, so a digest algorithm implementation would explicitly need to avoid + // this by calling toCharArray() instead. + // + // TLDR; the digest algorithm implementation has the final say whether a password instance is valid + Assert.notNull(alg, "SignatureAlgorithm cannot be null."); - alg.assertValidSigningKey(key); //since 0.10.0 for https://github.com/jwtk/jjwt/issues/334 - this.algorithm = alg; + String id = Assert.hasText(alg.getId(), "SignatureAlgorithm id cannot be null or empty."); + if (Jwts.SIG.NONE.getId().equalsIgnoreCase(id)) { + String msg = "The 'none' JWS algorithm cannot be used to sign JWTs."; + throw new IllegalArgumentException(msg); + } this.key = key; + //noinspection unchecked + this.sigAlg = (SecureDigestAlgorithm) alg; + this.signFunction = Functions.wrap(new Function, byte[]>() { + @Override + public byte[] apply(SecureRequest request) { + return sigAlg.digest(request); + } + }, SignatureException.class, "Unable to compute %s signature.", id); return this; } + @SuppressWarnings({"deprecation", "unchecked"}) // TODO: remove method for 1.0 @Override - public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKeyBytes) throws InvalidKeyException { + public JwtBuilder signWith(Key key, io.jsonwebtoken.SignatureAlgorithm alg) throws InvalidKeyException { Assert.notNull(alg, "SignatureAlgorithm cannot be null."); + alg.assertValidSigningKey(key); //since 0.10.0 for https://github.com/jwtk/jjwt/issues/334 + return signWith(key, (SecureDigestAlgorithm) Jwts.SIG.get().forKey(alg.getValue())); + } + + @SuppressWarnings("deprecation") // TODO: remove method for 1.0 + @Override + public JwtBuilder signWith(io.jsonwebtoken.SignatureAlgorithm alg, byte[] secretKeyBytes) throws InvalidKeyException { + Assert.notNull(alg, "SignatureAlgorithm cannot be null."); Assert.notEmpty(secretKeyBytes, "secret key byte array cannot be null or empty."); - Assert.isTrue(alg.isHmac(), "Key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead."); + Assert.isTrue(alg.isHmac(), "Key bytes may only be specified for HMAC signatures. " + + "If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead."); SecretKey key = new SecretKeySpec(secretKeyBytes, alg.getJcaName()); return signWith(key, alg); } + @SuppressWarnings("deprecation") // TODO: remove method for 1.0 @Override - public JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) throws InvalidKeyException { + public JwtBuilder signWith(io.jsonwebtoken.SignatureAlgorithm alg, String base64EncodedSecretKey) throws InvalidKeyException { Assert.hasText(base64EncodedSecretKey, "base64-encoded secret key cannot be null or empty."); - Assert.isTrue(alg.isHmac(), "Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead."); + Assert.isTrue(alg.isHmac(), "Base64-encoded key bytes may only be specified for HMAC signatures. " + + "If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead."); byte[] bytes = Decoders.BASE64.decode(base64EncodedSecretKey); return signWith(alg, bytes); } + @SuppressWarnings("deprecation") // TODO: remove method for 1.0 @Override - public JwtBuilder signWith(SignatureAlgorithm alg, Key key) { + public JwtBuilder signWith(io.jsonwebtoken.SignatureAlgorithm alg, Key key) { return signWith(key, alg); } @Override - public JwtBuilder compressWith(CompressionCodec compressionCodec) { - Assert.notNull(compressionCodec, "compressionCodec cannot be null"); - this.compressionCodec = compressionCodec; + public JwtBuilder encryptWith(SecretKey key, AeadAlgorithm enc) { + if (key instanceof Password) { + return encryptWith((Password) key, new Pbes2HsAkwAlgorithm(enc.getKeyBitLength()), enc); + } + return encryptWith(key, Jwts.KEY.DIRECT, enc); + } + + @Override + public JwtBuilder encryptWith(final K key, final KeyAlgorithm keyAlg, final AeadAlgorithm enc) { + this.enc = Assert.notNull(enc, "Encryption algorithm cannot be null."); + Assert.hasText(enc.getId(), "Encryption algorithm id cannot be null or empty."); + + Assert.notNull(key, "Encryption key cannot be null."); + if (key instanceof PrivateKey) { + throw new IllegalArgumentException(PRIV_KEY_ENC_MSG); + } + Assert.notNull(keyAlg, "KeyAlgorithm cannot be null."); + final String algId = Assert.hasText(keyAlg.getId(), "KeyAlgorithm id cannot be null or empty."); + + this.key = key; + //noinspection unchecked + this.keyAlg = (KeyAlgorithm) keyAlg; + final KeyAlgorithm alg = this.keyAlg; + + final String cekMsg = "Unable to obtain content encryption key from key management algorithm '%s'."; + this.keyAlgFunction = Functions.wrap(new Function, KeyResult>() { + @Override + public KeyResult apply(KeyRequest request) { + return alg.getEncryptionKey(request); + } + }, SecurityException.class, cekMsg, algId); + return this; } @Override + public JwtBuilder compressWith(CompressionAlgorithm alg) { + Assert.notNull(alg, "CompressionAlgorithm cannot be null"); + Assert.hasText(alg.getId(), "CompressionAlgorithm id cannot be null or empty."); + this.compressionAlgorithm = alg; + // clear out any previous value that might have been there. It'll be added back to match this + // specific algorithm in the compact() method implementation + return header().delete(DefaultHeader.COMPRESSION_ALGORITHM.getId()).and(); + } + + @Override public JwtBuilder setPayload(String payload) { - this.payload = payload; + return content(payload); + } + + @Override + public JwtBuilder content(String content) { + if (Strings.hasText(content)) { + this.payload = new Payload(content, null); + } return this; } - protected Claims ensureClaims() { - if (this.claims == null) { - this.claims = new DefaultClaims(); + @Override + public JwtBuilder content(byte[] content) { + if (!Bytes.isEmpty(content)) { + this.payload = new Payload(content, null); } - return this.claims; + return this; } @Override - public JwtBuilder setClaims(Claims claims) { - this.claims = claims; + public JwtBuilder content(InputStream in) { + if (in != null) { + this.payload = new Payload(in, null); + } return this; } @Override + public JwtBuilder content(byte[] content, String cty) { + Assert.notEmpty(content, "content byte array cannot be null or empty."); + Assert.hasText(cty, "Content Type String cannot be null or empty."); + this.payload = new Payload(content, cty); + // clear out any previous value - it will be set appropriately during compact() + return header().delete(DefaultHeader.CONTENT_TYPE.getId()).and(); + } + + @Override + public JwtBuilder content(String content, String cty) throws IllegalArgumentException { + Assert.hasText(content, "Content string cannot be null or empty."); + Assert.hasText(cty, "ContentType string cannot be null or empty."); + this.payload = new Payload(content, cty); + // clear out any previous value - it will be set appropriately during compact() + return header().delete(DefaultHeader.CONTENT_TYPE.getId()).and(); + } + + @Override + public JwtBuilder content(InputStream in, String cty) throws IllegalArgumentException { + Assert.notNull(in, "Payload InputStream cannot be null."); + Assert.hasText(cty, "ContentType string cannot be null or empty."); + this.payload = new Payload(in, cty); + // clear out any previous value - it will be set appropriately during compact() + return header().delete(DefaultHeader.CONTENT_TYPE.getId()).and(); + } + + @Override public JwtBuilder setClaims(Map claims) { - this.claims = new DefaultClaims(claims); - return this; + Assert.notNull(claims, "Claims map cannot be null."); + return claims().empty().add(claims).and(); } @Override - public JwtBuilder addClaims(Map claims) { - ensureClaims().putAll(claims); - return this; + public JwtBuilder addClaims(Map claims) { + return claims(claims); } @Override + public JwtBuilder claims(Map claims) { + return claims().add(claims).and(); + } + + @Override + public JwtBuilder claim(String name, Object value) { + return claims().add(name, value).and(); + } + + @Override public JwtBuilder setIssuer(String iss) { - if (Strings.hasText(iss)) { - ensureClaims().setIssuer(iss); - } else { - if (this.claims != null) { - claims.setIssuer(iss); - } - } - return this; + return issuer(iss); } @Override + public JwtBuilder issuer(String iss) { + return claims().issuer(iss).and(); + } + + @Override public JwtBuilder setSubject(String sub) { - if (Strings.hasText(sub)) { - ensureClaims().setSubject(sub); - } else { - if (this.claims != null) { - claims.setSubject(sub); - } - } - return this; + return subject(sub); } @Override + public JwtBuilder subject(String sub) { + return claims().subject(sub).and(); + } + + @Override public JwtBuilder setAudience(String aud) { - if (Strings.hasText(aud)) { - ensureClaims().setAudience(aud); - } else { - if (this.claims != null) { - claims.setAudience(aud); - } - } - return this; + //noinspection deprecation + return claims().setAudience(aud).and(); } @Override + public AudienceCollection audience() { + return new DelegateAudienceCollection<>((JwtBuilder) this, claims().audience()); + } + + @Override public JwtBuilder setExpiration(Date exp) { - if (exp != null) { - ensureClaims().setExpiration(exp); - } else { - if (this.claims != null) { - //noinspection ConstantConditions - this.claims.setExpiration(exp); - } - } - return this; + return expiration(exp); } @Override + public JwtBuilder expiration(Date exp) { + return claims().expiration(exp).and(); + } + + @Override public JwtBuilder setNotBefore(Date nbf) { - if (nbf != null) { - ensureClaims().setNotBefore(nbf); - } else { - if (this.claims != null) { - //noinspection ConstantConditions - this.claims.setNotBefore(nbf); - } - } - return this; + return notBefore(nbf); } @Override + public JwtBuilder notBefore(Date nbf) { + return claims().notBefore(nbf).and(); + } + + @Override public JwtBuilder setIssuedAt(Date iat) { - if (iat != null) { - ensureClaims().setIssuedAt(iat); - } else { - if (this.claims != null) { - //noinspection ConstantConditions - this.claims.setIssuedAt(iat); - } - } - return this; + return issuedAt(iat); } @Override + public JwtBuilder issuedAt(Date iat) { + return claims().issuedAt(iat).and(); + } + + @Override public JwtBuilder setId(String jti) { - if (Strings.hasText(jti)) { - ensureClaims().setId(jti); - } else { - if (this.claims != null) { - claims.setId(jti); - } - } - return this; + return id(jti); } @Override - public JwtBuilder claim(String name, Object value) { - Assert.hasText(name, "Claim property name cannot be null or empty."); - if (this.claims == null) { - if (value != null) { - ensureClaims().put(name, value); - } - } else { - if (value == null) { - this.claims.remove(name); - } else { - this.claims.put(name, value); - } - } + public JwtBuilder id(String jti) { + return claims().id(jti).and(); + } - return this; + private void assertPayloadEncoding(String type) { + if (!this.encodePayload) { + String msg = "Payload encoding may not be disabled for " + type + "s, only JWSs."; + throw new IllegalArgumentException(msg); + } } @Override public String compact() { - if (this.serializer == null) { - // try to find one based on the services available - // TODO: This util class will throw a UnavailableImplementationException here to retain behavior of previous version, remove in v1.0 - // use the previous commented out line instead - this.serializer = LegacyServices.loadFirst(Serializer.class); + final boolean jwe = this.enc != null; + + if (jwe && signFunction != null) { + String msg = "Both 'signWith' and 'encryptWith' cannot be specified. Choose either one."; + throw new IllegalStateException(msg); } - if (payload == null && Collections.isEmpty(claims)) { - payload = ""; + Payload payload = Assert.stateNotNull(this.payload, "Payload instance null, internal error"); + final Claims claims = this.claimsBuilder.build(); + + if (jwe && payload.isEmpty() && Collections.isEmpty(claims)) { // JWE payload can never be empty: + String msg = "Encrypted JWTs must have either 'claims' or non-empty 'content'."; + throw new IllegalStateException(msg); + } // otherwise JWS and Unprotected JWT payloads can be empty + + if (!payload.isEmpty() && !Collections.isEmpty(claims)) { + throw new IllegalStateException("Both 'content' and 'claims' cannot be specified. Choose either one."); } - if (payload != null && !Collections.isEmpty(claims)) { - throw new IllegalStateException("Both 'payload' and 'claims' cannot both be specified. Choose either one."); + if (this.serializer == null) { // try to find one based on the services available + //noinspection unchecked + json(Services.get(Serializer.class)); } - Header header = ensureHeader(); + if (!Collections.isEmpty(claims)) { // normalize so we have one object to deal with: + payload = new Payload(claims); + } + if (compressionAlgorithm != null && !payload.isEmpty()) { + payload.setZip(compressionAlgorithm); + this.headerBuilder.put(DefaultHeader.COMPRESSION_ALGORITHM.getId(), compressionAlgorithm.getId()); + } - JwsHeader jwsHeader; - if (header instanceof JwsHeader) { - jwsHeader = (JwsHeader) header; + if (Strings.hasText(payload.getContentType())) { + // We retain the value from the content* calls to prevent accidental removal from + // header().empty() or header().delete calls + this.headerBuilder.contentType(payload.getContentType()); + } + + Provider keyProvider = ProviderKey.getProvider(this.key, this.provider); + Key key = ProviderKey.getKey(this.key); + if (jwe) { + return encrypt(payload, key, keyProvider); + } else if (key != null) { + return sign(payload, key, keyProvider); } else { - //noinspection unchecked - jwsHeader = new DefaultJwsHeader(header); + return unprotected(payload); } + } - if (key != null) { - jwsHeader.setAlgorithm(algorithm.getValue()); + // automatically closes the OutputStream + private void writeAndClose(String name, Map map, OutputStream out) { + try { + Serializer> named = new NamedSerializer(name, this.serializer); + named.serialize(map, out); + } finally { + Objects.nullSafeClose(out); + } + } + + private void writeAndClose(String name, final Payload payload, OutputStream out) { + out = payload.compress(out); // compression if necessary + if (payload.isClaims()) { + writeAndClose(name, payload.getRequiredClaims(), out); } else { - //no signature - plaintext JWT: - jwsHeader.setAlgorithm(SignatureAlgorithm.NONE.getValue()); + try { + InputStream in = payload.toInputStream(); + Streams.copy(in, out, new byte[4096], "Unable to copy payload."); + } finally { + Objects.nullSafeClose(out); + } } + } - if (compressionCodec != null) { - jwsHeader.setCompressionAlgorithm(compressionCodec.getAlgorithmName()); + private String sign(final Payload payload, final Key key, final Provider provider) { + + Assert.stateNotNull(key, "Key is required."); // set by signWithWith* + Assert.stateNotNull(sigAlg, "SignatureAlgorithm is required."); // invariant + Assert.stateNotNull(signFunction, "Signature Algorithm function cannot be null."); + Assert.stateNotNull(payload, "Payload argument cannot be null."); + + final ByteArrayOutputStream jws = new ByteArrayOutputStream(4096); + + // ----- header ----- + this.headerBuilder.add(DefaultHeader.ALGORITHM.getId(), sigAlg.getId()); + if (!this.encodePayload) { // b64 extension: + String id = DefaultJwsHeader.B64.getId(); + this.headerBuilder.critical().add(id).and().add(id, false); } + final JwsHeader header = Assert.isInstanceOf(JwsHeader.class, this.headerBuilder.build()); + encodeAndWrite("JWS Protected Header", header, jws); - String base64UrlEncodedHeader = base64UrlEncode(jwsHeader, "Unable to serialize header to json."); + // ----- separator ----- + jws.write(DefaultJwtParser.SEPARATOR_CHAR); - byte[] bytes; - try { - bytes = this.payload != null ? payload.getBytes(Strings.UTF_8) : toJson(claims); - } catch (SerializationException e) { - throw new IllegalArgumentException("Unable to serialize claims object to json: " + e.getMessage(), e); + // ----- payload ----- + // Logic defined by table in https://datatracker.ietf.org/doc/html/rfc7797#section-3 : + InputStream signingInput; + InputStream payloadStream = null; // not needed unless b64 is enabled + if (this.encodePayload) { + encodeAndWrite("JWS Payload", payload, jws); + signingInput = Streams.of(jws.toByteArray()); + } else { // b64 + + // First, ensure we have the base64url header bytes + the SEPARATOR_CHAR byte: + InputStream prefixStream = Streams.of(jws.toByteArray()); + + // Next, b64 extension requires the raw (non-encoded) payload to be included directly in the signing input, + // so we ensure we have an input stream for that: + if (payload.isClaims() || payload.isCompressed()) { + ByteArrayOutputStream claimsOut = new ByteArrayOutputStream(8192); + writeAndClose("JWS Unencoded Payload", payload, claimsOut); + payloadStream = Streams.of(claimsOut.toByteArray()); + } else { + // No claims and not compressed, so just get the direct InputStream: + payloadStream = Assert.stateNotNull(payload.toInputStream(), "Payload InputStream cannot be null."); + } + if (!payload.isClaims()) { + payloadStream = new CountingInputStream(payloadStream); // we'll need to assert if it's empty later + } + if (payloadStream.markSupported()) { + payloadStream.mark(0); // to rewind + } + + // (base64url header bytes + separator char) + raw payload bytes: + // and don't let the SequenceInputStream close the payloadStream in case reset is needed: + signingInput = new SequenceInputStream(prefixStream, new UncloseableInputStream(payloadStream)); } - if (compressionCodec != null) { - bytes = compressionCodec.compress(bytes); + byte[] signature; + try { + SecureRequest request = new DefaultSecureRequest<>(signingInput, provider, secureRandom, key); + signature = signFunction.apply(request); + + // now that we've calculated the signature, if using the b64 extension, and the payload is + // attached ('non-detached'), we need to include it in the jws before the signature token. + // (Note that if encodePayload is true, the payload has already been written to jws at this point, so + // we only need to write if encodePayload is false and the payload is attached): + if (!this.encodePayload) { + if (!payload.isCompressed() // don't print raw compressed bytes + && (payload.isClaims() || payload.isString())) { + // now add the payload to the jws output: + Streams.copy(payloadStream, jws, new byte[8192], "Unable to copy attached Payload InputStream."); + } + if (payloadStream instanceof CountingInputStream && ((CountingInputStream) payloadStream).getCount() <= 0) { + String msg = "'b64' Unencoded payload option has been specified, but payload is empty."; + throw new IllegalStateException(msg); + } + } + } finally { + Streams.reset(payloadStream); } - String base64UrlEncodedBody = base64UrlEncoder.encode(bytes); + // ----- separator ----- + jws.write(DefaultJwtParser.SEPARATOR_CHAR); - String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedBody; + // ----- signature ----- + encodeAndWrite("JWS Signature", signature, jws); - if (key != null) { //jwt must be signed: + return Strings.utf8(jws.toByteArray()); + } - JwtSigner signer = createSigner(algorithm, key); + private String unprotected(final Payload content) { - String base64UrlSignature = signer.sign(jwt); + Assert.stateNotNull(content, "Content argument cannot be null."); + assertPayloadEncoding("unprotected JWT"); - jwt += JwtParser.SEPARATOR_CHAR + base64UrlSignature; + this.headerBuilder.add(DefaultHeader.ALGORITHM.getId(), Jwts.SIG.NONE.getId()); + + final ByteArrayOutputStream jwt = new ByteArrayOutputStream(512); + + // ----- header ----- + final Header header = this.headerBuilder.build(); + encodeAndWrite("JWT Header", header, jwt); + + // ----- separator ----- + jwt.write(DefaultJwtParser.SEPARATOR_CHAR); + + // ----- payload ----- + encodeAndWrite("JWT Payload", content, jwt); + + // ----- period terminator ----- + jwt.write(DefaultJwtParser.SEPARATOR_CHAR); // https://www.rfc-editor.org/rfc/rfc7519#section-6.1 + + return Strings.ascii(jwt.toByteArray()); + } + + private void encrypt(final AeadRequest req, final AeadResult res) throws SecurityException { + Function fn = Functions.wrap(new Function() { + @Override + public Object apply(Object o) { + enc.encrypt(req, res); + return null; + } + }, SecurityException.class, "%s encryption failed.", enc.getId()); + fn.apply(null); + } + + private String encrypt(final Payload content, final Key key, final Provider keyProvider) { + + Assert.stateNotNull(content, "Payload argument cannot be null."); + Assert.stateNotNull(key, "Key is required."); // set by encryptWith* + Assert.stateNotNull(enc, "Encryption algorithm is required."); // set by encryptWith* + Assert.stateNotNull(keyAlg, "KeyAlgorithm is required."); //set by encryptWith* + Assert.stateNotNull(keyAlgFunction, "KeyAlgorithm function cannot be null."); + assertPayloadEncoding("JWE"); + + InputStream plaintext; + if (content.isClaims()) { + ByteArrayOutputStream out = new ByteArrayOutputStream(4096); + writeAndClose("JWE Claims", content, out); + plaintext = Streams.of(out.toByteArray()); } else { - // no signature (plaintext), but must terminate w/ a period, see - // https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-6.1 - jwt += JwtParser.SEPARATOR_CHAR; + plaintext = content.toInputStream(); } - return jwt; + //only expose (mutable) JweHeader functionality to KeyAlgorithm instances, not the full headerBuilder + // (which exposes this JwtBuilder and shouldn't be referenced by KeyAlgorithms): + JweHeader delegate = new DefaultMutableJweHeader(this.headerBuilder); + KeyRequest keyRequest = new DefaultKeyRequest<>(key, keyProvider, this.secureRandom, delegate, enc); + KeyResult keyResult = keyAlgFunction.apply(keyRequest); + Assert.stateNotNull(keyResult, "KeyAlgorithm must return a KeyResult."); + + SecretKey cek = Assert.notNull(keyResult.getKey(), "KeyResult must return a content encryption key."); + byte[] encryptedCek = Assert.notNull(keyResult.getPayload(), + "KeyResult must return an encrypted key byte array, even if empty."); + + this.headerBuilder.add(DefaultHeader.ALGORITHM.getId(), keyAlg.getId()); + this.headerBuilder.put(DefaultJweHeader.ENCRYPTION_ALGORITHM.getId(), enc.getId()); + + final JweHeader header = Assert.isInstanceOf(JweHeader.class, this.headerBuilder.build(), + "Invalid header created: "); + + // ----- header ----- + ByteArrayOutputStream jwe = new ByteArrayOutputStream(8192); + encodeAndWrite("JWE Protected Header", header, jwe); + + // JWE RFC requires AAD to be the ASCII bytes of the Base64URL-encoded header. Since the header bytes are + // already Base64URL-encoded at this point (via the encoder.wrap call just above), and Base64Url-encoding uses + // only ASCII characters, we don't need to use StandardCharsets.US_ASCII to explicitly convert here - just + // use the already-encoded (ascii) bytes: + InputStream aad = Streams.of(jwe.toByteArray()); + + // During encryption, the configured Provider applies to the KeyAlgorithm, not the AeadAlgorithm, mostly + // because all JVMs support the standard AeadAlgorithms (especially with BouncyCastle in the classpath). + // As such, the provider here is intentionally omitted (null): + // TODO: add encProvider(Provider) builder method that applies to this request only? + ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream(8192); + AeadRequest req = new DefaultAeadRequest(plaintext, null, secureRandom, cek, aad); + DefaultAeadResult res = new DefaultAeadResult(ciphertextOut); + encrypt(req, res); + + byte[] iv = Assert.notEmpty(res.getIv(), "Encryption result must have a non-empty initialization vector."); + byte[] tag = Assert.notEmpty(res.getDigest(), "Encryption result must have a non-empty authentication tag."); + byte[] ciphertext = Assert.notEmpty(ciphertextOut.toByteArray(), "Encryption result must have non-empty ciphertext."); + + jwe.write(DefaultJwtParser.SEPARATOR_CHAR); + encodeAndWrite("JWE Encrypted CEK", encryptedCek, jwe); + + jwe.write(DefaultJwtParser.SEPARATOR_CHAR); + encodeAndWrite("JWE Initialization Vector", iv, jwe); + + jwe.write(DefaultJwtParser.SEPARATOR_CHAR); + encodeAndWrite("JWE Ciphertext", ciphertext, jwe); + + jwe.write(DefaultJwtParser.SEPARATOR_CHAR); + encodeAndWrite("JWE AAD Tag", tag, jwe); + + return Strings.utf8(jwe.toByteArray()); } - /* - * @since 0.5 mostly to allow testing overrides - */ - protected JwtSigner createSigner(SignatureAlgorithm alg, Key key) { - return new DefaultJwtSigner(alg, key, base64UrlEncoder); + private static class DefaultBuilderClaims extends DelegatingClaimsMutator implements BuilderClaims { + + private final JwtBuilder builder; + + private DefaultBuilderClaims(JwtBuilder builder) { + super(); + this.builder = builder; + } + + @Override + public JwtBuilder and() { + return this.builder; + } + + private io.jsonwebtoken.Claims build() { + return new DefaultClaims(this.DELEGATE); + } } - @Deprecated // remove before 1.0 - call the serializer and base64UrlEncoder directly - protected String base64UrlEncode(Object o, String errMsg) { - Assert.isInstanceOf(Map.class, o, "object argument must be a map."); - Map m = (Map)o; - byte[] bytes; - try { - bytes = toJson(m); - } catch (SerializationException e) { - throw new IllegalStateException(errMsg, e); + private static class DefaultBuilderHeader extends DefaultJweHeaderBuilder implements BuilderHeader { + + private final JwtBuilder builder; + + private DefaultBuilderHeader(JwtBuilder builder) { + super(); + this.builder = Assert.notNull(builder, "JwtBuilder cannot be null."); } - return base64UrlEncoder.encode(bytes); + @Override + public JwtBuilder and() { + return builder; + } + + @SuppressWarnings("SameParameterValue") + private T get(Parameter param) { + return this.DELEGATE.get(param); + } + + private Header build() { + return new DefaultJwtHeaderBuilder(this).build(); + } } - @SuppressWarnings("unchecked") - @Deprecated //remove before 1.0 - call the serializer directly - protected byte[] toJson(Object object) throws SerializationException { - Assert.isInstanceOf(Map.class, object, "object argument must be a map."); - Map m = (Map)object; - return serializer.serialize(m); + private OutputStream encode(OutputStream out, String name) { + out = this.encoder.encode(out); + return new EncodingOutputStream(out, "base64url", name); } + + private void encodeAndWrite(String name, Map map, OutputStream out) { + out = encode(out, name); + writeAndClose(name, map, out); + } + + private void encodeAndWrite(String name, Payload payload, OutputStream out) { + out = encode(out, name); + writeAndClose(name, payload, out); + } + + private void encodeAndWrite(String name, byte[] data, OutputStream out) { + out = encode(out, name); + Streams.writeAndClose(out, data, "Unable to write bytes"); + } + } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwtHeaderBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwtHeaderBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwtHeaderBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2021 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.Header; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.lang.Collections; + +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * @since 0.12.0 + */ +public class DefaultJwtHeaderBuilder extends DefaultJweHeaderBuilder implements Jwts.HeaderBuilder { + + public DefaultJwtHeaderBuilder() { + super(); + } + + public DefaultJwtHeaderBuilder(DefaultJweHeaderMutator src) { + super(src); + } + + // Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11 and + // https://datatracker.ietf.org/doc/html/rfc7516#section-4.1.13, 'crit' values MUST NOT include: + // + // 1. Any header parameter names defined in the JWS or JWE specifications + // 2. Any header parameter names that are not included in the final header + private static ParameterMap sanitizeCrit(ParameterMap m, boolean protectedHeader) { + Set crit = m.get(DefaultProtectedHeader.CRIT); + if (crit == null) return m; // nothing to do + + //Use a copy constructor to ensure subsequent changes to builder state do not change the constructed header: + m = new ParameterMap(DefaultJweHeader.PARAMS, m, true); + m.remove(DefaultProtectedHeader.CRIT.getId()); // remove the unsanitized value + + // Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11, non-protected headers are not allowed to + // have a 'crit' header parameter, so we're done, exit early: + if (!protectedHeader) return m; + + //otherwise we have a protected header (JWS or JWE), so remove unnecessary entries per the RFC sections above: + Set newCrit = new LinkedHashSet<>(crit); + for (String val : crit) { + if (DefaultJweHeader.PARAMS.containsKey(val) || // Defined in JWS or JWE spec, can't be in crit set (#1) + !m.containsKey(val)) { // not in the actual header, can't be in crit set either (#2) + newCrit.remove(val); + } + } + if (!Collections.isEmpty(newCrit)) { // we have a sanitized result per the RFC, so apply it: + m.put(DefaultProtectedHeader.CRIT, newCrit); + } + return m; + } + + @Override + public Header build() { + + this.x509.apply(); // apply any X.509 values as necessary based on builder state + + ParameterMap m = this.DELEGATE; + + // Note: conditional sequence matters here: JWE has more specific requirements than JWS, so check that first: + if (DefaultJweHeader.isCandidate(m)) { + return new DefaultJweHeader(sanitizeCrit(m, true)); + } else if (DefaultProtectedHeader.isCandidate(m)) { + return new DefaultJwsHeader(sanitizeCrit(m, true)); + } else { + // Per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11, 'crit' header is not allowed in + // non-protected headers: + return new DefaultHeader(sanitizeCrit(m, false)); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwtParser.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwtParser.java (.../DefaultJwtParser.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwtParser.java (.../DefaultJwtParser.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,412 +15,646 @@ */ package io.jsonwebtoken.impl; -import io.jsonwebtoken.ClaimJwtException; import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ClaimsBuilder; import io.jsonwebtoken.Clock; -import io.jsonwebtoken.CompressionCodec; import io.jsonwebtoken.CompressionCodecResolver; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Header; import io.jsonwebtoken.IncorrectClaimException; -import io.jsonwebtoken.InvalidClaimException; +import io.jsonwebtoken.Jwe; +import io.jsonwebtoken.JweHeader; import io.jsonwebtoken.Jws; import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.Jwt; +import io.jsonwebtoken.JwtException; import io.jsonwebtoken.JwtHandler; -import io.jsonwebtoken.JwtHandlerAdapter; import io.jsonwebtoken.JwtParser; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.Locator; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.MissingClaimException; import io.jsonwebtoken.PrematureJwtException; -import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.ProtectedHeader; import io.jsonwebtoken.SigningKeyResolver; import io.jsonwebtoken.UnsupportedJwtException; -import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver; -import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator; -import io.jsonwebtoken.impl.crypto.JwtSignatureValidator; -import io.jsonwebtoken.impl.lang.LegacyServices; +import io.jsonwebtoken.impl.io.AbstractParser; +import io.jsonwebtoken.impl.io.BytesInputStream; +import io.jsonwebtoken.impl.io.CharSequenceReader; +import io.jsonwebtoken.impl.io.JsonObjectDeserializer; +import io.jsonwebtoken.impl.io.Streams; +import io.jsonwebtoken.impl.io.UncloseableInputStream; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.Function; +import io.jsonwebtoken.impl.lang.RedactedSupplier; +import io.jsonwebtoken.impl.security.DefaultDecryptAeadRequest; +import io.jsonwebtoken.impl.security.DefaultDecryptionKeyRequest; +import io.jsonwebtoken.impl.security.DefaultVerifySecureDigestRequest; +import io.jsonwebtoken.impl.security.LocatingKeyResolver; +import io.jsonwebtoken.impl.security.ProviderKey; +import io.jsonwebtoken.io.CompressionAlgorithm; import io.jsonwebtoken.io.Decoder; -import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.DeserializationException; import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.DateFormats; import io.jsonwebtoken.lang.Objects; +import io.jsonwebtoken.lang.Registry; import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.DecryptAeadRequest; +import io.jsonwebtoken.security.DecryptionKeyRequest; import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.SecureDigestAlgorithm; import io.jsonwebtoken.security.SignatureException; +import io.jsonwebtoken.security.VerifySecureDigestRequest; import io.jsonwebtoken.security.WeakKeyException; -import javax.crypto.spec.SecretKeySpec; +import javax.crypto.SecretKey; +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.Reader; +import java.io.SequenceInputStream; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; import java.security.Key; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.util.Collection; import java.util.Date; +import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; @SuppressWarnings("unchecked") -public class DefaultJwtParser implements JwtParser { +public class DefaultJwtParser extends AbstractParser> implements JwtParser { - private static final int MILLISECONDS_PER_SECOND = 1000; + static final char SEPARATOR_CHAR = '.'; - // TODO: make the folling fields final for v1.0 - private byte[] keyBytes; + private static final JwtTokenizer jwtTokenizer = new JwtTokenizer(); - private Key key; + static final String PRIV_KEY_VERIFY_MSG = "PrivateKeys may not be used to verify digital signatures. " + + "PrivateKeys are used to sign, and PublicKeys are used to verify."; - private SigningKeyResolver signingKeyResolver; + static final String PUB_KEY_DECRYPT_MSG = "PublicKeys may not be used to decrypt data. PublicKeys are " + + "used to encrypt, and PrivateKeys are used to decrypt."; - private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver(); + public static final String INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was: %s."; - private Decoder base64UrlDecoder = Decoders.BASE64URL; + public static final String MISSING_EXPECTED_CLAIM_VALUE_MESSAGE_TEMPLATE = + "Missing expected '%s' value in '%s' claim %s."; - private Deserializer> deserializer; + public static final String MISSING_JWS_ALG_MSG = "JWS header does not contain a required 'alg' (Algorithm) " + + "header parameter. This header parameter is mandatory per the JWS Specification, Section 4.1.1. See " + + "https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.1 for more information."; - private Claims expectedClaims = new DefaultClaims(); + public static final String MISSING_JWE_ALG_MSG = "JWE header does not contain a required 'alg' (Algorithm) " + + "header parameter. This header parameter is mandatory per the JWE Specification, Section 4.1.1. See " + + "https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.1 for more information."; - private Clock clock = DefaultClock.INSTANCE; + public static final String MISSING_JWS_DIGEST_MSG_FMT = "The JWS header references signature algorithm '%s' but " + + "the compact JWE string is missing the required signature."; - private long allowedClockSkewMillis = 0; + public static final String MISSING_JWE_DIGEST_MSG_FMT = "The JWE header references key management algorithm '%s' " + + "but the compact JWE string is missing the required AAD authentication tag."; - /** - * TODO: remove this constructor before 1.0 - * @deprecated for backward compatibility only, see other constructors. - */ - @Deprecated - public DefaultJwtParser() { } + private static final String MISSING_ENC_MSG = "JWE header does not contain a required 'enc' (Encryption " + + "Algorithm) header parameter. This header parameter is mandatory per the JWE Specification, " + + "Section 4.1.2. See https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.2 for more information."; - DefaultJwtParser(SigningKeyResolver signingKeyResolver, - Key key, - byte[] keyBytes, - Clock clock, - long allowedClockSkewMillis, - Claims expectedClaims, - Decoder base64UrlDecoder, - Deserializer> deserializer, - CompressionCodecResolver compressionCodecResolver) { - this.signingKeyResolver = signingKeyResolver; - this.key = key; - this.keyBytes = keyBytes; - this.clock = clock; - this.allowedClockSkewMillis = allowedClockSkewMillis; - this.expectedClaims = expectedClaims; - this.base64UrlDecoder = base64UrlDecoder; - this.deserializer = deserializer; - this.compressionCodecResolver = compressionCodecResolver; - } + private static final String UNSECURED_DISABLED_MSG_PREFIX = "Unsecured JWSs (those with an " + + DefaultHeader.ALGORITHM + " header value of '" + Jwts.SIG.NONE.getId() + "') are disallowed by " + + "default as mandated by https://www.rfc-editor.org/rfc/rfc7518.html#section-3.6. If you wish to " + + "allow them to be parsed, call the JwtParserBuilder.unsecured() method, but please read the " + + "security considerations covered in that method's JavaDoc before doing so. Header: "; - @Override - public JwtParser deserializeJsonWith(Deserializer> deserializer) { - Assert.notNull(deserializer, "deserializer cannot be null."); - this.deserializer = deserializer; - return this; - } + private static final String CRIT_UNSECURED_MSG = "Unsecured JWSs (those with an " + DefaultHeader.ALGORITHM + + " header value of '" + Jwts.SIG.NONE.getId() + "') may not use the " + DefaultProtectedHeader.CRIT + + " header parameter per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11 (\"the [crit] Header " + + "Parameter MUST be integrity protected; therefore, it MUST occur only within [a] JWS Protected Header)\"." + + " Header: %s"; - @Override - public JwtParser base64UrlDecodeWith(Decoder base64UrlDecoder) { - Assert.notNull(base64UrlDecoder, "base64UrlDecoder cannot be null."); - this.base64UrlDecoder = base64UrlDecoder; - return this; - } + private static final String CRIT_MISSING_MSG = "Protected Header " + + DefaultProtectedHeader.CRIT + " set references header name '%s', but the header does not contain an " + + "associated '%s' header parameter as required by " + + "https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11. Header: %s"; - @Override - public JwtParser requireIssuedAt(Date issuedAt) { - expectedClaims.setIssuedAt(issuedAt); - return this; - } + private static final String CRIT_UNSUPPORTED_MSG = "Protected Header " + DefaultProtectedHeader.CRIT + + " set references unsupported header name '%s'. Application developers expecting to support a JWT " + + "extension using header '%s' in their application code must indicate it " + + "is supported by using the JwtParserBuilder.critical method. Header: %s"; - @Override - public JwtParser requireIssuer(String issuer) { - expectedClaims.setIssuer(issuer); - return this; - } + private static final String JWE_NONE_MSG = "JWEs do not support key management " + DefaultHeader.ALGORITHM + + " header value '" + Jwts.SIG.NONE.getId() + "' per " + + "https://www.rfc-editor.org/rfc/rfc7518.html#section-4.1"; - @Override - public JwtParser requireAudience(String audience) { - expectedClaims.setAudience(audience); - return this; - } + private static final String JWS_NONE_SIG_MISMATCH_MSG = "The JWS header references signature algorithm '" + + Jwts.SIG.NONE.getId() + "' yet the compact JWS string contains a signature. This is not permitted " + + "per https://tools.ietf.org/html/rfc7518#section-3.6."; - @Override - public JwtParser requireSubject(String subject) { - expectedClaims.setSubject(subject); - return this; - } + private static final String B64_MISSING_PAYLOAD = "Unable to verify JWS signature: the parser has encountered an " + + "Unencoded Payload JWS with detached payload, but the detached payload value required for signature " + + "verification has not been provided. If you expect to receive and parse Unencoded Payload JWSs in your " + + "application, the overloaded JwtParser.parseSignedContent or JwtParser.parseSignedClaims methods that " + + "accept a byte[] or InputStream must be used for these kinds of JWSs. Header: %s"; - @Override - public JwtParser requireId(String id) { - expectedClaims.setId(id); - return this; - } + private static final String B64_DECOMPRESSION_MSG = "The JWT header references compression algorithm " + + "'%s', but payload decompression for Unencoded JWSs (those with an " + DefaultJwsHeader.B64 + + " header value of false) that rely on a SigningKeyResolver are disallowed " + + "by default to protect against [Denial of Service attacks](" + + "https://www.usenix.org/system/files/conference/usenixsecurity15/sec15-paper-pellegrino.pdf). If you " + + "wish to enable Unencoded JWS payload decompression, configure the JwtParserBuilder." + + "keyLocator(Locator) and do not configure a SigningKeyResolver."; - @Override - public JwtParser requireExpiration(Date expiration) { - expectedClaims.setExpiration(expiration); - return this; - } + private static final String UNPROTECTED_DECOMPRESSION_MSG = "The JWT header references compression algorithm " + + "'%s', but payload decompression for Unprotected JWTs (those with an " + DefaultHeader.ALGORITHM + + " header value of '" + Jwts.SIG.NONE.getId() + "') or Unencoded JWSs (those with a " + + DefaultJwsHeader.B64 + " header value of false) that also rely on a SigningKeyResolver are disallowed " + + "by default to protect against [Denial of Service attacks](" + + "https://www.usenix.org/system/files/conference/usenixsecurity15/sec15-paper-pellegrino.pdf). If you " + + "wish to enable Unsecure JWS or Unencoded JWS payload decompression, call the JwtParserBuilder." + + "unsecuredDecompression() method, but please read the security considerations covered in that " + + "method's JavaDoc before doing so."; - @Override - public JwtParser requireNotBefore(Date notBefore) { - expectedClaims.setNotBefore(notBefore); - return this; - } + private final Provider provider; - @Override - public JwtParser require(String claimName, Object value) { - Assert.hasText(claimName, "claim name cannot be null or empty."); - Assert.notNull(value, "The value cannot be null for claim name: " + claimName); - expectedClaims.put(claimName, value); - return this; - } + @SuppressWarnings("deprecation") + private final SigningKeyResolver signingKeyResolver; - @Override - public JwtParser setClock(Clock clock) { - Assert.notNull(clock, "Clock instance cannot be null."); - this.clock = clock; - return this; - } + private final boolean unsecured; - @Override - public JwtParser setAllowedClockSkewSeconds(long seconds) throws IllegalArgumentException { - Assert.isTrue(seconds <= DefaultJwtParserBuilder.MAX_CLOCK_SKEW_MILLIS, DefaultJwtParserBuilder.MAX_CLOCK_SKEW_ILLEGAL_MSG); - this.allowedClockSkewMillis = Math.max(0, seconds * MILLISECONDS_PER_SECOND); - return this; - } + private final boolean unsecuredDecompression; - @Override - public JwtParser setSigningKey(byte[] key) { - Assert.notEmpty(key, "signing key cannot be null or empty."); - this.keyBytes = key; - return this; - } + private final Function> sigAlgs; - @Override - public JwtParser setSigningKey(String base64EncodedSecretKey) { - Assert.hasText(base64EncodedSecretKey, "signing key cannot be null or empty."); - this.keyBytes = Decoders.BASE64.decode(base64EncodedSecretKey); - return this; - } + private final Function encAlgs; - @Override - public JwtParser setSigningKey(Key key) { - Assert.notNull(key, "signing key cannot be null."); - this.key = key; - return this; - } + private final Function> keyAlgs; - @Override - public JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver) { - Assert.notNull(signingKeyResolver, "SigningKeyResolver cannot be null."); - this.signingKeyResolver = signingKeyResolver; - return this; - } + private final Function zipAlgs; - @Override - public JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver) { - Assert.notNull(compressionCodecResolver, "compressionCodecResolver cannot be null."); - this.compressionCodecResolver = compressionCodecResolver; - return this; - } + private final Locator keyLocator; - @Override - public boolean isSigned(String jwt) { + private final Decoder decoder; - if (jwt == null) { - return false; - } + private final Deserializer> deserializer; - int delimiterCount = 0; + private final ClaimsBuilder expectedClaims; - for (int i = 0; i < jwt.length(); i++) { - char c = jwt.charAt(i); + private final Clock clock; - if (delimiterCount == 2) { - return !Character.isWhitespace(c) && c != SEPARATOR_CHAR; - } + private final Set critical; - if (c == SEPARATOR_CHAR) { - delimiterCount++; - } - } + private final long allowedClockSkewMillis; - return false; + //SigningKeyResolver will be removed for 1.0: + @SuppressWarnings("deprecation") + DefaultJwtParser(Provider provider, + SigningKeyResolver signingKeyResolver, + boolean unsecured, + boolean unsecuredDecompression, + Locator keyLocator, + Clock clock, + Set critical, + long allowedClockSkewMillis, + DefaultClaims expectedClaims, + Decoder base64UrlDecoder, + Deserializer> deserializer, + CompressionCodecResolver compressionCodecResolver, + Registry zipAlgs, + Registry> sigAlgs, + Registry> keyAlgs, + Registry encAlgs) { + this.provider = provider; + this.unsecured = unsecured; + this.unsecuredDecompression = unsecuredDecompression; + this.signingKeyResolver = signingKeyResolver; + this.keyLocator = Assert.notNull(keyLocator, "Key Locator cannot be null."); + this.clock = Assert.notNull(clock, "Clock cannot be null."); + this.critical = Collections.nullSafe(critical); + this.allowedClockSkewMillis = allowedClockSkewMillis; + this.expectedClaims = Jwts.claims().add(expectedClaims); + this.decoder = Assert.notNull(base64UrlDecoder, "base64UrlDecoder 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.keyAlgs = new IdLocator<>(DefaultHeader.ALGORITHM, keyAlgs, MISSING_JWE_ALG_MSG); + this.encAlgs = new IdLocator<>(DefaultJweHeader.ENCRYPTION_ALGORITHM, encAlgs, MISSING_ENC_MSG); + this.zipAlgs = compressionCodecResolver != null ? new CompressionCodecLocator(compressionCodecResolver) : + new IdLocator<>(DefaultHeader.COMPRESSION_ALGORITHM, zipAlgs, null); } @Override - public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException { - - // TODO, this logic is only need for a now deprecated code path - // remove this block in v1.0 (the equivalent is already in DefaultJwtParserBuilder) - if (this.deserializer == null) { - // try to find one based on the services available - // TODO: This util class will throw a UnavailableImplementationException here to retain behavior of previous version, remove in v1.0 - this.deserializer = LegacyServices.loadFirst(Deserializer.class); + public boolean isSigned(CharSequence compact) { + if (!Strings.hasText(compact)) { + return false; } - - Assert.hasText(jwt, "JWT String argument cannot be null or empty."); - - if ("..".equals(jwt)) { - String msg = "JWT string '..' is missing a header."; - throw new MalformedJwtException(msg); + try { + final TokenizedJwt tokenized = jwtTokenizer.tokenize(new CharSequenceReader(compact)); + return !(tokenized instanceof TokenizedJwe) && Strings.hasText(tokenized.getDigest()); + } catch (MalformedJwtException e) { + return false; } + } - String base64UrlEncodedHeader = null; - String base64UrlEncodedPayload = null; - String base64UrlEncodedDigest = null; + private static boolean hasContentType(Header header) { + return header != null && Strings.hasText(header.getContentType()); + } - int delimiterCount = 0; + private void verifySignature(final TokenizedJwt tokenized, final JwsHeader jwsHeader, final String alg, + @SuppressWarnings("deprecation") SigningKeyResolver resolver, Claims claims, Payload payload) { - StringBuilder sb = new StringBuilder(128); + Assert.notNull(resolver, "SigningKeyResolver instance cannot be null."); - for (char c : jwt.toCharArray()) { + SecureDigestAlgorithm algorithm; + try { + algorithm = (SecureDigestAlgorithm) sigAlgs.apply(jwsHeader); + } catch (UnsupportedJwtException e) { + //For backwards compatibility. TODO: remove this try/catch block for 1.0 and let UnsupportedJwtException propagate + String msg = "Unsupported signature algorithm '" + alg + "'"; + throw new SignatureException(msg, e); + } + Assert.stateNotNull(algorithm, "JWS Signature Algorithm cannot be null."); - if (c == SEPARATOR_CHAR) { + //digitally signed, let's assert the signature: + Key key; + if (claims != null) { + key = resolver.resolveSigningKey(jwsHeader, claims); + } else { + key = resolver.resolveSigningKey(jwsHeader, payload.getBytes()); + } + if (key == null) { + String msg = "Cannot verify JWS signature: unable to locate signature verification key for JWS with header: " + jwsHeader; + throw new UnsupportedJwtException(msg); + } + Provider provider = ProviderKey.getProvider(key, this.provider); // extract if necessary + key = ProviderKey.getKey(key); // unwrap if necessary, MUST be called after ProviderKey.getProvider + Assert.stateNotNull(key, "ProviderKey cannot be null."); //ProviderKey impl doesn't allow null + if (key instanceof PrivateKey) { + throw new InvalidKeyException(PRIV_KEY_VERIFY_MSG); + } - CharSequence tokenSeq = Strings.clean(sb); - String token = tokenSeq != null ? tokenSeq.toString() : null; + final byte[] signature = decode(tokenized.getDigest(), "JWS signature"); - if (delimiterCount == 0) { - base64UrlEncodedHeader = token; - } else if (delimiterCount == 1) { - base64UrlEncodedPayload = token; - } + //re-create the jwt part without the signature. This is what is needed for signature verification: + InputStream payloadStream = null; + InputStream verificationInput; + if (jwsHeader.isPayloadEncoded()) { + int len = tokenized.getProtected().length() + 1 + tokenized.getPayload().length(); + CharBuffer cb = CharBuffer.allocate(len); + cb.put(Strings.wrap(tokenized.getProtected())); + cb.put(SEPARATOR_CHAR); + cb.put(Strings.wrap(tokenized.getPayload())); + cb.rewind(); + ByteBuffer bb = StandardCharsets.US_ASCII.encode(cb); + bb.rewind(); + byte[] data = new byte[bb.remaining()]; + bb.get(data); + verificationInput = Streams.of(data); + } else { // b64 extension + ByteBuffer headerBuf = StandardCharsets.US_ASCII.encode(Strings.wrap(tokenized.getProtected())); + headerBuf.rewind(); + ByteBuffer buf = ByteBuffer.allocate(headerBuf.remaining() + 1); + buf.put(headerBuf); + buf.put((byte) SEPARATOR_CHAR); + buf.rewind(); + byte[] data = new byte[buf.remaining()]; + buf.get(data); + InputStream prefixStream = Streams.of(data); + payloadStream = payload.toInputStream(); + // We wrap the payloadStream here in an UncloseableInputStream to prevent the SequenceInputStream from + // closing it since we'll need to rewind/reset it if decompression is enabled + verificationInput = new SequenceInputStream(prefixStream, new UncloseableInputStream(payloadStream)); + } - delimiterCount++; - sb.setLength(0); - } else { - sb.append(c); + try { + VerifySecureDigestRequest request = + new DefaultVerifySecureDigestRequest<>(verificationInput, provider, null, key, signature); + if (!algorithm.verify(request)) { + String msg = "JWT signature does not match locally computed signature. JWT validity cannot be " + + "asserted and should not be trusted."; + throw new SignatureException(msg); } + } catch (WeakKeyException e) { + throw e; + } catch (InvalidKeyException | IllegalArgumentException e) { + String algId = algorithm.getId(); + String msg = "The parsed JWT indicates it was signed with the '" + algId + "' signature " + + "algorithm, but the provided " + key.getClass().getName() + " key may " + + "not be used to verify " + algId + " signatures. Because the specified " + + "key reflects a specific and expected algorithm, and the JWT does not reflect " + + "this algorithm, it is likely that the JWT was not expected and therefore should not be " + + "trusted. Another possibility is that the parser was provided the incorrect " + + "signature verification key, but this cannot be assumed for security reasons."; + throw new UnsupportedJwtException(msg, e); + } finally { + Streams.reset(payloadStream); } + } - if (delimiterCount != 2) { - String msg = "JWT strings must contain exactly 2 period characters. Found: " + delimiterCount; + @Override + public Jwt parse(Reader reader) { + Assert.notNull(reader, "Reader cannot be null."); + return parse(reader, Payload.EMPTY); + } + + private Jwt parse(Reader compact, Payload unencodedPayload) { + + Assert.notNull(compact, "Compact reader cannot be null."); + Assert.stateNotNull(unencodedPayload, "internal error: unencodedPayload is null."); + + final TokenizedJwt tokenized = jwtTokenizer.tokenize(compact); + final CharSequence base64UrlHeader = tokenized.getProtected(); + if (!Strings.hasText(base64UrlHeader)) { + String msg = "Compact JWT strings MUST always have a Base64Url protected header per " + + "https://tools.ietf.org/html/rfc7519#section-7.2 (steps 2-4)."; throw new MalformedJwtException(msg); } - if (sb.length() > 0) { - base64UrlEncodedDigest = sb.toString(); - } - // =============== Header ================= - Header header = null; + final byte[] headerBytes = decode(base64UrlHeader, "protected header"); + Map m = deserialize(Streams.of(headerBytes), "protected header"); + Header header; + try { + header = tokenized.createHeader(m); + } catch (Exception e) { + String msg = "Invalid protected header: " + e.getMessage(); + throw new MalformedJwtException(msg, e); + } - CompressionCodec compressionCodec = null; + // https://tools.ietf.org/html/rfc7515#section-10.7 , second-to-last bullet point, note the use of 'always': + // + // * Require that the "alg" Header Parameter be carried in the JWS + // Protected Header. (This is always the case when using the JWS + // Compact Serialization and is the approach taken by CMS [RFC6211].) + // + final String alg = Strings.clean(header.getAlgorithm()); + if (!Strings.hasText(alg)) { + String msg = tokenized instanceof TokenizedJwe ? MISSING_JWE_ALG_MSG : MISSING_JWS_ALG_MSG; + throw new MalformedJwtException(msg); + } + final boolean unsecured = Jwts.SIG.NONE.getId().equalsIgnoreCase(alg); - if (base64UrlEncodedHeader != null) { - byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedHeader); - String origValue = new String(bytes, Strings.UTF_8); - Map m = (Map) readValue(origValue); - - if (base64UrlEncodedDigest != null) { - header = new DefaultJwsHeader(m); - } else { - header = new DefaultHeader(m); + final CharSequence base64UrlDigest = tokenized.getDigest(); + final boolean hasDigest = Strings.hasText(base64UrlDigest); + if (unsecured) { + if (tokenized instanceof TokenizedJwe) { + throw new MalformedJwtException(JWE_NONE_MSG); } - - compressionCodec = compressionCodecResolver.resolveCompressionCodec(header); + // Unsecured JWTs are disabled by default per the RFC: + if (!this.unsecured) { + String msg = UNSECURED_DISABLED_MSG_PREFIX + header; + throw new UnsupportedJwtException(msg); + } + if (hasDigest) { + throw new MalformedJwtException(JWS_NONE_SIG_MISMATCH_MSG); + } + if (header.containsKey(DefaultProtectedHeader.CRIT.getId())) { + String msg = String.format(CRIT_UNSECURED_MSG, header); + throw new MalformedJwtException(msg); + } + } else if (!hasDigest) { // something other than 'none'. Must have a digest component: + String fmt = tokenized instanceof TokenizedJwe ? MISSING_JWE_DIGEST_MSG_FMT : MISSING_JWS_DIGEST_MSG_FMT; + String msg = String.format(fmt, alg); + throw new MalformedJwtException(msg); } - - // =============== Body ================= - String payload = ""; // https://github.com/jwtk/jjwt/pull/540 - if (base64UrlEncodedPayload != null) { - byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedPayload); - if (compressionCodec != null) { - bytes = compressionCodec.decompress(bytes); + // ----- crit assertions ----- + if (header instanceof ProtectedHeader) { + Set crit = Collections.nullSafe(((ProtectedHeader) header).getCritical()); + Set supportedCrit = this.critical; + String b64Id = DefaultJwsHeader.B64.getId(); + if (!unencodedPayload.isEmpty() && !this.critical.contains(b64Id)) { + // The application developer explicitly indicates they're using a B64 payload, so + // ensure that the B64 crit header is supported, even if they forgot to configure it on the + // parser builder: + supportedCrit = new LinkedHashSet<>(Collections.size(this.critical) + 1); + supportedCrit.add(DefaultJwsHeader.B64.getId()); + supportedCrit.addAll(this.critical); } - payload = new String(bytes, Strings.UTF_8); + // assert any values per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.11: + for (String name : crit) { + if (!header.containsKey(name)) { + String msg = String.format(CRIT_MISSING_MSG, name, name, header); + throw new MalformedJwtException(msg); + } + if (!supportedCrit.contains(name)) { + String msg = String.format(CRIT_UNSUPPORTED_MSG, name, name, header); + throw new UnsupportedJwtException(msg); + } + } } - Claims claims = null; + // =============== Payload ================= + final CharSequence payloadToken = tokenized.getPayload(); + Payload payload; + boolean integrityVerified = false; // only true after successful signature verification or AEAD decryption - if (!payload.isEmpty() && payload.charAt(0) == '{' && payload.charAt(payload.length() - 1) == '}') { //likely to be json, parse it: - Map claimsMap = (Map) readValue(payload); - claims = new DefaultClaims(claimsMap); + // check if b64 extension enabled: + final boolean payloadBase64UrlEncoded = !(header instanceof JwsHeader) || ((JwsHeader) header).isPayloadEncoded(); + if (payloadBase64UrlEncoded) { + // standard encoding, so decode it: + byte[] data = decode(tokenized.getPayload(), "payload"); + payload = new Payload(data, header.getContentType()); + } else { + // The JWT uses the b64 extension, and we already know the parser supports that extension at this point + // in the code execution path because of the ----- crit ----- assertions section above as well as the + // (JwsHeader).isPayloadEncoded() check + if (Strings.hasText(payloadToken)) { + // we need to verify what was in the token, otherwise it'd be a security issue if we ignored it + // and assumed the (likely safe) unencodedPayload value instead: + payload = new Payload(payloadToken, header.getContentType()); + } else { + //no payload token (a detached payload), so we need to ensure that they've specified the payload value: + if (unencodedPayload.isEmpty()) { + String msg = String.format(B64_MISSING_PAYLOAD, header); + throw new SignatureException(msg); + } + // otherwise, use the specified payload: + payload = unencodedPayload; + } } - // =============== Signature ================= - if (base64UrlEncodedDigest != null) { //it is signed - validate the signature + if (tokenized instanceof TokenizedJwe && payload.isEmpty()) { + // Only JWS payload can be empty per https://github.com/jwtk/jjwt/pull/540 + String msg = "Compact JWE strings MUST always contain a payload (ciphertext)."; + throw new MalformedJwtException(msg); + } - JwsHeader jwsHeader = (JwsHeader) header; + byte[] iv = null; + byte[] tag = null; + if (tokenized instanceof TokenizedJwe) { - SignatureAlgorithm algorithm = null; + TokenizedJwe tokenizedJwe = (TokenizedJwe) tokenized; + JweHeader jweHeader = Assert.stateIsInstance(JweHeader.class, header, "Not a JweHeader. "); - if (header != null) { - String alg = jwsHeader.getAlgorithm(); - if (Strings.hasText(alg)) { - algorithm = SignatureAlgorithm.forName(alg); + byte[] cekBytes = Bytes.EMPTY; //ignored unless using an encrypted key algorithm + CharSequence base64Url = tokenizedJwe.getEncryptedKey(); + if (Strings.hasText(base64Url)) { + cekBytes = decode(base64Url, "JWE encrypted key"); + if (Bytes.isEmpty(cekBytes)) { + String msg = "Compact JWE string represents an encrypted key, but the key is empty."; + throw new MalformedJwtException(msg); } } - if (algorithm == null || algorithm == SignatureAlgorithm.NONE) { - //it is plaintext, but it has a signature. This is invalid: - String msg = "JWT string has a digest/signature, but the header does not reference a valid signature " + - "algorithm."; + base64Url = tokenizedJwe.getIv(); + if (Strings.hasText(base64Url)) { + iv = decode(base64Url, "JWE Initialization Vector"); + } + if (Bytes.isEmpty(iv)) { + String msg = "Compact JWE strings must always contain an Initialization Vector."; throw new MalformedJwtException(msg); } - if (key != null && keyBytes != null) { - throw new IllegalStateException("A key object and key bytes cannot both be specified. Choose either."); - } else if ((key != null || keyBytes != null) && signingKeyResolver != null) { - String object = key != null ? "a key object" : "key bytes"; - throw new IllegalStateException("A signing key resolver and " + object + " cannot both be specified. Choose either."); + // The AAD (Additional Authenticated Data) scheme for compact JWEs is to use the ASCII bytes of the + // raw base64url text as the AAD, and NOT the base64url-decoded bytes per + // https://www.rfc-editor.org/rfc/rfc7516.html#section-5.1, Step 14. + ByteBuffer buf = StandardCharsets.US_ASCII.encode(Strings.wrap(base64UrlHeader)); + final byte[] aadBytes = new byte[buf.remaining()]; + buf.get(aadBytes); + InputStream aad = Streams.of(aadBytes); + + base64Url = base64UrlDigest; + //guaranteed to be non-empty via the `alg` + digest check above: + Assert.hasText(base64Url, "JWE AAD Authentication Tag cannot be null or empty."); + tag = decode(base64Url, "JWE AAD Authentication Tag"); + if (Bytes.isEmpty(tag)) { + String msg = "Compact JWE strings must always contain an AAD Authentication Tag."; + throw new MalformedJwtException(msg); } - //digitally signed, let's assert the signature: - Key key = this.key; + String enc = jweHeader.getEncryptionAlgorithm(); + if (!Strings.hasText(enc)) { + throw new MalformedJwtException(MISSING_ENC_MSG); + } + final AeadAlgorithm encAlg = this.encAlgs.apply(jweHeader); + Assert.stateNotNull(encAlg, "JWE Encryption Algorithm cannot be null."); - if (key == null) { //fall back to keyBytes + @SuppressWarnings("rawtypes") final KeyAlgorithm keyAlg = this.keyAlgs.apply(jweHeader); + Assert.stateNotNull(keyAlg, "JWE Key Algorithm cannot be null."); - byte[] keyBytes = this.keyBytes; + Key key = this.keyLocator.locate(jweHeader); + if (key == null) { + String msg = "Cannot decrypt JWE payload: unable to locate key for JWE with header: " + jweHeader; + throw new UnsupportedJwtException(msg); + } + if (key instanceof PublicKey) { + throw new InvalidKeyException(PUB_KEY_DECRYPT_MSG); + } - if (Objects.isEmpty(keyBytes) && signingKeyResolver != null) { //use the signingKeyResolver - if (claims != null) { - key = signingKeyResolver.resolveSigningKey(jwsHeader, claims); - } else { - key = signingKeyResolver.resolveSigningKey(jwsHeader, payload); - } - } + // extract key-specific provider if necessary; + Provider provider = ProviderKey.getProvider(key, this.provider); + key = ProviderKey.getKey(key); // this must be called after ProviderKey.getProvider + DecryptionKeyRequest request = + new DefaultDecryptionKeyRequest<>(cekBytes, provider, null, jweHeader, encAlg, key); + final SecretKey cek = keyAlg.getDecryptionKey(request); + if (cek == null) { + String msg = "The '" + keyAlg.getId() + "' JWE key algorithm did not return a decryption key. " + + "Unable to perform '" + encAlg.getId() + "' decryption."; + throw new IllegalStateException(msg); + } - if (!Objects.isEmpty(keyBytes)) { + // During decryption, the available Provider applies to the KeyAlgorithm, not the AeadAlgorithm, mostly + // because all JVMs support the standard AeadAlgorithms (especially with BouncyCastle in the classpath). + // As such, the provider here is intentionally omitted (null): + // TODO: add encProvider(Provider) builder method that applies to this request only? + InputStream ciphertext = payload.toInputStream(); + ByteArrayOutputStream plaintext = new ByteArrayOutputStream(8192); + DecryptAeadRequest dreq = new DefaultDecryptAeadRequest(ciphertext, cek, aad, iv, tag); + encAlg.decrypt(dreq, plaintext); + payload = new Payload(plaintext.toByteArray(), header.getContentType()); - Assert.isTrue(algorithm.isHmac(), - "Key bytes can only be specified for HMAC signatures. Please specify a PublicKey or PrivateKey instance."); + integrityVerified = true; // AEAD performs integrity verification, so no exception = verified - key = new SecretKeySpec(keyBytes, algorithm.getJcaName()); + } else if (hasDigest && this.signingKeyResolver == null) { //TODO: for 1.0, remove the == null check + // not using a signing key resolver, so we can verify the signature before reading the payload, which is + // always safer: + JwsHeader jwsHeader = Assert.stateIsInstance(JwsHeader.class, header, "Not a JwsHeader. "); + verifySignature(tokenized, jwsHeader, alg, new LocatingKeyResolver(this.keyLocator), null, payload); + integrityVerified = true; // no exception means signature verified + } + + final CompressionAlgorithm compressionAlgorithm = zipAlgs.apply(header); + if (compressionAlgorithm != null) { + if (!integrityVerified) { + if (!payloadBase64UrlEncoded) { + String msg = String.format(B64_DECOMPRESSION_MSG, compressionAlgorithm.getId()); + throw new UnsupportedJwtException(msg); + } else if (!unsecuredDecompression) { + String msg = String.format(UNPROTECTED_DECOMPRESSION_MSG, compressionAlgorithm.getId()); + throw new UnsupportedJwtException(msg); } } + payload = payload.decompress(compressionAlgorithm); + } - Assert.notNull(key, "A signing key must be specified if the specified JWT is digitally signed."); + Claims claims = null; + byte[] payloadBytes = payload.getBytes(); + if (payload.isConsumable()) { - //re-create the jwt part without the signature. This is what needs to be signed for verification: - String jwtWithoutSignature = base64UrlEncodedHeader + SEPARATOR_CHAR; - if (base64UrlEncodedPayload != null) { - jwtWithoutSignature += base64UrlEncodedPayload; - } + InputStream in = payload.toInputStream(); - JwtSignatureValidator validator; - try { - algorithm.assertValidVerificationKey(key); //since 0.10.0: https://github.com/jwtk/jjwt/issues/334 - validator = createSignatureValidator(algorithm, key); - } catch (WeakKeyException e) { - throw e; - } catch (InvalidKeyException | IllegalArgumentException e) { - String algName = algorithm.getValue(); - String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " + - "algorithm, but the specified signing key of type " + key.getClass().getName() + - " may not be used to validate " + algName + " signatures. Because the specified " + - "signing key reflects a specific and expected algorithm, and the JWT does not reflect " + - "this algorithm, it is likely that the JWT was not expected and therefore should not be " + - "trusted. Another possibility is that the parser was configured with the incorrect " + - "signing key, but this cannot be assumed for security reasons."; - throw new UnsupportedJwtException(msg, e); + if (!hasContentType(header)) { // If there is a content type set, then the application using JJWT is expected + // to convert the byte payload themselves based on this content type + // https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10 : + // + // "This parameter is ignored by JWS implementations; any processing of this + // parameter is performed by the JWS application." + // + Map claimsMap = null; + try { + // if deserialization fails, we'll need to rewind to convert to a byte array. So if + // mark/reset isn't possible, we'll need to buffer: + if (!in.markSupported()) { + in = new BufferedInputStream(in); + in.mark(0); + } + claimsMap = deserialize(new UncloseableInputStream(in) /* Don't close in case we need to rewind */, "claims"); + } catch (DeserializationException | MalformedJwtException ignored) { // not JSON, treat it as a byte[] +// String msg = "Invalid claims: " + e.getMessage(); +// throw new MalformedJwtException(msg, e); + } finally { + Streams.reset(in); + } + if (claimsMap != null) { + try { + claims = new DefaultClaims(claimsMap); + } catch (Throwable t) { + String msg = "Invalid claims: " + t.getMessage(); + throw new MalformedJwtException(msg); + } + } } - - if (!validator.isValid(jwtWithoutSignature, base64UrlEncodedDigest)) { - String msg = "JWT signature does not match locally computed signature. JWT validity cannot be " + - "asserted and should not be trusted."; - throw new SignatureException(msg); + if (claims == null) { + // consumable, but not claims, so convert to byte array: + payloadBytes = Streams.bytes(in, "Unable to convert payload to byte array."); } } + Jwt jwt; + Object body = claims != null ? claims : payloadBytes; + if (header instanceof JweHeader) { + jwt = new DefaultJwe<>((JweHeader) header, body, iv, tag); + } else if (hasDigest) { + JwsHeader jwsHeader = Assert.isInstanceOf(JwsHeader.class, header, "JwsHeader required."); + jwt = new DefaultJws<>(jwsHeader, body, base64UrlDigest.toString()); + } else { + //noinspection rawtypes + jwt = new DefaultJwt(header, body); + } + + // =============== Signature ================= + if (hasDigest && signingKeyResolver != null) { // TODO: remove for 1.0 + // A SigningKeyResolver has been configured, and due to it's API, we have to verify the signature after + // parsing the body. This can be a security risk, so it needs to be removed before 1.0 + JwsHeader jwsHeader = Assert.stateIsInstance(JwsHeader.class, header, "Not a JwsHeader. "); + verifySignature(tokenized, jwsHeader, alg, this.signingKeyResolver, claims, payload); + } + final boolean allowSkew = this.allowedClockSkewMillis > 0; //since 0.3: @@ -429,57 +663,50 @@ final Date now = this.clock.now(); long nowTime = now.getTime(); - //https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.4 - //token MUST NOT be accepted on or after any specified exp time: + // https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4 + // token MUST NOT be accepted on or after any specified exp time: Date exp = claims.getExpiration(); if (exp != null) { long maxTime = nowTime - this.allowedClockSkewMillis; Date max = allowSkew ? new Date(maxTime) : now; if (max.after(exp)) { - String expVal = DateFormats.formatIso8601(exp, false); - String nowVal = DateFormats.formatIso8601(now, false); + String expVal = DateFormats.formatIso8601(exp, true); + String nowVal = DateFormats.formatIso8601(now, true); - long differenceMillis = maxTime - exp.getTime(); + long differenceMillis = nowTime - exp.getTime(); - String msg = "JWT expired at " + expVal + ". Current time: " + nowVal + ", a difference of " + - differenceMillis + " milliseconds. Allowed clock skew: " + - this.allowedClockSkewMillis + " milliseconds."; + String msg = "JWT expired " + differenceMillis + " milliseconds ago at " + expVal + ". " + + "Current time: " + nowVal + ". Allowed clock skew: " + + this.allowedClockSkewMillis + " milliseconds."; throw new ExpiredJwtException(header, claims, msg); } } - //https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.5 - //token MUST NOT be accepted before any specified nbf time: + // https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.5 + // token MUST NOT be accepted before any specified nbf time: Date nbf = claims.getNotBefore(); if (nbf != null) { long minTime = nowTime + this.allowedClockSkewMillis; Date min = allowSkew ? new Date(minTime) : now; if (min.before(nbf)) { - String nbfVal = DateFormats.formatIso8601(nbf, false); - String nowVal = DateFormats.formatIso8601(now, false); + String nbfVal = DateFormats.formatIso8601(nbf, true); + String nowVal = DateFormats.formatIso8601(now, true); - long differenceMillis = nbf.getTime() - minTime; + long differenceMillis = nbf.getTime() - nowTime; - String msg = "JWT must not be accepted before " + nbfVal + ". Current time: " + nowVal + - ", a difference of " + - differenceMillis + " milliseconds. Allowed clock skew: " + - this.allowedClockSkewMillis + " milliseconds."; + String msg = "JWT early by " + differenceMillis + " milliseconds before " + nbfVal + + ". Current time: " + nowVal + ". Allowed clock skew: " + + this.allowedClockSkewMillis + " milliseconds."; throw new PrematureJwtException(header, claims, msg); } } validateExpectedClaims(header, claims); } - Object body = claims != null ? claims : payload; - - if (base64UrlEncodedDigest != null) { - return new DefaultJws<>((JwsHeader) header, body, base64UrlEncodedDigest); - } else { - return new DefaultJwt<>(header, body); - } + return jwt; } /** @@ -494,134 +721,175 @@ private void validateExpectedClaims(Header header, Claims claims) { - for (String expectedClaimName : expectedClaims.keySet()) { + final Claims expected = expectedClaims.build(); - Object expectedClaimValue = normalize(expectedClaims.get(expectedClaimName)); + for (String expectedClaimName : expected.keySet()) { + + Object expectedClaimValue = normalize(expected.get(expectedClaimName)); Object actualClaimValue = normalize(claims.get(expectedClaimName)); if (expectedClaimValue instanceof Date) { try { actualClaimValue = claims.get(expectedClaimName, Date.class); } catch (Exception e) { String msg = "JWT Claim '" + expectedClaimName + "' was expected to be a Date, but its value " + - "cannot be converted to a Date using current heuristics. Value: " + actualClaimValue; - throw new IncorrectClaimException(header, claims, msg); + "cannot be converted to a Date using current heuristics. Value: " + actualClaimValue; + throw new IncorrectClaimException(header, claims, expectedClaimName, expectedClaimValue, msg); } } - InvalidClaimException invalidClaimException = null; - if (actualClaimValue == null) { + boolean collection = expectedClaimValue instanceof Collection; + String msg = "Missing '" + expectedClaimName + "' claim. Expected value"; + if (collection) { + msg += "s: " + expectedClaimValue; + } else { + msg += ": " + expectedClaimValue; + } + throw new MissingClaimException(header, claims, expectedClaimName, expectedClaimValue, msg); + } else if (expectedClaimValue instanceof Collection) { + Collection expectedValues = (Collection) expectedClaimValue; + Collection actualValues = actualClaimValue instanceof Collection ? (Collection) actualClaimValue : + Collections.setOf(actualClaimValue); + for (Object expectedValue : expectedValues) { + if (!Collections.contains(actualValues.iterator(), expectedValue)) { + String msg = String.format(MISSING_EXPECTED_CLAIM_VALUE_MESSAGE_TEMPLATE, + expectedValue, expectedClaimName, actualValues); + throw new IncorrectClaimException(header, claims, expectedClaimName, expectedClaimValue, msg); + } + } + } else if (!expectedClaimValue.equals(actualClaimValue)) { + String msg = String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, + expectedClaimName, expectedClaimValue, actualClaimValue); + throw new IncorrectClaimException(header, claims, expectedClaimName, expectedClaimValue, msg); + } + } + } - String msg = String.format(ClaimJwtException.MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, - expectedClaimName, expectedClaimValue); + @SuppressWarnings("deprecation") + @Override + public T parse(CharSequence compact, JwtHandler handler) { + return parse(compact, Payload.EMPTY).accept(handler); + } - invalidClaimException = new MissingClaimException(header, claims, msg); + private Jwt parse(CharSequence compact, Payload unencodedPayload) { + Assert.hasText(compact, "JWT String argument cannot be null or empty."); + return parse(new CharSequenceReader(compact), unencodedPayload); + } - } else if (!expectedClaimValue.equals(actualClaimValue)) { + @Override + public Jwt parseContentJwt(CharSequence jwt) { + return parse(jwt).accept(Jwt.UNSECURED_CONTENT); + } - String msg = String.format(ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, - expectedClaimName, expectedClaimValue, actualClaimValue); + @Override + public Jwt parseClaimsJwt(CharSequence jwt) { + return parse(jwt).accept(Jwt.UNSECURED_CLAIMS); + } - invalidClaimException = new IncorrectClaimException(header, claims, msg); - } + @Override + public Jws parseContentJws(CharSequence jws) { + return parseSignedContent(jws); + } - if (invalidClaimException != null) { - invalidClaimException.setClaimName(expectedClaimName); - invalidClaimException.setClaimValue(expectedClaimValue); - throw invalidClaimException; - } - } + @Override + public Jws parseClaimsJws(CharSequence jws) { + return parseSignedClaims(jws); } - /* - * @since 0.5 mostly to allow testing overrides - */ - protected JwtSignatureValidator createSignatureValidator(SignatureAlgorithm alg, Key key) { - return new DefaultJwtSignatureValidator(alg, key, base64UrlDecoder); + @Override + public Jwt parseUnsecuredContent(CharSequence jwt) throws JwtException, IllegalArgumentException { + return parse(jwt).accept(Jwt.UNSECURED_CONTENT); } @Override - public T parse(String compact, JwtHandler handler) - throws ExpiredJwtException, MalformedJwtException, SignatureException { - Assert.notNull(handler, "JwtHandler argument cannot be null."); - Assert.hasText(compact, "JWT String argument cannot be null or empty."); + public Jwt parseUnsecuredClaims(CharSequence jwt) throws JwtException, IllegalArgumentException { + return parse(jwt).accept(Jwt.UNSECURED_CLAIMS); + } - Jwt jwt = parse(compact); + @Override + public Jws parseSignedContent(CharSequence compact) { + return parse(compact).accept(Jws.CONTENT); + } - if (jwt instanceof Jws) { - Jws jws = (Jws) jwt; - Object body = jws.getBody(); - if (body instanceof Claims) { - return handler.onClaimsJws((Jws) jws); - } else { - return handler.onPlaintextJws((Jws) jws); - } - } else { - Object body = jwt.getBody(); - if (body instanceof Claims) { - return handler.onClaimsJwt((Jwt) jwt); - } else { - return handler.onPlaintextJwt((Jwt) jwt); - } - } + private Jws parseSignedContent(CharSequence jws, Payload unencodedPayload) { + return parse(jws, unencodedPayload).accept(Jws.CONTENT); } @Override - public Jwt parsePlaintextJwt(String plaintextJwt) { - return parse(plaintextJwt, new JwtHandlerAdapter>() { - @Override - public Jwt onPlaintextJwt(Jwt jwt) { - return jwt; - } - }); + public Jws parseSignedClaims(CharSequence compact) { + return parse(compact).accept(Jws.CLAIMS); } + private Jws parseSignedClaims(CharSequence jws, Payload unencodedPayload) { + unencodedPayload.setClaimsExpected(true); + return parse(jws, unencodedPayload).accept(Jws.CLAIMS); + } + @Override - public Jwt parseClaimsJwt(String claimsJwt) { - try { - return parse(claimsJwt, new JwtHandlerAdapter>() { - @Override - public Jwt onClaimsJwt(Jwt jwt) { - return jwt; - } - }); - } catch (IllegalArgumentException iae) { - throw new UnsupportedJwtException("Signed JWSs are not supported.", iae); + public Jws parseSignedContent(CharSequence jws, byte[] unencodedPayload) { + Assert.notEmpty(unencodedPayload, "unencodedPayload argument cannot be null or empty."); + return parseSignedContent(jws, new Payload(unencodedPayload, null)); + } + + private static Payload payloadFor(InputStream in) { + if (in instanceof BytesInputStream) { + byte[] data = Streams.bytes(in, "Unable to obtain payload InputStream bytes."); + return new Payload(data, null); } + //if (in.markSupported()) in.mark(0); + return new Payload(in, null); } @Override - public Jws parsePlaintextJws(String plaintextJws) { - try { - return parse(plaintextJws, new JwtHandlerAdapter>() { - @Override - public Jws onPlaintextJws(Jws jws) { - return jws; - } - }); - } catch (IllegalArgumentException iae) { - throw new UnsupportedJwtException("Signed JWSs are not supported.", iae); - } + public Jws parseSignedContent(CharSequence jws, InputStream unencodedPayload) { + Assert.notNull(unencodedPayload, "unencodedPayload InputStream cannot be null."); + return parseSignedContent(jws, payloadFor(unencodedPayload)); } @Override - public Jws parseClaimsJws(String claimsJws) { - return parse(claimsJws, new JwtHandlerAdapter>() { - @Override - public Jws onClaimsJws(Jws jws) { - return jws; - } - }); + public Jws parseSignedClaims(CharSequence jws, byte[] unencodedPayload) { + Assert.notEmpty(unencodedPayload, "unencodedPayload argument cannot be null or empty."); + return parseSignedClaims(jws, new Payload(unencodedPayload, null)); } - @SuppressWarnings("unchecked") - protected Map readValue(String val) { + @Override + public Jws parseSignedClaims(CharSequence jws, InputStream unencodedPayload) { + Assert.notNull(unencodedPayload, "unencodedPayload InputStream cannot be null."); + byte[] bytes = Streams.bytes(unencodedPayload, + "Unable to obtain Claims bytes from unencodedPayload InputStream"); + return parseSignedClaims(jws, new Payload(bytes, null)); + } + + @Override + public Jwe parseEncryptedContent(CharSequence compact) throws JwtException { + return parse(compact).accept(Jwe.CONTENT); + } + + @Override + public Jwe parseEncryptedClaims(CharSequence compact) throws JwtException { + return parse(compact).accept(Jwe.CLAIMS); + } + + protected byte[] decode(CharSequence base64UrlEncoded, String name) { try { - byte[] bytes = val.getBytes(Strings.UTF_8); - return deserializer.deserialize(bytes); - } catch (DeserializationException e) { - throw new MalformedJwtException("Unable to read JSON value: " + val, e); + InputStream decoding = this.decoder.decode(Streams.of(Strings.utf8(base64UrlEncoded))); + return Streams.bytes(decoding, "Unable to Base64Url-decode input."); + } catch (Throwable t) { + // Don't disclose potentially-sensitive information per https://github.com/jwtk/jjwt/issues/824: + String value = "payload".equals(name) ? RedactedSupplier.REDACTED_VALUE : base64UrlEncoded.toString(); + String msg = "Invalid Base64Url " + name + ": " + value; + throw new MalformedJwtException(msg, t); } } + + protected Map deserialize(InputStream in, final String name) { + try { + Reader reader = Streams.reader(in); + JsonObjectDeserializer deserializer = new JsonObjectDeserializer(this.deserializer, name); + return deserializer.apply(reader); + } finally { + Objects.nullSafeClose(in); + } + } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java (.../DefaultJwtParserBuilder.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultJwtParserBuilder.java (.../DefaultJwtParserBuilder.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,22 +15,46 @@ */ package io.jsonwebtoken.impl; -import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ClaimsBuilder; import io.jsonwebtoken.Clock; import io.jsonwebtoken.CompressionCodecResolver; import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.JwtParserBuilder; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.Locator; import io.jsonwebtoken.SigningKeyResolver; -import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver; +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.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.Decoder; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.io.Deserializer; import io.jsonwebtoken.lang.Assert; -import io.jsonwebtoken.impl.lang.Services; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.NestedCollection; +import io.jsonwebtoken.lang.Registry; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SecureDigestAlgorithm; +import javax.crypto.SecretKey; +import java.io.InputStream; import java.security.Key; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; import java.util.Date; import java.util.Map; +import java.util.Set; /** * @since 0.11.0 @@ -41,44 +65,92 @@ /** * To prevent overflow per Issue 583. - * + *

    * Package-protected on purpose to allow use in backwards-compatible {@link DefaultJwtParser} implementation. * TODO: enable private modifier on these two variables when deleting DefaultJwtParser */ static final long MAX_CLOCK_SKEW_MILLIS = Long.MAX_VALUE / MILLISECONDS_PER_SECOND; static final String MAX_CLOCK_SKEW_ILLEGAL_MSG = "Illegal allowedClockSkewMillis value: multiplying this " + - "value by 1000 to obtain the number of milliseconds would cause a numeric overflow."; + "value by 1000 to obtain the number of milliseconds would cause a numeric overflow."; - private byte[] keyBytes; + private Provider provider; - private Key key; + private boolean unsecured = false; - private SigningKeyResolver signingKeyResolver; + private boolean unsecuredDecompression = false; - private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver(); + private Locator keyLocator; - private Decoder base64UrlDecoder = Decoders.BASE64URL; + @SuppressWarnings("deprecation") //TODO: remove for 1.0 + private SigningKeyResolver signingKeyResolver = null; + private Registry encAlgs = Jwts.ENC.get(); + + private Registry> keyAlgs = Jwts.KEY.get(); + + private Registry> sigAlgs = Jwts.SIG.get(); + + private Registry zipAlgs = Jwts.ZIP.get(); + + @SuppressWarnings("deprecation") + private CompressionCodecResolver compressionCodecResolver; + + @SuppressWarnings("deprecation") + private Decoder decoder = new DelegateStringDecoder(Decoders.BASE64URL); + private Deserializer> deserializer; - private Claims expectedClaims = new DefaultClaims(); + private final ClaimsBuilder expectedClaims = Jwts.claims(); private Clock clock = DefaultClock.INSTANCE; + private Set critical = Collections.emptySet(); + private long allowedClockSkewMillis = 0; + private Key signatureVerificationKey; + private Key decryptionKey; @Override + public JwtParserBuilder unsecured() { + this.unsecured = true; + return this; + } + + @Override + public JwtParserBuilder unsecuredDecompression() { + this.unsecuredDecompression = true; + return this; + } + + @Override + public JwtParserBuilder provider(Provider provider) { + this.provider = provider; + return this; + } + + @Override public JwtParserBuilder deserializeJsonWith(Deserializer> deserializer) { - Assert.notNull(deserializer, "deserializer cannot be null."); - this.deserializer = deserializer; + return json(deserializer); + } + + @Override + public JwtParserBuilder json(Deserializer> reader) { + this.deserializer = Assert.notNull(reader, "JSON Deserializer cannot be null."); return this; } + @SuppressWarnings("deprecation") @Override - public JwtParserBuilder base64UrlDecodeWith(Decoder base64UrlDecoder) { - Assert.notNull(base64UrlDecoder, "base64UrlDecoder cannot be null."); - this.base64UrlDecoder = base64UrlDecoder; + public JwtParserBuilder base64UrlDecodeWith(final Decoder decoder) { + Assert.notNull(decoder, "decoder cannot be null."); + return b64Url(new DelegateStringDecoder(decoder)); + } + + @Override + public JwtParserBuilder b64Url(Decoder decoder) { + Assert.notNull(decoder, "decoder cannot be null."); + this.decoder = decoder; return this; } @@ -96,7 +168,7 @@ @Override public JwtParserBuilder requireAudience(String audience) { - expectedClaims.setAudience(audience); + expectedClaims.audience().add(audience).and(); return this; } @@ -128,78 +200,230 @@ public JwtParserBuilder require(String claimName, Object value) { Assert.hasText(claimName, "claim name cannot be null or empty."); Assert.notNull(value, "The value cannot be null for claim name: " + claimName); - expectedClaims.put(claimName, value); + expectedClaims.add(claimName, value); return this; } @Override public JwtParserBuilder setClock(Clock clock) { + return clock(clock); + } + + @Override + public JwtParserBuilder clock(Clock clock) { Assert.notNull(clock, "Clock instance cannot be null."); this.clock = clock; return this; } @Override + public NestedCollection critical() { + return new DefaultNestedCollection(this, this.critical) { + @Override + protected void changed() { + critical = Collections.asSet(getCollection()); + } + }; + } + + @Override public JwtParserBuilder setAllowedClockSkewSeconds(long seconds) throws IllegalArgumentException { + return clockSkewSeconds(seconds); + } + + @Override + public JwtParserBuilder clockSkewSeconds(long seconds) throws IllegalArgumentException { Assert.isTrue(seconds <= MAX_CLOCK_SKEW_MILLIS, MAX_CLOCK_SKEW_ILLEGAL_MSG); this.allowedClockSkewMillis = Math.max(0, seconds * MILLISECONDS_PER_SECOND); return this; } @Override public JwtParserBuilder setSigningKey(byte[] key) { - Assert.notEmpty(key, "signing key cannot be null or empty."); - this.keyBytes = key; - return this; + Assert.notEmpty(key, "signature verification key cannot be null or empty."); + return setSigningKey(Keys.hmacShaKeyFor(key)); } @Override public JwtParserBuilder setSigningKey(String base64EncodedSecretKey) { - Assert.hasText(base64EncodedSecretKey, "signing key cannot be null or empty."); - this.keyBytes = Decoders.BASE64.decode(base64EncodedSecretKey); + Assert.hasText(base64EncodedSecretKey, "signature verification key cannot be null or empty."); + byte[] bytes = Decoders.BASE64.decode(base64EncodedSecretKey); + return setSigningKey(bytes); + } + + @Override + public JwtParserBuilder setSigningKey(final Key key) { + if (key instanceof SecretKey) { + return verifyWith((SecretKey) key); + } else if (key instanceof PublicKey) { + return verifyWith((PublicKey) key); + } + String msg = "JWS verification key must be either a SecretKey (for MAC algorithms) or a PublicKey " + + "(for Signature algorithms)."; + throw new InvalidKeyException(msg); + } + + @Override + public JwtParserBuilder verifyWith(SecretKey key) { + return verifyWith((Key) key); + } + + @Override + public JwtParserBuilder verifyWith(PublicKey key) { + return verifyWith((Key) key); + } + + private JwtParserBuilder verifyWith(Key key) { + if (key instanceof PrivateKey) { + throw new IllegalArgumentException(DefaultJwtParser.PRIV_KEY_VERIFY_MSG); + } + this.signatureVerificationKey = Assert.notNull(key, "signature verification key cannot be null."); return this; } @Override - public JwtParserBuilder setSigningKey(Key key) { - Assert.notNull(key, "signing key cannot be null."); - this.key = key; + public JwtParserBuilder decryptWith(SecretKey key) { + return decryptWith((Key) key); + } + + @Override + public JwtParserBuilder decryptWith(PrivateKey key) { + return decryptWith((Key) key); + } + + private JwtParserBuilder decryptWith(final Key key) { + if (key instanceof PublicKey) { + throw new IllegalArgumentException(DefaultJwtParser.PUB_KEY_DECRYPT_MSG); + } + this.decryptionKey = Assert.notNull(key, "decryption key cannot be null."); return this; } @Override + public NestedCollection zip() { + return new DefaultNestedCollection(this, this.zipAlgs.values()) { + @Override + protected void changed() { + zipAlgs = new IdRegistry<>(StandardCompressionAlgorithms.NAME, getCollection()); + } + }; + } + + @Override + public NestedCollection enc() { + return new DefaultNestedCollection(this, this.encAlgs.values()) { + @Override + public void changed() { + encAlgs = new IdRegistry<>(StandardEncryptionAlgorithms.NAME, getCollection()); + } + }; + } + + @Override + public NestedCollection, JwtParserBuilder> sig() { + return new DefaultNestedCollection, JwtParserBuilder>(this, this.sigAlgs.values()) { + @Override + public void changed() { + sigAlgs = new IdRegistry<>(StandardSecureDigestAlgorithms.NAME, getCollection()); + } + }; + } + + @Override + public NestedCollection, JwtParserBuilder> key() { + return new DefaultNestedCollection, JwtParserBuilder>(this, this.keyAlgs.values()) { + @Override + public void changed() { + keyAlgs = new IdRegistry<>(StandardKeyAlgorithms.NAME, getCollection()); + } + }; + } + + @SuppressWarnings("deprecation") //TODO: remove for 1.0 + @Override public JwtParserBuilder setSigningKeyResolver(SigningKeyResolver signingKeyResolver) { Assert.notNull(signingKeyResolver, "SigningKeyResolver cannot be null."); this.signingKeyResolver = signingKeyResolver; return this; } @Override - public JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver) { - Assert.notNull(compressionCodecResolver, "compressionCodecResolver cannot be null."); - this.compressionCodecResolver = compressionCodecResolver; + public JwtParserBuilder keyLocator(Locator keyLocator) { + this.keyLocator = Assert.notNull(keyLocator, "Key locator cannot be null."); return this; } + @SuppressWarnings("deprecation") @Override + public JwtParserBuilder setCompressionCodecResolver(CompressionCodecResolver resolver) { + this.compressionCodecResolver = Assert.notNull(resolver, "CompressionCodecResolver cannot be null."); + return this; + } + + @Override public JwtParser build() { - // Only lookup the deserializer IF it is null. It is possible a Deserializer implementation was set - // that is NOT exposed as a service and no other implementations are available for lookup. if (this.deserializer == null) { - // try to find one based on the services available: - this.deserializer = Services.loadFirst(Deserializer.class); + //noinspection unchecked + json(Services.get(Deserializer.class)); } + if (this.signingKeyResolver != null && this.signatureVerificationKey != null) { + String msg = "Both a 'signingKeyResolver and a 'verifyWith' key cannot be configured. " + + "Choose either, or prefer `keyLocator` when possible."; + throw new IllegalStateException(msg); + } + if (this.keyLocator != null) { + if (this.signatureVerificationKey != null) { + String msg = "Both 'keyLocator' and a 'verifyWith' key cannot be configured. " + + "Prefer 'keyLocator' if possible."; + throw new IllegalStateException(msg); + } + if (this.decryptionKey != null) { + String msg = "Both 'keyLocator' and a 'decryptWith' key cannot be configured. " + + "Prefer 'keyLocator' if possible."; + throw new IllegalStateException(msg); + } + } - return new ImmutableJwtParser( - new DefaultJwtParser(signingKeyResolver, - key, - keyBytes, - clock, - allowedClockSkewMillis, - expectedClaims, - base64UrlDecoder, - deserializer, - compressionCodecResolver)); + Locator keyLocator = this.keyLocator; // user configured default, don't overwrite to ensure further build() calls work as expected + if (keyLocator == null) { + keyLocator = new ConstantKeyLocator(this.signatureVerificationKey, this.decryptionKey); + } + + if (!unsecured && unsecuredDecompression) { + String msg = "'unsecuredDecompression' is only relevant if 'unsecured' is also " + + "configured. Please read the JavaDoc of both features before enabling either " + + "due to their security implications."; + throw new IllegalStateException(msg); + } + if (this.compressionCodecResolver != null && !Jwts.ZIP.get().equals(this.zipAlgs)) { + String msg = "Both 'zip()' and 'compressionCodecResolver' " + + "cannot be configured. Choose either."; + throw new IllegalStateException(msg); + } + + // Invariants. If these are ever violated, it's an error in this class implementation: + Assert.stateNotNull(keyLocator, "Key locator should never be null."); + + final DefaultClaims expClaims = (DefaultClaims) this.expectedClaims.build(); + + return new DefaultJwtParser( + provider, + signingKeyResolver, + unsecured, + unsecuredDecompression, + keyLocator, + clock, + critical, + allowedClockSkewMillis, + expClaims, + decoder, + deserializer, + compressionCodecResolver, + zipAlgs, + sigAlgs, + keyAlgs, + encAlgs + ); } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultMutableJweHeader.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultMutableJweHeader.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultMutableJweHeader.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,152 @@ +/* + * 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.JweHeader; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.security.PublicJwk; + +import java.net.URI; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Set; + +public class DefaultMutableJweHeader extends DefaultJweHeaderMutator implements JweHeader { + + public DefaultMutableJweHeader(DefaultJweHeaderMutator src) { + super(src); + } + + private T get(Parameter param) { + return this.DELEGATE.get(param); + } + + // ============================================================= + // JWT Header methods + // ============================================================= + + @Override + public String getAlgorithm() { + return get(DefaultHeader.ALGORITHM); + } + + @Override + public String getContentType() { + return get(DefaultHeader.CONTENT_TYPE); + } + + @Override + public String getType() { + return get(DefaultHeader.TYPE); + } + + @Override + public String getCompressionAlgorithm() { + return get(DefaultHeader.COMPRESSION_ALGORITHM); + } + + // ============================================================= + // Protected Header methods + // ============================================================= + + @Override + public URI getJwkSetUrl() { + return get(DefaultProtectedHeader.JKU); + } + + @Override + public PublicJwk getJwk() { + return get(DefaultProtectedHeader.JWK); + } + + @Override + public String getKeyId() { + return get(DefaultProtectedHeader.KID); + } + + @Override + public Set getCritical() { + return get(DefaultProtectedHeader.CRIT); + } + + // ============================================================= + // X.509 methods + // ============================================================= + + @Override + public URI getX509Url() { + return get(DefaultProtectedHeader.X5U); + } + + @Override + public List getX509Chain() { + return get(DefaultProtectedHeader.X5C); + } + + @Override + public byte[] getX509Sha1Thumbprint() { + return get(DefaultProtectedHeader.X5T); + } + + @Override + public byte[] getX509Sha256Thumbprint() { + return get(DefaultProtectedHeader.X5T_S256); + } + + // ============================================================= + // JWE Header methods + // ============================================================= + + @Override + public byte[] getAgreementPartyUInfo() { + return get(DefaultJweHeader.APU); + } + + @Override + public byte[] getAgreementPartyVInfo() { + return get(DefaultJweHeader.APV); + } + + @Override + public Integer getPbes2Count() { + return get(DefaultJweHeader.P2C); + } + + @Override + public String getEncryptionAlgorithm() { + return get(DefaultJweHeader.ENCRYPTION_ALGORITHM); + } + + @Override + public PublicJwk getEphemeralPublicKey() { + return get(DefaultJweHeader.EPK); + } + + @Override + public byte[] getInitializationVector() { + return get(DefaultJweHeader.IV); + } + + @Override + public byte[] getAuthenticationTag() { + return get(DefaultJweHeader.TAG); + } + + @Override + public byte[] getPbes2Salt() { + return get(DefaultJweHeader.P2S); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultProtectedHeader.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultProtectedHeader.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultProtectedHeader.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2021 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.Jwts; +import io.jsonwebtoken.ProtectedHeader; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.impl.security.AbstractAsymmetricJwk; +import io.jsonwebtoken.impl.security.AbstractJwk; +import io.jsonwebtoken.impl.security.JwkConverter; +import io.jsonwebtoken.lang.Registry; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.PublicJwk; + +import java.net.URI; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Header implementation satisfying shared JWS and JWE header parameter requirements. Header parameters specific to + * either JWE or JWS will be defined in respective subclasses. + * + * @since 0.12.0 + */ +public class DefaultProtectedHeader extends DefaultHeader implements ProtectedHeader { + + static final Parameter JKU = Parameters.uri("jku", "JWK Set URL"); + + static final Parameter> JWK = Parameters.builder(JwkConverter.PUBLIC_JWK_CLASS) + .setId("jwk").setName("JSON Web Key") + .setConverter(JwkConverter.PUBLIC_JWK).build(); + static final Parameter> CRIT = Parameters.stringSet("crit", "Critical"); + + static final Parameter KID = AbstractJwk.KID; + + static final Parameter X5U = AbstractAsymmetricJwk.X5U; + + static final Parameter> X5C = AbstractAsymmetricJwk.X5C; + + static final Parameter X5T = AbstractAsymmetricJwk.X5T; + + static final Parameter X5T_S256 = AbstractAsymmetricJwk.X5T_S256; + + static final Registry> PARAMS = + Parameters.registry(DefaultHeader.PARAMS, CRIT, JKU, JWK, KID, X5U, X5C, X5T, X5T_S256); + + static boolean isCandidate(ParameterMap map) { + String id = map.get(DefaultHeader.ALGORITHM); + return Strings.hasText(id) && !id.equalsIgnoreCase(Jwts.SIG.NONE.getId()); // alg cannot be empty or 'none' +// return (Strings.hasText(id) && !Jwts.SIG.NONE.equals(Jwts.SIG.get().get(id))) || +// map.get(JKU) != null || +// map.get(JWK) != null || +// !Collections.isEmpty(map.get(CRIT)) || +// Strings.hasText(map.get(KID)) || +// map.get(X5U) != null || +// !Collections.isEmpty(map.get(X5C)) || +// !Bytes.isEmpty(map.get(X5T)) || +// !Bytes.isEmpty(map.get(X5T_S256)); + } + + protected DefaultProtectedHeader(Registry> registry, Map values) { + super(registry, values); + } + + @Override + public String getKeyId() { + return get(KID); + } + + @Override + public URI getJwkSetUrl() { + return get(JKU); + } + + @Override + public PublicJwk getJwk() { + return get(JWK); + } + + @Override + public URI getX509Url() { + return get(AbstractAsymmetricJwk.X5U); + } + + @Override + public List getX509Chain() { + return get(X5C); + } + + @Override + public byte[] getX509Sha1Thumbprint() { + return get(X5T); + } + + @Override + public byte[] getX509Sha256Thumbprint() { + return get(X5T_S256); + } + + @Override + public Set getCritical() { + return get(CRIT); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultProtectedJwt.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultProtectedJwt.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultProtectedJwt.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,65 @@ +/* + * 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.ProtectedHeader; +import io.jsonwebtoken.ProtectedJwt; +import io.jsonwebtoken.io.Encoders; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Objects; + +import java.security.MessageDigest; + +abstract class DefaultProtectedJwt extends DefaultJwt implements ProtectedJwt { + + protected final byte[] digest; + + private final String digestName; + + protected DefaultProtectedJwt(H header, P payload, byte[] digest, String digestName) { + super(header, payload); + this.digest = Assert.notEmpty(digest, "Digest byte array cannot be null or empty."); + this.digestName = Assert.hasText(digestName, "digestName cannot be null or empty."); + } + + @Override + public byte[] getDigest() { + return this.digest.clone(); + } + + @Override + protected StringBuilder toStringBuilder() { + String b64Url = Encoders.BASE64URL.encode(this.digest); + return super.toStringBuilder().append(',').append(this.digestName).append('=').append(b64Url); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof DefaultProtectedJwt) { + DefaultProtectedJwt pjwt = (DefaultProtectedJwt) obj; + return super.equals(pjwt) && MessageDigest.isEqual(this.digest, pjwt.digest); + } + return false; + } + + @Override + public int hashCode() { + return Objects.nullSafeHashCode(getHeader(), getPayload(), this.digest); + } +} Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultTextCodecFactory.java'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultTokenizedJwe.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultTokenizedJwe.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultTokenizedJwe.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021 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.Header; + +import java.util.Map; + +class DefaultTokenizedJwe extends DefaultTokenizedJwt implements TokenizedJwe { + + private final CharSequence encryptedKey; + private final CharSequence iv; + + DefaultTokenizedJwe(CharSequence protectedHeader, CharSequence body, CharSequence digest, + CharSequence encryptedKey, CharSequence iv) { + super(protectedHeader, body, digest); + this.encryptedKey = encryptedKey; + this.iv = iv; + } + + @Override + public CharSequence getEncryptedKey() { + return this.encryptedKey; + } + + @Override + public CharSequence getIv() { + return this.iv; + } + + @Override + public Header createHeader(Map m) { + return new DefaultJweHeader(m); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultTokenizedJwt.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultTokenizedJwt.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DefaultTokenizedJwt.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 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.Header; +import io.jsonwebtoken.lang.Strings; + +import java.util.Map; + +class DefaultTokenizedJwt implements TokenizedJwt { + + private final CharSequence protectedHeader; + private final CharSequence payload; + private final CharSequence digest; + + DefaultTokenizedJwt(CharSequence protectedHeader, CharSequence payload, CharSequence digest) { + this.protectedHeader = protectedHeader; + this.payload = payload; + this.digest = digest; + } + + @Override + public CharSequence getProtected() { + return this.protectedHeader; + } + + @Override + public CharSequence getPayload() { + return this.payload; + } + + @Override + public CharSequence getDigest() { + return this.digest; + } + + @Override + public Header createHeader(Map m) { + if (Strings.hasText(getDigest())) { + return new DefaultJwsHeader(m); + } + return new DefaultHeader(m); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DelegateAudienceCollection.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DelegateAudienceCollection.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DelegateAudienceCollection.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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

    implements ClaimsMutator.AudienceCollection

    { + + 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

    add(String s) { + delegate.add(s); + return this; + } + + @Override + public ClaimsMutator.AudienceCollection

    add(Collection c) { + delegate.add(c); + return this; + } + + @Override + public ClaimsMutator.AudienceCollection

    clear() { + delegate.clear(); + return this; + } + + @Override + public ClaimsMutator.AudienceCollection

    remove(String s) { + delegate.remove(s); + return this; + } + + @Override + public P and() { + delegate.and(); // allow any cleanup/finalization + return parent; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DelegatingClaimsMutator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DelegatingClaimsMutator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/DelegatingClaimsMutator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,182 @@ +/* + * 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.DelegatingMapMutator; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.MapMutator; +import io.jsonwebtoken.lang.Strings; + +import java.util.Date; +import java.util.Map; +import java.util.Set; + +/** + * @param subclass type + * @since 0.12.0 + */ +public class DelegatingClaimsMutator & ClaimsMutator> + extends DelegatingMapMutator + implements ClaimsMutator { + + private static final Parameter AUDIENCE_STRING = + Parameters.string(DefaultClaims.AUDIENCE.getId(), DefaultClaims.AUDIENCE.getName()); + + protected DelegatingClaimsMutator() { + super(new ParameterMap(DefaultClaims.PARAMS)); + } + + T put(Parameter param, F value) { + this.DELEGATE.put(param, value); + return self(); + } + + @Override // override starting in 0.12.4 + public Object put(String key, Object value) { + if (AUDIENCE_STRING.getId().equals(key)) { // https://github.com/jwtk/jjwt/issues/890 + if (value instanceof String) { + Object existing = get(key); + //noinspection deprecation + audience().single((String) value); + return existing; + } + // otherwise ensure that the Parameter type is the RFC-default data type (JSON Array of Strings): + getAudience(); + } + // otherwise retain expected behavior: + return super.put(key, value); + } + + @Override // overridden starting in 0.12.4 + public void putAll(Map m) { + if (m == null) return; + for (Map.Entry entry : m.entrySet()) { + String s = entry.getKey(); + put(s, entry.getValue()); // ensure local put is called per https://github.com/jwtk/jjwt/issues/890 + } + } + + F get(Parameter param) { + return this.DELEGATE.get(param); + } + + @Override + public T setIssuer(String iss) { + return issuer(iss); + } + + @Override + public T issuer(String iss) { + return put(DefaultClaims.ISSUER, iss); + } + + @Override + public T setSubject(String sub) { + return subject(sub); + } + + @Override + public T subject(String sub) { + return put(DefaultClaims.SUBJECT, sub); + } + + @Override + public T setAudience(String aud) { + //noinspection deprecation + return audience().single(aud); + } + + private Set getAudience() { + // caller expects that we're working with a String so ensure that: + if (!this.DELEGATE.PARAMS.get(AUDIENCE_STRING.getId()).supports(Collections.emptySet())) { + String existing = get(AUDIENCE_STRING); + remove(AUDIENCE_STRING.getId()); // clear out any canonical/idiomatic values since we're replacing + setDelegate(this.DELEGATE.replace(DefaultClaims.AUDIENCE)); + put(DefaultClaims.AUDIENCE, Collections.setOf(existing)); // replace as Set + } + return get(DefaultClaims.AUDIENCE); + } + + private T audienceSingle(String aud) { + if (!Strings.hasText(aud)) { + return put(DefaultClaims.AUDIENCE, null); + } + // 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: + remove(AUDIENCE_STRING.getId()); //remove any existing value, as conversion will throw an exception + setDelegate(this.DELEGATE.replace(AUDIENCE_STRING)); + return put(AUDIENCE_STRING, aud); + } + + @Override + public AudienceCollection audience() { + return new AbstractAudienceCollection(self(), getAudience()) { + @Override + public T single(String audience) { + return audienceSingle(audience); + // DO NOT call changed() here - we don't want to replace the value with a collection + } + + @Override + protected void changed() { + put(DefaultClaims.AUDIENCE, Collections.asSet(getCollection())); + } + }; + } + + @Override + public T setExpiration(Date exp) { + return expiration(exp); + } + + @Override + public T expiration(Date exp) { + return put(DefaultClaims.EXPIRATION, exp); + } + + @Override + public T setNotBefore(Date nbf) { + return notBefore(nbf); + } + + @Override + public T notBefore(Date nbf) { + return put(DefaultClaims.NOT_BEFORE, nbf); + } + + @Override + public T setIssuedAt(Date iat) { + return issuedAt(iat); + } + + @Override + public T issuedAt(Date iat) { + return put(DefaultClaims.ISSUED_AT, iat); + } + + @Override + public T setId(String jti) { + return id(jti); + } + + @Override + public T id(String jti) { + return put(DefaultClaims.JTI, jti); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/FixedClock.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/FixedClock.java (.../FixedClock.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/FixedClock.java (.../FixedClock.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -47,6 +47,16 @@ this.now = now; } + /** + * Creates a new fixed clock using the specified seed timestamp. All calls to + * {@link #now now()} will always return this seed Date. + * + * @param timeInMillis the specified Date in milliseconds to always return from all calls to {@link #now now()}. + */ + public FixedClock(long timeInMillis) { + this(new Date(timeInMillis)); + } + @Override public Date now() { return this.now; Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/IdLocator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/IdLocator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/IdLocator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2021 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.Header; +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.JweHeader; +import io.jsonwebtoken.JwsHeader; +import io.jsonwebtoken.Locator; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.impl.lang.Function; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Registry; +import io.jsonwebtoken.lang.Strings; + +public class IdLocator implements Locator, Function { + + private final Parameter param; + private final String requiredMsg; + private final boolean valueRequired; + + private final Registry registry; + + public IdLocator(Parameter param, Registry registry, String requiredExceptionMessage) { + this.param = Assert.notNull(param, "Header param cannot be null."); + this.requiredMsg = Strings.clean(requiredExceptionMessage); + this.valueRequired = Strings.hasText(this.requiredMsg); + Assert.notEmpty(registry, "Registry cannot be null or empty."); + this.registry = registry; + } + + private static String type(Header header) { + if (header instanceof JweHeader) { + return "JWE"; + } else if (header instanceof JwsHeader) { + return "JWS"; + } else { + return "JWT"; + } + } + + @Override + public R locate(Header header) { + Assert.notNull(header, "Header argument cannot be null."); + + Object val = header.get(this.param.getId()); + String id = val != null ? val.toString() : null; + + if (!Strings.hasText(id)) { + if (this.valueRequired) { + throw new MalformedJwtException(requiredMsg); + } + return null; // otherwise header value not required, so short circuit + } + + try { + return registry.forKey(id); + } catch (Exception e) { + String msg = "Unrecognized " + type(header) + " " + this.param + " header value: " + id; + throw new UnsupportedJwtException(msg, e); + } + } + + @Override + public R apply(H header) { + return locate(header); + } +} \ No newline at end of file Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/ImmutableJwtParser.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/JwtMap.java'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/JwtTokenizer.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/JwtTokenizer.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/JwtTokenizer.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2021 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.MalformedJwtException; +import io.jsonwebtoken.impl.io.Streams; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Strings; + +import java.io.IOException; +import java.io.Reader; + +public class JwtTokenizer { + + static final char DELIMITER = '.'; + + private static final String DELIM_ERR_MSG_PREFIX = "Invalid compact JWT string: Compact JWSs must contain " + + "exactly 2 period characters, and compact JWEs must contain exactly 4. Found: "; + + private static int read(Reader r, char[] buf) { + try { + return r.read(buf); + } catch (IOException e) { + String msg = "Unable to read compact JWT: " + e.getMessage(); + throw new MalformedJwtException(msg, e); + } + } + + @SuppressWarnings("unchecked") + public T tokenize(Reader reader) { + + Assert.notNull(reader, "Reader argument cannot be null."); + + CharSequence protectedHeader = Strings.EMPTY; //Both JWS and JWE + CharSequence body = Strings.EMPTY; //JWS payload or JWE Ciphertext + CharSequence encryptedKey = Strings.EMPTY; //JWE only + CharSequence iv = Strings.EMPTY; //JWE only + CharSequence digest = Strings.EMPTY; //JWS Signature or JWE AAD Tag + + int delimiterCount = 0; + char[] buf = new char[4096]; + int len = 0; + StringBuilder sb = new StringBuilder(4096); + while (len != Streams.EOF) { + + len = read(reader, buf); + + for (int i = 0; i < len; i++) { + + char c = buf[i]; + + if (Character.isWhitespace(c)) { + String msg = "Compact JWT strings may not contain whitespace."; + throw new MalformedJwtException(msg); + } + + if (c == DELIMITER) { + + CharSequence seq = Strings.clean(sb); + String token = seq != null ? seq.toString() : Strings.EMPTY; + + switch (delimiterCount) { + case 0: + protectedHeader = token; + break; + case 1: + body = token; //for JWS + encryptedKey = token; //for JWE + break; + case 2: + body = Strings.EMPTY; //clear out value set for JWS + iv = token; + break; + case 3: + body = token; + break; + } + + delimiterCount++; + sb.setLength(0); + } else { + sb.append(c); + } + } + } + + if (delimiterCount != 2 && delimiterCount != 4) { + String msg = DELIM_ERR_MSG_PREFIX + delimiterCount; + throw new MalformedJwtException(msg); + } + + if (sb.length() > 0) { + digest = sb.toString(); + } + + if (delimiterCount == 2) { + return (T) new DefaultTokenizedJwt(protectedHeader, body, digest); + } + + return (T) new DefaultTokenizedJwe(protectedHeader, body, digest, encryptedKey, iv); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/ParameterMap.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/ParameterMap.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/ParameterMap.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2014 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.impl.lang.Nameable; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.impl.lang.RedactedSupplier; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Objects; +import io.jsonwebtoken.lang.Registry; +import io.jsonwebtoken.lang.Strings; + +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +public class ParameterMap implements Map, ParameterReadable, Nameable { + + protected final Registry> PARAMS; + protected final Map values; // canonical values formatted per RFC requirements + protected final Map idiomaticValues; // the values map with any RFC values converted to Java type-safe values where possible + + private final boolean initialized; + + private final boolean mutable; + + public ParameterMap(Set> params) { + this(Parameters.registry(params)); + } + + public ParameterMap(Registry> registry) { // mutable constructor + this(registry, null, true); + } + + /** + * Copy constructor producing an immutable instance. + * + * @param registry registry of idiomatic parameters relevant for the map values + * @param values map values + */ + public ParameterMap(Registry> registry, Map values) { + this(registry, Assert.notNull(values, "Map argument cannot be null."), false); + } + + public ParameterMap(Registry> registry, Map values, boolean mutable) { + Assert.notNull(registry, "Parameter registry cannot be null."); + Assert.notEmpty(registry.values(), "Parameter registry cannot be empty."); + this.PARAMS = registry; + this.values = new LinkedHashMap<>(); + this.idiomaticValues = new LinkedHashMap<>(); + if (!Collections.isEmpty(values)) { + putAll(values); + } + this.mutable = mutable; + this.initialized = true; + } + + private void assertMutable() { + if (initialized && !mutable) { + String msg = getName() + " instance is immutable and may not be modified."; + throw new UnsupportedOperationException(msg); + } + } + + protected ParameterMap replace(Parameter param) { + Registry> registry = Parameters.replace(this.PARAMS, param); + return new ParameterMap(registry, this, this.mutable); + } + + @Override + public String getName() { + return "Map"; + } + + @Override + public T get(Parameter param) { + Assert.notNull(param, "Parameter cannot be null."); + final String id = Assert.hasText(param.getId(), "Parameter id cannot be null or empty."); + Object value = idiomaticValues.get(id); + return param.cast(value); + } + + @Override + public int size() { + return values.size(); + } + + @Override + public boolean isEmpty() { + return values.isEmpty(); + } + + @Override + public boolean containsKey(Object o) { + return values.containsKey(o); + } + + @Override + public boolean containsValue(Object o) { + return values.containsValue(o); + } + + @Override + public Object get(Object o) { + return values.get(o); + } + + /** + * Convenience method to put a value for an idiomatic param. + * + * @param param the param representing the property name to set + * @param value the value to set + * @return the previous value for the param, or {@code null} if there was no previous value + * @since 0.12.0 + */ + protected final Object put(Parameter param, Object value) { + assertMutable(); + Assert.notNull(param, "Parameter cannot be null."); + Assert.hasText(param.getId(), "Parameter id cannot be null or empty."); + return apply(param, value); + } + + @Override + public final Object put(String name, Object value) { + assertMutable(); + name = Assert.notNull(Strings.clean(name), "Member name cannot be null or empty."); + Parameter param = PARAMS.get(name); + if (param != null) { + // standard property, represent it idiomatically: + return put(param, value); + } else { + // non-standard or custom property, just apply directly: + return nullSafePut(name, value); + } + } + + private Object nullSafePut(String name, Object value) { + if (value == null) { + return remove(name); + } else { + this.idiomaticValues.put(name, value); + return this.values.put(name, value); + } + } + + private Object apply(Parameter param, Object rawValue) { + + final String id = param.getId(); + + if (Objects.isEmpty(rawValue)) { + return remove(id); + } + + T idiomaticValue; // preferred Java format + Object canonicalValue; // as required by the RFC + try { + idiomaticValue = param.applyFrom(rawValue); + Assert.notNull(idiomaticValue, "Parameter's resulting idiomaticValue cannot be null."); + canonicalValue = param.applyTo(idiomaticValue); + Assert.notNull(canonicalValue, "Parameter's resulting canonicalValue cannot be null."); + } catch (Exception e) { + StringBuilder sb = new StringBuilder(100); + sb.append("Invalid ").append(getName()).append(" ").append(param).append(" value"); + if (param.isSecret()) { + sb.append(": ").append(RedactedSupplier.REDACTED_VALUE); + } else if (!(rawValue instanceof byte[])) { + // don't print raw byte array gibberish. We can't base64[url] encode it either because that could + // make the exception message confusing: the developer would see an encoded string and could think + // that was the rawValue specified when it wasn't. + sb.append(": ").append(Objects.nullSafeToString(rawValue)); + } + sb.append(". ").append(e.getMessage()); + String msg = sb.toString(); + throw new IllegalArgumentException(msg, e); + } + this.idiomaticValues.put(id, idiomaticValue); + return this.values.put(id, canonicalValue); + } + + @Override + public Object remove(Object key) { + assertMutable(); + this.idiomaticValues.remove(key); + return this.values.remove(key); + } + + @Override + public void putAll(Map m) { + if (m == null) { + return; + } + for (Map.Entry entry : m.entrySet()) { + String s = entry.getKey(); + put(s, entry.getValue()); + } + } + + @Override + public void clear() { + assertMutable(); + this.values.clear(); + this.idiomaticValues.clear(); + } + + @Override + public Set keySet() { + return new KeySet(); + } + + @Override + public Collection values() { + return new ValueSet(); + } + + @Override + public Set> entrySet() { + return new EntrySet(); + } + + @Override + public String toString() { + return values.toString(); + } + + @Override + public int hashCode() { + return values.hashCode(); + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object obj) { + return values.equals(obj); + } + + private abstract class ParameterMapSet extends AbstractSet { + + @Override + public int size() { + return ParameterMap.this.size(); + } + } + + private class KeySet extends ParameterMapSet { + @Override + public Iterator iterator() { + return new KeyIterator(); + } + } + + private class ValueSet extends ParameterMapSet { + @Override + public Iterator iterator() { + return new ValueIterator(); + } + } + + private class EntrySet extends ParameterMapSet> { + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + } + + private abstract class ParameterMapIterator implements Iterator { + + final Iterator> i; + + transient Map.Entry current; + + ParameterMapIterator() { + this.i = ParameterMap.this.values.entrySet().iterator(); + this.current = null; + } + + @Override + public boolean hasNext() { + return i.hasNext(); + } + + protected Map.Entry nextEntry() { + current = i.next(); + return current; + } + + @Override + public void remove() { + if (current == null) { + throw new IllegalStateException(); + } + String key = current.getKey(); + ParameterMap.this.remove(key); + } + } + + private class ValueIterator extends ParameterMapIterator { + @Override + public Object next() { + return nextEntry().getValue(); + } + } + + private class KeyIterator extends ParameterMapIterator { + @Override + public String next() { + return nextEntry().getKey(); + } + } + + private class EntryIterator extends ParameterMapIterator> { + @Override + public Entry next() { + return nextEntry(); + } + } + +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/Payload.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/Payload.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/Payload.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,152 @@ +/* + * 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.Claims; +import io.jsonwebtoken.CompressionCodec; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.impl.io.Streams; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.io.CompressionAlgorithm; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Strings; + +import java.io.InputStream; +import java.io.OutputStream; + +class Payload { + + static final Payload EMPTY = new Payload(Bytes.EMPTY, null); + + private final CharSequence string; + private final byte[] bytes; + private final Claims claims; + private final InputStream inputStream; + private final boolean inputStreamEmpty; + private final String contentType; + private CompressionAlgorithm zip; + private boolean claimsExpected; + + Payload(Claims claims) { + this(claims, null, null, null, null); + } + + Payload(CharSequence content, String contentType) { + this(null, content, null, null, contentType); + } + + Payload(byte[] content, String contentType) { + this(null, null, content, null, contentType); + } + + Payload(InputStream inputStream, String contentType) { + this(null, null, null, inputStream, contentType); + } + + private Payload(Claims claims, CharSequence string, byte[] bytes, InputStream inputStream, String contentType) { + this.claims = claims; + this.string = Strings.clean(string); + this.contentType = Strings.clean(contentType); + InputStream in = inputStream; + byte[] data = Bytes.nullSafe(bytes); + if (Strings.hasText(this.string)) { + data = Strings.utf8(this.string); + } + this.bytes = data; + if (in == null && !Bytes.isEmpty(this.bytes)) { + in = Streams.of(data); + } + this.inputStreamEmpty = in == null; + this.inputStream = this.inputStreamEmpty ? Streams.of(Bytes.EMPTY) : in; + } + + boolean isClaims() { + return !Collections.isEmpty(this.claims); + } + + Claims getRequiredClaims() { + return Assert.notEmpty(this.claims, "Claims cannot be null or empty when calling this method."); + } + + boolean isString() { + return Strings.hasText(this.string); + } + + String getContentType() { + return this.contentType; + } + + public void setZip(CompressionAlgorithm zip) { + this.zip = zip; + } + + boolean isCompressed() { + return this.zip != null; + } + + public void setClaimsExpected(boolean claimsExpected) { + this.claimsExpected = claimsExpected; + } + + /** + * Returns {@code true} if the payload may be fully consumed and retained in memory, {@code false} if empty, + * already extracted, or a potentially too-large InputStream. + * + * @return {@code true} if the payload may be fully consumed and retained in memory, {@code false} if empty, + * already extracted, or a potentially too-large InputStream. + */ + boolean isConsumable() { + return !isClaims() && (isString() || !Bytes.isEmpty(this.bytes) || (inputStream != null && claimsExpected)); + } + + boolean isEmpty() { + return !isClaims() && !isString() && Bytes.isEmpty(this.bytes) && this.inputStreamEmpty; + } + + public OutputStream compress(OutputStream out) { + return this.zip != null ? zip.compress(out) : out; + } + + public Payload decompress(CompressionAlgorithm alg) { + Assert.notNull(alg, "CompressionAlgorithm cannot be null."); + Payload payload = this; + if (!isString() && isConsumable()) { + if (alg.equals(Jwts.ZIP.DEF) && !Bytes.isEmpty(this.bytes)) { // backwards compatibility + byte[] data = ((CompressionCodec) alg).decompress(this.bytes); + payload = new Payload(claims, string, data, null, getContentType()); + } else { + InputStream in = toInputStream(); + in = alg.decompress(in); + payload = new Payload(claims, string, bytes, in, getContentType()); + } + payload.setClaimsExpected(claimsExpected); + } + // otherwise it's a String or b64/detached payload, in either case, we don't decompress since the caller is + // providing the bytes necessary for signature verification as-is, and there's no conversion we need to perform + return payload; + } + + public byte[] getBytes() { + return this.bytes; + } + + InputStream toInputStream() { + // should only ever call this when claims don't exist: + Assert.state(!isClaims(), "Claims exist, cannot convert to InputStream directly."); + return this.inputStream; + } +} Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/TextCodecFactory.java'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/TokenizedJwe.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/TokenizedJwe.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/TokenizedJwe.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 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; + +public interface TokenizedJwe extends TokenizedJwt { + + CharSequence getEncryptedKey(); + + CharSequence getIv(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/TokenizedJwt.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/TokenizedJwt.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/TokenizedJwt.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 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.Header; + +import java.util.Map; + +public interface TokenizedJwt { + + /** + * Protected header. + * + * @return protected header. + */ + CharSequence getProtected(); + + /** + * Returns the Payload for a JWS or Ciphertext for a JWE. + * + * @return the Payload for a JWS or Ciphertext for a JWE. + */ + CharSequence getPayload(); + + /** + * Returns the Signature for JWS or AAD Tag for JWE. + * + * @return the Signature for JWS or AAD Tag for JWE. + */ + CharSequence getDigest(); + + /** + * Returns a new {@link Header} instance with the specified map state. + * + * @param m the header state + * @return a new header instance. + */ + Header createHeader(Map m); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/X509Context.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/X509Context.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/X509Context.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,22 @@ +/* + * 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; + +import io.jsonwebtoken.security.X509Accessor; +import io.jsonwebtoken.security.X509Mutator; + +public interface X509Context> extends X509Accessor, X509Mutator { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/AbstractCompressionAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/AbstractCompressionAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/AbstractCompressionAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2015 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.compression; + +import io.jsonwebtoken.CompressionCodec; +import io.jsonwebtoken.CompressionException; +import io.jsonwebtoken.impl.io.Streams; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.CheckedFunction; +import io.jsonwebtoken.impl.lang.Function; +import io.jsonwebtoken.impl.lang.PropagatingExceptionFunction; +import io.jsonwebtoken.io.CompressionAlgorithm; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Objects; +import io.jsonwebtoken.lang.Strings; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Abstract class that asserts arguments and wraps IOException with CompressionException. + * + * @since 0.6.0 + */ +@SuppressWarnings("deprecation") +public abstract class AbstractCompressionAlgorithm implements CompressionAlgorithm, CompressionCodec { + + private static Function propagate(CheckedFunction fn, String msg) { + return new PropagatingExceptionFunction<>(fn, CompressionException.class, msg); + } + + private static Function forCompression(CheckedFunction fn) { + return propagate(fn, "Compression failed."); + } + + private static Function forDecompression(CheckedFunction fn) { + return propagate(fn, "Decompression failed."); + } + + private final String id; + private final Function OS_WRAP_FN; + private final Function IS_WRAP_FN; + private final Function COMPRESS_FN; + + private final Function DECOMPRESS_FN; + + protected AbstractCompressionAlgorithm(String id) { + this.id = Assert.hasText(Strings.clean(id), "id argument cannot be null or empty."); + this.OS_WRAP_FN = forCompression(new CheckedFunction() { + @Override + public OutputStream apply(OutputStream out) throws Exception { + return doCompress(out); + } + }); + this.COMPRESS_FN = forCompression(new CheckedFunction() { + @Override + public byte[] apply(byte[] data) throws Exception { + return doCompress(data); + } + }); + this.IS_WRAP_FN = forDecompression(new CheckedFunction() { + @Override + public InputStream apply(InputStream is) throws Exception { + return doDecompress(is); + } + }); + this.DECOMPRESS_FN = forDecompression(new CheckedFunction() { + @Override + public byte[] apply(byte[] data) throws Exception { + return doDecompress(data); + } + }); + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String getAlgorithmName() { + return getId(); + } + + @Override + public final OutputStream compress(final OutputStream out) throws CompressionException { + return OS_WRAP_FN.apply(out); + } + + protected abstract OutputStream doCompress(OutputStream out) throws IOException; + + @Override + public final InputStream decompress(InputStream is) throws CompressionException { + return IS_WRAP_FN.apply(is); + } + + protected abstract InputStream doDecompress(InputStream is) throws IOException; + + @Override + public final byte[] compress(byte[] content) { + if (Bytes.isEmpty(content)) return Bytes.EMPTY; + return this.COMPRESS_FN.apply(content); + } + + private byte[] doCompress(byte[] data) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(512); + OutputStream compression = compress(out); + try { + compression.write(data); + compression.flush(); + } finally { + Objects.nullSafeClose(compression); + } + return out.toByteArray(); + } + + + /** + * Asserts the compressed bytes is not null and calls {@link #doDecompress(byte[]) doDecompress} + * + * @param compressed compressed bytes + * @return decompressed bytes + * @throws CompressionException if {@link #doDecompress(byte[]) doDecompress} throws an IOException + */ + @Override + public final byte[] decompress(byte[] compressed) { + if (Bytes.isEmpty(compressed)) return Bytes.EMPTY; + return this.DECOMPRESS_FN.apply(compressed); + } + + /** + * Implement this method to do the actual work of decompressing the compressed bytes. + * + * @param compressed compressed bytes + * @return decompressed bytes + * @throws IOException if the decompression runs into an IO problem + */ + protected byte[] doDecompress(byte[] compressed) throws IOException { + InputStream is = Streams.of(compressed); + InputStream decompress = decompress(is); + byte[] buffer = new byte[512]; + ByteArrayOutputStream out = new ByteArrayOutputStream(buffer.length); + int read = 0; + try { + while (read != Streams.EOF) { + read = decompress.read(buffer); //assignment separate from loop invariant check for code coverage checks + if (read > 0) out.write(buffer, 0, read); + } + } finally { + Objects.nullSafeClose(decompress); + } + return out.toByteArray(); + } +} Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/AbstractCompressionCodec.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/CompressionCodecs.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/DeflateCompressionAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/DeflateCompressionAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/DeflateCompressionAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2015 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.compression; + +import io.jsonwebtoken.lang.Objects; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterInputStream; +import java.util.zip.InflaterOutputStream; + +/** + * Codec implementing the deflate compression algorithm. + * + * @since 0.6.0 + */ +public class DeflateCompressionAlgorithm extends AbstractCompressionAlgorithm { + + private static final String ID = "DEF"; + + public DeflateCompressionAlgorithm() { + super(ID); + } + + @Override + protected OutputStream doCompress(OutputStream out) { + return new DeflaterOutputStream(out); + } + + @Override + protected InputStream doDecompress(InputStream is) { + return new InflaterInputStream(is); + } + + @Override + protected byte[] doDecompress(final byte[] compressed) throws IOException { + try { + return super.doDecompress(compressed); + } catch (IOException e1) { + try { + return doDecompressBackCompat(compressed); + } catch (IOException e2) { + throw e1; //retain/report original exception + } + } + } + + /** + * This implementation was in 0.10.6 and earlier - it will be used as a fallback for backwards compatibility if + * {@link #doDecompress(byte[])} fails per Issue 536. + * + * @param compressed the compressed byte array + * @return decompressed bytes + * @throws IOException if unable to decompress using the 0.10.6 and earlier logic + * @since 0.10.8 + */ + // package protected on purpose + byte[] doDecompressBackCompat(byte[] compressed) throws IOException { + InflaterOutputStream inflaterOutputStream = null; + ByteArrayOutputStream decompressedOutputStream = null; + + try { + decompressedOutputStream = new ByteArrayOutputStream(); + inflaterOutputStream = new InflaterOutputStream(decompressedOutputStream); + inflaterOutputStream.write(compressed); + inflaterOutputStream.flush(); + return decompressedOutputStream.toByteArray(); + } finally { + Objects.nullSafeClose(decompressedOutputStream, inflaterOutputStream); + } + } +} Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/GzipCompressionAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/GzipCompressionAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/GzipCompressionAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 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.compression; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * Codec implementing the gzip compression algorithm. + * + * @since 0.6.0 + */ +public class GzipCompressionAlgorithm extends AbstractCompressionAlgorithm { + + private static final String ID = "GZIP"; + + public GzipCompressionAlgorithm() { + super(ID); + } + + @Override + protected OutputStream doCompress(OutputStream out) throws IOException { + return new GZIPOutputStream(out); + } + + @Override + protected InputStream doDecompress(InputStream is) throws IOException { + return new GZIPInputStream(is); + } +} Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/DefaultJwtSignatureValidator.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/DefaultJwtSigner.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/DefaultSignatureValidatorFactory.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/DefaultSignerFactory.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/EllipticCurveProvider.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidator.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/EllipticCurveSigner.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/JwtSignatureValidator.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/JwtSigner.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/MacProvider.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/MacSigner.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/MacValidator.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/RsaProvider.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/RsaSignatureValidator.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/RsaSigner.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/SignatureProvider.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/SignatureValidator.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/SignatureValidatorFactory.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/Signer.java'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/crypto/SignerFactory.java'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/AbstractParser.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/AbstractParser.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/AbstractParser.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,45 @@ +/* + * 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.io; + +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.lang.Assert; + +import java.io.InputStream; +import java.io.Reader; + +public abstract class AbstractParser implements Parser { + + @Override + public final T parse(CharSequence input) { + Assert.hasText(input, "CharSequence cannot be null or empty."); + return parse(input, 0, input.length()); + } + + @Override + public T parse(CharSequence input, int start, int end) { + Assert.hasText(input, "CharSequence cannot be null or empty."); + Reader reader = new CharSequenceReader(input, start, end); + return parse(reader); + } + + @Override + public final T parse(InputStream in) { + Assert.notNull(in, "InputStream cannot be null."); + Reader reader = Streams.reader(in); + return parse(reader); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/AbstractParserBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/AbstractParserBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/AbstractParserBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,59 @@ +/* + * 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.io; + +import io.jsonwebtoken.impl.lang.Services; +import io.jsonwebtoken.io.Deserializer; +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.io.ParserBuilder; + +import java.security.Provider; +import java.util.Map; + +public abstract class AbstractParserBuilder> implements ParserBuilder { + + protected Provider provider; + + protected Deserializer> deserializer; + + @SuppressWarnings("unchecked") + protected final B self() { + return (B) this; + } + + @Override + public B provider(Provider provider) { + this.provider = provider; + return self(); + } + + @Override + public B json(Deserializer> reader) { + this.deserializer = reader; + return self(); + } + + @Override + public final Parser build() { + if (this.deserializer == null) { + //noinspection unchecked + this.deserializer = Services.get(Deserializer.class); + } + return doBuild(); + } + + protected abstract Parser doBuild(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Base64Codec.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Base64Codec.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Base64Codec.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,796 @@ +/* + * 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.io; + +import io.jsonwebtoken.lang.Strings; + +/** + * Provides Base64 encoding and decoding as defined by RFC 2045. + * + *

    + * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose + * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. + *

    + *

    + * The class can be parameterized in the following manner with various constructors: + *

    + *
      + *
    • URL-safe mode: Default off.
    • + *
    • Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of + * 4 in the encoded data. + *
    • Line separator: Default is CRLF ("\r\n")
    • + *
    + *

    + * The URL-safe parameter is only applied to encode operations. Decoding seamlessly handles both modes. + *

    + *

    + * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only + * encode/decode character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, + * UTF-8, etc). + *

    + *

    + * This class is thread-safe. + *

    + * + * @see RFC 2045 + * @since 0.12.0, copied from + * commons-codec + * 585497f09b026f6602daf986723a554e051bdfe6 + */ +class Base64Codec extends BaseNCodec { + + /** + * BASE64 characters are 6 bits in length. + * They are formed by taking a block of 3 octets to form a 24-bit string, + * which is converted into 4 BASE64 characters. + */ + private static final int BITS_PER_ENCODED_BYTE = 6; + private static final int BYTES_PER_UNENCODED_BLOCK = 3; + private static final int BYTES_PER_ENCODED_BLOCK = 4; + + /** + * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" + * equivalents as specified in Table 1 of RFC 2045. + *

    + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

    + */ + private static final byte[] STANDARD_ENCODE_TABLE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + }; + + /** + * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / + * changed to - and _ to make the encoded Base64 results more URL-SAFE. + * This table is only used when the Base64's mode is set to URL-SAFE. + */ + private static final byte[] URL_SAFE_ENCODE_TABLE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' + }; + + /** + * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified + * in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64 + * alphabet but fall within the bounds of the array are translated to -1. + *

    + * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both + * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit). + *

    + *

    + * Thanks to "commons" project in ws.apache.org for this code. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

    + */ + // @formatter:off + private static final byte[] DECODE_TABLE = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, // 20-2f + - / + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 30-3f 0-9 + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-O + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, // 50-5f P-Z _ + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60-6f a-o + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 // 70-7a p-z + }; + // @formatter:on + + // The static final fields above are used for the original static byte[] methods on Base64. + // The private member fields below are used with the new streaming approach, which requires + // some state be preserved between calls of encode() and decode(). + + /* Base64 uses 6-bit fields. */ + /** + * Mask used to extract 6 bits, used when encoding + */ + private static final int MASK_6BITS = 0x3f; + /** + * Mask used to extract 4 bits, used when decoding final trailing character. + */ + private static final int MASK_4BITS = 0xf; + /** + * Mask used to extract 2 bits, used when decoding final trailing character. + */ + private static final int MASK_2BITS = 0x3; + + /** + * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able + * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch + * between the two modes. + */ + private final byte[] encodeTable; + + /** + * Only one decode table currently; keep for consistency with Base32 code. + */ + private final byte[] decodeTable = DECODE_TABLE; + + /** + * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. + */ + private final byte[] lineSeparator; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * {@code decodeSize = 3 + lineSeparator.length;} + */ + private final int decodeSize; + + /** + * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. + * {@code encodeSize = 4 + lineSeparator.length;} + */ + private final int encodeSize; + +// /** +// * Decodes Base64 data into octets. +// *

    +// * Note: this method seamlessly handles data encoded in URL-safe or normal mode. +// *

    +// * +// * @param base64Data Byte array containing Base64 data +// * @return Array containing decoded data. +// */ +// public static byte[] decodeBase64(final byte[] base64Data) { +// return new Base64().decode(base64Data); +// } +// +// /** +// * Decodes a Base64 String into octets. +// *

    +// * Note: this method seamlessly handles data encoded in URL-safe or normal mode. +// *

    +// * +// * @param base64String String containing Base64 data +// * @return Array containing decoded data. +// * @since 1.4 +// */ +// public static byte[] decodeBase64(final String base64String) { +// return new Base64().decode(base64String); +// } +// +// // Implementation of integer encoding used for crypto +// +// /** +// * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. +// * +// * @param pArray a byte array containing base64 character data +// * @return A BigInteger +// * @since 1.4 +// */ +// public static BigInteger decodeInteger(final byte[] pArray) { +// return new BigInteger(1, decodeBase64(pArray)); +// } +// +// /** +// * Encodes binary data using the base64 algorithm but does not chunk the output. +// * +// * @param binaryData binary data to encode +// * @return byte[] containing Base64 characters in their UTF-8 representation. +// */ +// public static byte[] encodeBase64(final byte[] binaryData) { +// return encodeBase64(binaryData, false); +// } +// +// /** +// * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. +// * +// * @param binaryData Array containing binary data to encode. +// * @param isChunked if {@code true} this encoder will chunk the base64 output into 76 character blocks +// * @return Base64-encoded data. +// * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} +// */ +// public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) { +// return encodeBase64(binaryData, isChunked, false); +// } +// +// /** +// * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. +// * +// * @param binaryData Array containing binary data to encode. +// * @param isChunked if {@code true} this encoder will chunk the base64 output into 76 character blocks +// * @param urlSafe if {@code true} this encoder will emit - and _ instead of the usual + and / characters. +// * Note: no padding is added when encoding using the URL-safe alphabet. +// * @return Base64-encoded data. +// * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} +// * @since 1.4 +// */ +// public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) { +// return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE); +// } +// +// /** +// * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. +// * +// * @param binaryData Array containing binary data to encode. +// * @param isChunked if {@code true} this encoder will chunk the base64 output into 76 character blocks +// * @param urlSafe if {@code true} this encoder will emit - and _ instead of the usual + and / characters. +// * Note: no padding is added when encoding using the URL-safe alphabet. +// * @param maxResultSize The maximum result size to accept. +// * @return Base64-encoded data. +// * @throws IllegalArgumentException Thrown when the input array needs an output array bigger than maxResultSize +// * @since 1.4 +// */ +// public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, +// final boolean urlSafe, final int maxResultSize) { +// if (Bytes.isEmpty(binaryData)) { +// return binaryData; +// } +// +// // Create this so can use the super-class method +// // Also ensures that the same roundings are performed by the ctor and the code +// final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); +// final long len = b64.getEncodedLength(binaryData); +// if (len > maxResultSize) { +// throw new IllegalArgumentException("Input array too big, the output array would be bigger (" + +// len + +// ") than the specified maximum size of " + +// maxResultSize); +// } +// +// return b64.encode(binaryData); +// } +// +// /** +// * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks +// * +// * @param binaryData binary data to encode +// * @return Base64 characters chunked in 76 character blocks +// */ +// public static byte[] encodeBase64Chunked(final byte[] binaryData) { +// return encodeBase64(binaryData, true); +// } +// +// /** +// * Encodes binary data using the base64 algorithm but does not chunk the output. +// *

    +// * NOTE: We changed the behavior of this method from multi-line chunking (commons-codec-1.4) to +// * single-line non-chunking (commons-codec-1.5). +// * +// * @param binaryData binary data to encode +// * @return String containing Base64 characters. +// * @since 1.4 (NOTE: 1.4 chunked the output, whereas 1.5 does not). +// */ +// public static String encodeBase64String(final byte[] binaryData) { +// byte[] encoded = encodeBase64(binaryData, false); +// return new String(encoded, StandardCharsets.US_ASCII); +// } +// +// /** +// * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The +// * url-safe variation emits - and _ instead of + and / characters. +// * Note: no padding is added. +// * +// * @param binaryData binary data to encode +// * @return byte[] containing Base64 characters in their UTF-8 representation. +// * @since 1.4 +// */ +// public static byte[] encodeBase64URLSafe(final byte[] binaryData) { +// return encodeBase64(binaryData, false, true); +// } +// +// /** +// * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The +// * url-safe variation emits - and _ instead of + and / characters. +// * Note: no padding is added. +// * +// * @param binaryData binary data to encode +// * @return String containing Base64 characters +// * @since 1.4 +// */ +// public static String encodeBase64URLSafeString(final byte[] binaryData) { +// byte[] encoded = encodeBase64(binaryData, false, true); +// return new String(encoded, StandardCharsets.US_ASCII); +// } +// +// /** +// * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature. +// * +// * @param bigInteger a BigInteger +// * @return A byte array containing base64 character data +// * @throws NullPointerException if null is passed in +// * @since 1.4 +// */ +// public static byte[] encodeInteger(final BigInteger bigInteger) { +// Objects.requireNonNull(bigInteger, "bigInteger"); +// return encodeBase64(toIntegerBytes(bigInteger), false); +// } +// +// /** +// * Returns whether or not the {@code octet} is in the base 64 alphabet. +// * +// * @param octet The value to test +// * @return {@code true} if the value is defined in the base 64 alphabet, {@code false} otherwise. +// * @since 1.4 +// */ +// public static boolean isBase64(final byte octet) { +// return octet == PAD_DEFAULT || octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1; +// } +// +// /** +// * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the +// * method treats whitespace as valid. +// * +// * @param arrayOctet byte array to test +// * @return {@code true} if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; +// * {@code false}, otherwise +// * @since 1.5 +// */ +// public static boolean isBase64(final byte[] arrayOctet) { +// for (final byte element : arrayOctet) { +// if (!isBase64(element) && !Character.isWhitespace(element)) { +// return false; +// } +// } +// return true; +// } +// +// /** +// * Tests a given String to see if it contains only valid characters within the Base64 alphabet. Currently the +// * method treats whitespace as valid. +// * +// * @param base64 String to test +// * @return {@code true} if all characters in the String are valid characters in the Base64 alphabet or if +// * the String is empty; {@code false}, otherwise +// * @since 1.5 +// */ +// public static boolean isBase64(final String base64) { +// return isBase64(Strings.utf8(base64)); +// } +// +// /** +// * Returns a byte-array representation of a {@code BigInteger} without sign bit. +// * +// * @param bigInt {@code BigInteger} to be converted +// * @return a byte array representation of the BigInteger parameter +// */ +// static byte[] toIntegerBytes(final BigInteger bigInt) { +// int bitlen = bigInt.bitLength(); +// // round bitlen +// bitlen = bitlen + 7 >> 3 << 3; +// final byte[] bigBytes = bigInt.toByteArray(); +// +// if (bigInt.bitLength() % 8 != 0 && bigInt.bitLength() / 8 + 1 == bitlen / 8) { +// return bigBytes; +// } +// // set up params for copying everything but sign bit +// int startSrc = 0; +// int len = bigBytes.length; +// +// // if bigInt is exactly byte-aligned, just skip signbit in copy +// if (bigInt.bitLength() % 8 == 0) { +// startSrc = 1; +// len--; +// } +// final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec +// final byte[] resizedBytes = new byte[bitlen / 8]; +// System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); +// return resizedBytes; +// } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

    + * When encoding the line length is 0 (no chunking), and the encoding table is STANDARD_ENCODE_TABLE. + *

    + * + *

    + * When decoding all variants are supported. + *

    + */ + Base64Codec() { + this(0); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode. + *

    + * When encoding the line length is 76, the line separator is CRLF, and the encoding table is STANDARD_ENCODE_TABLE. + *

    + * + *

    + * When decoding all variants are supported. + *

    + * + * @param urlSafe if {@code true}, URL-safe encoding is used. In most cases this should be set to + * {@code false}. + * @since 1.4 + */ + Base64Codec(final boolean urlSafe) { + this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

    + * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

    + *

    + * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

    + *

    + * When decoding all variants are supported. + *

    + * + * @param lineLength Each line of encoded data will be at most of the given length (rounded down to the nearest multiple of + * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when + * decoding. + * @since 1.4 + */ + Base64Codec(final int lineLength) { + this(lineLength, CHUNK_SEPARATOR); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

    + * When encoding the line length and line separator are given in the constructor, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

    + *

    + * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

    + *

    + * When decoding all variants are supported. + *

    + * + * @param lineLength Each line of encoded data will be at most of the given length (rounded down to the nearest multiple of + * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when + * decoding. + * @param lineSeparator Each line of encoded data will end with this sequence of bytes. + * @throws IllegalArgumentException Thrown when the provided lineSeparator included some base64 characters. + * @since 1.4 + */ + Base64Codec(final int lineLength, final byte[] lineSeparator) { + this(lineLength, lineSeparator, false); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

    + * When encoding the line length and line separator are given in the constructor, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

    + *

    + * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

    + *

    + * When decoding all variants are supported. + *

    + * + * @param lineLength Each line of encoded data will be at most of the given length (rounded down to the nearest multiple of + * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when + * decoding. + * @param lineSeparator Each line of encoded data will end with this sequence of bytes. + * @param urlSafe Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode + * operations. Decoding seamlessly handles both modes. + * Note: no padding is added when using the URL-safe alphabet. + * @throws IllegalArgumentException Thrown when the {@code lineSeparator} contains Base64 characters. + * @since 1.4 + */ + Base64Codec(final int lineLength, final byte[] lineSeparator, final boolean urlSafe) { + this(lineLength, lineSeparator, urlSafe, DECODING_POLICY_DEFAULT); + } + + /** + * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. + *

    + * When encoding the line length and line separator are given in the constructor, and the encoding table is + * STANDARD_ENCODE_TABLE. + *

    + *

    + * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. + *

    + *

    + * When decoding all variants are supported. + *

    + * + * @param lineLength Each line of encoded data will be at most of the given length (rounded down to the nearest multiple of + * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when + * decoding. + * @param lineSeparator Each line of encoded data will end with this sequence of bytes. + * @param urlSafe Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode + * operations. Decoding seamlessly handles both modes. + * Note: no padding is added when using the URL-safe alphabet. + * @param decodingPolicy The decoding policy. + * @throws IllegalArgumentException Thrown when the {@code lineSeparator} contains Base64 characters. + * @since 1.15 + */ + Base64Codec(final int lineLength, final byte[] lineSeparator, final boolean urlSafe, final CodecPolicy decodingPolicy) { + super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, lineLength, BaseNCodec.length(lineSeparator), + PAD_DEFAULT, decodingPolicy); + // TODO could be simplified if there is no requirement to reject invalid line sep when length <=0 + // @see test case Base64Test.testConstructors() + if (lineSeparator != null) { + if (containsAlphabetOrPad(lineSeparator)) { + final String sep = Strings.utf8(lineSeparator); + throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]"); + } + if (lineLength > 0) { // null line-sep forces no chunking rather than throwing IAE + this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length; + this.lineSeparator = lineSeparator.clone(); + } else { + this.encodeSize = BYTES_PER_ENCODED_BLOCK; + this.lineSeparator = null; + } + } else { + this.encodeSize = BYTES_PER_ENCODED_BLOCK; + this.lineSeparator = null; + } + this.decodeSize = this.encodeSize - 1; + this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; + } + + // Implementation of the Encoder Interface + + /** + *

    + * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once + * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" + * call is not necessary when decoding, but it doesn't hurt, either. + *

    + *

    + * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are + * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, + * garbage-out philosophy: it will not check the provided data for validity. + *

    + *

    + * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

    + * + * @param input byte[] array of ASCII data to base64 decode. + * @param inPos Position to start reading data from. + * @param inAvail Amount of bytes available from input for decoding. + * @param context the context to be used + */ + @Override + void decode(final byte[] input, int inPos, final int inAvail, final Context context) { + if (context.eof) { + return; + } + if (inAvail < 0) { + context.eof = true; + } + for (int i = 0; i < inAvail; i++) { + final byte[] buffer = ensureBufferSize(decodeSize, context); + final byte b = input[inPos++]; + if (b == pad) { + // We're done. + context.eof = true; + break; + } + if (b >= 0 && b < DECODE_TABLE.length) { + final int result = DECODE_TABLE[b]; + if (result >= 0) { + context.modulus = (context.modulus + 1) % BYTES_PER_ENCODED_BLOCK; + context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result; + if (context.modulus == 0) { + buffer[context.pos++] = (byte) (context.ibitWorkArea >> 16 & MASK_8BITS); + buffer[context.pos++] = (byte) (context.ibitWorkArea >> 8 & MASK_8BITS); + buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS); + } + } + } + } + + // Two forms of EOF as far as base64 decoder is concerned: actual + // EOF (-1) and first time '=' character is encountered in stream. + // This approach makes the '=' padding characters completely optional. + if (context.eof && context.modulus != 0) { + final byte[] buffer = ensureBufferSize(decodeSize, context); + + // We have some spare bits remaining + // Output all whole multiples of 8 bits and ignore the rest + switch (context.modulus) { +// case 0 : // impossible, as excluded above + case 1: // 6 bits - either ignore entirely, or raise an exception + validateTrailingCharacter(); + break; + case 2: // 12 bits = 8 + 4 + validateCharacter(MASK_4BITS, context); + context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits + buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS); + break; + case 3: // 18 bits = 8 + 8 + 2 + validateCharacter(MASK_2BITS, context); + context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits + buffer[context.pos++] = (byte) (context.ibitWorkArea >> 8 & MASK_8BITS); + buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS); + break; + default: + throw new IllegalStateException("Impossible modulus " + context.modulus); + } + } + } + + /** + *

    + * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with + * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, to flush last + * remaining bytes (if not multiple of 3). + *

    + *

    Note: no padding is added when encoding using the URL-safe alphabet.

    + *

    + * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. + * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ + *

    + * + * @param in byte[] array of binary data to base64 encode. + * @param inPos Position to start reading data from. + * @param inAvail Amount of bytes available from input for encoding. + * @param context the context to be used + */ + @Override + void encode(final byte[] in, int inPos, final int inAvail, final Context context) { + if (context.eof) { + return; + } + // inAvail < 0 is how we're informed of EOF in the underlying data we're + // encoding. + if (inAvail < 0) { + context.eof = true; + if (0 == context.modulus && lineLength == 0) { + return; // no leftovers to process and not using chunking + } + final byte[] buffer = ensureBufferSize(encodeSize, context); + final int savedPos = context.pos; + switch (context.modulus) { // 0-2 + case 0: // nothing to do here + break; + case 1: // 8 bits = 6 + 2 + // top 6 bits: + buffer[context.pos++] = encodeTable[context.ibitWorkArea >> 2 & MASK_6BITS]; + // remaining 2: + buffer[context.pos++] = encodeTable[context.ibitWorkArea << 4 & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[context.pos++] = pad; + buffer[context.pos++] = pad; + } + break; + + case 2: // 16 bits = 6 + 6 + 4 + buffer[context.pos++] = encodeTable[context.ibitWorkArea >> 10 & MASK_6BITS]; + buffer[context.pos++] = encodeTable[context.ibitWorkArea >> 4 & MASK_6BITS]; + buffer[context.pos++] = encodeTable[context.ibitWorkArea << 2 & MASK_6BITS]; + // URL-SAFE skips the padding to further reduce size. + if (encodeTable == STANDARD_ENCODE_TABLE) { + buffer[context.pos++] = pad; + } + break; + default: + throw new IllegalStateException("Impossible modulus " + context.modulus); + } + context.currentLinePos += context.pos - savedPos; // keep track of current line position + // if currentPos == 0 we are at the start of a line, so don't add CRLF + if (lineLength > 0 && context.currentLinePos > 0) { + System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); + context.pos += lineSeparator.length; + } + } else { + for (int i = 0; i < inAvail; i++) { + final byte[] buffer = ensureBufferSize(encodeSize, context); + context.modulus = (context.modulus + 1) % BYTES_PER_UNENCODED_BLOCK; + int b = in[inPos++]; + if (b < 0) { + b += 256; + } + context.ibitWorkArea = (context.ibitWorkArea << 8) + b; // BITS_PER_BYTE + if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract + buffer[context.pos++] = encodeTable[context.ibitWorkArea >> 18 & MASK_6BITS]; + buffer[context.pos++] = encodeTable[context.ibitWorkArea >> 12 & MASK_6BITS]; + buffer[context.pos++] = encodeTable[context.ibitWorkArea >> 6 & MASK_6BITS]; + buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS]; + context.currentLinePos += BYTES_PER_ENCODED_BLOCK; + if (lineLength > 0 && lineLength <= context.currentLinePos) { + System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); + context.pos += lineSeparator.length; + context.currentLinePos = 0; + } + } + } + } + } + + /** + * Returns whether or not the {@code octet} is in the Base64 alphabet. + * + * @param octet The value to test + * @return {@code true} if the value is defined in the Base64 alphabet {@code false} otherwise. + */ + @Override + protected boolean isInAlphabet(final byte octet) { + return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1; + } + + /** + * Returns our current encode mode. True if we're URL-SAFE, false otherwise. + * + * @return true if we're in URL-SAFE mode, false otherwise. + * @since 1.4 + */ + public boolean isUrlSafe() { + return this.encodeTable == URL_SAFE_ENCODE_TABLE; + } + + /** + * Validates whether decoding the final trailing character is possible in the context + * of the set of possible base 64 values. + *

    + * The character is valid if the lower bits within the provided mask are zero. This + * is used to test the final trailing base-64 digit is zero in the bits that will be discarded. + *

    + * + * @param emptyBitsMask The mask of the lower bits that should be empty + * @param context the context to be used + * @throws IllegalArgumentException if the bits being checked contain any non-zero value + */ + private void validateCharacter(final int emptyBitsMask, final Context context) { + if (isStrictDecoding() && (context.ibitWorkArea & emptyBitsMask) != 0) { + throw new IllegalArgumentException( + "Strict decoding: Last encoded character (before the paddings if any) is a valid " + + "base 64 alphabet but not a possible encoding. " + + "Expected the discarded bits from the character to be zero."); + } + } + + /** + * Validates whether decoding allows an entire final trailing character that cannot be + * used for a complete byte. + * + * @throws IllegalArgumentException if strict decoding is enabled + */ + private void validateTrailingCharacter() { + if (isStrictDecoding()) { + throw new IllegalArgumentException( + "Strict decoding: Last encoded character (before the paddings if any) is a valid " + + "base 64 alphabet but not a possible encoding. " + + "Decoding requires at least two trailing 6-bit characters to create bytes."); + } + } + +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Base64InputStream.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Base64InputStream.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Base64InputStream.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,114 @@ +/* + * 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.io; + +import java.io.InputStream; + +/** + * Provides Base64 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength + * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate + * constructor. + *

    + * The default behavior of the Base64InputStream is to DECODE, whereas the default behavior of the Base64OutputStream + * is to ENCODE, but this behavior can be overridden by using a different constructor. + *

    + *

    + * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose + * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. + *

    + *

    + * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode + * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). + *

    + *

    + * You can set the decoding behavior when the input bytes contain leftover trailing bits that cannot be created by a + * valid encoding. These can be bits that are unused from the final character or entire characters. The default mode is + * lenient decoding. + *

    + *
      + *
    • Lenient: Any trailing bits are composed into 8-bit bytes where possible. The remainder are discarded. + *
    • Strict: The decoding will raise an {@link IllegalArgumentException} if trailing bits are not part of a valid + * encoding. Any unused bits from the final character must be zero. Impossible counts of entire final characters are not + * allowed. + *
    + *

    + * When strict decoding is enabled it is expected that the decoded bytes will be re-encoded to a byte array that matches + * the original, i.e. no changes occur on the final character. This requires that the input bytes use the same padding + * and alphabet as the encoder. + *

    + * + * @see RFC 2045 + * @since 0.12.0, copied from + * commons-codec + * 585497f09b026f6602daf986723a554e051bdfe6 + */ +public class Base64InputStream extends BaseNCodecInputStream { + + /** + * Creates a Base64InputStream such that all data read is Base64-decoded from the original provided InputStream. + * + * @param inputStream InputStream to wrap. + */ + public Base64InputStream(final InputStream inputStream) { + this(inputStream, false); + } + + /** + * Creates a Base64InputStream such that all data read is either Base64-encoded or Base64-decoded from the original + * provided InputStream. + * + * @param inputStream InputStream to wrap. + * @param doEncode true if we should encode all data read from us, false if we should decode. + */ + Base64InputStream(final InputStream inputStream, final boolean doEncode) { + super(inputStream, new Base64Codec(0, BaseNCodec.CHUNK_SEPARATOR, false, CodecPolicy.STRICT), doEncode); + } + +// /** +// * Creates a Base64InputStream such that all data read is either Base64-encoded or Base64-decoded from the original +// * provided InputStream. +// * +// * @param inputStream InputStream to wrap. +// * @param doEncode true if we should encode all data read from us, false if we should decode. +// * @param lineLength If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to +// * the nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If +// * doEncode is false, lineLength is ignored. +// * @param lineSeparator If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). +// * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. +// */ +// Base64InputStream(final InputStream inputStream, final boolean doEncode, final int lineLength, final byte[] lineSeparator) { +// super(inputStream, new Base64Codec(lineLength, lineSeparator), doEncode); +// } +// +// /** +// * Creates a Base64InputStream such that all data read is either Base64-encoded or Base64-decoded from the original +// * provided InputStream. +// * +// * @param inputStream InputStream to wrap. +// * @param doEncode true if we should encode all data read from us, false if we should decode. +// * @param lineLength If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to +// * the nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If +// * doEncode is false, lineLength is ignored. +// * @param lineSeparator If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). +// * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. +// * @param decodingPolicy The decoding policy. +// * @since 1.15 +// */ +// Base64InputStream(final InputStream inputStream, final boolean doEncode, final int lineLength, final byte[] lineSeparator, +// final CodecPolicy decodingPolicy) { +// super(inputStream, new Base64Codec(lineLength, lineSeparator, false, decodingPolicy), doEncode); +// } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Base64OutputStream.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Base64OutputStream.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Base64OutputStream.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,118 @@ +/* + * 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.io; + +import java.io.OutputStream; + +/** + * Provides Base64 encoding and decoding in a streaming fashion (unlimited size). When encoding the default lineLength + * is 76 characters and the default lineEnding is CRLF, but these can be overridden by using the appropriate + * constructor. + *

    + * The default behavior of the Base64OutputStream is to ENCODE, whereas the default behavior of the Base64InputStream + * is to DECODE. But this behavior can be overridden by using a different constructor. + *

    + *

    + * This class implements section 6.8. Base64 Content-Transfer-Encoding from RFC 2045 Multipurpose + * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies by Freed and Borenstein. + *

    + *

    + * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only encode/decode + * character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, UTF-8, etc). + *

    + *

    + * Note: It is mandatory to close the stream after the last byte has been written to it, otherwise the + * final padding will be omitted and the resulting data will be incomplete/inconsistent. + *

    + *

    + * You can set the decoding behavior when the input bytes contain leftover trailing bits that cannot be created by a + * valid encoding. These can be bits that are unused from the final character or entire characters. The default mode is + * lenient decoding. + *

    + *
      + *
    • Lenient: Any trailing bits are composed into 8-bit bytes where possible. The remainder are discarded. + *
    • Strict: The decoding will raise an {@link IllegalArgumentException} if trailing bits are not part of a valid + * encoding. Any unused bits from the final character must be zero. Impossible counts of entire final characters are not + * allowed. + *
    + *

    + * When strict decoding is enabled it is expected that the decoded bytes will be re-encoded to a byte array that matches + * the original, i.e. no changes occur on the final character. This requires that the input bytes use the same padding + * and alphabet as the encoder. + *

    + * + * @see RFC 2045 + * @since 0.12.0, copied from + * commons-codec + * 585497f09b026f6602daf986723a554e051bdfe6 + */ +class Base64OutputStream extends BaseNCodecOutputStream { + + /** + * Creates a Base64OutputStream such that all data written is Base64-encoded to the original provided OutputStream. + * + * @param outputStream OutputStream to wrap. + */ + Base64OutputStream(final OutputStream outputStream) { + this(outputStream, true, true); + } + + /** + * Creates a Base64OutputStream such that all data written is either Base64-encoded or Base64-decoded to the + * original provided OutputStream. + * + * @param outputStream OutputStream to wrap. + * @param doEncode true if we should encode all data written to us, false if we should decode. + */ + Base64OutputStream(final OutputStream outputStream, final boolean doEncode, boolean urlSafe) { + super(outputStream, new Base64Codec(0, BaseNCodec.CHUNK_SEPARATOR, urlSafe), doEncode); + } + + /** + * Creates a Base64OutputStream such that all data written is either Base64-encoded or Base64-decoded to the + * original provided OutputStream. + * + * @param outputStream OutputStream to wrap. + * @param doEncode true if we should encode all data written to us, false if we should decode. + * @param lineLength If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to + * the nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If + * doEncode is false, lineLength is ignored. + * @param lineSeparator If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). + * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. + */ + Base64OutputStream(final OutputStream outputStream, final boolean doEncode, final int lineLength, final byte[] lineSeparator) { + super(outputStream, new Base64Codec(lineLength, lineSeparator), doEncode); + } + + /** + * Creates a Base64OutputStream such that all data written is either Base64-encoded or Base64-decoded to the + * original provided OutputStream. + * + * @param outputStream OutputStream to wrap. + * @param doEncode true if we should encode all data written to us, false if we should decode. + * @param lineLength If doEncode is true, each line of encoded data will contain lineLength characters (rounded down to + * the nearest multiple of 4). If lineLength <= 0, the encoded data is not divided into lines. If + * doEncode is false, lineLength is ignored. + * @param lineSeparator If doEncode is true, each line of encoded data will be terminated with this byte sequence (e.g. \r\n). + * If lineLength <= 0, the lineSeparator is not used. If doEncode is false lineSeparator is ignored. + * @param decodingPolicy The decoding policy. + * @since 1.15 + */ + Base64OutputStream(final OutputStream outputStream, final boolean doEncode, final int lineLength, + final byte[] lineSeparator, final CodecPolicy decodingPolicy) { + super(outputStream, new Base64Codec(lineLength, lineSeparator, false, decodingPolicy), doEncode); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Base64UrlStreamEncoder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Base64UrlStreamEncoder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Base64UrlStreamEncoder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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.io; + +import io.jsonwebtoken.io.Encoder; +import io.jsonwebtoken.io.EncodingException; + +import java.io.OutputStream; + +public final class Base64UrlStreamEncoder implements Encoder { + + public static final Base64UrlStreamEncoder INSTANCE = new Base64UrlStreamEncoder(); + + private Base64UrlStreamEncoder() { + } + + @Override + public OutputStream encode(OutputStream outputStream) throws EncodingException { + return new Base64OutputStream(outputStream); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/BaseNCodec.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/BaseNCodec.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/BaseNCodec.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,660 @@ +/* + * 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.io; + +import io.jsonwebtoken.lang.Strings; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Abstract superclass for Base-N encoders and decoders. + * + *

    + * This class is thread-safe. + *

    + *

    + * You can set the decoding behavior when the input bytes contain leftover trailing bits that cannot be created by a + * valid encoding. These can be bits that are unused from the final character or entire characters. The default mode is + * lenient decoding. + *

    + *
      + *
    • Lenient: Any trailing bits are composed into 8-bit bytes where possible. The remainder are discarded. + *
    • Strict: The decoding will raise an {@link IllegalArgumentException} if trailing bits are not part of a valid + * encoding. Any unused bits from the final character must be zero. Impossible counts of entire final characters are not + * allowed. + *
    + *

    + * When strict decoding is enabled it is expected that the decoded bytes will be re-encoded to a byte array that matches + * the original, i.e. no changes occur on the final character. This requires that the input bytes use the same padding + * and alphabet as the encoder. + *

    + * + * @since 0.12.0, copied from + * commons-codec + * 585497f09b026f6602daf986723a554e051bdfe6 + */ +abstract class BaseNCodec { + + /** + * EOF + */ + static final int EOF = -1; + + /** + * MIME chunk size per RFC 2045 section 6.8. + * + *

    + * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any + * equal signs. + *

    + * + * @see RFC 2045 section 6.8 + */ + public static final int MIME_CHUNK_SIZE = 76; + +// /** +// * PEM chunk size per RFC 1421 section 4.3.2.4. +// * +// *

    +// * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any +// * equal signs. +// *

    +// * +// * @see RFC 1421 section 4.3.2.4 +// */ +// public static final int PEM_CHUNK_SIZE = 64; + + private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2; + + /** + * Defines the default buffer size - currently {@value} + * - must be large enough for at least one encoded block+separator + */ + private static final int DEFAULT_BUFFER_SIZE = 8192; + + /** + * The maximum size buffer to allocate. + * + *

    This is set to the same size used in the JDK {@code java.util.ArrayList}:

    + *
    + * Some VMs reserve some header words in an array. + * Attempts to allocate larger arrays may result in + * OutOfMemoryError: Requested array size exceeds VM limit. + *
    + */ + private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; + + /** + * Mask used to extract 8 bits, used in decoding bytes + */ + protected static final int MASK_8BITS = 0xff; + + /** + * Byte used to pad output. + */ + protected static final byte PAD_DEFAULT = '='; // Allow static access to default + + /** + * The default decoding policy. + * + * @since 1.15 + */ + protected static final CodecPolicy DECODING_POLICY_DEFAULT = CodecPolicy.LENIENT; + + /** + * Chunk separator per RFC 2045 section 2.1. + * + * @see RFC 2045 section 2.1 + */ + static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; + + /** + * Pad byte. Instance variable just in case it needs to vary later. + */ + protected final byte pad; + + /** + * Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 + */ + private final int unencodedBlockSize; + + /** + * Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 + */ + private final int encodedBlockSize; + + /** + * Chunksize for encoding. Not used when decoding. + * A value of zero or less implies no chunking of the encoded data. + * Rounded down to the nearest multiple of encodedBlockSize. + */ + protected final int lineLength; + + /** + * Size of chunk separator. Not used unless {@link #lineLength} > 0. + */ + private final int chunkSeparatorLength; + + /** + * Defines the decoding behavior when the input bytes contain leftover trailing bits that + * cannot be created by a valid encoding. These can be bits that are unused from the final + * character or entire characters. The default mode is lenient decoding. Set this to + * {@code true} to enable strict decoding. + *
      + *
    • Lenient: Any trailing bits are composed into 8-bit bytes where possible. + * The remainder are discarded. + *
    • Strict: The decoding will raise an {@link IllegalArgumentException} if trailing bits + * are not part of a valid encoding. Any unused bits from the final character must + * be zero. Impossible counts of entire final characters are not allowed. + *
    + *

    + * When strict decoding is enabled it is expected that the decoded bytes will be re-encoded + * to a byte array that matches the original, i.e. no changes occur on the final + * character. This requires that the input bytes use the same padding and alphabet + * as the encoder. + *

    + */ + private final CodecPolicy decodingPolicy; + + /** + * Holds thread context so classes can be thread-safe. + *

    + * This class is not itself thread-safe; each thread must allocate its own copy. + * + * @since 1.7 + */ + static class Context { + + /** + * Placeholder for the bytes we're dealing with for our based logic. + * Bitwise operations store and extract the encoding or decoding from this variable. + */ + int ibitWorkArea; + + /** + * Placeholder for the bytes we're dealing with for our based logic. + * Bitwise operations store and extract the encoding or decoding from this variable. + */ + long lbitWorkArea; + + /** + * Buffer for streaming. + */ + byte[] buffer; + + /** + * Position where next character should be written in the buffer. + */ + int pos; + + /** + * Position where next character should be read from the buffer. + */ + int readPos; + + /** + * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless, + * and must be thrown away. + */ + boolean eof; + + /** + * Variable tracks how many characters have been written to the current line. Only used when encoding. We use + * it to make sure each encoded line never goes beyond lineLength (if lineLength > 0). + */ + int currentLinePos; + + /** + * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding. This + * variable helps track that. + */ + int modulus; + + /** + * Returns a String useful for debugging (especially within a debugger.) + * + * @return a String useful for debugging. + */ + @Override + public String toString() { + return String.format("%s[buffer=%s, currentLinePos=%s, eof=%s, ibitWorkArea=%s, lbitWorkArea=%s, " + + "modulus=%s, pos=%s, readPos=%s]", getClass().getSimpleName(), Arrays.toString(buffer), + currentLinePos, eof, ibitWorkArea, lbitWorkArea, modulus, pos, readPos); + } + } + + /** + * Create a positive capacity at least as large the minimum required capacity. + * If the minimum capacity is negative then this throws an OutOfMemoryError as no array + * can be allocated. + * + * @param minCapacity the minimum capacity + * @return the capacity + * @throws OutOfMemoryError if the {@code minCapacity} is negative + */ + private static int createPositiveCapacity(final int minCapacity) { + if (minCapacity < 0) { + // overflow + throw new OutOfMemoryError("Unable to allocate array size: " + (minCapacity & 0xffffffffL)); + } + // This is called when we require buffer expansion to a very big array. + // Use the conservative maximum buffer size if possible, otherwise the biggest required. + // + // Note: In this situation JDK 1.8 java.util.ArrayList returns Integer.MAX_VALUE. + // This excludes some VMs that can exceed MAX_BUFFER_SIZE but not allocate a full + // Integer.MAX_VALUE length array. + // The result is that we may have to allocate an array of this size more than once if + // the capacity must be expanded again. + return Math.max(minCapacity, MAX_BUFFER_SIZE); + } + +// /** +// * Gets a copy of the chunk separator per RFC 2045 section 2.1. +// * +// * @return the chunk separator +// * @see RFC 2045 section 2.1 +// * @since 1.15 +// */ +// public static byte[] getChunkSeparator() { +// return CHUNK_SEPARATOR.clone(); +// } + + /** + * Checks if a byte value is whitespace or not. + * + * @param byteToCheck the byte to check + * @return true if byte is whitespace, false otherwise + * @see Character#isWhitespace(int) + * @deprecated Use {@link Character#isWhitespace(int)}. + */ + @Deprecated + protected static boolean isWhiteSpace(final byte byteToCheck) { + return Character.isWhitespace(byteToCheck); + } + + private static int compareUnsigned(int x, int y) { + return Integer.compare(x + Integer.MIN_VALUE, y + Integer.MIN_VALUE); + } + + /** + * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}. + * + * @param context the context to be used + * @param minCapacity the minimum required capacity + * @return the resized byte[] buffer + * @throws OutOfMemoryError if the {@code minCapacity} is negative + */ + private static byte[] resizeBuffer(final Context context, final int minCapacity) { + // Overflow-conscious code treats the min and new capacity as unsigned. + final int oldCapacity = context.buffer.length; + int newCapacity = oldCapacity * DEFAULT_BUFFER_RESIZE_FACTOR; + if (compareUnsigned(newCapacity, minCapacity) < 0) { + newCapacity = minCapacity; + } + if (compareUnsigned(newCapacity, MAX_BUFFER_SIZE) > 0) { + newCapacity = createPositiveCapacity(minCapacity); + } + + final byte[] b = Arrays.copyOf(context.buffer, newCapacity); + context.buffer = b; + return b; + } + + /** + * Note {@code lineLength} is rounded down to the nearest multiple of the encoded block size. + * If {@code chunkSeparatorLength} is zero, then chunking is disabled. + * + * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) + * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) + * @param lineLength if > 0, use chunking with a length {@code lineLength} + * @param chunkSeparatorLength the chunk separator length, if relevant + */ + protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, + final int lineLength, final int chunkSeparatorLength) { + this(unencodedBlockSize, encodedBlockSize, lineLength, chunkSeparatorLength, PAD_DEFAULT); + } + + /** + * Note {@code lineLength} is rounded down to the nearest multiple of the encoded block size. + * If {@code chunkSeparatorLength} is zero, then chunking is disabled. + * + * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) + * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) + * @param lineLength if > 0, use chunking with a length {@code lineLength} + * @param chunkSeparatorLength the chunk separator length, if relevant + * @param pad byte used as padding byte. + */ + protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, + final int lineLength, final int chunkSeparatorLength, final byte pad) { + this(unencodedBlockSize, encodedBlockSize, lineLength, chunkSeparatorLength, pad, DECODING_POLICY_DEFAULT); + } + + /** + * Note {@code lineLength} is rounded down to the nearest multiple of the encoded block size. + * If {@code chunkSeparatorLength} is zero, then chunking is disabled. + * + * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) + * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) + * @param lineLength if > 0, use chunking with a length {@code lineLength} + * @param chunkSeparatorLength the chunk separator length, if relevant + * @param pad byte used as padding byte. + * @param decodingPolicy Decoding policy. + * @since 1.15 + */ + protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, + final int lineLength, final int chunkSeparatorLength, final byte pad, + final CodecPolicy decodingPolicy) { + this.unencodedBlockSize = unencodedBlockSize; + this.encodedBlockSize = encodedBlockSize; + final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0; + this.lineLength = useChunking ? lineLength / encodedBlockSize * encodedBlockSize : 0; + this.chunkSeparatorLength = chunkSeparatorLength; + this.pad = pad; + this.decodingPolicy = Objects.requireNonNull(decodingPolicy, "codecPolicy"); + } + + /** + * Returns the amount of buffered data available for reading. + * + * @param context the context to be used + * @return The amount of buffered data available for reading. + */ + int available(final Context context) { // package protected for access from I/O streams + return hasData(context) ? context.pos - context.readPos : 0; + } + + /** + * Tests a given byte array to see if it contains any characters within the alphabet or PAD. + *

    + * Intended for use in checking line-ending arrays + * + * @param arrayOctet byte array to test + * @return {@code true} if any byte is a valid character in the alphabet or PAD; {@code false} otherwise + */ + protected boolean containsAlphabetOrPad(final byte[] arrayOctet) { + if (arrayOctet == null) { + return false; + } + for (final byte element : arrayOctet) { + if (pad == element || isInAlphabet(element)) { + return true; + } + } + return false; + } + + static int length(byte[] bytes) { + return bytes != null ? bytes.length : 0; + } + + static boolean isEmpty(byte[] bytes) { + return length(bytes) == 0; + } + + /** + * Decodes a byte[] containing characters in the Base-N alphabet. + * + * @param pArray A byte array containing Base-N character data + * @return a byte array containing binary data + */ + public byte[] decode(final byte[] pArray) { + if (isEmpty(pArray)) { + return pArray; + } + final Context context = new Context(); + decode(pArray, 0, pArray.length, context); + decode(pArray, 0, EOF, context); // Notify decoder of EOF. + final byte[] result = new byte[context.pos]; + readResults(result, 0, result.length, context); + return result; + } + + // package protected for access from I/O streams + abstract void decode(byte[] pArray, int i, int length, Context context); + + /** + * Decodes a String containing characters in the Base-N alphabet. + * + * @param pArray A String containing Base-N character data + * @return a byte array containing binary data + */ + public byte[] decode(final String pArray) { + return decode(Strings.utf8(pArray)); + } + + /** + * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet. + * + * @param pArray a byte array containing binary data + * @return A byte array containing only the base N alphabetic character data + */ + public byte[] encode(final byte[] pArray) { + if (isEmpty(pArray)) { + return pArray; + } + return encode(pArray, 0, pArray.length); + } + + /** + * Encodes a byte[] containing binary data, into a byte[] containing + * characters in the alphabet. + * + * @param pArray a byte array containing binary data + * @param offset initial offset of the subarray. + * @param length length of the subarray. + * @return A byte array containing only the base N alphabetic character data + */ + public byte[] encode(final byte[] pArray, final int offset, final int length) { + if (isEmpty(pArray)) { + return pArray; + } + final Context context = new Context(); + encode(pArray, offset, length, context); + encode(pArray, offset, EOF, context); // Notify encoder of EOF. + final byte[] buf = new byte[context.pos - context.readPos]; + readResults(buf, 0, buf.length, context); + return buf; + } + + // package protected for access from I/O streams + abstract void encode(byte[] pArray, int i, int length, Context context); + + /** + * Encodes a byte[] containing binary data, into a String containing characters in the appropriate alphabet. + * Uses UTF8 encoding. + * + * @param pArray a byte array containing binary data + * @return String containing only character data in the appropriate alphabet. + * @since 1.5 + * This is a duplicate of {@link #encodeToString(byte[])}; it was merged during refactoring. + */ + public String encodeAsString(final byte[] pArray) { + return Strings.utf8(encode(pArray)); + } + + /** + * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet. + * Uses UTF8 encoding. + * + * @param pArray a byte array containing binary data + * @return A String containing only Base-N character data + */ + public String encodeToString(final byte[] pArray) { + return Strings.utf8(encode(pArray)); + } + + /** + * Ensure that the buffer has room for {@code size} bytes + * + * @param size minimum spare space required + * @param context the context to be used + * @return the buffer + */ + protected byte[] ensureBufferSize(final int size, final Context context) { + if (context.buffer == null) { + context.buffer = new byte[Math.max(size, getDefaultBufferSize())]; + context.pos = 0; + context.readPos = 0; + + // Overflow-conscious: + // x + y > z == x + y - z > 0 + } else if (context.pos + size - context.buffer.length > 0) { + return resizeBuffer(context, context.pos + size); + } + return context.buffer; + } + +// /** +// * Returns the decoding behavior policy. +// * +// *

    +// * The default is lenient. If the decoding policy is strict, then decoding will raise an +// * {@link IllegalArgumentException} if trailing bits are not part of a valid encoding. Decoding will compose +// * trailing bits into 8-bit bytes and discard the remainder. +// *

    +// * +// * @return true if using strict decoding +// * @since 1.15 +// */ +// public CodecPolicy getCodecPolicy() { +// return decodingPolicy; +// } + + /** + * Get the default buffer size. Can be overridden. + * + * @return the default buffer size. + */ + protected int getDefaultBufferSize() { + return DEFAULT_BUFFER_SIZE; + } + + /** + * Calculates the amount of space needed to encode the supplied array. + * + * @param pArray byte[] array which will later be encoded + * @return amount of space needed to encode the supplied array. + * Returns a long since a max-len array will require > Integer.MAX_VALUE + */ + public long getEncodedLength(final byte[] pArray) { + // Calculate non-chunked size - rounded up to allow for padding + // cast to long is needed to avoid possibility of overflow + long len = (pArray.length + unencodedBlockSize - 1) / unencodedBlockSize * (long) encodedBlockSize; + if (lineLength > 0) { // We're using chunking + // Round up to nearest multiple + len += (len + lineLength - 1) / lineLength * chunkSeparatorLength; + } + return len; + } + + /** + * Returns true if this object has buffered data for reading. + * + * @param context the context to be used + * @return true if there is data still available for reading. + */ + boolean hasData(final Context context) { // package protected for access from I/O streams + return context.pos > context.readPos; + } + + /** + * Returns whether or not the {@code octet} is in the current alphabet. + * Does not allow whitespace or pad. + * + * @param value The value to test + * @return {@code true} if the value is defined in the current alphabet, {@code false} otherwise. + */ + protected abstract boolean isInAlphabet(byte value); + + /** + * Tests a given byte array to see if it contains only valid characters within the alphabet. + * The method optionally treats whitespace and pad as valid. + * + * @param arrayOctet byte array to test + * @param allowWSPad if {@code true}, then whitespace and PAD are also allowed + * @return {@code true} if all bytes are valid characters in the alphabet or if the byte array is empty; + * {@code false}, otherwise + */ + public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) { + for (final byte octet : arrayOctet) { + if (!isInAlphabet(octet) && + (!allowWSPad || octet != pad && !Character.isWhitespace(octet))) { + return false; + } + } + return true; + } + + /** + * Tests a given String to see if it contains only valid characters within the alphabet. + * The method treats whitespace and PAD as valid. + * + * @param basen String to test + * @return {@code true} if all characters in the String are valid characters in the alphabet or if + * the String is empty; {@code false}, otherwise + * @see #isInAlphabet(byte[], boolean) + */ + public boolean isInAlphabet(final String basen) { + return isInAlphabet(Strings.utf8(basen), true); + } + + /** + * Returns true if decoding behavior is strict. Decoding will raise an {@link IllegalArgumentException} if trailing + * bits are not part of a valid encoding. + * + *

    + * The default is false for lenient decoding. Decoding will compose trailing bits into 8-bit bytes and discard the + * remainder. + *

    + * + * @return true if using strict decoding + * @since 1.15 + */ + public boolean isStrictDecoding() { + return decodingPolicy == CodecPolicy.STRICT; + } + + /** + * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail + * bytes. Returns how many bytes were actually extracted. + *

    + * Package private for access from I/O streams. + *

    + * + * @param b byte[] array to extract the buffered data into. + * @param bPos position in byte[] array to start extraction at. + * @param bAvail amount of bytes we're allowed to extract. We may extract fewer (if fewer are available). + * @param context the context to be used + * @return The number of bytes successfully extracted into the provided byte[] array. + */ + int readResults(final byte[] b, final int bPos, final int bAvail, final Context context) { + if (hasData(context)) { + final int len = Math.min(available(context), bAvail); + System.arraycopy(context.buffer, context.readPos, b, bPos, len); + context.readPos += len; + if (!hasData(context)) { + // All data read. + // Reset position markers but do not set buffer to null to allow its reuse. + // hasData(context) will still return false, and this method will return 0 until + // more data is available, or -1 if EOF. + context.pos = context.readPos = 0; + } + return len; + } + return context.eof ? EOF : 0; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/BaseNCodecInputStream.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/BaseNCodecInputStream.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/BaseNCodecInputStream.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,236 @@ +/* + * 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.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; + +/** + * Abstract superclass for Base-N input streams. + * + * @since 0.12.0, copied from + * commons-codec + * 585497f09b026f6602daf986723a554e051bdfe6 + */ +class BaseNCodecInputStream extends FilterInputStream { + + private final BaseNCodec baseNCodec; + + private final boolean doEncode; + + private final byte[] singleByte = new byte[1]; + + private final byte[] buf; + + private final BaseNCodec.Context context = new BaseNCodec.Context(); + + /** + * Create an instance. + * + * @param inputStream the input stream + * @param baseNCodec the codec + * @param doEncode set to true to perform encoding, else decoding + */ + protected BaseNCodecInputStream(final InputStream inputStream, final BaseNCodec baseNCodec, final boolean doEncode) { + super(inputStream); + this.doEncode = doEncode; + this.baseNCodec = baseNCodec; + this.buf = new byte[doEncode ? 4096 : 8192]; + } + + /** + * {@inheritDoc} + * + * @return {@code 0} if the {@link InputStream} has reached {@code EOF}, + * {@code 1} otherwise + * @since 1.7 + */ + @Override + public int available() throws IOException { + // Note: the logic is similar to the InflaterInputStream: + // as long as we have not reached EOF, indicate that there is more + // data available. As we do not know for sure how much data is left, + // just return 1 as a safe guess. + + return context.eof ? 0 : 1; + } + + /** + * Returns true if decoding behavior is strict. Decoding will raise an + * {@link IllegalArgumentException} if trailing bits are not part of a valid encoding. + * + *

    + * The default is false for lenient encoding. Decoding will compose trailing bits + * into 8-bit bytes and discard the remainder. + *

    + * + * @return true if using strict decoding + * @since 1.15 + */ + public boolean isStrictDecoding() { + return baseNCodec.isStrictDecoding(); + } + + /** + * Marks the current position in this input stream. + *

    + * The {@link #mark} method of {@link BaseNCodecInputStream} does nothing. + *

    + * + * @param readLimit the maximum limit of bytes that can be read before the mark position becomes invalid. + * @see #markSupported() + * @since 1.7 + */ + @Override + public synchronized void mark(final int readLimit) { + // noop + } + + /** + * {@inheritDoc} + * + * @return Always returns {@code false} + */ + @Override + public boolean markSupported() { + return false; // not an easy job to support marks + } + + /** + * Reads one {@code byte} from this input stream. + * + * @return the byte as an integer in the range 0 to 255. Returns -1 if EOF has been reached. + * @throws IOException if an I/O error occurs. + */ + @Override + public int read() throws IOException { + int r = read(singleByte, 0, 1); + while (r == 0) { + r = read(singleByte, 0, 1); + } + if (r > 0) { + final byte b = singleByte[0]; + return b < 0 ? 256 + b : b; + } + return BaseNCodec.EOF; + } + + /** + * Attempts to read {@code len} bytes into the specified {@code b} array starting at {@code offset} + * from this InputStream. + * + * @param array destination byte array + * @param offset where to start writing the bytes + * @param len maximum number of bytes to read + * @return number of bytes read + * @throws IOException if an I/O error occurs. + * @throws NullPointerException if the byte array parameter is null + * @throws IndexOutOfBoundsException if offset, len or buffer size are invalid + */ + @Override + public int read(final byte[] array, final int offset, final int len) throws IOException { + Objects.requireNonNull(array, "array"); + if (offset < 0 || len < 0) { + throw new IndexOutOfBoundsException(); + } + if (offset > array.length || offset + len > array.length) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return 0; + } + int readLen = 0; + /* + Rationale for while-loop on (readLen == 0): + ----- + Base32.readResults() usually returns > 0 or EOF (-1). In the + rare case where it returns 0, we just keep trying. + + This is essentially an undocumented contract for InputStream + implementors that want their code to work properly with + java.io.InputStreamReader, since the latter hates it when + InputStream.read(byte[]) returns a zero. Unfortunately our + readResults() call must return 0 if a large amount of the data + being decoded was non-base32, so this while-loop enables proper + interop with InputStreamReader for that scenario. + ----- + This is a fix for CODEC-101 + */ + // Attempt to read the request length + while (readLen < len) { + if (!baseNCodec.hasData(context)) { + // Obtain more data. + // buf is reused across calls to read to avoid repeated allocations + final int c = in.read(buf); + if (doEncode) { + baseNCodec.encode(buf, 0, c, context); + } else { + baseNCodec.decode(buf, 0, c, context); + } + } + final int read = baseNCodec.readResults(array, offset + readLen, len - readLen, context); + if (read < 0) { + // Return the amount read or EOF + return readLen != 0 ? readLen : -1; + } + readLen += read; + } + return readLen; + } + + /** + * Repositions this stream to the position at the time the mark method was last called on this input stream. + *

    + * The {@link #reset} method of {@link BaseNCodecInputStream} does nothing except throw an {@link IOException}. + * + * @throws IOException if this method is invoked + * @since 1.7 + */ + @Override + public synchronized void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + /** + * {@inheritDoc} + * + * @throws IllegalArgumentException if the provided skip length is negative + * @since 1.7 + */ + @Override + public long skip(final long n) throws IOException { + if (n < 0) { + throw new IllegalArgumentException("Negative skip length: " + n); + } + + // skip in chunks of 512 bytes + final byte[] b = new byte[512]; + long todo = n; + + while (todo > 0) { + int len = (int) Math.min(b.length, todo); + len = this.read(b, 0, len); + if (len == BaseNCodec.EOF) { + break; + } + todo -= len; + } + + return n - todo; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/BaseNCodecOutputStream.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/BaseNCodecOutputStream.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/BaseNCodecOutputStream.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,180 @@ +/* + * 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.io; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Objects; + +/** + * Abstract superclass for Base-N output streams. + *

    + * To write the EOF marker without closing the stream, call {@link #eof()} or use an Apache Commons IO CloseShieldOutputStream. + *

    + * + * @since 0.12.0, copied from + * commons-codec + * 585497f09b026f6602daf986723a554e051bdfe6 + */ +class BaseNCodecOutputStream extends FilterOutputStream { + + private final boolean doEncode; + + private final BaseNCodec baseNCodec; + + private final byte[] singleByte = new byte[1]; + + private final BaseNCodec.Context context = new BaseNCodec.Context(); + + /** + * TODO should this be protected? + * + * @param outputStream the underlying output or null. + * @param basedCodec a BaseNCodec. + * @param doEncode true to encode, false to decode, TODO should be an enum? + */ + BaseNCodecOutputStream(final OutputStream outputStream, final BaseNCodec basedCodec, final boolean doEncode) { + super(outputStream); + this.baseNCodec = basedCodec; + this.doEncode = doEncode; + } + + /** + * Closes this output stream and releases any system resources associated with the stream. + *

    + * To write the EOF marker without closing the stream, call {@link #eof()} or use an + * Apache Commons IO CloseShieldOutputStream. + *

    + * + * @throws IOException if an I/O error occurs. + */ + @Override + public void close() throws IOException { + eof(); + flush(); + out.close(); + } + + /** + * Writes EOF. + * + * @since 1.11 + */ + public void eof() { + // Notify encoder of EOF (-1). + if (doEncode) { + baseNCodec.encode(singleByte, 0, BaseNCodec.EOF, context); + } else { + baseNCodec.decode(singleByte, 0, BaseNCodec.EOF, context); + } + } + + /** + * Flushes this output stream and forces any buffered output bytes to be written out to the stream. + * + * @throws IOException if an I/O error occurs. + */ + @Override + public void flush() throws IOException { + flush(true); + } + + /** + * Flushes this output stream and forces any buffered output bytes to be written out to the stream. If propagate is + * true, the wrapped stream will also be flushed. + * + * @param propagate boolean flag to indicate whether the wrapped OutputStream should also be flushed. + * @throws IOException if an I/O error occurs. + */ + private void flush(final boolean propagate) throws IOException { + final int avail = baseNCodec.available(context); + if (avail > 0) { + final byte[] buf = new byte[avail]; + final int c = baseNCodec.readResults(buf, 0, avail, context); + if (c > 0) { + out.write(buf, 0, c); + } + } + if (propagate) { + out.flush(); + } + } + + /** + * Returns true if decoding behavior is strict. Decoding will raise an + * {@link IllegalArgumentException} if trailing bits are not part of a valid encoding. + * + *

    + * The default is false for lenient encoding. Decoding will compose trailing bits + * into 8-bit bytes and discard the remainder. + *

    + * + * @return true if using strict decoding + * @since 1.15 + */ + public boolean isStrictDecoding() { + return baseNCodec.isStrictDecoding(); + } + + /** + * Writes {@code len} bytes from the specified {@code b} array starting at {@code offset} to this + * output stream. + * + * @param array source byte array + * @param offset where to start reading the bytes + * @param len maximum number of bytes to write + * @throws IOException if an I/O error occurs. + * @throws NullPointerException if the byte array parameter is null + * @throws IndexOutOfBoundsException if offset, len or buffer size are invalid + */ + @Override + public void write(final byte[] array, final int offset, final int len) throws IOException { + Objects.requireNonNull(array, "array"); + if (offset < 0 || len < 0) { + throw new IndexOutOfBoundsException(); + } + if (offset > array.length || offset + len > array.length) { + throw new IndexOutOfBoundsException(); + } + if (len > 0) { + if (doEncode) { + baseNCodec.encode(array, offset, len, context); + } else { + baseNCodec.decode(array, offset, len, context); + } + flush(false); + } + } + + /** + * Writes the specified {@code byte} to this output stream. + * + * @param i source byte + * @throws IOException if an I/O error occurs. + */ + @Override + public void write(final int i) throws IOException { + singleByte[0] = (byte) i; + write(singleByte, 0, 1); + } + +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/ByteBase64UrlStreamEncoder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/ByteBase64UrlStreamEncoder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/ByteBase64UrlStreamEncoder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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.impl.io; + +import io.jsonwebtoken.io.Encoder; +import io.jsonwebtoken.io.EncodingException; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Strings; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class ByteBase64UrlStreamEncoder implements Encoder { + + private final Encoder delegate; + + public ByteBase64UrlStreamEncoder(Encoder delegate) { + this.delegate = Assert.notNull(delegate, "delegate cannot be null."); + } + + @Override + public OutputStream encode(OutputStream outputStream) throws EncodingException { + Assert.notNull(outputStream, "outputStream cannot be null."); + return new TranslatingOutputStream(outputStream, delegate); + } + + private static class TranslatingOutputStream extends FilteredOutputStream { + + private final OutputStream dst; + private final Encoder delegate; + + public TranslatingOutputStream(OutputStream dst, Encoder delegate) { + super(new ByteArrayOutputStream()); + this.dst = dst; + this.delegate = delegate; + } + + @Override + public void close() throws IOException { + byte[] data = ((ByteArrayOutputStream) out).toByteArray(); + String s = delegate.encode(data); + dst.write(Strings.utf8(s)); + dst.flush(); + dst.close(); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/BytesInputStream.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/BytesInputStream.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/BytesInputStream.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,43 @@ +/* + * 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.io; + +import io.jsonwebtoken.impl.lang.Bytes; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +/** + * Allows read access to the internal byte array, avoiding the need copy/extract to a + * {@link java.io.ByteArrayOutputStream}. + * + * @since 0.12.2 + */ +public final class BytesInputStream extends ByteArrayInputStream { + + BytesInputStream(byte[] buf) { + super(Bytes.isEmpty(buf) ? Bytes.EMPTY : buf); + } + + public byte[] getBytes() { + return this.buf; + } + + @Override + public void close() throws IOException { + reset(); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/CharSequenceReader.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/CharSequenceReader.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/CharSequenceReader.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,297 @@ +/* + * 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.io; + +import java.io.Reader; +import java.io.Serializable; +import java.util.Objects; + +/** + * {@link Reader} implementation that can read from String, StringBuffer, StringBuilder or CharBuffer. + * + *

    + * Note: Supports {@link #mark(int)} and {@link #reset()}. + *

    + * + * @since 0.12.0, copied from commons-io + * 2.14.0 + */ +public class CharSequenceReader extends Reader implements Serializable { + + private static final long serialVersionUID = 3724187752191401220L; + private final CharSequence charSequence; + private int idx; + private int mark; + + /** + * The start index in the character sequence, inclusive. + *

    + * When de-serializing a CharSequenceReader that was serialized before + * this fields was added, this field will be initialized to 0, which + * gives the same behavior as before: start reading from the start. + *

    + * + * @see #start() + * @since 2.7 + */ + private final int start; + + /** + * The end index in the character sequence, exclusive. + *

    + * When de-serializing a CharSequenceReader that was serialized before + * this fields was added, this field will be initialized to {@code null}, + * which gives the same behavior as before: stop reading at the + * CharSequence's length. + * If this field was an int instead, it would be initialized to 0 when the + * CharSequenceReader is de-serialized, causing it to not return any + * characters at all. + *

    + * + * @see #end() + * @since 2.7 + */ + private final Integer end; + + /** + * Constructs a new instance with the specified character sequence. + * + * @param charSequence The character sequence, may be {@code null} + */ + public CharSequenceReader(final CharSequence charSequence) { + this(charSequence, 0); + } + + /** + * Constructs a new instance with a portion of the specified character sequence. + *

    + * The start index is not strictly enforced to be within the bounds of the + * character sequence. This allows the character sequence to grow or shrink + * in size without risking any {@link IndexOutOfBoundsException} to be thrown. + * Instead, if the character sequence grows smaller than the start index, this + * instance will act as if all characters have been read. + *

    + * + * @param charSequence The character sequence, may be {@code null} + * @param start The start index in the character sequence, inclusive + * @throws IllegalArgumentException if the start index is negative + */ + public CharSequenceReader(final CharSequence charSequence, final int start) { + this(charSequence, start, Integer.MAX_VALUE); + } + + /** + * Constructs a new instance with a portion of the specified character sequence. + *

    + * The start and end indexes are not strictly enforced to be within the bounds + * of the character sequence. This allows the character sequence to grow or shrink + * in size without risking any {@link IndexOutOfBoundsException} to be thrown. + * Instead, if the character sequence grows smaller than the start index, this + * instance will act as if all characters have been read; if the character sequence + * grows smaller than the end, this instance will use the actual character sequence + * length. + *

    + * + * @param charSequence The character sequence, may be {@code null} + * @param start The start index in the character sequence, inclusive + * @param end The end index in the character sequence, exclusive + * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index + */ + public CharSequenceReader(final CharSequence charSequence, final int start, final int end) { + if (start < 0) { + throw new IllegalArgumentException("Start index is less than zero: " + start); + } + if (end < start) { + throw new IllegalArgumentException("End index is less than start " + start + ": " + end); + } + // Don't check the start and end indexes against the CharSequence, + // to let it grow and shrink without breaking existing behavior. + + this.charSequence = charSequence != null ? charSequence : ""; + this.start = start; + this.end = end; + + this.idx = start; + this.mark = start; + } + + /** + * Close resets the file back to the start and removes any marked position. + */ + @Override + public void close() { + idx = start; + mark = start; + } + + /** + * Returns the index in the character sequence to end reading at, taking into account its length. + * + * @return The end index in the character sequence (exclusive). + */ + private int end() { + /* + * end == null for de-serialized instances that were serialized before start and end were added. + * Use Integer.MAX_VALUE to get the same behavior as before - use the entire CharSequence. + */ + return Math.min(charSequence.length(), end == null ? Integer.MAX_VALUE : end); + } + + /** + * Mark the current position. + * + * @param readAheadLimit ignored + */ + @Override + public void mark(final int readAheadLimit) { + mark = idx; + } + + /** + * Mark is supported (returns true). + * + * @return {@code true} + */ + @Override + public boolean markSupported() { + return true; + } + + /** + * Read a single character. + * + * @return the next character from the character sequence + * or -1 if the end has been reached. + */ + @Override + public int read() { + if (idx >= end()) { + return Streams.EOF; + } + return charSequence.charAt(idx++); + } + + /** + * Read the specified number of characters into the array. + * + * @param array The array to store the characters in + * @param offset The starting position in the array to store + * @param length The maximum number of characters to read + * @return The number of characters read or -1 if there are + * no more + */ + @Override + public int read(final char[] array, final int offset, final int length) { + if (idx >= end()) { + return Streams.EOF; + } + Objects.requireNonNull(array, "array"); + if (length < 0 || offset < 0 || offset + length > array.length) { + throw new IndexOutOfBoundsException("Array Size=" + array.length + + ", offset=" + offset + ", length=" + length); + } + + if (charSequence instanceof String) { + final int count = Math.min(length, end() - idx); + ((String) charSequence).getChars(idx, idx + count, array, offset); + idx += count; + return count; + } + if (charSequence instanceof StringBuilder) { + final int count = Math.min(length, end() - idx); + ((StringBuilder) charSequence).getChars(idx, idx + count, array, offset); + idx += count; + return count; + } + if (charSequence instanceof StringBuffer) { + final int count = Math.min(length, end() - idx); + ((StringBuffer) charSequence).getChars(idx, idx + count, array, offset); + idx += count; + return count; + } + + int count = 0; + for (int i = 0; i < length; i++) { + final int c = read(); + if (c == Streams.EOF) { + return count; + } + array[offset + i] = (char) c; + count++; + } + return count; + } + + /** + * Tells whether this stream is ready to be read. + * + * @return {@code true} if more characters from the character sequence are available, or {@code false} otherwise. + */ + @Override + public boolean ready() { + return idx < end(); + } + + /** + * Reset the reader to the last marked position (or the beginning if + * mark has not been called). + */ + @Override + public void reset() { + idx = mark; + } + + /** + * Skip the specified number of characters. + * + * @param n The number of characters to skip + * @return The actual number of characters skipped + */ + @Override + public long skip(final long n) { + if (n < 0) { + throw new IllegalArgumentException("Number of characters to skip is less than zero: " + n); + } + if (idx >= end()) { + return 0; + } + final int dest = (int) Math.min(end(), idx + n); + final int count = dest - idx; + idx = dest; + return count; + } + + /** + * Returns the index in the character sequence to start reading from, taking into account its length. + * + * @return The start index in the character sequence (inclusive). + */ + private int start() { + return Math.min(charSequence.length(), start); + } + + /** + * Return a String representation of the underlying + * character sequence. + * + * @return The contents of the character sequence + */ + @Override + public String toString() { + final CharSequence subSequence = charSequence.subSequence(start(), end()); + return subSequence.toString(); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/ClosedInputStream.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/ClosedInputStream.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/ClosedInputStream.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,37 @@ +/* + * 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.io; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @since 0.12.0, copied from + * + * commons-io 3a17f5259b105e734c8adce1d06d68f29884d1bb + */ +public final class ClosedInputStream extends InputStream { + + public static final ClosedInputStream INSTANCE = new ClosedInputStream(); + + private ClosedInputStream() { + } + + @Override + public int read() throws IOException { + return Streams.EOF; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Codec.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Codec.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Codec.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,53 @@ +/* + * Copyright © 2021 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.io; + +import io.jsonwebtoken.impl.lang.Converter; +import io.jsonwebtoken.io.Decoder; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.io.DecodingException; +import io.jsonwebtoken.io.Encoder; +import io.jsonwebtoken.io.Encoders; +import io.jsonwebtoken.lang.Assert; + +public class Codec implements Converter { + + public static final Codec BASE64 = new Codec(Encoders.BASE64, Decoders.BASE64); + public static final Codec BASE64URL = new Codec(Encoders.BASE64URL, Decoders.BASE64URL); + + private final Encoder encoder; + private final Decoder decoder; + + public Codec(Encoder encoder, Decoder decoder) { + this.encoder = Assert.notNull(encoder, "Encoder cannot be null."); + this.decoder = Assert.notNull(decoder, "Decoder cannot be null."); + } + + @Override + public String applyTo(byte[] a) { + return this.encoder.encode(a); + } + + @Override + public byte[] applyFrom(CharSequence b) { + try { + return this.decoder.decode(b); + } catch (DecodingException e) { + String msg = "Cannot decode input String. Cause: " + e.getMessage(); + throw new IllegalArgumentException(msg, e); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/CodecPolicy.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/CodecPolicy.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/CodecPolicy.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.io; + +/** + * Defines encoding and decoding policies. + * + * @since 0.12.0, copied from + * commons-codec + * 585497f09b026f6602daf986723a554e051bdfe6 + */ +enum CodecPolicy { + + /** + * The strict policy. Data that causes a codec to fail should throw an exception. + */ + STRICT, + + /** + * The lenient policy. Data that causes a codec to fail should not throw an exception. + */ + LENIENT +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/ConvertingParser.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/ConvertingParser.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/ConvertingParser.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,41 @@ +/* + * 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.io; + +import io.jsonwebtoken.impl.lang.Converter; +import io.jsonwebtoken.impl.lang.Function; +import io.jsonwebtoken.lang.Assert; + +import java.io.Reader; +import java.util.Map; + +public class ConvertingParser extends AbstractParser { + + private final Function> deserializer; + private final Converter converter; + + public ConvertingParser(Function> deserializer, Converter converter) { + this.deserializer = Assert.notNull(deserializer, "Deserializer function cannot be null."); + this.converter = Assert.notNull(converter, "Converter cannot be null."); + } + + @Override + public final T parse(Reader reader) { + Assert.notNull(reader, "Reader cannot be null."); + Map m = this.deserializer.apply(reader); + return this.converter.applyFrom(m); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/CountingInputStream.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/CountingInputStream.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/CountingInputStream.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,67 @@ +/* + * 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.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicLong; + +public class CountingInputStream extends FilterInputStream { + + private final AtomicLong count = new AtomicLong(0); + + public CountingInputStream(InputStream in) { + super(in); + } + + public long getCount() { + return count.get(); + } + + private void add(long n) { + // n can be -1 for EOF, and 0 for no bytes read, so we only add if we actually read 1 or more bytes: + if (n > 0) count.addAndGet(n); + } + + @Override + public int read() throws IOException { + int next = super.read(); + add(next == Streams.EOF ? Streams.EOF : 1); + return next; + } + + @Override + public int read(byte[] b) throws IOException { + int n = super.read(b); + add(n); + return n; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int n = super.read(b, off, len); + add(n); + return n; + } + + @Override + public long skip(long n) throws IOException { + final long skipped = super.skip(n); + add(skipped); + return skipped; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/DecodingInputStream.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/DecodingInputStream.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/DecodingInputStream.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,39 @@ +/* + * 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.io; + +import io.jsonwebtoken.io.DecodingException; +import io.jsonwebtoken.lang.Assert; + +import java.io.InputStream; + +public class DecodingInputStream extends FilteredInputStream { + + private final String codecName; + private final String name; + + public DecodingInputStream(InputStream in, String codecName, String name) { + super(in); + this.codecName = Assert.hasText(codecName, "codecName cannot be null or empty."); + this.name = Assert.hasText(name, "Name cannot be null or empty."); + } + + @Override + protected void onThrowable(Throwable t) { + String msg = "Unable to " + this.codecName + "-decode " + this.name + ": " + t.getMessage(); + throw new DecodingException(msg, t); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/DelegateStringDecoder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/DelegateStringDecoder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/DelegateStringDecoder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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.io; + +import io.jsonwebtoken.io.Decoder; +import io.jsonwebtoken.io.DecodingException; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Strings; + +import java.io.InputStream; + +@SuppressWarnings("DeprecatedIsStillUsed") +@Deprecated //TODO: delete when deleting JwtParserBuilder#base64UrlDecodeWith +public class DelegateStringDecoder implements Decoder { + + private final Decoder delegate; + + public DelegateStringDecoder(Decoder delegate) { + this.delegate = Assert.notNull(delegate, "delegate cannot be null."); + } + + @Override + public InputStream decode(InputStream in) throws DecodingException { + try { + byte[] data = Streams.bytes(in, "Unable to Base64URL-decode input."); + data = delegate.decode(Strings.utf8(data)); + return Streams.of(data); + } catch (Throwable t) { + String msg = "Unable to Base64Url-decode InputStream: " + t.getMessage(); + throw new DecodingException(msg, t); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/EncodingOutputStream.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/EncodingOutputStream.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/EncodingOutputStream.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,39 @@ +/* + * 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.io; + +import io.jsonwebtoken.io.EncodingException; +import io.jsonwebtoken.lang.Assert; + +import java.io.OutputStream; + +public class EncodingOutputStream extends FilteredOutputStream { + + private final String codecName; + private final String name; + + public EncodingOutputStream(OutputStream out, String codecName, String name) { + super(out); + this.codecName = Assert.hasText(codecName, "codecName cannot be null or empty."); + this.name = Assert.hasText(name, "name cannot be null or empty."); + } + + @Override + protected void onThrowable(Throwable t) { + String msg = "Unable to " + this.codecName + "-encode " + this.name + ": " + t.getMessage(); + throw new EncodingException(msg, t); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/FilteredInputStream.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/FilteredInputStream.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/FilteredInputStream.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,251 @@ +/* + * 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.io; + +import io.jsonwebtoken.impl.lang.Bytes; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A filter stream that delegates its calls to an internal delegate stream without changing behavior, but also providing + * pre/post/error handling hooks. It is useful as a base for extending and adding custom functionality. + * + *

    It is an alternative base class to FilterInputStream to increase re-usability, because FilterInputStream changes + * the methods being called, such as read(byte[]) to read(byte[], int, int).

    + * + * @since 0.12.0, copied from + * + * commons-io 3a17f5259b105e734c8adce1d06d68f29884d1bb + */ +public abstract class FilteredInputStream extends FilterInputStream { + + /** + * Constructs a new FilteredInputStream that delegates to the specified {@link InputStream}. + * + * @param in the InputStream to delegate to + */ + public FilteredInputStream(final InputStream in) { + super(in); // the delegate is stored in a protected superclass variable named 'in' + } + + /** + * Invoked by the read methods after the proxied call has returned + * successfully. The number of bytes returned to the caller (or -1 if + * the end of stream was reached) is given as an argument. + *

    + * Subclasses can override this method to add common post-processing + * functionality without having to override all the read methods. + * The default implementation does nothing. + *

    + *

    + * Note this method is not called from {@link #skip(long)} or + * {@link #reset()}. You need to explicitly override those methods if + * you want to add post-processing steps also to them. + *

    + * + * @param n number of bytes read, or -1 if the end of stream was reached + * @throws IOException if the post-processing fails + * @since 2.0 + */ + @SuppressWarnings({"unused", "RedundantThrows"}) // Possibly thrown from subclasses. + protected void afterRead(final int n) throws IOException { + // no-op + } + + /** + * Invokes the delegate's {@code available()} method. + * + * @return the number of available bytes + * @throws IOException if an I/O error occurs. + */ + @Override + public int available() throws IOException { + try { + return super.available(); + } catch (final Throwable t) { + onThrowable(t); + return 0; + } + } + + /** + * Invoked by the read methods before the call is proxied. The number + * of bytes that the caller wanted to read (1 for the {@link #read()} + * method, buffer length for {@link #read(byte[])}, etc.) is given as + * an argument. + *

    + * Subclasses can override this method to add common pre-processing + * functionality without having to override all the read methods. + * The default implementation does nothing. + *

    + *

    + * Note this method is not called from {@link #skip(long)} or + * {@link #reset()}. You need to explicitly override those methods if + * you want to add pre-processing steps also to them. + *

    + * + * @param n number of bytes that the caller asked to be read + * @throws IOException if the pre-processing fails + * @since 2.0 + */ + @SuppressWarnings({"unused", "RedundantThrows"}) // Possibly thrown from subclasses. + protected void beforeRead(final int n) throws IOException { + // no-op + } + + /** + * Invokes the delegate's {@code close()} method. + * + * @throws IOException if an I/O error occurs. + */ + @Override + public void close() throws IOException { + try { + super.close(); + } catch (Throwable t) { + onThrowable(t); + } + } + + /** + * Handle any Throwable thrown; by default, throws the given exception. + *

    + * This method provides a point to implement custom exception + * handling. The default behavior is to re-throw the exception. + *

    + * + * @param t The IOException thrown + * @throws IOException if an I/O error occurs. + */ + protected void onThrowable(final Throwable t) throws IOException { + if (t instanceof IOException) throw (IOException) t; + throw new IOException("IO Exception: " + t.getMessage(), t); + } + + /** + * Invokes the delegate's {@code mark(int)} method. + * + * @param readlimit read ahead limit + */ + @Override + public synchronized void mark(final int readlimit) { + in.mark(readlimit); + } + + /** + * Invokes the delegate's {@code markSupported()} method. + * + * @return true if mark is supported, otherwise false + */ + @Override + public boolean markSupported() { + return in.markSupported(); + } + + /** + * Invokes the delegate's {@code read()} method. + * + * @return the byte read or -1 if the end of stream + * @throws IOException if an I/O error occurs. + */ + @Override + public int read() throws IOException { + try { + beforeRead(1); + final int b = in.read(); + afterRead(b != Streams.EOF ? 1 : Streams.EOF); + return b; + } catch (final Throwable t) { + onThrowable(t); + return Streams.EOF; + } + } + + /** + * Invokes the delegate's {@code read(byte[])} method. + * + * @param bts the buffer to read the bytes into + * @return the number of bytes read or EOF if the end of stream + * @throws IOException if an I/O error occurs. + */ + @Override + public int read(final byte[] bts) throws IOException { + try { + beforeRead(Bytes.length(bts)); + final int n = in.read(bts); + afterRead(n); + return n; + } catch (final Throwable t) { + onThrowable(t); + return Streams.EOF; + } + } + + /** + * Invokes the delegate's {@code read(byte[], int, int)} method. + * + * @param bts the buffer to read the bytes into + * @param off The start offset + * @param len The number of bytes to read + * @return the number of bytes read or -1 if the end of stream + * @throws IOException if an I/O error occurs. + */ + @Override + public int read(final byte[] bts, final int off, final int len) throws IOException { + try { + beforeRead(len); + final int n = in.read(bts, off, len); + afterRead(n); + return n; + } catch (final Throwable t) { + onThrowable(t); + return Streams.EOF; + } + } + + /** + * Invokes the delegate's {@code reset()} method. + * + * @throws IOException if an I/O error occurs. + */ + @Override + public synchronized void reset() throws IOException { + try { + in.reset(); + } catch (final Throwable t) { + onThrowable(t); + } + } + + /** + * Invokes the delegate's {@code skip(long)} method. + * + * @param ln the number of bytes to skip + * @return the actual number of bytes skipped + * @throws IOException if an I/O error occurs. + */ + @Override + public long skip(final long ln) throws IOException { + try { + return in.skip(ln); + } catch (final Throwable t) { + onThrowable(t); + return 0; + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/FilteredOutputStream.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/FilteredOutputStream.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/FilteredOutputStream.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,181 @@ +/* + * 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.io; + +import io.jsonwebtoken.impl.lang.Bytes; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * A Proxy stream which acts as expected, that is it passes the method + * calls on to the proxied stream and doesn't change which methods are + * being called. It is an alternative base class to FilterOutputStream + * to increase reusability. + *

    + * See the protected methods for ways in which a subclass can easily decorate + * a stream with custom pre-, post- or error processing functionality. + *

    + * + * @since 0.12.0, copied from + * + * commons-io 3a17f5259b105e734c8adce1d06d68f29884d1bb + */ +public class FilteredOutputStream extends FilterOutputStream { + + /** + * Constructs a new ProxyOutputStream. + * + * @param out the OutputStream to delegate to + */ + public FilteredOutputStream(final OutputStream out) { + super(out); // the proxy is stored in a protected superclass variable named 'out' + } + + /** + * Invoked by the write methods after the proxied call has returned + * successfully. The number of bytes written (1 for the + * {@link #write(int)} method, buffer length for {@link #write(byte[])}, + * etc.) is given as an argument. + *

    + * Subclasses can override this method to add common post-processing + * functionality without having to override all the write methods. + * The default implementation does nothing. + * + * @param n number of bytes written + * @throws IOException if the post-processing fails + * @since 2.0 + */ + @SuppressWarnings({"unused", "RedundantThrows"}) // Possibly thrown from subclasses. + protected void afterWrite(final int n) throws IOException { + // noop + } + + /** + * Invoked by the write methods before the call is proxied. The number + * of bytes to be written (1 for the {@link #write(int)} method, buffer + * length for {@link #write(byte[])}, etc.) is given as an argument. + *

    + * Subclasses can override this method to add common pre-processing + * functionality without having to override all the write methods. + * The default implementation does nothing. + * + * @param n number of bytes to be written + * @throws IOException if the pre-processing fails + */ + @SuppressWarnings({"unused", "RedundantThrows"}) // Possibly thrown from subclasses. + protected void beforeWrite(final int n) throws IOException { + // noop + } + + /** + * Invokes the delegate's {@code close()} method. + * + * @throws IOException if an I/O error occurs. + */ + @Override + public void close() throws IOException { + try { + super.close(); + } catch (Throwable t) { + onThrowable(t); + } + } + + /** + * Invokes the delegate's {@code flush()} method. + * + * @throws IOException if an I/O error occurs. + */ + @Override + public void flush() throws IOException { + try { + out.flush(); + } catch (final Throwable t) { + onThrowable(t); + } + } + + /** + * Handle any IOExceptions thrown. + *

    + * This method provides a point to implement custom exception + * handling. The default behavior is to re-throw the exception. + * + * @param t The Throwable thrown + * @throws IOException if an I/O error occurs. + */ + protected void onThrowable(final Throwable t) throws IOException { + if (t instanceof IOException) throw (IOException) t; + throw new IOException("IO Exception " + t.getMessage(), t); + } + + /** + * Invokes the delegate's {@code write(byte[])} method. + * + * @param bts the bytes to write + * @throws IOException if an I/O error occurs. + */ + @Override + public void write(final byte[] bts) throws IOException { + try { + final int len = Bytes.length(bts); + beforeWrite(len); + out.write(bts); + afterWrite(len); + } catch (final Throwable t) { + onThrowable(t); + } + } + + /** + * Invokes the delegate's {@code write(byte[])} method. + * + * @param bts the bytes to write + * @param st The start offset + * @param end The number of bytes to write + * @throws IOException if an I/O error occurs. + */ + @Override + public void write(final byte[] bts, final int st, final int end) throws IOException { + try { + beforeWrite(end); + out.write(bts, st, end); + afterWrite(end); + } catch (final Throwable t) { + onThrowable(t); + } + } + + /** + * Invokes the delegate's {@code write(int)} method. + * + * @param idx the byte to write + * @throws IOException if an I/O error occurs. + */ + @Override + public void write(final int idx) throws IOException { + try { + beforeWrite(1); + out.write(idx); + afterWrite(1); + } catch (final Throwable t) { + onThrowable(t); + } + } + +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/JsonObjectDeserializer.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/JsonObjectDeserializer.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/JsonObjectDeserializer.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 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.io; + +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.impl.lang.Function; +import io.jsonwebtoken.io.DeserializationException; +import io.jsonwebtoken.io.Deserializer; +import io.jsonwebtoken.lang.Assert; + +import java.io.Reader; +import java.util.Map; + +/** + * Function that wraps a {@link Deserializer} to add JWT-related error handling. + * + * @since 0.11.3 (renamed from JwtDeserializer) + */ +public class JsonObjectDeserializer implements Function> { + + private static final String MALFORMED_ERROR = "Malformed %s JSON: %s"; + private static final String MALFORMED_COMPLEX_ERROR = "Malformed or excessively complex %s JSON. " + + "If experienced in a production environment, this could reflect a potential malicious %s, please " + + "investigate the source further. Cause: %s"; + + private final Deserializer deserializer; + private final String name; + + public JsonObjectDeserializer(Deserializer deserializer, String name) { + this.deserializer = Assert.notNull(deserializer, "JSON Deserializer cannot be null."); + this.name = Assert.hasText(name, "name cannot be null or empty."); + } + + @Override + public Map apply(Reader in) { + Assert.notNull(in, "InputStream argument cannot be null."); + Object value; + try { + value = this.deserializer.deserialize(in); + if (value == null) { + String msg = "Deserialized data resulted in a null value; cannot create Map"; + throw new DeserializationException(msg); + } + if (!(value instanceof Map)) { + String msg = "Deserialized data is not a JSON Object; cannot create Map"; + throw new DeserializationException(msg); + } + // JSON Specification requires all JSON Objects to have string-only keys. So instead of + // checking that the val.keySet() has all Strings, we blindly cast to a Map + // since input would rarely, if ever, have non-string keys. + //noinspection unchecked + return (Map) value; + } catch (StackOverflowError e) { + String msg = String.format(MALFORMED_COMPLEX_ERROR, this.name, this.name, e.getMessage()); + throw new DeserializationException(msg, e); + } catch (Throwable t) { + throw malformed(t); + } + } + + protected RuntimeException malformed(Throwable t) { + String msg = String.format(MALFORMED_ERROR, this.name, t.getMessage()); + throw new MalformedJwtException(msg, t); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/NamedSerializer.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/NamedSerializer.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/NamedSerializer.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,45 @@ +/* + * 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.io; + +import io.jsonwebtoken.io.AbstractSerializer; +import io.jsonwebtoken.io.SerializationException; +import io.jsonwebtoken.io.Serializer; +import io.jsonwebtoken.lang.Assert; + +import java.io.OutputStream; +import java.util.Map; + +public class NamedSerializer extends AbstractSerializer> { + + private final String name; + private final Serializer> DELEGATE; + + public NamedSerializer(String name, Serializer> serializer) { + this.DELEGATE = Assert.notNull(serializer, "JSON Serializer cannot be null."); + this.name = Assert.hasText(name, "Name cannot be null or empty."); + } + + @Override + protected void doSerialize(Map m, OutputStream out) throws SerializationException { + try { + this.DELEGATE.serialize(m, out); + } catch (Throwable t) { + String msg = String.format("Cannot serialize %s to JSON. Cause: %s", this.name, t.getMessage()); + throw new SerializationException(msg, t); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/StandardCompressionAlgorithms.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/StandardCompressionAlgorithms.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/StandardCompressionAlgorithms.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,35 @@ +/* + * 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.io; + +import io.jsonwebtoken.impl.compression.DeflateCompressionAlgorithm; +import io.jsonwebtoken.impl.compression.GzipCompressionAlgorithm; +import io.jsonwebtoken.impl.lang.IdRegistry; +import io.jsonwebtoken.io.CompressionAlgorithm; +import io.jsonwebtoken.lang.Collections; + +@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.ZIP +public final class StandardCompressionAlgorithms extends IdRegistry { + + public static final String NAME = "Compression Algorithm"; + + public StandardCompressionAlgorithms() { + super(NAME, Collections.of( + new DeflateCompressionAlgorithm(), + new GzipCompressionAlgorithm() + )); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Streams.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Streams.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/Streams.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,173 @@ +/* + * 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.io; + +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Objects; +import io.jsonwebtoken.lang.Strings; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Flushable; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.util.concurrent.Callable; + +/** + * @since 0.12.0 + */ +public class Streams { + + /** + * Represents the end-of-file (or stream). + */ + public static final int EOF = -1; + + public static byte[] bytes(final InputStream in, String exmsg) { + if (in instanceof BytesInputStream) { + return ((BytesInputStream) in).getBytes(); + } + // otherwise we have to copy over: + ByteArrayOutputStream out = new ByteArrayOutputStream(8192); + copy(in, out, new byte[8192], exmsg); + return out.toByteArray(); + } + + public static InputStream of(byte[] bytes) { + return new BytesInputStream(bytes); + } + + public static InputStream of(CharSequence seq) { + return of(Strings.utf8(seq)); + } + + public static Reader reader(byte[] bytes) { + return reader(Streams.of(bytes)); + } + + public static Reader reader(InputStream in) { + return new InputStreamReader(in, Strings.UTF_8); + } + + public static Reader reader(CharSequence seq) { + return new CharSequenceReader(seq); + } + + public static void flush(Flushable... flushables) { + Objects.nullSafeFlush(flushables); + } + + /** + * Copies bytes from a {@link InputStream} to an {@link OutputStream} using the specified {@code buffer}, avoiding + * the need for a {@link BufferedInputStream}. + * + * @param inputStream the {@link InputStream} to read. + * @param outputStream the {@link OutputStream} to write. + * @param buffer the buffer to use for the copy + * @return the number of bytes copied. + * @throws IllegalArgumentException if the InputStream is {@code null}. + * @throws IllegalArgumentException if the OutputStream is {@code null}. + * @throws IOException if an I/O error occurs. + */ + public static long copy(final InputStream inputStream, final OutputStream outputStream, final byte[] buffer) + throws IOException { + Assert.notNull(inputStream, "inputStream cannot be null."); + Assert.notNull(outputStream, "outputStream cannot be null."); + Assert.notEmpty(buffer, "buffer cannot be null or empty."); + long count = 0; + int n = 0; + while (n != EOF) { + n = inputStream.read(buffer); + if (n > 0) outputStream.write(buffer, 0, n); + count += n; + } + return count; + } + + public static long copy(final InputStream in, final OutputStream out, final byte[] buffer, final String exmsg) { + return run(new Callable() { + @Override + public Long call() throws IOException { + try { + reset(in); + return copy(in, out, buffer); + } finally { + Objects.nullSafeFlush(out); + reset(in); + } + } + }, exmsg); + } + + public static void reset(final InputStream in) { + if (in == null) return; + Callable callable = new Callable() { + @Override + public Object call() { + try { + in.reset(); + } catch (Throwable ignored) { + } + return null; + } + }; + try { + callable.call(); + } catch (Throwable ignored) { + } + } + + public static void write(final OutputStream out, final byte[] bytes, String exMsg) { + write(out, bytes, 0, Bytes.length(bytes), exMsg); + } + + public static void write(final OutputStream out, final byte[] data, final int offset, final int len, String exMsg) { + if (out == null || Bytes.isEmpty(data) || len <= 0) return; + run(new Callable() { + @Override + public Object call() throws Exception { + out.write(data, offset, len); + return null; + } + }, exMsg); + } + + public static void writeAndClose(final OutputStream out, final byte[] data, String exMsg) { + try { + write(out, data, exMsg); + } finally { + Objects.nullSafeClose(out); + } + } + + public static V run(Callable c, String ioExMsg) { + Assert.hasText(ioExMsg, "IO Exception Message cannot be null or empty."); + try { + return c.call(); + } catch (Throwable t) { + String msg = "IO failure: " + ioExMsg; + if (!msg.endsWith(".")) { + msg += "."; + } + msg += " Cause: " + t.getMessage(); + throw new io.jsonwebtoken.io.IOException(msg, t); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/TeeOutputStream.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/TeeOutputStream.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/TeeOutputStream.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,67 @@ +/* + * 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.io; + +import io.jsonwebtoken.lang.Assert; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * @since 0.12.0 + */ +public class TeeOutputStream extends FilteredOutputStream { + + private final OutputStream other; + + public TeeOutputStream(OutputStream one, OutputStream two) { + super(one); + this.other = Assert.notNull(two, "Second OutputStream cannot be null."); + } + + @Override + public void close() throws IOException { + try { + super.close(); + } finally { + this.other.close(); + } + } + + @Override + public void flush() throws IOException { + super.flush(); + this.other.flush(); + } + + @Override + public void write(byte[] bts) throws IOException { + super.write(bts); + this.other.write(bts); + } + + @Override + public void write(byte[] bts, int st, int end) throws IOException { + super.write(bts, st, end); + this.other.write(bts, st, end); + } + + @Override + public void write(int idx) throws IOException { + super.write(idx); + this.other.write(idx); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/UncloseableInputStream.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/UncloseableInputStream.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/io/UncloseableInputStream.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,36 @@ +/* + * 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.io; + +import java.io.FilterInputStream; +import java.io.InputStream; + +/** + * @since 0.12.0, copied from + * + * commons-io 3a17f5259b105e734c8adce1d06d68f29884d1bb + */ +public final class UncloseableInputStream extends FilterInputStream { + + public UncloseableInputStream(InputStream in) { + super(in); + } + + @Override + public void close() { + in = ClosedInputStream.INSTANCE; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/BiConsumer.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/BiConsumer.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/BiConsumer.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,21 @@ +/* + * Copyright © 2023 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.impl.lang; + +public interface BiConsumer { + + void accept(T t, U u); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/BigIntegerUBytesConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/BigIntegerUBytesConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/BigIntegerUBytesConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,55 @@ +/* + * Copyright © 2021 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.Assert; + +import java.math.BigInteger; + +public class BigIntegerUBytesConverter implements Converter { + + private static final String NEGATIVE_MSG = + "JWA Base64urlUInt values MUST be >= 0 (non-negative) per the 'Base64urlUInt' definition in " + + "[JWA RFC 7518, Section 2](https://www.rfc-editor.org/rfc/rfc7518.html#section-2)"; + + @Override + public byte[] applyTo(BigInteger bigInt) { + Assert.notNull(bigInt, "BigInteger argument cannot be null."); + if (BigInteger.ZERO.compareTo(bigInt) > 0) { + throw new IllegalArgumentException(NEGATIVE_MSG); + } + + final int bitLen = bigInt.bitLength(); + final byte[] bytes = bigInt.toByteArray(); + // Determine minimal number of bytes necessary to represent an unsigned byte array. + // It must be 1 or more because zero still requires one byte + final int unsignedByteLen = Math.max(1, Bytes.length(bitLen)); // always need at least one byte + + if (bytes.length == unsignedByteLen) { // already in the form we need + return bytes; + } + //otherwise, we need to strip the sign byte (start copying at index 1 instead of 0): + byte[] ubytes = new byte[unsignedByteLen]; + System.arraycopy(bytes, 1, ubytes, 0, unsignedByteLen); + return ubytes; + } + + @Override + public BigInteger applyFrom(byte[] bytes) { + Assert.notEmpty(bytes, "Byte array cannot be null or empty."); + return new BigInteger(1, bytes); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Bytes.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Bytes.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Bytes.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,264 @@ +/* + * Copyright © 2020 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.impl.security.Randoms; +import io.jsonwebtoken.lang.Arrays; +import io.jsonwebtoken.lang.Assert; + +public final class Bytes { + + public static final byte[] EMPTY = new byte[0]; + + private static final int LONG_BYTE_LENGTH = Long.SIZE / Byte.SIZE; + private static final int INT_BYTE_LENGTH = Integer.SIZE / Byte.SIZE; + public static final String LONG_REQD_MSG = "Long byte arrays must be " + LONG_BYTE_LENGTH + " bytes in length."; + public static final String INT_REQD_MSG = "Integer byte arrays must be " + INT_BYTE_LENGTH + " bytes in length."; + + //prevent instantiation + private Bytes() { + } + + public static byte[] nullSafe(byte[] bytes) { + return bytes != null ? bytes : Bytes.EMPTY; + } + + public static byte[] randomBits(int numBits) { + return random(numBits / Byte.SIZE); + } + + public static byte[] random(int numBytes) { + if (numBytes <= 0) { + throw new IllegalArgumentException("numBytes argument must be >= 0"); + } + byte[] bytes = new byte[numBytes]; + Randoms.secureRandom().nextBytes(bytes); + return bytes; + } + + public static byte[] toBytes(int i) { + return new byte[]{ + (byte) (i >>> 24), + (byte) (i >>> 16), + (byte) (i >>> 8), + (byte) i + }; + } + + public static byte[] toBytes(long l) { + return new byte[]{ + (byte) (l >>> 56), + (byte) (l >>> 48), + (byte) (l >>> 40), + (byte) (l >>> 32), + (byte) (l >>> 24), + (byte) (l >>> 16), + (byte) (l >>> 8), + (byte) l + }; + } + + public static long toLong(byte[] bytes) { + Assert.isTrue(Arrays.length(bytes) == LONG_BYTE_LENGTH, LONG_REQD_MSG); + return ((bytes[0] & 0xFFL) << 56) | + ((bytes[1] & 0xFFL) << 48) | + ((bytes[2] & 0xFFL) << 40) | + ((bytes[3] & 0xFFL) << 32) | + ((bytes[4] & 0xFFL) << 24) | + ((bytes[5] & 0xFFL) << 16) | + ((bytes[6] & 0xFFL) << 8) | + (bytes[7] & 0xFFL); + } + + public static int toInt(byte[] bytes) { + Assert.isTrue(Arrays.length(bytes) == INT_BYTE_LENGTH, INT_REQD_MSG); + return ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + (bytes[3] & 0xFF); + } + + public static int indexOf(byte[] source, byte[] target) { + return indexOf(source, target, 0); + } + + public static int indexOf(byte[] source, byte[] target, int fromIndex) { + return indexOf(source, 0, length(source), target, 0, length(target), fromIndex); + } + + + static int indexOf(byte[] source, int srcOffset, int srcLen, + byte[] target, int targetOffset, int targetLen, + int fromIndex) { + + if (fromIndex >= srcLen) { + return (targetLen == 0 ? srcLen : -1); + } + if (fromIndex < 0) { + fromIndex = 0; + } + if (targetLen == 0) { + return fromIndex; + } + + byte first = target[targetOffset]; + int max = srcOffset + (srcLen - targetLen); + + for (int i = srcOffset + fromIndex; i <= max; i++) { // + + if (source[i] != first) { // continue on to find the first matching byte + //noinspection StatementWithEmptyBody + while (++i <= max && source[i] != first) ; + } + + if (i <= max) { // found first byte in target, now try to find the rest: + int j = i + 1; + int end = j + targetLen - 1; + //noinspection StatementWithEmptyBody + for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++) ; + if (j == end) { + return i - srcOffset; // found entire target byte array + } + } + } + return -1; + } + + public static boolean startsWith(byte[] src, byte[] prefix) { + return startsWith(src, prefix, 0); + } + + public static boolean startsWith(byte[] src, byte[] prefix, int offset) { + int to = offset; + int po = 0; + int pc = length(prefix); + if ((offset < 0) || (offset > length(src) - pc)) { + return false; + } + while (--pc >= 0) { + if (src[to++] != prefix[po++]) { + return false; + } + } + return true; + } + + public static boolean endsWith(byte[] src, byte[] suffix) { + return startsWith(src, suffix, length(src) - length(suffix)); + } + + public static byte[] concat(byte[]... arrays) { + int len = 0; + int numArrays = Arrays.length(arrays); + for (int i = 0; i < numArrays; i++) { + len += length(arrays[i]); + } + byte[] output = new byte[len]; + int position = 0; + if (len > 0) { + for (byte[] array : arrays) { + int alen = length(array); + if (alen > 0) { + System.arraycopy(array, 0, output, position, alen); + position += alen; + } + } + } + return output; + } + + /** + * Clears the array by filling it with all zeros. Does nothing with a null or empty argument. + * + * @param bytes the (possibly null or empty) byte array to clear + */ + public static void clear(byte[] bytes) { + if (isEmpty(bytes)) return; + java.util.Arrays.fill(bytes, (byte) 0); + } + + public static boolean isEmpty(byte[] bytes) { + return length(bytes) == 0; + } + + public static int length(byte[] bytes) { + return bytes == null ? 0 : bytes.length; + } + + public static long bitLength(byte[] bytes) { + return length(bytes) * (long) Byte.SIZE; + } + + /** + * Returns the minimum number of bytes required to represent the specified number of bits. + * + *

    This is defined/used by many specifications, such as:

    + * + * + * @param bitLength the number of bits to represent as a byte array, must be >= 0 + * @return the minimum number of bytes required to represent the specified number of bits. + * @throws IllegalArgumentException if {@code bitLength} is less than zero. + */ + public static int length(int bitLength) { + if (bitLength < 0) throw new IllegalArgumentException("bitLength argument must be >= 0"); + return (bitLength + 7) / Byte.SIZE; + } + + public static String bitsMsg(long bitLength) { + return bitLength + " bits (" + bitLength / Byte.SIZE + " bytes)"; + } + + public static String bytesMsg(int byteArrayLength) { + return bitsMsg((long) byteArrayLength * Byte.SIZE); + } + + public static void increment(byte[] a) { + for (int i = a.length - 1; i >= 0; --i) { + if (++a[i] != 0) { + break; + } + } + } + + /** + * Pads the front of the specified byte array with zeros if necessary, returning a new padded result, or the + * original array unmodified if padding isn't necessary. Padding is only performed if {@code length} is greater + * than {@code bytes.length}. + * + * @param bytes the byte array to pre-pad with zeros if necessary + * @param length the length of the required output array + * @return the potentially pre-padded byte array, or the existing {@code bytes} array if padding wasn't necessary. + * @since 0.12.4 + */ + public static byte[] prepad(byte[] bytes, int length) { + Assert.notNull(bytes, "byte array cannot be null."); + Assert.gt(length, 0, "length must be positive (> 0)."); + if (bytes.length < length) { // need to pad with leading zero(es): + byte[] padded = new byte[length]; + System.arraycopy(bytes, 0, padded, length - bytes.length, bytes.length); + bytes = padded; + } + return bytes; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CheckedFunction.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CheckedFunction.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CheckedFunction.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,20 @@ +/* + * Copyright © 2020 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.impl.lang; + +public interface CheckedFunction { + R apply(T t) throws Exception; +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CheckedSupplier.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CheckedSupplier.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CheckedSupplier.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,24 @@ +/* + * Copyright © 2021 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; + +/** + * @since 0.12.0 + */ +public interface CheckedSupplier { + + T get() throws Exception; +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CollectionConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CollectionConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CollectionConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,104 @@ +/* + * Copyright © 2020 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.Assert; +import io.jsonwebtoken.lang.Collections; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +class CollectionConverter> implements Converter { + + private final Converter elementConverter; + private final Function fn; + + public static CollectionConverter> forList(Converter elementConverter) { + return new CollectionConverter<>(elementConverter, new CreateListFunction()); + } + + public static CollectionConverter> forSet(Converter elementConverter) { + return new CollectionConverter<>(elementConverter, new CreateSetFunction()); + } + + public CollectionConverter(Converter elementConverter, Function fn) { + this.elementConverter = Assert.notNull(elementConverter, "Element converter cannot be null."); + this.fn = Assert.notNull(fn, "Collection function cannot be null."); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public Object applyTo(C ts) { + if (Collections.isEmpty(ts)) { + return ts; + } + Collection c = fn.apply(ts.size()); + for (T element : ts) { + Object encoded = elementConverter.applyTo(element); + c.add(encoded); + } + return c; + } + + private C toElementList(Collection c) { + Assert.notEmpty(c, "Collection cannot be null or empty."); + C result = fn.apply(c.size()); + for (Object o : c) { + T element = elementConverter.applyFrom(o); + result.add(element); + } + return result; + } + + @Override + public C applyFrom(Object value) { + if (value == null) { + return null; + } + Collection c; + if (value.getClass().isArray() && !value.getClass().getComponentType().isPrimitive()) { + c = Collections.arrayToList(value); + } else if (value instanceof Collection) { + c = (Collection) value; + } else { + c = java.util.Collections.singletonList(value); + } + C result; + if (Collections.isEmpty(c)) { + result = fn.apply(0); + } else { + result = toElementList(c); + } + return result; + } + + private static class CreateListFunction implements Function> { + @Override + public List apply(Integer size) { + return size > 0 ? new ArrayList(size) : new ArrayList(); + } + } + + private static class CreateSetFunction implements Function> { + @Override + public Set apply(Integer size) { + return size > 0 ? new LinkedHashSet(size) : new LinkedHashSet(); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CompactMediaTypeIdConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CompactMediaTypeIdConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CompactMediaTypeIdConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,68 @@ +/* + * Copyright © 2022 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.Assert; +import io.jsonwebtoken.lang.Strings; + +public final class CompactMediaTypeIdConverter implements Converter { + + public static final Converter INSTANCE = new CompactMediaTypeIdConverter(); + + private static final char FORWARD_SLASH = '/'; + + private static final String APP_MEDIA_TYPE_PREFIX = "application" + FORWARD_SLASH; + + static String compactIfPossible(String cty) { + Assert.hasText(cty, "Value cannot be null or empty."); + if (Strings.startsWithIgnoreCase(cty, APP_MEDIA_TYPE_PREFIX)) { + // per https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10 + // we can only use the compact form if no other '/' exists in the string + for (int i = cty.length() - 1; i >= APP_MEDIA_TYPE_PREFIX.length(); i--) { + char c = cty.charAt(i); + if (c == FORWARD_SLASH) { + return cty; // found another '/', can't compact, so just return unmodified + } + } + // no additional '/' found, we can strip the prefix: + return cty.substring(APP_MEDIA_TYPE_PREFIX.length()); + } + return cty; // didn't start with 'application/', so we can't trim it - just return unmodified + } + + @Override + public Object applyTo(String s) { + return compactIfPossible(s); + } + + @Override + public String applyFrom(Object o) { + Assert.notNull(o, "Value cannot be null."); + String s = Assert.isInstanceOf(String.class, o, "Value must be a string."); + + // https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.10: + // + // A recipient using the media type value MUST treat it as if + // "application/" were prepended to any "cty" value not containing a + // '/'. + // + if (s.indexOf(FORWARD_SLASH) < 0) { + s = APP_MEDIA_TYPE_PREFIX + s; + } + + return s; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CompoundConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CompoundConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/CompoundConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,41 @@ +/* + * Copyright © 2021 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.Assert; + +public class CompoundConverter implements Converter { + + private final Converter first; + private final Converter second; + + public CompoundConverter(Converter first, Converter second) { + this.first = Assert.notNull(first, "First converter cannot be null."); + this.second = Assert.notNull(second, "Second converter cannot be null."); + } + + @Override + public C applyTo(A a) { + B b = first.applyTo(a); + return second.applyTo(b); + } + + @Override + public A applyFrom(C c) { + B b = second.applyFrom(c); + return first.applyFrom(b); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/ConstantFunction.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/ConstantFunction.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/ConstantFunction.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,37 @@ +/* + * Copyright © 2020 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; + + +/** + * Function that always returns the same value + * + * @param Input type + * @param Return value type + */ +public final class ConstantFunction implements Function { + + private final R value; + + public ConstantFunction(R value) { + this.value = value; + } + + @Override + public R apply(T t) { + return this.value; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Converter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Converter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Converter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,35 @@ +/* + * Copyright © 2020 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.impl.lang; + +public interface Converter { + + /** + * Converts the specified (Java idiomatic type) value to the canonical RFC-required data type. + * + * @param a the preferred idiomatic value + * @return the canonical RFC-required data type value. + */ + B applyTo(A a); + + /** + * Converts the specified canonical (RFC-compliant data type) value to the preferred Java idiomatic type. + * + * @param b the canonical value to convert + * @return the preferred Java idiomatic type value. + */ + A applyFrom(B b); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Converters.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Converters.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Converters.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,63 @@ +/* + * Copyright © 2020 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.impl.io.Codec; +import io.jsonwebtoken.impl.security.JwtX509StringConverter; + +import java.math.BigInteger; +import java.net.URI; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Set; + +public final class Converters { + + public static final Converter URI = Converters.forEncoded(URI.class, new UriStringConverter()); + + public static final Converter BASE64URL_BYTES = Converters.forEncoded(byte[].class, Codec.BASE64URL); + + public static final Converter X509_CERTIFICATE = + Converters.forEncoded(X509Certificate.class, JwtX509StringConverter.INSTANCE); + + public static final Converter BIGINT_UBYTES = new BigIntegerUBytesConverter(); + public static final Converter BIGINT = Converters.forEncoded(BigInteger.class, + compound(BIGINT_UBYTES, Codec.BASE64URL)); + + //prevent instantiation + private Converters() { + } + + public static Converter forType(Class clazz) { + return new RequiredTypeConverter<>(clazz); + } + + public static Converter, Object> forSet(Converter elementConverter) { + return CollectionConverter.forSet(elementConverter); + } + + public static Converter, Object> forList(Converter elementConverter) { + return CollectionConverter.forList(elementConverter); + } + + public static Converter forEncoded(Class elementType, Converter elementConverter) { + return new EncodedObjectConverter<>(elementType, elementConverter); + } + + public static Converter compound(final Converter aConv, final Converter bConv) { + return new CompoundConverter<>(aConv, bConv); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultCollectionMutator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultCollectionMutator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultCollectionMutator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,89 @@ +/* + * 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> implements CollectionMutator { + + private final Collection collection; + + public DefaultCollectionMutator(Collection seed) { + this.collection = new LinkedHashSet<>(Collections.nullSafe(seed)); + } + + @SuppressWarnings("unchecked") + protected final M self() { + return (M) this; + } + + private boolean doAdd(E e) { + if (Objects.isEmpty(e)) return false; + if (e instanceof Identifiable && !Strings.hasText(((Identifiable) e).getId())) { + String msg = e.getClass() + " getId() value cannot be null or empty."; + throw new IllegalArgumentException(msg); + } + return this.collection.add(e); + } + + @Override + public M add(E e) { + if (doAdd(e)) changed(); + return self(); + } + + @Override + public M remove(E e) { + if (this.collection.remove(e)) changed(); + return self(); + } + + @Override + public M add(Collection c) { + boolean changed = false; + for (E element : Collections.nullSafe(c)) { + changed = doAdd(element) || changed; + } + if (changed) changed(); + return self(); + } + + @Override + public M clear() { + boolean changed = !Collections.isEmpty(this.collection); + this.collection.clear(); + if (changed) changed(); + return self(); + } + + /** + * Callback for subclasses that wish to be notified if the internal collection has changed via builder mutation + * methods. + */ + protected void changed() { + } + + protected Collection getCollection() { + return Collections.immutable(this.collection); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultNestedCollection.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultNestedCollection.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultNestedCollection.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,37 @@ +/* + * 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.lang.Assert; +import io.jsonwebtoken.lang.NestedCollection; + +import java.util.Collection; + +public class DefaultNestedCollection extends DefaultCollectionMutator> + implements NestedCollection { + + private final P parent; + + public DefaultNestedCollection(P parent, Collection seed) { + super(seed); + this.parent = Assert.notNull(parent, "Parent cannot be null."); + } + + @Override + public P and() { + return this.parent; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultParameter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultParameter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultParameter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,126 @@ +/* + * Copyright © 2021 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.Assert; +import io.jsonwebtoken.lang.Strings; + +import java.util.Collection; + +public class DefaultParameter implements Parameter { + + private final String ID; + private final String NAME; + private final boolean SECRET; + private final Class IDIOMATIC_TYPE; // data type, or if collection, element type + private final Class> COLLECTION_TYPE; // null if param doesn't represent collection + private final Converter CONVERTER; + + public DefaultParameter(String id, String name, boolean secret, + Class idiomaticType, + Class> collectionType, + Converter converter) { + this.ID = Strings.clean(Assert.hasText(id, "ID argument cannot be null or empty.")); + this.NAME = Strings.clean(Assert.hasText(name, "Name argument cannot be null or empty.")); + this.IDIOMATIC_TYPE = Assert.notNull(idiomaticType, "idiomaticType argument cannot be null."); + this.CONVERTER = Assert.notNull(converter, "Converter argument cannot be null."); + this.SECRET = secret; + this.COLLECTION_TYPE = collectionType; // can be null if parameter isn't a collection + } + + @Override + public String getId() { + return this.ID; + } + + @Override + public String getName() { + return this.NAME; + } + + @Override + public boolean supports(Object value) { + if (value == null) { + return true; + } + if (COLLECTION_TYPE != null && COLLECTION_TYPE.isInstance(value)) { + Collection c = COLLECTION_TYPE.cast(value); + return c.isEmpty() || IDIOMATIC_TYPE.isInstance(c.iterator().next()); + } + return IDIOMATIC_TYPE.isInstance(value); + } + + @SuppressWarnings("unchecked") + @Override + public T cast(Object value) { + if (value != null) { + if (COLLECTION_TYPE != null) { // parameter represents a collection, ensure it and its elements are the expected type: + if (!COLLECTION_TYPE.isInstance(value)) { + String msg = "Cannot cast " + value.getClass().getName() + " to " + + COLLECTION_TYPE.getName() + "<" + IDIOMATIC_TYPE.getName() + ">"; + throw new ClassCastException(msg); + } + Collection c = COLLECTION_TYPE.cast(value); + if (!c.isEmpty()) { + Object element = c.iterator().next(); + if (!IDIOMATIC_TYPE.isInstance(element)) { + String msg = "Cannot cast " + value.getClass().getName() + " to " + + COLLECTION_TYPE.getName() + "<" + IDIOMATIC_TYPE.getName() + ">: At least one " + + "element is not an instance of " + IDIOMATIC_TYPE.getName(); + throw new ClassCastException(msg); + } + } + } else if (!IDIOMATIC_TYPE.isInstance(value)) { + String msg = "Cannot cast " + value.getClass().getName() + " to " + IDIOMATIC_TYPE.getName(); + throw new ClassCastException(msg); + } + } + return (T) value; + } + + @Override + public boolean isSecret() { + return SECRET; + } + + @Override + public int hashCode() { + return this.ID.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Parameter) { + return this.ID.equals(((Parameter) obj).getId()); + } + return false; + } + + @Override + public String toString() { + return "'" + this.ID + "' (" + this.NAME + ")"; + } + + @Override + public Object applyTo(T t) { + return CONVERTER.applyTo(t); + } + + @Override + public T applyFrom(Object o) { + return CONVERTER.applyFrom(o); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultParameterBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultParameterBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultParameterBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2022 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.Assert; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +public class DefaultParameterBuilder implements ParameterBuilder { + + private String id; + private String name; + private boolean secret; + private final Class type; + private Converter converter; + private Class> collectionType; // will be null if parameter doesn't represent a collection (list or set) + + public DefaultParameterBuilder(Class type) { + this.type = Assert.notNull(type, "Type cannot be null."); + } + + @Override + public ParameterBuilder setId(String id) { + this.id = id; + return this; + } + + @Override + public ParameterBuilder setName(String name) { + this.name = name; + return this; + } + + @Override + public ParameterBuilder setSecret(boolean secret) { + this.secret = secret; + return this; + } + + @SuppressWarnings("unchecked") + @Override + public ParameterBuilder> list() { + Class clazz = List.class; + this.collectionType = (Class>) clazz; + return (ParameterBuilder>) this; + } + + @SuppressWarnings("unchecked") + @Override + public ParameterBuilder> set() { + Class clazz = Set.class; + this.collectionType = (Class>) clazz; + return (ParameterBuilder>) this; + } + + @Override + public ParameterBuilder setConverter(Converter converter) { + this.converter = converter; + return this; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public Parameter build() { + Assert.notNull(this.type, "Type must be set."); + Converter conv = this.converter; + if (conv == null) { + conv = Converters.forType(this.type); + } + if (this.collectionType != null) { + conv = List.class.isAssignableFrom(collectionType) ? Converters.forList(conv) : Converters.forSet(conv); + } + if (this.secret) { + conv = new RedactedValueConverter(conv); + } + return new DefaultParameter<>(this.id, this.name, this.secret, this.type, this.collectionType, conv); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultRegistry.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultRegistry.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DefaultRegistry.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,108 @@ +/* + * Copyright © 2022 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.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Registry; +import io.jsonwebtoken.lang.Strings; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +public class DefaultRegistry extends DelegatingMap> implements Registry, Function { + + private final String qualifiedKeyName; + + private static Map toMap(Collection values, Function keyFn) { + Assert.notEmpty(values, "Collection of values may not be null or empty."); + Assert.notNull(keyFn, "Key function cannot be null."); + Map m = new LinkedHashMap<>(values.size()); + for (V value : values) { + K key = Assert.notNull(keyFn.apply(value), "Key function cannot return a null value."); + m.put(key, value); + } + return Collections.immutable(m); + } + + public DefaultRegistry(String name, String keyName, Collection values, Function keyFn) { + super(toMap(values, keyFn)); + name = Assert.hasText(Strings.clean(name), "name cannot be null or empty."); + keyName = Assert.hasText(Strings.clean(keyName), "keyName cannot be null or empty."); + this.qualifiedKeyName = name + " " + keyName; + } + + @Override + public V apply(K k) { + return get(k); + } + + @Override + public V forKey(K key) { + V value = get(key); + if (value == null) { + String msg = "Unrecognized " + this.qualifiedKeyName + ": " + key; + throw new IllegalArgumentException(msg); + } + return value; + } + + static T immutable() { + throw new UnsupportedOperationException("Registries are immutable and cannot be modified."); + } + + @Override + public V put(K key, V value) { + return immutable(); + } + + @Override + public V remove(Object key) { + return immutable(); + } + + @Override + public void putAll(Map m) { + immutable(); + } + + @Override + public void clear() { + 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(); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DelegatingCheckedFunction.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DelegatingCheckedFunction.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DelegatingCheckedFunction.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,30 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.impl.lang; + +public class DelegatingCheckedFunction implements CheckedFunction { + + final Function delegate; + + public DelegatingCheckedFunction(Function delegate) { + this.delegate = delegate; + } + + @Override + public R apply(T t) throws Exception { + return delegate.apply(t); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DelegatingMap.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DelegatingMap.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DelegatingMap.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,108 @@ +/* + * 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.lang.Assert; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * A {@code Map} implementation that delegates all calls to an internal Map instance. + * + * @param Map key type + * @param Map value type + * @since 0.12.0 + */ +public class DelegatingMap> implements Map { + + protected T DELEGATE; + + /** + * Initializes the instance with specified non-null backing delegate Map. + * + * @param delegate non-null delegate map to use for all map method implementations + * @throws IllegalArgumentException if {@code delegate} is null. + */ + protected DelegatingMap(T delegate) { + setDelegate(delegate); + } + + protected void setDelegate(T delegate) { + this.DELEGATE = Assert.notNull(delegate, "Delegate cannot be null."); + } + + @Override + public int size() { + return DELEGATE.size(); + } + + @Override + public Collection values() { + return DELEGATE.values(); + } + + @Override + public V get(Object id) { + return DELEGATE.get(id); + } + + @Override + public boolean isEmpty() { + return DELEGATE.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return DELEGATE.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return DELEGATE.containsValue(value); + } + + @Override + public V put(K key, V value) { + return DELEGATE.put(key, value); + } + + @Override + public V remove(Object key) { + return DELEGATE.remove(key); + } + + @Override + public void putAll(Map m) { + DELEGATE.putAll(m); + } + + @Override + public void clear() { + DELEGATE.clear(); + } + + @Override + public Set keySet() { + return DELEGATE.keySet(); + } + + @Override + public Set> entrySet() { + return DELEGATE.entrySet(); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DelegatingMapMutator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DelegatingMapMutator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/DelegatingMapMutator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,60 @@ +/* + * 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.lang.MapMutator; + +import java.util.Map; + +/** + * @since 0.12.0 + */ +public class DelegatingMapMutator, T extends MapMutator> + extends DelegatingMap implements MapMutator { + + protected DelegatingMapMutator(D delegate) { + super(delegate); + } + + @SuppressWarnings("unchecked") + protected final T self() { + return (T) this; + } + + @Override + public T empty() { + clear(); + return self(); + } + + @Override + public T add(K key, V value) { + put(key, value); + return self(); + } + + @Override + public T add(Map m) { + putAll(m); + return self(); + } + + @Override + public T delete(K key) { + remove(key); + return self(); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/EncodedObjectConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/EncodedObjectConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/EncodedObjectConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,49 @@ +/* + * Copyright © 2020 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.Assert; + +public class EncodedObjectConverter implements Converter { + + private final Class type; + private final Converter converter; + + public EncodedObjectConverter(Class type, Converter converter) { + this.type = Assert.notNull(type, "Value type cannot be null."); + this.converter = Assert.notNull(converter, "Value converter cannot be null."); + } + + @Override + public Object applyTo(T t) { + Assert.notNull(t, "Value argument cannot be null."); + return converter.applyTo(t); + } + + @Override + public T applyFrom(Object value) { + Assert.notNull(value, "Value argument cannot be null."); + if (type.isInstance(value)) { + return type.cast(value); + } else if (value instanceof CharSequence) { + return converter.applyFrom((CharSequence) value); + } else { + String msg = "Values must be either String or " + type.getName() + + " instances. Value type found: " + value.getClass().getName() + "."; + throw new IllegalArgumentException(msg); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/FormattedStringFunction.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/FormattedStringFunction.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/FormattedStringFunction.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,32 @@ +/* + * Copyright © 2022 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.Assert; + +public class FormattedStringFunction implements Function { + + private final String msg; + + public FormattedStringFunction(String msg) { + this.msg = Assert.hasText(msg, "msg argument cannot be null or empty."); + } + + @Override + public String apply(T arg) { + return String.format(msg, arg); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/FormattedStringSupplier.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/FormattedStringSupplier.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/FormattedStringSupplier.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 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.Assert; +import io.jsonwebtoken.lang.Supplier; + +public class FormattedStringSupplier implements Supplier { + + private final String msg; + + private final Object[] args; + + public FormattedStringSupplier(String msg, Object[] args) { + this.msg = Assert.hasText(msg, "Message cannot be null or empty."); + this.args = Assert.notEmpty(args, "Arguments cannot be null or empty."); + } + + @Override + public String get() { + return String.format(this.msg, this.args); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Function.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Function.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Function.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,21 @@ +/* + * Copyright © 2020 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.impl.lang; + +public interface Function { + + R apply(T t); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Functions.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Functions.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Functions.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,115 @@ +/* + * Copyright © 2022 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.Assert; + +public final class Functions { + + private Functions() { + } + + public static Function identity() { + return new Function() { + @Override + public T apply(T t) { + return t; + } + }; + } + + /** + * Wraps the specified function to ensure that if any exception occurs, it is of the specified type and/or with + * the specified message. If no exception occurs, the function's return value is returned as expected. + * + *

    If {@code fn} throws an exception, its type is checked. If it is already of type {@code exClass}, that + * exception is immediately thrown. If it is not the expected exception type, a message is created with the + * specified {@code msg} template, and a new exception of the specified type is thrown with the formatted message, + * using the original exception as its cause.

    + * + * @param fn the function to execute + * @param exClass the exception type expected, if any + * @param msg the formatted message to use if throwing a new exception, used as the first argument to {@link String#format(String, Object...) String.format}. + * @param the function argument type + * @param the function's return type + * @param type of exception to ensure + * @return the wrapping function instance. + */ + public static Function wrapFmt(CheckedFunction fn, Class exClass, String msg) { + return new PropagatingExceptionFunction<>(fn, exClass, new FormattedStringFunction(msg)); + } + + public static Function wrap(Function fn, Class exClass, String fmt, Object... args) { + return new PropagatingExceptionFunction<>(new DelegatingCheckedFunction<>(fn), exClass, new FormattedStringSupplier(fmt, args)); + } + + /** + * Returns a composed function that first applies the {@code before} function to its input, and then applies + * the {@code after} function to the result. If evaluation of either function throws an exception, it is relayed to + * the caller of the composed function. + * + * @param type of input to the {@code before} function and the resulting composed function. + * @param the type of output of the {@code before} function, and of the input to the {@code after} function. + * @param return type of the {@code after} function and the resulting composed function. + * @param before the function to invoke first + * @param after the function to invoke second with the output from the first + * @return a composed function that first applies the {@code before} function and then + * applies the {@code after} function. + * @throws IllegalArgumentException if either {@code before} or {@code after} are null. + */ + public static Function andThen(final Function before, final Function after) { + Assert.notNull(before, "Before function cannot be null."); + Assert.notNull(after, "After function cannot be null."); + return new Function() { + @Override + public R apply(T t) { + V result = before.apply(t); + return after.apply(result); + } + }; + } + + /** + * Returns a composed function that invokes the specified functions in iteration order, and returns the first + * non-null result. Once a non-null result is discovered, no further functions will be invoked, 'short-circuiting' + * any remaining functions. If evaluation of any function throws an exception, it is relayed to the caller of the + * composed function. + * + * @param the type of input of the functions, and of the composed function + * @param the type of output of the functions, and of the composed function + * @param fns the functions to iterate + * @return a composed function that invokes the specified functions in iteration order, returning the first non-null + * result. + * @throws NullPointerException if after is null + */ + @SafeVarargs + public static Function firstResult(final Function... fns) { + Assert.notEmpty(fns, "Function list cannot be null or empty."); + return new Function() { + @Override + public R apply(T t) { + for (Function fn : fns) { + Assert.notNull(fn, "Function cannot be null."); + R result = fn.apply(t); + if (result != null) { + return result; + } + } + return null; + } + }; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/IdRegistry.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/IdRegistry.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/IdRegistry.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,60 @@ +/* + * Copyright © 2022 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.Assert; +import io.jsonwebtoken.lang.Strings; + +import java.util.Collection; + +public class IdRegistry extends StringRegistry { + + public static final Function FN = new Function() { + @Override + public String apply(Identifiable identifiable) { + Assert.notNull(identifiable, "Identifiable argument cannot be null."); + return Assert.notNull(Strings.clean(identifiable.getId()), "Identifier cannot be null or empty."); + } + }; + + @SuppressWarnings("unchecked") + public static Function fn() { + return (Function) FN; + } + + public IdRegistry(String name, Collection instances) { + // 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 instances, boolean caseSensitive) { + super(name, "id", + Assert.notEmpty(instances, "Collection of Identifiable instances may not be null or empty."), + IdRegistry.fn(), + caseSensitive); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/JwtDateConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/JwtDateConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/JwtDateConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,111 @@ +/* + * Copyright © 2021 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.DateFormats; + +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; + +public class JwtDateConverter implements Converter { + + public static final JwtDateConverter INSTANCE = new JwtDateConverter(); + + @Override + public Object applyTo(Date date) { + if (date == null) { + return null; + } + // https://www.rfc-editor.org/rfc/rfc7519.html#section-2, 'Numeric Date' definition: + return date.getTime() / 1000L; + } + + @Override + public Date applyFrom(Object o) { + return toSpecDate(o); + } + + /** + * Returns an RFC-compatible {@link Date} equivalent of the specified object value using heuristics. + * + * @param value object to convert to a {@code Date} using heuristics. + * @return an RFC-compatible {@link Date} equivalent of the specified object value using heuristics. + * @since 0.10.0 + */ + public static Date toSpecDate(Object value) { + if (value == null) { + return null; + } + if (value instanceof String) { + try { + value = Long.parseLong((String) value); + } catch (NumberFormatException ignored) { // will try in the fallback toDate method call below + } + } + if (value instanceof Number) { + // https://github.com/jwtk/jjwt/issues/122: + // The JWT RFC *mandates* NumericDate values are represented as seconds. + // Because java.util.Date requires milliseconds, we need to multiply by 1000: + long seconds = ((Number) value).longValue(); + value = seconds * 1000; + } + //v would have been normalized to milliseconds if it was a number value, so perform normal date conversion: + return toDate(value); + } + + /** + * Returns a {@link Date} equivalent of the specified object value using heuristics. + * + * @param v the object value to represent as a Date. + * @return a {@link Date} equivalent of the specified object value using heuristics. + */ + public static Date toDate(Object v) { + if (v == null) { + return null; + } else if (v instanceof Date) { + return (Date) v; + } else if (v instanceof Calendar) { //since 0.10.0 + return ((Calendar) v).getTime(); + } else if (v instanceof Number) { + //assume millis: + long millis = ((Number) v).longValue(); + return new Date(millis); + } else if (v instanceof String) { + return parseIso8601Date((String) v); //ISO-8601 parsing since 0.10.0 + } else { + String msg = "Cannot create Date from object of type " + v.getClass().getName() + "."; + throw new IllegalArgumentException(msg); + } + } + + /** + * Parses the specified ISO-8601-formatted string and returns the corresponding {@link Date} instance. + * + * @param value an ISO-8601-formatted string. + * @return a {@link Date} instance reflecting the specified ISO-8601-formatted string. + * @since 0.10.0 + */ + private static Date parseIso8601Date(String value) throws IllegalArgumentException { + try { + return DateFormats.parseIso8601Date(value); + } catch (ParseException e) { + String msg = "String value is not a JWT NumericDate, nor is it ISO-8601-formatted. " + + "All heuristics exhausted. Cause: " + e.getMessage(); + throw new IllegalArgumentException(msg, e); + } + } +} Fisheye: Tag ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 refers to a dead (removed) revision in file `3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/LegacyServices.java'. Fisheye: No comparison available. Pass `N' to diff? Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/LocatorFunction.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/LocatorFunction.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/LocatorFunction.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,34 @@ +/* + * Copyright © 2020 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.Header; +import io.jsonwebtoken.Locator; +import io.jsonwebtoken.lang.Assert; + +public class LocatorFunction implements Function { + + private final Locator locator; + + public LocatorFunction(Locator locator) { + this.locator = Assert.notNull(locator, "Locator instance cannot be null."); + } + + @Override + public T apply(Header h) { + return this.locator.locate(h); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Nameable.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Nameable.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Nameable.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,21 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.impl.lang; + +public interface Nameable { + + String getName(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/NullSafeConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/NullSafeConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/NullSafeConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,37 @@ +/* + * Copyright © 2020 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.Assert; + +public class NullSafeConverter implements Converter { + + private final Converter converter; + + public NullSafeConverter(Converter converter) { + this.converter = Assert.notNull(converter, "Delegate converter cannot be null."); + } + + @Override + public B applyTo(A a) { + return a == null ? null : converter.applyTo(a); + } + + @Override + public A applyFrom(B b) { + return b == null ? null : converter.applyFrom(b); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/OptionalMethodInvoker.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/OptionalMethodInvoker.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/OptionalMethodInvoker.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,64 @@ +/* + * 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.lang.Classes; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class OptionalMethodInvoker extends ReflectionFunction { + + private final Class CLASS; + private final Method METHOD; + + private final Class[] PARAM_TYPES; + private final boolean STATIC; + + public OptionalMethodInvoker(String fqcn, String methodName) { + this(fqcn, methodName, null, false); + } + + public OptionalMethodInvoker(String fqcn, String methodName, Class paramType, boolean isStatic) { + Class clazz = null; + Method method = null; + Class[] paramTypes = paramType != null ? new Class[]{paramType} : null; + try { + clazz = Classes.forName(fqcn); + method = clazz.getMethod(methodName, paramTypes); + } catch (Throwable ignored) { + } + this.CLASS = clazz; + this.METHOD = method; + this.PARAM_TYPES = paramTypes; + this.STATIC = isStatic; + } + + @Override + protected boolean supports(T input) { + Class clazz = null; + if (CLASS != null && METHOD != null) { + clazz = STATIC && PARAM_TYPES != null ? PARAM_TYPES[0] : CLASS; + } + return clazz != null && clazz.isInstance(input); + } + + @SuppressWarnings("unchecked") + @Override + protected R invoke(T input) throws InvocationTargetException, IllegalAccessException { + return (STATIC) ? (R) METHOD.invoke(null, input) : (R) METHOD.invoke(input); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Parameter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Parameter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Parameter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,29 @@ +/* + * Copyright © 2021 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; + +public interface Parameter extends Identifiable, Converter { + + String getName(); + + boolean supports(Object value); + + T cast(Object value); + + boolean isSecret(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/ParameterBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/ParameterBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/ParameterBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,39 @@ +/* + * Copyright © 2021 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.Builder; + +import java.util.List; +import java.util.Set; + +/** + * @since 0.12.0 + */ +public interface ParameterBuilder extends Builder> { + + ParameterBuilder setId(String id); + + ParameterBuilder setName(String name); + + ParameterBuilder setSecret(boolean secret); + + ParameterBuilder> list(); + + ParameterBuilder> set(); + + ParameterBuilder setConverter(Converter converter); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/ParameterReadable.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/ParameterReadable.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/ParameterReadable.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,21 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.impl.lang; + +public interface ParameterReadable { + + T get(Parameter param); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Parameters.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Parameters.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Parameters.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,144 @@ +/* + * Copyright © 2021 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.Arrays; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Objects; +import io.jsonwebtoken.lang.Registry; + +import java.math.BigInteger; +import java.net.URI; +import java.security.MessageDigest; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public final class Parameters { + + private Parameters() { // prevent instantiation + } + + public static Parameter string(String id, String name) { + return builder(String.class).setId(id).setName(name).build(); + } + + public static Parameter rfcDate(String id, String name) { + return builder(Date.class).setConverter(JwtDateConverter.INSTANCE).setId(id).setName(name).build(); + } + + public static Parameter> x509Chain(String id, String name) { + return builder(X509Certificate.class) + .setConverter(Converters.X509_CERTIFICATE).list() + .setId(id).setName(name).build(); + } + + public static ParameterBuilder builder(Class type) { + return new DefaultParameterBuilder<>(type); + } + + public static Parameter> stringSet(String id, String name) { + return builder(String.class).set().setId(id).setName(name).build(); + } + + public static Parameter uri(String id, String name) { + return builder(URI.class).setConverter(Converters.URI).setId(id).setName(name).build(); + } + + public static ParameterBuilder bytes(String id, String name) { + return builder(byte[].class).setConverter(Converters.BASE64URL_BYTES).setId(id).setName(name); + } + + public static ParameterBuilder bigInt(String id, String name) { + return builder(BigInteger.class).setConverter(Converters.BIGINT).setId(id).setName(name); + } + + public static Parameter secretBigInt(String id, String name) { + return bigInt(id, name).setSecret(true).build(); + } + + public static Registry> registry(Parameter... params) { + return registry(Arrays.asList(params)); + } + + public static Registry> registry(Collection> params) { + return new IdRegistry<>("Parameter", params, true); + } + + public static Registry> registry(Registry> parent, Parameter... params) { + Set> set = new LinkedHashSet<>(parent.size() + params.length); + set.addAll(parent.values()); + set.addAll(Arrays.asList(params)); + return new IdRegistry<>("Parameter", set, true); + } + + public static Registry> replace(Registry> registry, Parameter param) { + Assert.notEmpty(registry, "Registry cannot be null or empty."); + Assert.notNull(param, "Parameter cannot be null."); + String id = Assert.hasText(param.getId(), "Parameter id cannot be null or empty."); + Map> newParams = new LinkedHashMap<>(registry); + newParams.remove(id); // remove old/default + newParams.put(id, param); // add new one + return registry(newParams.values()); + } + + private static byte[] bytes(BigInteger i) { + return i != null ? i.toByteArray() : null; + } + + public static boolean bytesEquals(BigInteger a, BigInteger b) { + //noinspection NumberEquality + if (a == b) return true; + if (a == null || b == null) return false; + byte[] aBytes = bytes(a); + byte[] bBytes = bytes(b); + try { + return MessageDigest.isEqual(aBytes, bBytes); + } finally { + Bytes.clear(aBytes); + Bytes.clear(bBytes); + } + } + + private static boolean equals(T a, T b, Parameter param) { + if (a == b) return true; + if (a == null || b == null) return false; + if (param.isSecret()) { + // byte[] and BigInteger are the only types of secret Parameters in the JJWT codebase + // (i.e. Parameter.isSecret() == true). If a Parameter is ever marked as secret, and it's not one of these two + // data types, we need to know about it. So we use the 'assertSecret' helper above to ensure we do: + if (a instanceof byte[]) { + return b instanceof byte[] && MessageDigest.isEqual((byte[]) a, (byte[]) b); + } else if (a instanceof BigInteger) { + return b instanceof BigInteger && bytesEquals((BigInteger) a, (BigInteger) b); + } + } + // default to a standard null-safe comparison: + return Objects.nullSafeEquals(a, b); + } + + public static boolean equals(ParameterReadable a, Object o, Parameter param) { + if (a == o) return true; + if (a == null || !(o instanceof ParameterReadable)) return false; + ParameterReadable b = (ParameterReadable) o; + return equals(a.get(param), b.get(param), param); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/PositiveIntegerConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/PositiveIntegerConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/PositiveIntegerConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,55 @@ +/* + * Copyright © 2022 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.Assert; + +import java.util.concurrent.atomic.AtomicInteger; + +public class PositiveIntegerConverter implements Converter { + + public static final PositiveIntegerConverter INSTANCE = new PositiveIntegerConverter(); + + @Override + public Object applyTo(Integer integer) { + return integer; + } + + @Override + public Integer applyFrom(Object o) { + Assert.notNull(o, "Argument cannot be null."); + int i; + if (o instanceof Byte || o instanceof Short || o instanceof Integer || o instanceof AtomicInteger) { + i = ((Number) o).intValue(); + } else { // could be Long, AtomicLong, Float, Decimal, BigInteger, BigDecimal, String, etc., all of which + // may not be accurately converted into an Integer, either due to overflow or fractional values. The + // easiest way to account for all of them is to parse the string value as an int instead of testing all + // the types: + String sval = String.valueOf(o); + try { + i = Integer.parseInt(sval); + } catch (NumberFormatException e) { + String msg = "Value cannot be represented as a java.lang.Integer."; + throw new IllegalArgumentException(msg, e); + } + } + if (i <= 0) { + String msg = "Value must be a positive integer."; + throw new IllegalArgumentException(msg); + } + return i; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/PropagatingExceptionFunction.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/PropagatingExceptionFunction.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/PropagatingExceptionFunction.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 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.Assert; +import io.jsonwebtoken.lang.Classes; +import io.jsonwebtoken.lang.Supplier; + +import java.lang.reflect.Constructor; + +public class PropagatingExceptionFunction implements Function { + + private final CheckedFunction function; + + private final Function msgFunction; + private final Class clazz; + + public PropagatingExceptionFunction(Function f, Class exceptionClass, String msg) { + this(new DelegatingCheckedFunction<>(f), exceptionClass, new ConstantFunction(msg)); + } + + public PropagatingExceptionFunction(CheckedFunction f, Class exceptionClass, final String msg) { + this(f, exceptionClass, new ConstantFunction(msg)); + } + + public PropagatingExceptionFunction(CheckedFunction fn, Class exceptionClass, final Supplier msgSupplier) { + this(fn, exceptionClass, new Function() { + @Override + public String apply(T t) { + return msgSupplier.get(); + } + }); + } + + public PropagatingExceptionFunction(CheckedFunction f, Class exceptionClass, Function msgFunction) { + this.clazz = Assert.notNull(exceptionClass, "Exception class cannot be null."); + this.msgFunction = Assert.notNull(msgFunction, "msgFunction cannot be null."); + this.function = Assert.notNull(f, "Function cannot be null"); + } + + @SuppressWarnings("unchecked") + public R apply(T t) { + try { + return function.apply(t); + } catch (Exception e) { + if (clazz.isAssignableFrom(e.getClass())) { + throw clazz.cast(e); + } + String msg = this.msgFunction.apply(t); + if (!msg.endsWith(".")) { + msg += "."; + } + msg += " Cause: " + e.getMessage(); + Class clazzz = (Class) clazz; + Constructor ctor = Classes.getConstructor(clazzz, String.class, Throwable.class); + throw Classes.instantiate(ctor, msg, e); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RedactedSupplier.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RedactedSupplier.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RedactedSupplier.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 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.Assert; +import io.jsonwebtoken.lang.Objects; +import io.jsonwebtoken.lang.Supplier; + +public class RedactedSupplier implements Supplier { + + public static final String REDACTED_VALUE = ""; + + private final T value; + + public RedactedSupplier(T value) { + this.value = Assert.notNull(value, "value cannot be null."); + } + + @Override + public T get() { + return value; + } + + @Override + public int hashCode() { + return Objects.nullSafeHashCode(value); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof RedactedSupplier) { + obj = ((RedactedSupplier) obj).value; // get the wrapped value + } + return Objects.nullSafeEquals(this.value, obj); + } + + @Override + public String toString() { + return REDACTED_VALUE; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RedactedValueConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RedactedValueConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RedactedValueConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 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.Assert; +import io.jsonwebtoken.lang.Supplier; + +public class RedactedValueConverter implements Converter { + + private final Converter delegate; + + public RedactedValueConverter(Converter delegate) { + this.delegate = Assert.notNull(delegate, "Delegate cannot be null."); + } + + @Override + public Object applyTo(T t) { + Object value = this.delegate.applyTo(t); + if (value != null && !(value instanceof RedactedSupplier)) { + value = new RedactedSupplier<>(value); + } + return value; + } + + @Override + public T applyFrom(Object o) { + if (o instanceof RedactedSupplier) { + o = ((Supplier) o).get(); + } + return this.delegate.applyFrom(o); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/ReflectionFunction.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/ReflectionFunction.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/ReflectionFunction.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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.lang; + +abstract class ReflectionFunction implements Function { + + public static final String ERR_MSG = "Reflection operation failed. This is likely due to an internal " + + "implementation programming error. Please report this to the JJWT development team. Cause: "; + + protected abstract boolean supports(T input); + + protected abstract R invoke(T input) throws Throwable; + + @Override + public final R apply(T input) { + if (supports(input)) { + try { + return invoke(input); + } catch (Throwable throwable) { + // should never happen if supportsInput is true since that would mean we're using the API incorrectly + String msg = ERR_MSG + throwable.getMessage(); + throw new IllegalStateException(msg, throwable); + } + } + return null; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RequiredBitLengthConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RequiredBitLengthConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RequiredBitLengthConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,60 @@ +/* + * Copyright © 2022 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.Assert; + +public class RequiredBitLengthConverter implements Converter { + + private final Converter converter; + + private final int bitLength; + private final boolean exact; + + public RequiredBitLengthConverter(Converter converter, int bitLength) { + this(converter, bitLength, true); + } + + public RequiredBitLengthConverter(Converter converter, int bitLength, boolean exact) { + this.converter = Assert.notNull(converter, "Converter cannot be null."); + this.bitLength = Assert.gt(bitLength, 0, "bitLength must be greater than 0"); + this.exact = exact; + } + + private byte[] assertLength(byte[] bytes) { + long len = Bytes.bitLength(bytes); + if (exact && len != this.bitLength) { + String msg = "Byte array must be exactly " + Bytes.bitsMsg(this.bitLength) + ". Found " + Bytes.bitsMsg(len); + throw new IllegalArgumentException(msg); + } else if (len < this.bitLength) { + String msg = "Byte array must be at least " + Bytes.bitsMsg(this.bitLength) + ". Found " + Bytes.bitsMsg(len); + throw new IllegalArgumentException(msg); + } + return bytes; + } + + @Override + public Object applyTo(byte[] bytes) { + assertLength(bytes); + return this.converter.applyTo(bytes); + } + + @Override + public byte[] applyFrom(Object o) { + byte[] result = this.converter.applyFrom(o); + return assertLength(result); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RequiredParameterReader.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RequiredParameterReader.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RequiredParameterReader.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,60 @@ +/* + * Copyright © 2022 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.Header; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.impl.security.JwkContext; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.MalformedKeyException; + +public class RequiredParameterReader implements ParameterReadable { + + private final ParameterReadable src; + + public RequiredParameterReader(Header header) { + this(Assert.isInstanceOf(ParameterReadable.class, header, "Header implementations must implement ParameterReadable: ")); + } + + public RequiredParameterReader(ParameterReadable src) { + this.src = Assert.notNull(src, "Source ParameterReadable cannot be null."); + Assert.isInstanceOf(Nameable.class, src, "ParameterReadable implementations must implement Nameable."); + } + + private String name() { + return ((Nameable) this.src).getName(); + } + + private JwtException malformed(String msg) { + if (this.src instanceof JwkContext || this.src instanceof Jwk) { + return new MalformedKeyException(msg); + } else { + return new MalformedJwtException(msg); + } + } + + @Override + public T get(Parameter param) { + T value = this.src.get(param); + if (value == null) { + String msg = name() + " is missing required " + param + " value."; + throw malformed(msg); + } + return value; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RequiredTypeConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RequiredTypeConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/RequiredTypeConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,48 @@ +/* + * Copyright © 2021 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.Assert; + +/** + * @since 0.12.0 + */ +public class RequiredTypeConverter implements Converter { + + private final Class type; + + public RequiredTypeConverter(Class type) { + this.type = Assert.notNull(type, "type argument cannot be null."); + } + + @Override + public Object applyTo(T t) { + return t; + } + + @Override + public T applyFrom(Object o) { + if (o == null) { + return null; + } + Class clazz = o.getClass(); + if (!type.isAssignableFrom(clazz)) { + String msg = "Unsupported value type. Expected: " + type.getName() + ", found: " + clazz.getName(); + throw new IllegalArgumentException(msg); + } + return type.cast(o); + } +} \ No newline at end of file Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Services.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Services.java (.../Services.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/Services.java (.../Services.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,22 +15,24 @@ */ package io.jsonwebtoken.impl.lang; +import io.jsonwebtoken.lang.Arrays; import io.jsonwebtoken.lang.Assert; -import java.util.ArrayList; -import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; -import static io.jsonwebtoken.lang.Collections.arrayToList; - /** * Helper class for loading services from the classpath, using a {@link ServiceLoader}. Decouples loading logic for * better separation of concerns and testability. */ public final class Services { - private static final List CLASS_LOADER_ACCESSORS = arrayToList(new ClassLoaderAccessor[] { + private static final ConcurrentMap, Object> SERVICES = new ConcurrentHashMap<>(); + + private static final List CLASS_LOADER_ACCESSORS = Arrays.asList(new ClassLoaderAccessor[]{ new ClassLoaderAccessor() { @Override public ClassLoader getClassLoader() { @@ -51,66 +53,58 @@ } }); - private Services() {} + private Services() { + } /** - * Loads and instantiates all service implementation of the given SPI class and returns them as a List. + * Returns the first available implementation for the given SPI class, checking an internal thread-safe cache first, + * and, if not found, using a {@link ServiceLoader} to find implementations. When multiple implementations are + * available it will return the first one that it encounters. There is no guarantee with regard to ordering. * * @param spi The class of the Service Provider Interface * @param The type of the SPI - * @return An unmodifiable list with an instance of all available implementations of the SPI. No guarantee is given - * on the order of implementations, if more than one. + * @return The first available instance of the service. + * @throws UnavailableImplementationException When no implementation of the SPI class can be found. + * @since 0.12.4 */ - public static List loadAll(Class spi) { - Assert.notNull(spi, "Parameter 'spi' must not be null."); - - for (ClassLoaderAccessor classLoaderAccessor : CLASS_LOADER_ACCESSORS) { - List implementations = loadAll(spi, classLoaderAccessor.getClassLoader()); - if (!implementations.isEmpty()) { - return Collections.unmodifiableList(implementations); - } + public static T get(Class spi) { + // TODO: JDK8, replace this find/putIfAbsent logic with ConcurrentMap.computeIfAbsent + T instance = findCached(spi); + if (instance == null) { + instance = loadFirst(spi); // throws UnavailableImplementationException if not found, which is what we want + SERVICES.putIfAbsent(spi, instance); // cache if not already cached } - - throw new UnavailableImplementationException(spi); + return instance; } - private static List loadAll(Class spi, ClassLoader classLoader) { - ServiceLoader serviceLoader = ServiceLoader.load(spi, classLoader); - List implementations = new ArrayList<>(); - for (T implementation : serviceLoader) { - implementations.add(implementation); + private static T findCached(Class spi) { + Assert.notNull(spi, "Service interface cannot be null."); + Object obj = SERVICES.get(spi); + if (obj != null) { + return Assert.isInstanceOf(spi, obj, "Unexpected cached service implementation type."); } - return implementations; + return null; } - /** - * Loads the first available implementation the given SPI class from the classpath. Uses the {@link ServiceLoader} - * to find implementations. When multiple implementations are available it will return the first one that it - * encounters. There is no guarantee with regard to ordering. - * - * @param spi The class of the Service Provider Interface - * @param The type of the SPI - * @return A new instance of the service. - * @throws UnavailableImplementationException When no implementation the SPI is available on the classpath. - */ - public static T loadFirst(Class spi) { - Assert.notNull(spi, "Parameter 'spi' must not be null."); - - for (ClassLoaderAccessor classLoaderAccessor : CLASS_LOADER_ACCESSORS) { - T result = loadFirst(spi, classLoaderAccessor.getClassLoader()); - if (result != null) { - return result; + private static T loadFirst(Class spi) { + for (ClassLoaderAccessor accessor : CLASS_LOADER_ACCESSORS) { + ServiceLoader loader = ServiceLoader.load(spi, accessor.getClassLoader()); + Assert.stateNotNull(loader, "JDK ServiceLoader#load should never return null."); + Iterator i = loader.iterator(); + Assert.stateNotNull(i, "JDK ServiceLoader#iterator() should never return null."); + if (i.hasNext()) { + return i.next(); } } throw new UnavailableImplementationException(spi); } - private static T loadFirst(Class spi, ClassLoader classLoader) { - ServiceLoader serviceLoader = ServiceLoader.load(spi, classLoader); - if (serviceLoader.iterator().hasNext()) { - return serviceLoader.iterator().next(); - } - return null; + /** + * Clears internal cache of service singletons. This is useful when testing, or for applications that dynamically + * change classloaders. + */ + public static void reload() { + SERVICES.clear(); } private interface ClassLoaderAccessor { Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/StringRegistry.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/StringRegistry.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/StringRegistry.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,77 @@ +/* + * Copyright © 2022 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.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Strings; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; + +public class StringRegistry extends DefaultRegistry { + + private final Function CASE_FN; + + private final Map CI_VALUES; + + public StringRegistry(String name, String keyName, Collection values, Function keyFn, boolean caseSensitive) { + this(name, keyName, values, keyFn, caseSensitive ? Functions.identity() : CaseInsensitiveFunction.ENGLISH); + } + + public StringRegistry(String name, String keyName, Collection values, Function keyFn, Function caseFn) { + super(name, keyName, values, keyFn); + this.CASE_FN = Assert.notNull(caseFn, "Case function cannot be null."); + Map m = new LinkedHashMap<>(values().size()); + for (V value : values) { + String key = keyFn.apply(value); + key = this.CASE_FN.apply(key); + m.put(key, value); + } + this.CI_VALUES = Collections.immutable(m); + } + + @Override + public V get(Object key) { + String id = (String) key; // could throw ClassCastException as allowed per Map 'get' contract + Assert.hasText(id, "id argument cannot be null or empty."); + V instance = super.get(id); //try standard ID lookup first. This will satisfy 99% of invocations + if (instance == null) { // fall back to case-insensitive ID lookup: + id = CASE_FN.apply(id); + instance = CI_VALUES.get(id); + } + return instance; + } + + private static final class CaseInsensitiveFunction implements Function { + + private static final CaseInsensitiveFunction ENGLISH = new CaseInsensitiveFunction(Locale.ENGLISH); + + private final Locale LOCALE; + + private CaseInsensitiveFunction(Locale locale) { + this.LOCALE = Assert.notNull(locale, "Case insensitive Locale argument cannot be null."); + } + + @Override + public String apply(String s) { + s = Assert.notNull(Strings.clean(s), "String identifier cannot be null or empty."); + return s.toUpperCase(LOCALE); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/UnavailableImplementationException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/UnavailableImplementationException.java (.../UnavailableImplementationException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/UnavailableImplementationException.java (.../UnavailableImplementationException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -17,13 +17,17 @@ /** * Exception indicating that no implementation of an jjwt-api SPI was found on the classpath. + * * @since 0.11.0 */ public final class UnavailableImplementationException extends RuntimeException { - private static final String DEFAULT_NOT_FOUND_MESSAGE = "Unable to find an implementation for %s using java.util.ServiceLoader. Ensure you include a backing implementation .jar in the classpath, for example jjwt-impl.jar, or your own .jar for custom implementations."; + private static final String DEFAULT_NOT_FOUND_MESSAGE = "Unable to find an implementation for %s using " + + "java.util.ServiceLoader. Ensure you include a backing implementation .jar in the classpath, " + + "for example jjwt-jackson.jar, jjwt-gson.jar or jjwt-orgjson.jar, or your own .jar for " + + "custom implementations."; - UnavailableImplementationException(final Class klass) { + UnavailableImplementationException(final Class klass) { super(String.format(DEFAULT_NOT_FOUND_MESSAGE, klass)); } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/UriStringConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/UriStringConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/lang/UriStringConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,40 @@ +/* + * Copyright © 2020 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.Assert; + +import java.net.URI; + +public class UriStringConverter implements Converter { + + @Override + public String applyTo(URI uri) { + Assert.notNull(uri, "URI cannot be null."); + return uri.toString(); + } + + @Override + public URI applyFrom(CharSequence s) { + Assert.hasText(s, "URI string cannot be null or empty."); + try { + return URI.create(s.toString()); + } catch (Exception e) { + String msg = "Unable to convert String value '" + s + "' to URI instance: " + e.getMessage(); + throw new IllegalArgumentException(msg, e); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractAsymmetricJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractAsymmetricJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractAsymmetricJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 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.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Arrays; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.AsymmetricJwk; + +import java.net.URI; +import java.security.Key; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.Set; + +public abstract class AbstractAsymmetricJwk extends AbstractJwk implements AsymmetricJwk { + + static final Parameter USE = Parameters.string("use", "Public Key Use"); + public static final Parameter> X5C = Parameters.x509Chain("x5c", "X.509 Certificate Chain"); + public static final Parameter X5T = Parameters.bytes("x5t", "X.509 Certificate SHA-1 Thumbprint").build(); + public static final Parameter X5T_S256 = Parameters.bytes("x5t#S256", "X.509 Certificate SHA-256 Thumbprint").build(); + public static final Parameter X5U = Parameters.uri("x5u", "X.509 URL"); + static final Set> PARAMS = Collections.concat(AbstractJwk.PARAMS, USE, X5C, X5T, X5T_S256, X5U); + + AbstractAsymmetricJwk(JwkContext ctx, List> thumbprintParams) { + super(ctx, thumbprintParams); + } + + @Override + public String getPublicKeyUse() { + return this.context.getPublicKeyUse(); + } + + @Override + public URI getX509Url() { + return this.context.getX509Url(); + } + + @Override + public List getX509Chain() { + return Collections.immutable(this.context.getX509Chain()); + } + + @Override + public byte[] getX509Sha1Thumbprint() { + return (byte[])Arrays.copy(this.context.getX509Sha1Thumbprint()); + } + + @Override + public byte[] getX509Sha256Thumbprint() { + return (byte[])Arrays.copy(this.context.getX509Sha256Thumbprint()); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractAsymmetricJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2021 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.impl.ParameterMap; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.AsymmetricJwk; +import io.jsonwebtoken.security.AsymmetricJwkBuilder; +import io.jsonwebtoken.security.EcPrivateJwk; +import io.jsonwebtoken.security.EcPrivateJwkBuilder; +import io.jsonwebtoken.security.EcPublicJwk; +import io.jsonwebtoken.security.EcPublicJwkBuilder; +import io.jsonwebtoken.security.MalformedKeyException; +import io.jsonwebtoken.security.OctetPrivateJwk; +import io.jsonwebtoken.security.OctetPrivateJwkBuilder; +import io.jsonwebtoken.security.OctetPublicJwk; +import io.jsonwebtoken.security.OctetPublicJwkBuilder; +import io.jsonwebtoken.security.PrivateJwk; +import io.jsonwebtoken.security.PrivateJwkBuilder; +import io.jsonwebtoken.security.PublicJwk; +import io.jsonwebtoken.security.PublicJwkBuilder; +import io.jsonwebtoken.security.RsaPrivateJwk; +import io.jsonwebtoken.security.RsaPrivateJwkBuilder; +import io.jsonwebtoken.security.RsaPublicJwk; +import io.jsonwebtoken.security.RsaPublicJwkBuilder; + +import java.net.URI; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.List; + +abstract class AbstractAsymmetricJwkBuilder, T extends AsymmetricJwkBuilder> + extends AbstractJwkBuilder implements AsymmetricJwkBuilder { + + protected Boolean applyX509KeyUse = null; + private KeyUseStrategy keyUseStrategy = DefaultKeyUseStrategy.INSTANCE; + + private final X509BuilderSupport x509; + + public AbstractAsymmetricJwkBuilder(JwkContext ctx) { + super(ctx); + ParameterMap map = Assert.isInstanceOf(ParameterMap.class, this.DELEGATE); + this.x509 = new X509BuilderSupport(map, MalformedKeyException.class); + } + + AbstractAsymmetricJwkBuilder(AbstractAsymmetricJwkBuilder b, JwkContext ctx) { + this(ctx); + this.applyX509KeyUse = b.applyX509KeyUse; + this.keyUseStrategy = b.keyUseStrategy; + } + + @Override + public T publicKeyUse(String use) { + Assert.hasText(use, "publicKeyUse cannot be null or empty."); + this.DELEGATE.setPublicKeyUse(use); + return self(); + } + + /* + public T setKeyUseStrategy(KeyUseStrategy strategy) { + this.keyUseStrategy = Assert.notNull(strategy, "KeyUseStrategy cannot be null."); + return tthis(); + } + */ + + @Override + public T x509Chain(List chain) { + Assert.notEmpty(chain, "X509Certificate chain cannot be null or empty."); + this.x509.x509Chain(chain); + return self(); + } + + @Override + public T x509Url(URI uri) { + Assert.notNull(uri, "X509Url cannot be null."); + this.x509.x509Url(uri); + return self(); + } + + /* + @Override + public T withX509KeyUse(boolean enable) { + this.applyX509KeyUse = enable; + return tthis(); + } + */ + + @Override + public T x509Sha1Thumbprint(byte[] thumbprint) { + this.x509.x509Sha1Thumbprint(thumbprint); + return self(); + } + + @Override + public T x509Sha256Thumbprint(byte[] thumbprint) { + this.x509.x509Sha256Thumbprint(thumbprint); + return self(); + } + + @Override + public T x509Sha1Thumbprint(boolean enable) { + this.x509.x509Sha1Thumbprint(enable); + return self(); + } + + @Override + public T x509Sha256Thumbprint(boolean enable) { + this.x509.x509Sha256Thumbprint(enable); + return self(); + } + + @Override + public J build() { + this.x509.apply(); + return super.build(); + } + + private abstract static class DefaultPublicJwkBuilder, M extends PrivateJwk, P extends PrivateJwkBuilder, + T extends PublicJwkBuilder> + extends AbstractAsymmetricJwkBuilder + implements PublicJwkBuilder { + + DefaultPublicJwkBuilder(JwkContext ctx) { + super(ctx); + } + + @Override + public P privateKey(L privateKey) { + Assert.notNull(privateKey, "PrivateKey argument cannot be null."); + final K publicKey = Assert.notNull(DELEGATE.getKey(), "PublicKey cannot be null."); + return newPrivateBuilder(newContext(privateKey)).publicKey(publicKey); + } + + protected abstract P newPrivateBuilder(JwkContext ctx); + } + + private abstract static class DefaultPrivateJwkBuilder, M extends PrivateJwk, + T extends PrivateJwkBuilder> + extends AbstractAsymmetricJwkBuilder + implements PrivateJwkBuilder { + + DefaultPrivateJwkBuilder(JwkContext ctx) { + super(ctx); + } + + DefaultPrivateJwkBuilder(DefaultPublicJwkBuilder b, JwkContext ctx) { + super(b, ctx); + this.DELEGATE.setPublicKey(b.DELEGATE.getKey()); + } + + @Override + public T publicKey(L publicKey) { + this.DELEGATE.setPublicKey(publicKey); + return self(); + } + } + + static class DefaultRsaPublicJwkBuilder + extends DefaultPublicJwkBuilder + implements RsaPublicJwkBuilder { + + DefaultRsaPublicJwkBuilder(JwkContext ctx) { + super(ctx); + } + + @Override + protected RsaPrivateJwkBuilder newPrivateBuilder(JwkContext ctx) { + return new DefaultRsaPrivateJwkBuilder(this, ctx); + } + } + + static class DefaultEcPublicJwkBuilder + extends DefaultPublicJwkBuilder + implements EcPublicJwkBuilder { + DefaultEcPublicJwkBuilder(JwkContext src) { + super(src); + } + + @Override + protected EcPrivateJwkBuilder newPrivateBuilder(JwkContext ctx) { + return new DefaultEcPrivateJwkBuilder(this, ctx); + } + } + + static class DefaultOctetPublicJwkBuilder
    + extends DefaultPublicJwkBuilder, OctetPrivateJwk, + OctetPrivateJwkBuilder, OctetPublicJwkBuilder> + implements OctetPublicJwkBuilder { + DefaultOctetPublicJwkBuilder(JwkContext ctx) { + super(ctx); + EdwardsCurve.assertEdwards(ctx.getKey()); + } + + @Override + protected OctetPrivateJwkBuilder newPrivateBuilder(JwkContext ctx) { + return new DefaultOctetPrivateJwkBuilder<>(this, ctx); + } + } + + static class DefaultRsaPrivateJwkBuilder + extends DefaultPrivateJwkBuilder + implements RsaPrivateJwkBuilder { + DefaultRsaPrivateJwkBuilder(JwkContext src) { + super(src); + } + + DefaultRsaPrivateJwkBuilder(DefaultRsaPublicJwkBuilder b, JwkContext ctx) { + super(b, ctx); + } + } + + static class DefaultEcPrivateJwkBuilder + extends DefaultPrivateJwkBuilder + implements EcPrivateJwkBuilder { + DefaultEcPrivateJwkBuilder(JwkContext src) { + super(src); + } + + DefaultEcPrivateJwkBuilder(DefaultEcPublicJwkBuilder b, JwkContext ctx) { + super(b, ctx); + } + } + + static class DefaultOctetPrivateJwkBuilder + extends DefaultPrivateJwkBuilder, OctetPrivateJwk, + OctetPrivateJwkBuilder> implements OctetPrivateJwkBuilder { + DefaultOctetPrivateJwkBuilder(JwkContext src) { + super(src); + EdwardsCurve.assertEdwards(src.getKey()); + } + + DefaultOctetPrivateJwkBuilder(DefaultOctetPublicJwkBuilder b, JwkContext ctx) { + super(b, ctx); + EdwardsCurve.assertEdwards(ctx.getKey()); + EdwardsCurve.assertEdwards(ctx.getPublicKey()); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractCurve.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractCurve.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractCurve.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 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.lang.Assert; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.Curve; +import io.jsonwebtoken.security.KeyPairBuilder; + +import java.security.Key; + +abstract class AbstractCurve implements Curve { + + private final String ID; + + private final String JCA_NAME; + + AbstractCurve(String id, String jcaName) { + this.ID = Assert.notNull(Strings.clean(id), "Curve ID cannot be null or empty."); + this.JCA_NAME = Assert.notNull(Strings.clean(jcaName), "Curve jcaName cannot be null or empty."); + } + + @Override + public String getId() { + return this.ID; + } + + public String getJcaName() { + return this.JCA_NAME; + } + + @Override + public int hashCode() { + return ID.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Curve) { + Curve curve = (Curve) obj; + return ID.equals(curve.getId()); + } + return false; + } + + @Override + public String toString() { + return ID; + } + + public KeyPairBuilder keyPair() { + return new DefaultKeyPairBuilder(this.JCA_NAME); + } + + /** + * Returns {@code true} if the specified key is known to represent a point on the curve, {@code false} otherwise. + * + * @param key the key to test + * @return {@code true} if the specified key is known to represent a point on the curve, {@code false} otherwise. + */ + abstract boolean contains(Key key); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractEcJwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractEcJwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractEcJwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 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.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.Converters; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.io.Encoders; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.UnsupportedKeyException; + +import java.math.BigInteger; +import java.security.Key; +import java.security.interfaces.ECKey; +import java.security.spec.EllipticCurve; +import java.util.Set; + +abstract class AbstractEcJwkFactory> extends AbstractFamilyJwkFactory { + + protected static ECCurve getCurveByJwaId(String jwaCurveId) { + ECCurve curve = ECCurve.findById(jwaCurveId); + if (curve == null) { + String msg = "Unrecognized JWA EC curve id '" + jwaCurveId + "'"; + throw new UnsupportedKeyException(msg); + } + return curve; + } + + /** + * https://tools.ietf.org/html/rfc7518#section-6.2.1.2 indicates that this algorithm logic is defined in + * http://www.secg.org/sec1-v2.pdf Section 2.3.5. + * + * @param curve EllipticCurve + * @param coordinate EC point coordinate (e.g. x or y) on the {@code curve} + * @return A base64Url-encoded String representing the EC field element per the RFC format + */ + // Algorithm defined in http://www.secg.org/sec1-v2.pdf Section 2.3.5 + static String toOctetString(EllipticCurve curve, BigInteger coordinate) { + byte[] bytes = Converters.BIGINT_UBYTES.applyTo(coordinate); + int fieldSizeInBits = curve.getField().getFieldSize(); + int mlen = Bytes.length(fieldSizeInBits); + bytes = Bytes.prepad(bytes, mlen); + return Encoders.BASE64URL.encode(bytes); + } + + AbstractEcJwkFactory(Class keyType, Set> params) { + super(DefaultEcPublicJwk.TYPE_VALUE, keyType, params); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractFamilyJwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractFamilyJwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractFamilyJwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2021 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.impl.lang.CheckedFunction; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.KeyException; + +import java.security.Key; +import java.security.KeyFactory; +import java.util.Set; + +abstract class AbstractFamilyJwkFactory> implements FamilyJwkFactory { + + protected static void put(JwkContext ctx, Parameter param, T value) { + ctx.put(param.getId(), param.applyTo(value)); + } + + private final String ktyValue; + private final Class keyType; + private final Set> params; + + AbstractFamilyJwkFactory(String ktyValue, Class keyType, Set> params) { + this.ktyValue = Assert.hasText(ktyValue, "keyType argument cannot be null or empty."); + this.keyType = Assert.notNull(keyType, "keyType class cannot be null."); + this.params = Assert.notEmpty(params, "Parameters collection cannot be null or empty."); + } + + @Override + public String getId() { + return this.ktyValue; + } + + @Override + public boolean supports(Key key) { + return this.keyType.isInstance(key); + } + + @Override + public JwkContext newContext(JwkContext src, K key) { + Assert.notNull(src, "Source JwkContext cannot be null."); + return key != null ? + new DefaultJwkContext<>(this.params, src, key) : + new DefaultJwkContext(this.params, src, false); + } + + @Override + public boolean supports(JwkContext ctx) { + return supports(ctx.getKey()) || supportsKeyValues(ctx); + } + + protected boolean supportsKeyValues(JwkContext ctx) { + return this.ktyValue.equals(ctx.getType()); + } + + protected K generateKey(final JwkContext ctx, final CheckedFunction fn) { + return generateKey(ctx, this.keyType, fn); + } + + protected String getKeyFactoryJcaName(final JwkContext ctx) { + String jcaName = KeysBridge.findAlgorithm(ctx.getKey()); + return Strings.hasText(jcaName) ? jcaName : getId(); + } + + protected T generateKey(final JwkContext ctx, final Class type, final CheckedFunction fn) { + String jcaName = getKeyFactoryJcaName(ctx); + JcaTemplate template = new JcaTemplate(jcaName, ctx.getProvider(), ctx.getRandom()); + return template.withKeyFactory(new CheckedFunction() { + @Override + public T apply(KeyFactory instance) { + try { + return fn.apply(instance); + } catch (KeyException keyException) { + throw keyException; // propagate + } catch (Exception e) { + String msg = "Unable to create " + type.getSimpleName() + " from JWK " + ctx + ": " + e.getMessage(); + throw new InvalidKeyException(msg, e); + } + } + }); + } + + @Override + public final J createJwk(JwkContext ctx) { + Assert.notNull(ctx, "JwkContext argument cannot be null."); + if (!supports(ctx)) { //should be asserted by caller, but assert just in case: + String msg = "Unsupported JwkContext."; + throw new IllegalArgumentException(msg); + } + K key = ctx.getKey(); + if (key != null) { + ctx.setType(this.ktyValue); + return createJwkFromKey(ctx); + } else { + return createJwkFromValues(ctx); + } + } + + //when called, ctx.getKey() is guaranteed to be non-null + protected abstract J createJwkFromKey(JwkContext ctx); + + //when called ctx.getType() is guaranteed to equal this.ktyValue + protected abstract J createJwkFromValues(JwkContext ctx); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2021 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.impl.io.Streams; +import io.jsonwebtoken.impl.lang.Nameable; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Arrays; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Objects; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.lang.Supplier; +import io.jsonwebtoken.security.HashAlgorithm; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.JwkThumbprint; +import io.jsonwebtoken.security.Jwks; +import io.jsonwebtoken.security.KeyOperation; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public abstract class AbstractJwk implements Jwk, ParameterReadable, Nameable { + + static final Parameter ALG = Parameters.string("alg", "Algorithm"); + public static final Parameter KID = Parameters.string("kid", "Key ID"); + static final Parameter> KEY_OPS = + Parameters.builder(KeyOperation.class).setConverter(KeyOperationConverter.DEFAULT) + .set().setId("key_ops").setName("Key Operations").build(); + static final Parameter KTY = Parameters.string("kty", "Key Type"); + static final Set> PARAMS = Collections.setOf(ALG, KID, KEY_OPS, KTY); + public static final String IMMUTABLE_MSG = "JWKs are immutable and may not be modified."; + + protected final JwkContext context; + private final List> THUMBPRINT_PARAMS; + private final int hashCode; + + /** + * @param ctx the backing JwkContext containing the JWK parameter values. + * @param thumbprintParams the required parameters to include in the JWK Thumbprint canonical JSON representation, + * sorted in lexicographic order as defined by + * RFC 7638, Section 3.2. + */ + AbstractJwk(JwkContext ctx, List> thumbprintParams) { + this.context = Assert.notNull(ctx, "JwkContext cannot be null."); + Assert.isTrue(!ctx.isEmpty(), "JwkContext cannot be empty."); + Assert.hasText(ctx.getType(), "JwkContext type cannot be null or empty."); + Assert.notNull(ctx.getKey(), "JwkContext key cannot be null."); + this.THUMBPRINT_PARAMS = Assert.notEmpty(thumbprintParams, "JWK Thumbprint parameters cannot be null or empty."); + HashAlgorithm idThumbprintAlg = ctx.getIdThumbprintAlgorithm(); + if (!Strings.hasText(getId()) && idThumbprintAlg != null) { + JwkThumbprint thumbprint = thumbprint(idThumbprintAlg); + String kid = thumbprint.toString(); + ctx.setId(kid); + } + this.hashCode = computeHashCode(); + } + + /** + * Compute and return the JWK hashCode. As JWKs are immutable, this value will be cached as a final constant + * upon JWK instantiation. This uses the JWK's thumbprint parameters during computation, but differs from + * JwkThumbprint calculation in two ways: + *
      + *
    1. JwkThumbprints use a MessageDigest calculation, which is unnecessary overhead for a hashcode
    2. + *
    3. The hashCode calculation uses each parameter's idiomatic (Java) object value instead of the + * JwkThumbprint-required canonical (String) value.
    4. + *
    + * + * @return the JWK hashcode + */ + private int computeHashCode() { + List list = new ArrayList<>(this.THUMBPRINT_PARAMS.size() + 1 /* possible discriminator */); + // So we don't leak information about the private key value, we need a discriminator to ensure that + // public and private key hashCodes are not identical (in case both JWKs need to be in the same hash set). + // So we add a discriminator String to the list of values that are used during hashCode calculation + Key key = Assert.notNull(toKey(), "JWK toKey() value cannot be null."); + if (key instanceof PublicKey) { + list.add("Public"); + } else if (key instanceof PrivateKey) { + list.add("Private"); + } + for (Parameter param : this.THUMBPRINT_PARAMS) { + // Unlike thumbprint calculation, we get the idiomatic (Java) value, not canonical (String) value + // (We could have used either actually, but the idiomatic value hashCode calculation is probably + // faster). + Object val = Assert.notNull(get(param), "computeHashCode: Parameter idiomatic value cannot be null."); + list.add(val); + } + return Objects.nullSafeHashCode(list.toArray()); + } + + private String getRequiredThumbprintValue(Parameter param) { + Object value = get(param.getId()); + if (value instanceof Supplier) { + value = ((Supplier) value).get(); + } + return Assert.isInstanceOf(String.class, value, "Parameter canonical value is not a String."); + } + + /** + * Returns the JWK's canonically ordered JSON for JWK thumbprint computation as defined by + * RFC 7638, Section 3.2. + * + * @return the JWK's canonically ordered JSON for JWK thumbprint computation. + */ + private String toThumbprintJson() { + StringBuilder sb = new StringBuilder().append('{'); + Iterator> i = this.THUMBPRINT_PARAMS.iterator(); + while (i.hasNext()) { + Parameter param = i.next(); + String value = getRequiredThumbprintValue(param); + sb.append('"').append(param.getId()).append("\":\"").append(value).append('"'); + if (i.hasNext()) { + sb.append(","); + } + } + sb.append('}'); + return sb.toString(); + } + + @Override + public JwkThumbprint thumbprint() { + return thumbprint(Jwks.HASH.SHA256); + } + + @Override + public JwkThumbprint thumbprint(final HashAlgorithm alg) { + String json = toThumbprintJson(); + Assert.hasText(json, "Canonical JWK Thumbprint JSON cannot be null or empty."); + byte[] bytes = json.getBytes(StandardCharsets.UTF_8); // https://www.rfc-editor.org/rfc/rfc7638#section-3 #2 + InputStream in = Streams.of(bytes); + byte[] digest = alg.digest(new DefaultRequest<>(in, this.context.getProvider(), this.context.getRandom())); + return new DefaultJwkThumbprint(digest, alg); + } + + @Override + public String getType() { + return this.context.getType(); + } + + @Override + public String getName() { + return this.context.getName(); + } + + @Override + public Set getOperations() { + return Collections.immutable(this.context.getOperations()); + } + + @Override + public String getAlgorithm() { + return this.context.getAlgorithm(); + } + + @Override + public String getId() { + return this.context.getId(); + } + + @Override + public K toKey() { + return this.context.getKey(); + } + + @Override + public int size() { + return this.context.size(); + } + + @Override + public boolean isEmpty() { + return this.context.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return this.context.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return this.context.containsValue(value); + } + + @Override + public Object get(Object key) { + Object val = this.context.get(key); + if (val instanceof Map) { + return Collections.immutable((Map) val); + } else if (val instanceof Collection) { + return Collections.immutable((Collection) val); + } else if (Objects.isArray(val)) { + return Arrays.copy(val); + } else { + return val; + } + } + + @Override + public T get(Parameter param) { + return this.context.get(param); + } + + @Override + public Set keySet() { + return Collections.immutable(this.context.keySet()); + } + + @Override + public Collection values() { + return Collections.immutable(this.context.values()); + } + + @Override + public Set> entrySet() { + return Collections.immutable(this.context.entrySet()); + } + + private static Object immutable() { + throw new UnsupportedOperationException(IMMUTABLE_MSG); + } + + @Override + public Object put(String s, Object o) { + return immutable(); + } + + @Override + public Object remove(Object o) { + return immutable(); + } + + @Override + public void putAll(Map m) { + immutable(); + } + + @Override + public void clear() { + immutable(); + } + + @Override + public String toString() { + return this.context.toString(); + } + + @Override + public final int hashCode() { + return this.hashCode; + } + + @Override + public final boolean equals(Object obj) { + if (obj == this) return true; + if (obj instanceof Jwk) { + Jwk other = (Jwk) obj; + // this.getType() guaranteed non-null in constructor: + return getType().equals(other.getType()) && equals(other); + } + return false; + } + + protected abstract boolean equals(Jwk jwk); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2021 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.impl.lang.DefaultNestedCollection; +import io.jsonwebtoken.impl.lang.DelegatingMapMutator; +import io.jsonwebtoken.impl.lang.IdRegistry; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.NestedCollection; +import io.jsonwebtoken.lang.Registry; +import io.jsonwebtoken.security.HashAlgorithm; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.JwkBuilder; +import io.jsonwebtoken.security.Jwks; +import io.jsonwebtoken.security.KeyOperation; +import io.jsonwebtoken.security.KeyOperationPolicy; +import io.jsonwebtoken.security.MalformedKeyException; +import io.jsonwebtoken.security.SecretJwk; +import io.jsonwebtoken.security.SecretJwkBuilder; + +import javax.crypto.SecretKey; +import java.security.Key; +import java.security.Provider; +import java.security.SecureRandom; +import java.util.Collection; +import java.util.Set; + +abstract class AbstractJwkBuilder, T extends JwkBuilder> + extends DelegatingMapMutator, T> + implements JwkBuilder { + + protected final JwkFactory jwkFactory; + + static final KeyOperationPolicy DEFAULT_OPERATION_POLICY = Jwks.OP.policy().build(); + + protected KeyOperationPolicy opsPolicy = DEFAULT_OPERATION_POLICY; // default + + @SuppressWarnings("unchecked") + protected AbstractJwkBuilder(JwkContext jwkContext) { + this(jwkContext, (JwkFactory) DispatchingJwkFactory.DEFAULT_INSTANCE); + } + + // visible for testing + protected AbstractJwkBuilder(JwkContext context, JwkFactory factory) { + super(context); + this.jwkFactory = Assert.notNull(factory, "JwkFactory cannot be null."); + } + + @SuppressWarnings("unchecked") + protected JwkContext newContext(A key) { + return (JwkContext) this.jwkFactory.newContext(this.DELEGATE, (K) key); + } + + @Override + public T provider(Provider provider) { + this.DELEGATE.setProvider(provider); + return self(); + } + + @Override + public T random(SecureRandom random) { + this.DELEGATE.setRandom(random); + return self(); + } + + @Override + public T algorithm(String alg) { + Assert.hasText(alg, "Algorithm cannot be null or empty."); + this.DELEGATE.setAlgorithm(alg); + return self(); + } + + @Override + public T id(String id) { + Assert.hasText(id, "Id cannot be null or empty."); + this.DELEGATE.setIdThumbprintAlgorithm(null); //clear out any previously set value + this.DELEGATE.setId(id); + return self(); + } + + @Override + public T idFromThumbprint() { + return idFromThumbprint(Jwks.HASH.SHA256); + } + + @Override + public T idFromThumbprint(HashAlgorithm alg) { + Assert.notNull(alg, "Thumbprint HashAlgorithm cannot be null."); + Assert.notNull(alg.getId(), "Thumbprint HashAlgorithm ID cannot be null."); + this.DELEGATE.setId(null); // clear out any previous value + this.DELEGATE.setIdThumbprintAlgorithm(alg); + return self(); + } + + @Override + public NestedCollection operations() { + return new DefaultNestedCollection(self(), this.DELEGATE.getOperations()) { + @Override + protected void changed() { + Collection c = getCollection(); + opsPolicy.validate(c); + DELEGATE.setOperations(c); + } + }; + } + + @Override + public T operationPolicy(KeyOperationPolicy policy) throws IllegalArgumentException { + Assert.notNull(policy, "Policy cannot be null."); + Collection ops = policy.getOperations(); + Assert.notEmpty(ops, "Policy operations cannot be null or empty."); + this.opsPolicy = policy; + + // update the JWK internal param to enable the policy's values: + Registry registry = new IdRegistry<>("JSON Web Key Operation", ops); + Parameter> param = Parameters.builder(KeyOperation.class) + .setConverter(new KeyOperationConverter(registry)).set() + .setId(AbstractJwk.KEY_OPS.getId()) + .setName(AbstractJwk.KEY_OPS.getName()) + .build(); + setDelegate(this.DELEGATE.parameter(param)); + return self(); + } + + @Override + public J build() { + + //should always exist as there isn't a way to set it outside the constructor: + Assert.stateNotNull(this.DELEGATE, "JwkContext should always be non-null"); + + K key = this.DELEGATE.getKey(); + if (key == null && isEmpty()) { + String msg = "A " + Key.class.getName() + " or one or more name/value pairs must be provided to create a JWK."; + throw new IllegalStateException(msg); + } + + try { + this.opsPolicy.validate(this.DELEGATE.get(AbstractJwk.KEY_OPS)); + return jwkFactory.createJwk(this.DELEGATE); + } catch (IllegalArgumentException iae) { + //if we get an IAE, it means the builder state wasn't configured enough in order to create + String msg = "Unable to create JWK: " + iae.getMessage(); + throw new MalformedKeyException(msg, iae); + } + } + + static class DefaultSecretJwkBuilder extends AbstractJwkBuilder + implements SecretJwkBuilder { + public DefaultSecretJwkBuilder(JwkContext ctx) { + super(ctx); + // assign a standard algorithm if possible: + Key key = Assert.notNull(ctx.getKey(), "SecretKey cannot be null."); + DefaultMacAlgorithm mac = DefaultMacAlgorithm.findByKey(key); + if (mac != null) { + algorithm(mac.getId()); + } + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractJwkParserBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractJwkParserBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractJwkParserBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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.impl.security; + +import io.jsonwebtoken.impl.io.AbstractParserBuilder; +import io.jsonwebtoken.io.ParserBuilder; +import io.jsonwebtoken.security.KeyOperationPolicied; +import io.jsonwebtoken.security.KeyOperationPolicy; + +abstract class AbstractJwkParserBuilder & KeyOperationPolicied> + extends AbstractParserBuilder implements KeyOperationPolicied { + + protected KeyOperationPolicy operationPolicy = AbstractJwkBuilder.DEFAULT_OPERATION_POLICY; + + @Override + public B operationPolicy(KeyOperationPolicy policy) throws IllegalArgumentException { + this.operationPolicy = policy; + return self(); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractPrivateJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractPrivateJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractPrivateJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 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.impl.lang.Parameter; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.KeyPair; +import io.jsonwebtoken.security.PrivateJwk; +import io.jsonwebtoken.security.PublicJwk; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.List; + +abstract class AbstractPrivateJwk> extends AbstractAsymmetricJwk implements PrivateJwk { + + private final M publicJwk; + private final KeyPair keyPair; + + AbstractPrivateJwk(JwkContext ctx, List> thumbprintParams, M pubJwk) { + super(ctx, thumbprintParams); + this.publicJwk = Assert.notNull(pubJwk, "PublicJwk instance cannot be null."); + L publicKey = Assert.notNull(pubJwk.toKey(), "PublicJwk key instance cannot be null."); + this.context.setPublicKey(publicKey); + this.keyPair = new DefaultKeyPair<>(publicKey, toKey()); + } + + @Override + public M toPublicJwk() { + return this.publicJwk; + } + + @Override + public KeyPair toKeyPair() { + return this.keyPair; + } + + @Override + protected final boolean equals(Jwk jwk) { + return jwk instanceof PrivateJwk && equals((PrivateJwk) jwk); + } + + protected abstract boolean equals(PrivateJwk jwk); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractPublicJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractPublicJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractPublicJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 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.impl.lang.Parameter; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.PublicJwk; + +import java.security.PublicKey; +import java.util.List; + +abstract class AbstractPublicJwk extends AbstractAsymmetricJwk implements PublicJwk { + AbstractPublicJwk(JwkContext ctx, List> thumbprintParams) { + super(ctx, thumbprintParams); + } + + @Override + protected final boolean equals(Jwk jwk) { + return jwk instanceof PublicJwk && equals((PublicJwk) jwk); + } + + protected abstract boolean equals(PublicJwk jwk); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractSecureDigestAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractSecureDigestAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractSecureDigestAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 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.lang.Assert; +import io.jsonwebtoken.security.KeyException; +import io.jsonwebtoken.security.SecureDigestAlgorithm; +import io.jsonwebtoken.security.SecureRequest; +import io.jsonwebtoken.security.SecurityException; +import io.jsonwebtoken.security.SignatureException; +import io.jsonwebtoken.security.VerifySecureDigestRequest; + +import java.io.InputStream; +import java.security.Key; + +abstract class AbstractSecureDigestAlgorithm extends CryptoAlgorithm implements SecureDigestAlgorithm { + + AbstractSecureDigestAlgorithm(String id, String jcaName) { + super(id, jcaName); + } + + protected static String keyType(boolean signing) { + return signing ? "signing" : "verification"; + } + + protected abstract void validateKey(Key key, boolean signing); + + @Override + public final byte[] digest(SecureRequest request) throws SecurityException { + Assert.notNull(request, "Request cannot be null."); + final S key = Assert.notNull(request.getKey(), "Signing key cannot be null."); + Assert.notNull(request.getPayload(), "Request content cannot be null."); + try { + validateKey(key, true); + return doDigest(request); + } catch (SignatureException | KeyException e) { + throw e; //propagate + } catch (Exception e) { + String msg = "Unable to compute " + getId() + " signature with JCA algorithm '" + getJcaName() + "' " + + "using key {" + KeysBridge.toString(key) + "}: " + e.getMessage(); + throw new SignatureException(msg, e); + } + } + + protected abstract byte[] doDigest(SecureRequest request) throws Exception; + + @Override + public final boolean verify(VerifySecureDigestRequest request) throws SecurityException { + Assert.notNull(request, "Request cannot be null."); + final V key = Assert.notNull(request.getKey(), "Verification key cannot be null."); + Assert.notNull(request.getPayload(), "Request content cannot be null or empty."); + Assert.notEmpty(request.getDigest(), "Request signature byte array cannot be null or empty."); + try { + validateKey(key, false); + return doVerify(request); + } catch (SignatureException | KeyException e) { + throw e; //propagate + } catch (Exception e) { + String msg = "Unable to verify " + getId() + " signature with JCA algorithm '" + getJcaName() + "' " + + "using key {" + KeysBridge.toString(key) + "}: " + e.getMessage(); + throw new SignatureException(msg, e); + } + } + + protected abstract boolean doVerify(VerifySecureDigestRequest request); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractSecurityBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractSecurityBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractSecurityBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,44 @@ +/* + * 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.SecurityBuilder; + +import java.security.Provider; +import java.security.SecureRandom; + +abstract class AbstractSecurityBuilder> implements SecurityBuilder { + + protected Provider provider; + protected SecureRandom random; + + @SuppressWarnings("unchecked") + protected final B self() { + return (B) this; + } + + @Override + public B provider(Provider provider) { + this.provider = provider; + return self(); + } + + @Override + public B random(SecureRandom random) { + this.random = random != null ? random : Randoms.secureRandom(); + return self(); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractSignatureAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractSignatureAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AbstractSignatureAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,93 @@ +/* + * Copyright © 2018 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.impl.lang.CheckedFunction; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.SecureRequest; +import io.jsonwebtoken.security.SignatureAlgorithm; +import io.jsonwebtoken.security.VerifySecureDigestRequest; + +import java.io.InputStream; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.text.MessageFormat; + +abstract class AbstractSignatureAlgorithm extends AbstractSecureDigestAlgorithm + implements SignatureAlgorithm { + + private static final String KEY_TYPE_MSG_PATTERN = + "{0} {1} keys must be {2}s (implement {3}). Provided key type: {4}."; + + AbstractSignatureAlgorithm(String id, String jcaName) { + super(id, jcaName); + } + + @Override + protected void validateKey(Key key, boolean signing) { + // https://github.com/jwtk/jjwt/issues/68: + Class type = signing ? PrivateKey.class : PublicKey.class; + if (!type.isInstance(key)) { + String msg = MessageFormat.format(KEY_TYPE_MSG_PATTERN, getId(), + keyType(signing), type.getSimpleName(), type.getName(), key.getClass().getName()); + throw new InvalidKeyException(msg); + } + } + + protected final byte[] sign(Signature sig, InputStream payload) throws Exception { + byte[] buf = new byte[2048]; + int len = 0; + while (len != -1) { + len = payload.read(buf); + if (len > 0) sig.update(buf, 0, len); + } + return sig.sign(); + } + + @Override + protected byte[] doDigest(final SecureRequest request) { + return jca(request).withSignature(new CheckedFunction() { + @Override + public byte[] apply(Signature sig) throws Exception { + sig.initSign(request.getKey()); + return sign(sig, request.getPayload()); + } + }); + } + + protected boolean verify(Signature sig, InputStream payload, byte[] digest) throws Exception { + byte[] buf = new byte[1024]; + int len = 0; + while (len != -1) { + len = payload.read(buf); + if (len > 0) sig.update(buf, 0, len); + } + return sig.verify(digest); + } + + @Override + protected boolean doVerify(final VerifySecureDigestRequest request) { + return jca(request).withSignature(new CheckedFunction() { + @Override + public Boolean apply(Signature sig) throws Exception { + sig.initVerify(request.getKey()); + return verify(sig, request.getPayload(), request.getDigest()); + } + }); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AesAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AesAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AesAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2021 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.impl.io.Streams; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.lang.Arrays; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.IvSupplier; +import io.jsonwebtoken.security.KeyBuilderSupplier; +import io.jsonwebtoken.security.KeyLengthSupplier; +import io.jsonwebtoken.security.Request; +import io.jsonwebtoken.security.SecretKeyBuilder; +import io.jsonwebtoken.security.WeakKeyException; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; + +/** + * @since 0.12.0 + */ +abstract class AesAlgorithm extends CryptoAlgorithm implements KeyBuilderSupplier, KeyLengthSupplier { + + protected static final String KEY_ALG_NAME = "AES"; + protected static final int BLOCK_SIZE = 128; + protected static final int BLOCK_BYTE_SIZE = BLOCK_SIZE / Byte.SIZE; + protected static final int GCM_IV_SIZE = 96; // https://tools.ietf.org/html/rfc7518#section-5.3 + //protected static final int GCM_IV_BYTE_SIZE = GCM_IV_SIZE / Byte.SIZE; + protected static final String DECRYPT_NO_IV = "This algorithm implementation rejects decryption " + + "requests that do not include initialization vectors. AES ciphertext without an IV is weak and " + + "susceptible to attack."; + + protected final int keyBitLength; + protected final int ivBitLength; + protected final int tagBitLength; + protected final boolean gcm; + + /** + * Ensures {@code keyBitLength is a valid AES key length} + * @param keyBitLength the key length (in bits) to check + * @since 0.12.4 + */ + static void assertKeyBitLength(int keyBitLength) { + if (keyBitLength == 128 || keyBitLength == 192 || keyBitLength == 256) return; // valid + String msg = "Invalid AES key length: " + Bytes.bitsMsg(keyBitLength) + ". AES only supports " + + "128, 192, or 256 bit keys."; + throw new IllegalArgumentException(msg); + } + + static SecretKey keyFor(byte[] bytes) { + int bitlen = (int) Bytes.bitLength(bytes); + assertKeyBitLength(bitlen); + return new SecretKeySpec(bytes, KEY_ALG_NAME); + } + + AesAlgorithm(String id, final String jcaTransformation, int keyBitLength) { + super(id, jcaTransformation); + assertKeyBitLength(keyBitLength); + this.keyBitLength = keyBitLength; + this.gcm = jcaTransformation.startsWith("AES/GCM"); + this.ivBitLength = jcaTransformation.equals("AESWrap") ? 0 : (this.gcm ? GCM_IV_SIZE : BLOCK_SIZE); + // https://tools.ietf.org/html/rfc7518#section-5.2.3 through https://tools.ietf.org/html/rfc7518#section-5.3 : + this.tagBitLength = this.gcm ? BLOCK_SIZE : this.keyBitLength; + } + + @Override + public int getKeyBitLength() { + return this.keyBitLength; + } + + @Override + public SecretKeyBuilder key() { + return new DefaultSecretKeyBuilder(KEY_ALG_NAME, getKeyBitLength()); + } + + protected SecretKey assertKey(SecretKey key) { + Assert.notNull(key, "Request key cannot be null."); + validateLengthIfPossible(key); + return key; + } + + private void validateLengthIfPossible(SecretKey key) { + validateLength(key, this.keyBitLength, false); + } + + protected static String lengthMsg(String id, String type, int requiredLengthInBits, long actualLengthInBits) { + return "The '" + id + "' algorithm requires " + type + " with a length of " + + Bytes.bitsMsg(requiredLengthInBits) + ". The provided key has a length of " + + Bytes.bitsMsg(actualLengthInBits) + "."; + } + + protected byte[] validateLength(SecretKey key, int requiredBitLength, boolean propagate) { + byte[] keyBytes; + + try { + keyBytes = key.getEncoded(); + } catch (RuntimeException re) { + if (propagate) { + throw re; + } + //can't get the bytes to validate, e.g. hardware security module or later Android, so just return: + return null; + } + long keyBitLength = Bytes.bitLength(keyBytes); + if (keyBitLength < requiredBitLength) { + throw new WeakKeyException(lengthMsg(getId(), "keys", requiredBitLength, keyBitLength)); + } + + return keyBytes; + } + + protected byte[] assertBytes(byte[] bytes, String type, int requiredBitLen) { + long bitLen = Bytes.bitLength(bytes); + if (requiredBitLen != bitLen) { + String msg = lengthMsg(getId(), type, requiredBitLen, bitLen); + throw new IllegalArgumentException(msg); + } + return bytes; + } + + byte[] assertIvLength(final byte[] iv) { + return assertBytes(iv, "initialization vectors", this.ivBitLength); + } + + byte[] assertTag(byte[] tag) { + return assertBytes(tag, "authentication tags", this.tagBitLength); + } + + byte[] assertDecryptionIv(IvSupplier src) throws IllegalArgumentException { + byte[] iv = src.getIv(); + Assert.notEmpty(iv, DECRYPT_NO_IV); + return assertIvLength(iv); + } + + protected byte[] ensureInitializationVector(Request request) { + byte[] iv = null; + if (request instanceof IvSupplier) { + iv = Arrays.clean(((IvSupplier) request).getIv()); + } + int ivByteLength = this.ivBitLength / Byte.SIZE; + if (iv == null || iv.length == 0) { + iv = new byte[ivByteLength]; + SecureRandom random = ensureSecureRandom(request); + random.nextBytes(iv); + } else { + assertIvLength(iv); + } + return iv; + } + + protected AlgorithmParameterSpec getIvSpec(byte[] iv) { + Assert.notEmpty(iv, "Initialization Vector byte array cannot be null or empty."); + return this.gcm ? new GCMParameterSpec(BLOCK_SIZE, iv) : new IvParameterSpec(iv); + } + + protected void withCipher(Cipher cipher, InputStream in, OutputStream out) throws Exception { + byte[] last = withCipher(cipher, in, null, out); + out.write(last); // no AAD, so no tag, so we can just append + } + + private void updateAAD(Cipher cipher, InputStream aad) throws Exception { + if (aad == null) return; + byte[] buf = new byte[2048]; + int len = 0; + while (len != -1) { + len = aad.read(buf); + if (len > 0) { + cipher.updateAAD(buf, 0, len); + } + } + } + + protected byte[] withCipher(Cipher cipher, InputStream in, InputStream aad, OutputStream out) throws Exception { + updateAAD(cipher, aad); // no-op if aad is null + byte[] buf = new byte[2048]; + try { + int len = 0; + while (len != -1) { + len = in.read(buf); + if (len > 0) { + byte[] enc = cipher.update(buf, 0, len); + Streams.write(out, enc, "Unable to write Cipher output to OutputStream"); + } + } + return cipher.doFinal(); + } finally { + Bytes.clear(buf); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AesGcmKeyAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AesGcmKeyAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AesGcmKeyAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2021 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.JweHeader; +import io.jsonwebtoken.impl.DefaultJweHeader; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.CheckedFunction; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.RequiredParameterReader; +import io.jsonwebtoken.io.Encoders; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.DecryptionKeyRequest; +import io.jsonwebtoken.security.KeyRequest; +import io.jsonwebtoken.security.KeyResult; +import io.jsonwebtoken.security.SecretKeyAlgorithm; +import io.jsonwebtoken.security.SecurityException; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; + +/** + * @since 0.12.0 + */ +public class AesGcmKeyAlgorithm extends AesAlgorithm implements SecretKeyAlgorithm { + + public static final String TRANSFORMATION = "AES/GCM/NoPadding"; + + public AesGcmKeyAlgorithm(int keyLen) { + super("A" + keyLen + "GCMKW", TRANSFORMATION, keyLen); + } + + @Override + public KeyResult getEncryptionKey(final KeyRequest request) throws SecurityException { + + Assert.notNull(request, "request cannot be null."); + final JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null."); + final SecretKey kek = assertKey(request.getPayload()); + final SecretKey cek = generateCek(request); + final byte[] iv = ensureInitializationVector(request); + final AlgorithmParameterSpec ivSpec = getIvSpec(iv); + + byte[] taggedCiphertext = jca(request).withCipher(new CheckedFunction() { + @Override + public byte[] apply(Cipher cipher) throws Exception { + cipher.init(Cipher.WRAP_MODE, kek, ivSpec); + return cipher.wrap(cek); + } + }); + + int tagByteLength = this.tagBitLength / Byte.SIZE; + // When using GCM mode, the JDK appends the authentication tag to the ciphertext, so let's extract it: + int ciphertextLength = taggedCiphertext.length - tagByteLength; + byte[] ciphertext = new byte[ciphertextLength]; + System.arraycopy(taggedCiphertext, 0, ciphertext, 0, ciphertextLength); + byte[] tag = new byte[tagByteLength]; + System.arraycopy(taggedCiphertext, ciphertextLength, tag, 0, tagByteLength); + + String encodedIv = Encoders.BASE64URL.encode(iv); + String encodedTag = Encoders.BASE64URL.encode(tag); + header.put(DefaultJweHeader.IV.getId(), encodedIv); + header.put(DefaultJweHeader.TAG.getId(), encodedTag); + + return new DefaultKeyResult(cek, ciphertext); + } + + @Override + public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { + Assert.notNull(request, "request cannot be null."); + final SecretKey kek = assertKey(request.getKey()); + final byte[] cekBytes = Assert.notEmpty(request.getPayload(), "Decryption request content (ciphertext) cannot be null or empty."); + final JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null."); + ParameterReadable frHeader = Assert.isInstanceOf(ParameterReadable.class, header, "Header must implement ParameterReadable."); + final ParameterReadable reader = new RequiredParameterReader(frHeader); + final byte[] tag = reader.get(DefaultJweHeader.TAG); + final byte[] iv = reader.get(DefaultJweHeader.IV); + final AlgorithmParameterSpec ivSpec = getIvSpec(iv); + + //for tagged GCM, the JCA spec requires that the tag be appended to the end of the ciphertext byte array: + final byte[] taggedCiphertext = Bytes.concat(cekBytes, tag); + + return jca(request).withCipher(new CheckedFunction() { + @Override + public SecretKey apply(Cipher cipher) throws Exception { + cipher.init(Cipher.UNWRAP_MODE, kek, ivSpec); + Key key = cipher.unwrap(taggedCiphertext, KEY_ALG_NAME, Cipher.SECRET_KEY); + Assert.state(key instanceof SecretKey, "cipher.unwrap must produce a SecretKey instance."); + return (SecretKey) key; + } + }); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AesWrapKeyAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AesWrapKeyAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AesWrapKeyAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 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.impl.lang.CheckedFunction; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.DecryptionKeyRequest; +import io.jsonwebtoken.security.KeyRequest; +import io.jsonwebtoken.security.KeyResult; +import io.jsonwebtoken.security.SecretKeyAlgorithm; +import io.jsonwebtoken.security.SecurityException; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import java.security.Key; + +/** + * @since 0.12.0 + */ +public class AesWrapKeyAlgorithm extends AesAlgorithm implements SecretKeyAlgorithm { + + private static final String TRANSFORMATION = "AESWrap"; + + public AesWrapKeyAlgorithm(int keyLen) { + super("A" + keyLen + "KW", TRANSFORMATION, keyLen); + } + + @Override + public KeyResult getEncryptionKey(final KeyRequest request) throws SecurityException { + Assert.notNull(request, "request cannot be null."); + final SecretKey kek = assertKey(request.getPayload()); + final SecretKey cek = generateCek(request); + + byte[] ciphertext = jca(request).withCipher(new CheckedFunction() { + @Override + public byte[] apply(Cipher cipher) throws Exception { + cipher.init(Cipher.WRAP_MODE, kek); + return cipher.wrap(cek); + } + }); + + return new DefaultKeyResult(cek, ciphertext); + } + + @Override + public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { + Assert.notNull(request, "request cannot be null."); + final SecretKey kek = assertKey(request.getKey()); + final byte[] cekBytes = Assert.notEmpty(request.getPayload(), "Request content (encrypted key) cannot be null or empty."); + + return jca(request).withCipher(new CheckedFunction() { + @Override + public SecretKey apply(Cipher cipher) throws Exception { + cipher.init(Cipher.UNWRAP_MODE, kek); + Key key = cipher.unwrap(cekBytes, KEY_ALG_NAME, Cipher.SECRET_KEY); + Assert.state(key instanceof SecretKey, "Cipher unwrap must return a SecretKey instance."); + return (SecretKey) key; + } + }); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AsymmetricJwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AsymmetricJwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/AsymmetricJwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 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.lang.Assert; +import io.jsonwebtoken.security.Jwk; + +import java.security.Key; + +class AsymmetricJwkFactory implements FamilyJwkFactory> { + + private final String id; + private final FamilyJwkFactory> publicFactory; + private final FamilyJwkFactory> privateFactory; + + @SuppressWarnings({"unchecked", "rawtypes"}) + AsymmetricJwkFactory(FamilyJwkFactory publicFactory, FamilyJwkFactory privateFactory) { + this.publicFactory = (FamilyJwkFactory>) Assert.notNull(publicFactory, "publicFactory cannot be null."); + this.privateFactory = (FamilyJwkFactory>) Assert.notNull(privateFactory, "privateFactory cannot be null."); + this.id = Assert.notNull(publicFactory.getId(), "publicFactory id cannot be null or empty."); + Assert.isTrue(this.id.equals(privateFactory.getId()), "privateFactory id must equal publicFactory id"); + } + + @Override + public String getId() { + return this.id; + } + + @Override + public boolean supports(JwkContext ctx) { + return ctx != null && + (this.id.equals(ctx.getType()) || privateFactory.supports(ctx) || publicFactory.supports(ctx)); + } + + @Override + public boolean supports(Key key) { + return key != null && (privateFactory.supports(key) || publicFactory.supports(key)); + } + + @Override + public JwkContext newContext(JwkContext src, Key key) { + return (privateFactory.supports(key) || privateFactory.supports(src)) ? + privateFactory.newContext(src, key) : + publicFactory.newContext(src, key); + } + + @Override + public Jwk createJwk(JwkContext ctx) { + if (privateFactory.supports(ctx)) { + return this.privateFactory.createJwk(ctx); + } + return this.publicFactory.createJwk(ctx); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ConcatKDF.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ConcatKDF.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ConcatKDF.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2021 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.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.CheckedFunction; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.SecurityException; +import io.jsonwebtoken.security.UnsupportedKeyException; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.security.Key; +import java.security.MessageDigest; + +import static io.jsonwebtoken.impl.lang.Bytes.*; + +/** + * 'Clean room' implementation of the Concat KDF algorithm based solely on + * NIST.800-56A, + * Section 5.8.1.1. Call the {@link #deriveKey(byte[], long, byte[]) deriveKey} method. + */ +final class ConcatKDF extends CryptoAlgorithm { + + private static final long MAX_REP_COUNT = 0xFFFFFFFFL; + private static final long MAX_HASH_INPUT_BYTE_LENGTH = Integer.MAX_VALUE; //no Java byte arrays bigger than this + private static final long MAX_HASH_INPUT_BIT_LENGTH = MAX_HASH_INPUT_BYTE_LENGTH * Byte.SIZE; + + private final int hashBitLength; + + /** + * NIST.SP.800-56Ar2.pdf, Section 5.8.1.1, Input requirement #2 says that the maximum bit length of the + * derived key cannot be more than this: + *
    +     *     hashBitLength * (2^32 - 1)
    +     * 
    + * However, this number is always greater than Integer.MAX_VALUE * Byte.SIZE, which is the maximum number of + * bits that can be represented in a Java byte array. So our implementation must be limited to that size + * regardless of what the spec allows: + */ + private static final long MAX_DERIVED_KEY_BIT_LENGTH = (long) Integer.MAX_VALUE * (long) Byte.SIZE; + + ConcatKDF(String jcaName) { + super("ConcatKDF", jcaName); + int hashByteLength = jca().withMessageDigest(new CheckedFunction() { + @Override + public Integer apply(MessageDigest instance) { + return instance.getDigestLength(); + } + }); + this.hashBitLength = hashByteLength * Byte.SIZE; + Assert.state(this.hashBitLength > 0, "MessageDigest length must be a positive value."); + } + + /** + * 'Clean room' implementation of the Concat KDF algorithm based solely on + * NIST.800-56A, + * Section 5.8.1.1. + * + * @param Z shared secret key to use to seed the derived secret. Cannot be null or empty. + * @param derivedKeyBitLength the total number of bits (not bytes) required in the returned derived + * key. + * @param otherInfo any additional party info to be associated with the derived key. May be null/empty. + * @return the derived key + * @throws UnsupportedKeyException if unable to obtain {@code sharedSecretKey}'s + * {@link Key#getEncoded() encoded byte array}. + * @throws SecurityException if unable to perform the necessary {@link MessageDigest} computations to + * generate the derived key. + */ + public SecretKey deriveKey(final byte[] Z, final long derivedKeyBitLength, final byte[] otherInfo) + throws UnsupportedKeyException, SecurityException { + + // sharedSecretKey argument assertions: + Assert.notEmpty(Z, "Z cannot be null or empty."); + + // derivedKeyBitLength argument assertions: + Assert.isTrue(derivedKeyBitLength > 0, "derivedKeyBitLength must be a positive integer."); + if (derivedKeyBitLength > MAX_DERIVED_KEY_BIT_LENGTH) { + String msg = "derivedKeyBitLength may not exceed " + bitsMsg(MAX_DERIVED_KEY_BIT_LENGTH) + + ". Specified size: " + bitsMsg(derivedKeyBitLength) + "."; + throw new IllegalArgumentException(msg); + } + final long derivedKeyByteLength = derivedKeyBitLength / Byte.SIZE; + + final byte[] OtherInfo = otherInfo == null ? EMPTY : otherInfo; + + // Section 5.8.1.1, Process step #1: + final double repsd = derivedKeyBitLength / (double) this.hashBitLength; + final long reps = (long) Math.ceil(repsd); + // If repsd didn't result in a whole number, the last derived key byte will be partially filled per + // Section 5.8.1.1, Process step #6: + final boolean kLastPartial = repsd != (double) reps; + + // Section 5.8.1.1, Process step #2: + Assert.state(reps <= MAX_REP_COUNT, "derivedKeyBitLength is too large."); + + // Section 5.8.1.1, Process step #3: + final byte[] counter = new byte[]{0, 0, 0, 1}; // same as 0x0001L, but no extra step to convert to byte[] + + // Section 5.8.1.1, Process step #4: + long inputBitLength = bitLength(counter) + bitLength(Z) + bitLength(OtherInfo); + Assert.state(inputBitLength <= MAX_HASH_INPUT_BIT_LENGTH, "Hash input is too large."); + + final ClearableByteArrayOutputStream stream = new ClearableByteArrayOutputStream((int) derivedKeyByteLength); + byte[] derivedKeyBytes = EMPTY; + + try { + derivedKeyBytes = jca().withMessageDigest(new CheckedFunction() { + @Override + public byte[] apply(MessageDigest md) throws Exception { + + // Section 5.8.1.1, Process step #5. We depart from Java idioms here by starting iteration index at 1 + // (instead of 0) and continue to <= reps (instead of < reps) to match the NIST publication algorithm + // notation convention (so variables like Ki and kLast below match the NIST definitions). + for (long i = 1; i <= reps; i++) { + + // Section 5.8.1.1, Process step #5.1: + md.update(counter); + md.update(Z); + md.update(OtherInfo); + byte[] Ki = md.digest(); + + // Section 5.8.1.1, Process step #5.2: + increment(counter); + + // Section 5.8.1.1, Process step #6: + if (i == reps && kLastPartial) { + long leftmostBitLength = derivedKeyBitLength % hashBitLength; + int leftmostByteLength = (int) (leftmostBitLength / Byte.SIZE); + byte[] kLast = new byte[leftmostByteLength]; + System.arraycopy(Ki, 0, kLast, 0, kLast.length); + Ki = kLast; + } + + stream.write(Ki); + } + + // Section 5.8.1.1, Process step #7: + return stream.toByteArray(); + } + }); + return new SecretKeySpec(derivedKeyBytes, AesAlgorithm.KEY_ALG_NAME); + } finally { + // key cleanup + Bytes.clear(derivedKeyBytes); // SecretKeySpec clones this, so we can clear it out safely + Bytes.clear(counter); + stream.reset(); + // we don't clear out 'Z', since that is the responsibility of the caller + } + } + + /** + * Calling ByteArrayOutputStream.toByteArray returns a copy of the bytes, so this class allows us to completely + * zero-out the buffer upon reset (whereas BAOS just resets the position marker, leaving the bytes in tact) + */ + private static class ClearableByteArrayOutputStream extends ByteArrayOutputStream { + + public ClearableByteArrayOutputStream(int size) { + super(size); + } + + @Override + public synchronized void reset() { + super.reset(); + Bytes.clear(buf); // zero out internal buffer + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ConstantKeyLocator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ConstantKeyLocator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ConstantKeyLocator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021 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.Header; +import io.jsonwebtoken.JweHeader; +import io.jsonwebtoken.JwsHeader; +import io.jsonwebtoken.LocatorAdapter; +import io.jsonwebtoken.impl.lang.Function; + +import java.security.Key; + +public class ConstantKeyLocator extends LocatorAdapter implements Function { + + private final Key jwsKey; + private final Key jweKey; + + public ConstantKeyLocator(Key jwsKey, Key jweKey) { + this.jwsKey = jwsKey; + this.jweKey = jweKey; + } + + @Override + protected Key locate(JwsHeader header) { + return this.jwsKey; + } + + @Override + protected Key locate(JweHeader header) { + return this.jweKey; + } + + @Override + public Key apply(Header header) { + return locate(header); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/CryptoAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/CryptoAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/CryptoAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2021 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.Identifiable; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.KeyRequest; +import io.jsonwebtoken.security.Request; +import io.jsonwebtoken.security.SecretKeyBuilder; + +import javax.crypto.SecretKey; +import java.security.Provider; +import java.security.SecureRandom; + +/** + * @since 0.12.0 + */ +abstract class CryptoAlgorithm implements Identifiable { + + private final String ID; + + private final String jcaName; + + CryptoAlgorithm(String id, String jcaName) { + Assert.hasText(id, "id cannot be null or empty."); + this.ID = id; + Assert.hasText(jcaName, "jcaName cannot be null or empty."); + this.jcaName = jcaName; + } + + @Override + public String getId() { + return this.ID; + } + + String getJcaName() { + return this.jcaName; + } + + static SecureRandom ensureSecureRandom(Request request) { + SecureRandom random = request != null ? request.getSecureRandom() : null; + return random != null ? random : Randoms.secureRandom(); + } + + protected JcaTemplate jca() { + return new JcaTemplate(getJcaName()); + } + + protected JcaTemplate jca(Request request) { + Assert.notNull(request, "request cannot be null."); + String jcaName = Assert.hasText(getJcaName(request), "Request jcaName cannot be null or empty."); + Provider provider = request.getProvider(); + SecureRandom random = ensureSecureRandom(request); + return new JcaTemplate(jcaName, provider, random); + } + + protected String getJcaName(Request request) { + return getJcaName(); + } + + protected SecretKey generateCek(KeyRequest request) { + AeadAlgorithm enc = Assert.notNull(request.getEncryptionAlgorithm(), "Request encryptionAlgorithm cannot be null."); + SecretKeyBuilder builder = Assert.notNull(enc.key(), "Request encryptionAlgorithm KeyBuilder cannot be null."); + SecretKey key = builder.random(request.getSecureRandom()).build(); + return Assert.notNull(key, "Request encryptionAlgorithm SecretKeyBuilder cannot produce null keys."); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof CryptoAlgorithm) { + CryptoAlgorithm other = (CryptoAlgorithm) obj; + return this.ID.equals(other.getId()) && this.jcaName.equals(other.getJcaName()); + } + return false; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + ID.hashCode(); + hash = 31 * hash + jcaName.hashCode(); + return hash; + } + + @Override + public String toString() { + return ID; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultAeadRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultAeadRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultAeadRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 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.AeadRequest; +import io.jsonwebtoken.security.IvSupplier; + +import javax.crypto.SecretKey; +import java.io.InputStream; +import java.security.Provider; +import java.security.SecureRandom; + +/** + * @since 0.12.0 + */ +public class DefaultAeadRequest extends DefaultSecureRequest + implements AeadRequest, IvSupplier { + + private final byte[] IV; + + private final InputStream AAD; + + DefaultAeadRequest(InputStream payload, Provider provider, SecureRandom secureRandom, + SecretKey key, InputStream aad, byte[] iv) { + super(payload, provider, secureRandom, key); + this.AAD = aad; + this.IV = iv; + } + + public DefaultAeadRequest(InputStream payload, Provider provider, SecureRandom secureRandom, + SecretKey key, InputStream aad) { + this(payload, provider, secureRandom, key, aad, null); + } + + @Override + public InputStream getAssociatedData() { + return this.AAD; + } + + @Override + public byte[] getIv() { + return this.IV; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultAeadResult.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultAeadResult.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultAeadResult.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 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.lang.Assert; +import io.jsonwebtoken.security.AeadResult; +import io.jsonwebtoken.security.DigestSupplier; +import io.jsonwebtoken.security.IvSupplier; + +import java.io.OutputStream; + +public class DefaultAeadResult implements AeadResult, DigestSupplier, IvSupplier { + + private final OutputStream out; + private byte[] tag; + private byte[] iv; + + public DefaultAeadResult(OutputStream out) { + this.out = Assert.notNull(out, "OutputStream cannot be null."); + } + + @Override + public OutputStream getOutputStream() { + return this.out; + } + + @Override + public byte[] getDigest() { + return this.tag; + } + + @Override + public AeadResult setTag(byte[] tag) { + this.tag = Assert.notEmpty(tag, "Authentication Tag cannot be null or empty."); + return this; + } + + @Override + public AeadResult setIv(byte[] iv) { + this.iv = Assert.notEmpty(iv, "Initialization Vector cannot be null or empty."); + return this; + } + + @Override + public byte[] getIv() { + return this.iv; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultDecryptAeadRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultDecryptAeadRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultDecryptAeadRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,41 @@ +/* + * 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.lang.Assert; +import io.jsonwebtoken.security.DecryptAeadRequest; + +import javax.crypto.SecretKey; +import java.io.InputStream; + +/** + * @since 0.12.0 + */ +public class DefaultDecryptAeadRequest extends DefaultAeadRequest implements DecryptAeadRequest { + + private final byte[] TAG; + + public DefaultDecryptAeadRequest(InputStream payload, SecretKey key, InputStream aad, byte[] iv, byte[] tag) { + super(payload, null, null, key, aad, + Assert.notEmpty(iv, "Initialization Vector cannot be null or empty.")); + this.TAG = Assert.notEmpty(tag, "AAD Authentication Tag cannot be null or empty."); + } + + @Override + public byte[] getDigest() { + return this.TAG; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultDecryptionKeyRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultDecryptionKeyRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultDecryptionKeyRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 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.JweHeader; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.DecryptionKeyRequest; + +import java.security.Key; +import java.security.Provider; +import java.security.SecureRandom; + +public class DefaultDecryptionKeyRequest extends DefaultKeyRequest implements DecryptionKeyRequest { + + private final K decryptionKey; + + public DefaultDecryptionKeyRequest(byte[] encryptedCek, Provider provider, SecureRandom secureRandom, + JweHeader header, AeadAlgorithm encryptionAlgorithm, K decryptionKey) { + super(encryptedCek, provider, secureRandom, header, encryptionAlgorithm); + this.decryptionKey = Assert.notNull(decryptionKey, "decryption key cannot be null."); + } + + @Override + protected void assertBytePayload(byte[] payload) { + Assert.notNull(payload, "encrypted key bytes cannot be null (but may be empty."); + } + + @Override + public K getKey() { + return this.decryptionKey; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultDynamicJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultDynamicJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultDynamicJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2021 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.lang.Assert; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.DynamicJwkBuilder; +import io.jsonwebtoken.security.EcPrivateJwkBuilder; +import io.jsonwebtoken.security.EcPublicJwkBuilder; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.OctetPrivateJwkBuilder; +import io.jsonwebtoken.security.OctetPublicJwkBuilder; +import io.jsonwebtoken.security.PrivateJwkBuilder; +import io.jsonwebtoken.security.PublicJwkBuilder; +import io.jsonwebtoken.security.RsaPrivateJwkBuilder; +import io.jsonwebtoken.security.RsaPublicJwkBuilder; +import io.jsonwebtoken.security.SecretJwkBuilder; +import io.jsonwebtoken.security.UnsupportedKeyException; + +import javax.crypto.SecretKey; +import java.security.Key; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.List; + +@SuppressWarnings("unused") //used via reflection by io.jsonwebtoken.security.Jwks +public class DefaultDynamicJwkBuilder> + extends AbstractJwkBuilder> implements DynamicJwkBuilder { + + public DefaultDynamicJwkBuilder() { + this(new DefaultJwkContext()); + } + + public DefaultDynamicJwkBuilder(JwkContext ctx) { + super(ctx); + } + + @Override + public SecretJwkBuilder key(SecretKey key) { + return new AbstractJwkBuilder.DefaultSecretJwkBuilder(newContext(key)); + } + + @Override + public RsaPublicJwkBuilder key(RSAPublicKey key) { + return new AbstractAsymmetricJwkBuilder.DefaultRsaPublicJwkBuilder(newContext(key)); + } + + @Override + public RsaPrivateJwkBuilder key(RSAPrivateKey key) { + return new AbstractAsymmetricJwkBuilder.DefaultRsaPrivateJwkBuilder(newContext(key)); + } + + @Override + public EcPublicJwkBuilder key(ECPublicKey key) { + return new AbstractAsymmetricJwkBuilder.DefaultEcPublicJwkBuilder(newContext(key)); + } + + @Override + public EcPrivateJwkBuilder key(ECPrivateKey key) { + return new AbstractAsymmetricJwkBuilder.DefaultEcPrivateJwkBuilder(newContext(key)); + } + + private static UnsupportedKeyException unsupportedKey(Key key, Exception e) { + String msg = "There is no builder that supports specified key [" + KeysBridge.toString(key) + "]."; + return new UnsupportedKeyException(msg, e); + } + + @SuppressWarnings("unchecked") + @Override + public PublicJwkBuilder key(A key) { + if (key instanceof RSAPublicKey) { + return (PublicJwkBuilder) key((RSAPublicKey) key); + } else if (key instanceof ECPublicKey) { + return (PublicJwkBuilder) key((ECPublicKey) key); + } else { + try { + return octetKey(key); + } catch (Exception e) { + throw unsupportedKey(key, e); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public PrivateJwkBuilder key(B key) { + Assert.notNull(key, "Key cannot be null."); + if (key instanceof RSAPrivateKey) { + return (PrivateJwkBuilder) key((RSAPrivateKey) key); + } else if (key instanceof ECPrivateKey) { + return (PrivateJwkBuilder) key((ECPrivateKey) key); + } else { + try { + return octetKey(key); + } catch (Exception e) { + throw unsupportedKey(key, e); + } + } + } + + @Override + public OctetPublicJwkBuilder octetKey(A key) { + return new AbstractAsymmetricJwkBuilder.DefaultOctetPublicJwkBuilder<>(newContext(key)); + } + + @Override + public OctetPrivateJwkBuilder octetKey(A key) { + return new AbstractAsymmetricJwkBuilder.DefaultOctetPrivateJwkBuilder<>(newContext(key)); + } + + @SuppressWarnings("unchecked") + @Override + public PublicJwkBuilder chain(List chain) + throws UnsupportedKeyException { + Assert.notEmpty(chain, "chain cannot be null or empty."); + X509Certificate cert = Assert.notNull(chain.get(0), "The first X509Certificate cannot be null."); + PublicKey key = Assert.notNull(cert.getPublicKey(), "The first X509Certificate's PublicKey cannot be null."); + return this.key((A) key).x509Chain(chain); + } + + @Override + public RsaPublicJwkBuilder rsaChain(List chain) { + Assert.notEmpty(chain, "X509Certificate chain cannot be empty."); + X509Certificate cert = chain.get(0); + PublicKey key = cert.getPublicKey(); + RSAPublicKey pubKey = KeyPairs.assertKey(key, RSAPublicKey.class, "The first X509Certificate's "); + return key(pubKey).x509Chain(chain); + } + + @Override + public EcPublicJwkBuilder ecChain(List chain) { + Assert.notEmpty(chain, "X509Certificate chain cannot be empty."); + X509Certificate cert = chain.get(0); + PublicKey key = cert.getPublicKey(); + ECPublicKey pubKey = KeyPairs.assertKey(key, ECPublicKey.class, "The first X509Certificate's "); + return key(pubKey).x509Chain(chain); + } + + @SuppressWarnings("unchecked") // ok because of the EdwardsCurve.assertEdwards calls + @Override + public OctetPrivateJwkBuilder octetKeyPair(KeyPair pair) { + PublicKey pub = KeyPairs.getKey(pair, PublicKey.class); + PrivateKey priv = KeyPairs.getKey(pair, PrivateKey.class); + EdwardsCurve.assertEdwards(pub); + EdwardsCurve.assertEdwards(priv); + return (OctetPrivateJwkBuilder) octetKey(priv).publicKey(pub); + } + + @SuppressWarnings("unchecked") // ok because of the EdwardsCurve.assertEdwards calls + @Override + public OctetPublicJwkBuilder octetChain(List chain) { + Assert.notEmpty(chain, "X509Certificate chain cannot be empty."); + X509Certificate cert = chain.get(0); + PublicKey key = cert.getPublicKey(); + Assert.notNull(key, "The first X509Certificate's PublicKey cannot be null."); + EdwardsCurve.assertEdwards(key); + return this.octetKey((A) key).x509Chain(chain); + } + + @Override + public RsaPrivateJwkBuilder rsaKeyPair(KeyPair pair) { + RSAPublicKey pub = KeyPairs.getKey(pair, RSAPublicKey.class); + RSAPrivateKey priv = KeyPairs.getKey(pair, RSAPrivateKey.class); + return key(priv).publicKey(pub); + } + + @Override + public EcPrivateJwkBuilder ecKeyPair(KeyPair pair) { + ECPublicKey pub = KeyPairs.getKey(pair, ECPublicKey.class); + ECPrivateKey priv = KeyPairs.getKey(pair, ECPrivateKey.class); + return key(priv).publicKey(pub); + } + + @SuppressWarnings("unchecked") + @Override + public PrivateJwkBuilder keyPair(KeyPair keyPair) + throws UnsupportedKeyException { + A pub = (A) KeyPairs.getKey(keyPair, PublicKey.class); + B priv = (B) KeyPairs.getKey(keyPair, PrivateKey.class); + return this.key(priv).publicKey(pub); + } + + @Override + public J build() { + if (Strings.hasText(this.DELEGATE.get(AbstractJwk.KTY))) { + // Ensure we have a context that represents the configured kty value. Converting the existing context to + // the type-specific context will also perform any necessary parameter value type conversion / error checking + // this will also perform any necessary parameter value type conversions / error checking + setDelegate(this.jwkFactory.newContext(this.DELEGATE, this.DELEGATE.getKey())); + } + return super.build(); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultEcPrivateJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultEcPrivateJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultEcPrivateJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 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.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.EcPrivateJwk; +import io.jsonwebtoken.security.EcPublicJwk; +import io.jsonwebtoken.security.PrivateJwk; + +import java.math.BigInteger; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.util.Set; + +import static io.jsonwebtoken.impl.security.DefaultEcPublicJwk.equalsPublic; + +class DefaultEcPrivateJwk extends AbstractPrivateJwk implements EcPrivateJwk { + + static final Parameter D = Parameters.bigInt("d", "ECC Private Key") + .setConverter(FieldElementConverter.B64URL_CONVERTER) + .setSecret(true) // important! + .build(); + static final Set> PARAMS = Collections.concat(DefaultEcPublicJwk.PARAMS, D); + + DefaultEcPrivateJwk(JwkContext ctx, EcPublicJwk pubJwk) { + super(ctx, + // only public members are included in Private JWK Thumbprints per + // https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 + DefaultEcPublicJwk.THUMBPRINT_PARAMS, + pubJwk); + } + + @Override + protected boolean equals(PrivateJwk jwk) { + return jwk instanceof EcPrivateJwk && equalsPublic(this, jwk) && Parameters.equals(this, jwk, D); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultEcPublicJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultEcPublicJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultEcPublicJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 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.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.EcPublicJwk; +import io.jsonwebtoken.security.PublicJwk; + +import java.math.BigInteger; +import java.security.interfaces.ECPublicKey; +import java.util.List; +import java.util.Set; + +class DefaultEcPublicJwk extends AbstractPublicJwk implements EcPublicJwk { + + static final String TYPE_VALUE = "EC"; + + static final Parameter CRV = Parameters.string("crv", "Curve"); + static final Parameter X = Parameters.bigInt("x", "X Coordinate") + .setConverter(FieldElementConverter.B64URL_CONVERTER).build(); + static final Parameter Y = Parameters.bigInt("y", "Y Coordinate") + .setConverter(FieldElementConverter.B64URL_CONVERTER).build(); + static final Set> PARAMS = Collections.concat(AbstractAsymmetricJwk.PARAMS, CRV, X, Y); + + // https://www.rfc-editor.org/rfc/rfc7638#section-3.2 + static final List> THUMBPRINT_PARAMS = Collections.>of(CRV, KTY, X, Y); + + DefaultEcPublicJwk(JwkContext ctx) { + super(ctx, THUMBPRINT_PARAMS); + } + + static boolean equalsPublic(ParameterReadable self, Object candidate) { + return Parameters.equals(self, candidate, CRV) && + Parameters.equals(self, candidate, X) && + Parameters.equals(self, candidate, Y); + } + + @Override + protected boolean equals(PublicJwk jwk) { + return jwk instanceof EcPublicJwk && equalsPublic(this, jwk); + } + +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultHashAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultHashAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultHashAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 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.impl.lang.CheckedFunction; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.HashAlgorithm; +import io.jsonwebtoken.security.Request; +import io.jsonwebtoken.security.VerifyDigestRequest; + +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.util.Locale; + +public final class DefaultHashAlgorithm extends CryptoAlgorithm implements HashAlgorithm { + + public static final HashAlgorithm SHA1 = new DefaultHashAlgorithm("sha-1"); + + DefaultHashAlgorithm(String id) { + super(id, id.toUpperCase(Locale.ENGLISH)); + } + + @Override + public byte[] digest(final Request request) { + Assert.notNull(request, "Request cannot be null."); + final InputStream payload = Assert.notNull(request.getPayload(), "Request payload cannot be null."); + return jca(request).withMessageDigest(new CheckedFunction() { + @Override + public byte[] apply(MessageDigest md) throws IOException { + byte[] buf = new byte[1024]; + int len = 0; + while (len != -1) { + len = payload.read(buf); + if (len > 0) md.update(buf, 0, len); + } + return md.digest(); + } + }); + } + + @Override + public boolean verify(VerifyDigestRequest request) { + Assert.notNull(request, "VerifyDigestRequest cannot be null."); + byte[] digest = Assert.notNull(request.getDigest(), "Digest cannot be null."); + byte[] computed = digest(request); + return MessageDigest.isEqual(computed, digest); // time-constant comparison required, not standard equals + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkContext.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkContext.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkContext.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2022 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.impl.AbstractX509Context; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Registry; +import io.jsonwebtoken.security.HashAlgorithm; +import io.jsonwebtoken.security.Jwks; +import io.jsonwebtoken.security.KeyOperation; + +import java.security.Key; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import static io.jsonwebtoken.lang.Strings.nespace; + +public class DefaultJwkContext extends AbstractX509Context> implements JwkContext { + + private static final Set> DEFAULT_PARAMS; + + static { // assume all JWA params: + Set> set = new LinkedHashSet<>(); + set.addAll(DefaultSecretJwk.PARAMS); // Private/Secret JWKs has both public and private params + set.addAll(DefaultEcPrivateJwk.PARAMS); // Private JWKs have both public and private params + set.addAll(DefaultRsaPrivateJwk.PARAMS); // Private JWKs have both public and private params + set.addAll(DefaultOctetPrivateJwk.PARAMS); // Private JWKs have both public and private params + + // EC JWKs and Octet JWKs have two params that are named identically, but have different type requirements. So + // we swap out those params with placeholders that allow either. When the JwkContext is converted to its + // type-specific context by the ProtoBuilder, the values will be correctly converted to their required types + // at that time. It is also important to retain toString security (via parameter.setSecret(true)) to ensure + // any printing of the builder or its internal context does not print secure data. + set.remove(DefaultEcPublicJwk.X); + set.remove(DefaultEcPrivateJwk.D); + set.add(Parameters.string(DefaultEcPublicJwk.X.getId(), "Elliptic Curve public key X coordinate")); + set.add(Parameters.builder(String.class).setSecret(true) + .setId(DefaultEcPrivateJwk.D.getId()).setName("Elliptic Curve private key").build()); + + DEFAULT_PARAMS = Collections.immutable(set); + } + + private K key; + private PublicKey publicKey; + private Provider provider; + + private SecureRandom random; + + private HashAlgorithm idThumbprintAlgorithm; + + public DefaultJwkContext() { + // For the default constructor case, we don't know how it will be used or what values will be populated, + // so we can't know ahead of time what the sensitive data is. As such, for security reasons, we assume all + // the known params for all supported keys/algorithms in case it is used for any of them: + this(DEFAULT_PARAMS); + } + + public DefaultJwkContext(Set> params) { + super(params); + } + + public DefaultJwkContext(Set> params, JwkContext other) { + this(params, other, true); + } + + public DefaultJwkContext(Set> params, JwkContext other, K key) { + //if the key is null or a PublicKey, we don't want to redact - we want to fully remove the items that are + //private names (public JWKs should never contain any private key params, even if redacted): + this(params, other, (key == null || key instanceof PublicKey)); + this.key = Assert.notNull(key, "Key cannot be null."); + } + + public DefaultJwkContext(Set> params, JwkContext other, boolean removePrivate) { + super(Assert.notEmpty(params, "Parameters cannot be null or empty.")); + Assert.notNull(other, "JwkContext cannot be null."); + Assert.isInstanceOf(DefaultJwkContext.class, other, "JwkContext must be a DefaultJwkContext instance."); + DefaultJwkContext src = (DefaultJwkContext) other; + this.provider = other.getProvider(); + this.random = other.getRandom(); + this.idThumbprintAlgorithm = other.getIdThumbprintAlgorithm(); + this.values.putAll(src.values); + // Ensure the source's idiomatic values match the types expected by this object: + for (Map.Entry entry : src.idiomaticValues.entrySet()) { + String id = entry.getKey(); + Object value = entry.getValue(); + Parameter param = this.PARAMS.get(id); + if (param != null && !param.supports(value)) { // src idiomatic value is not what is expected, so convert: + value = this.values.get(param.getId()); + put(param, value); // perform idiomatic conversion with original/raw src value + } else { + this.idiomaticValues.put(id, value); + } + } + if (removePrivate) { + for (Parameter param : src.PARAMS.values()) { + if (param.isSecret()) { + remove(param.getId()); + } + } + } + } + + @Override + public JwkContext parameter(Parameter param) { + Registry> registry = Parameters.replace(this.PARAMS, param); + Set> params = new LinkedHashSet<>(registry.values()); + return this.key != null ? + new DefaultJwkContext<>(params, this, key) : + new DefaultJwkContext(params, this, false); + } + + @Override + public String getName() { + String value = get(AbstractJwk.KTY); + if (DefaultSecretJwk.TYPE_VALUE.equals(value)) { + value = "Secret"; + } else if (DefaultOctetPublicJwk.TYPE_VALUE.equals(value)) { + value = "Octet"; + } + StringBuilder sb = value != null ? new StringBuilder(value) : new StringBuilder(); + K key = getKey(); + if (key instanceof PublicKey) { + nespace(sb).append("Public"); + } else if (key instanceof PrivateKey) { + nespace(sb).append("Private"); + } + nespace(sb).append("JWK"); + return sb.toString(); + } + + @Override + public void putAll(Map m) { + Assert.notEmpty(m, "JWK values cannot be null or empty."); + super.putAll(m); + } + + @Override + public String getAlgorithm() { + return get(AbstractJwk.ALG); + } + + @Override + public JwkContext setAlgorithm(String algorithm) { + put(AbstractJwk.ALG, algorithm); + return this; + } + + @Override + public String getId() { + return get(AbstractJwk.KID); + } + + @Override + public JwkContext setId(String id) { + put(AbstractJwk.KID, id); + return this; + } + + @Override + public JwkContext setIdThumbprintAlgorithm(HashAlgorithm alg) { + this.idThumbprintAlgorithm = alg; + return this; + } + + @Override + public HashAlgorithm getIdThumbprintAlgorithm() { + return this.idThumbprintAlgorithm; + } + + @Override + public Set getOperations() { + return get(AbstractJwk.KEY_OPS); + } + + @Override + public JwkContext setOperations(Collection ops) { + put(AbstractJwk.KEY_OPS, ops); + return this; + } + + @Override + public String getType() { + return get(AbstractJwk.KTY); + } + + @Override + public JwkContext setType(String type) { + put(AbstractJwk.KTY, type); + return this; + } + + @Override + public String getPublicKeyUse() { + return get(AbstractAsymmetricJwk.USE); + } + + @Override + public JwkContext setPublicKeyUse(String use) { + put(AbstractAsymmetricJwk.USE, use); + return this; + } + + @Override + public boolean isSigUse() { + // Even though 'use' is for PUBLIC KEY use (as defined in RFC 7515), RFC 7520 shows secret keys with + // 'use' values, so we'll account for that as well: + if ("sig".equals(getPublicKeyUse())) { + return true; + } + Set ops = getOperations(); + if (Collections.isEmpty(ops)) { + return false; + } + return ops.contains(Jwks.OP.SIGN) || ops.contains(Jwks.OP.VERIFY); + } + + @Override + public K getKey() { + return this.key; + } + + @Override + public JwkContext setKey(K key) { + this.key = key; + return this; + } + + @Override + public PublicKey getPublicKey() { + return this.publicKey; + } + + @Override + public JwkContext setPublicKey(PublicKey publicKey) { + this.publicKey = publicKey; + return this; + } + + @Override + public Provider getProvider() { + return this.provider; + } + + @Override + public JwkContext setProvider(Provider provider) { + this.provider = provider; + return this; + } + + @Override + public SecureRandom getRandom() { + return this.random; + } + + @Override + public JwkContext setRandom(SecureRandom random) { + this.random = random; + return this; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkParserBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkParserBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkParserBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 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.impl.io.ConvertingParser; +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.JwkParserBuilder; + +public class DefaultJwkParserBuilder extends AbstractJwkParserBuilder, JwkParserBuilder> + implements JwkParserBuilder { + @Override + public Parser> doBuild() { + JwkDeserializer deserializer = new JwkDeserializer(this.deserializer); + JwkBuilderSupplier supplier = new JwkBuilderSupplier(this.provider, this.operationPolicy); + JwkConverter> converter = new JwkConverter<>(supplier); + return new ConvertingParser<>(deserializer, converter); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkSet.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkSet.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkSet.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,66 @@ +/* + * 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.impl.ParameterMap; +import io.jsonwebtoken.impl.lang.Converter; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.JwkSet; + +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class DefaultJwkSet extends ParameterMap implements JwkSet { + + private static final String NAME = "JWK Set"; + + static Parameter>> param(Converter, ?> converter) { + return Parameters.builder(JwkConverter.JWK_CLASS) + .setConverter(converter).set() + .setId("keys").setName("JSON Web Keys") + .setSecret(true) + .build(); + } + + static final Parameter>> KEYS = param(JwkConverter.ANY); + + public DefaultJwkSet(Parameter>> param, Map src) { + super(Parameters.registry(param), src); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public Set> getKeys() { + Set> jwks = get(KEYS); + if (Collections.isEmpty(jwks)) { + return Collections.emptySet(); + } + return Collections.immutable(jwks); + } + + @Override + public Iterator> iterator() { + return getKeys().iterator(); // immutable because of getKeys() return value + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkSetBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkSetBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkSetBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,132 @@ +/* + * 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.impl.ParameterMap; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.JwkSet; +import io.jsonwebtoken.security.JwkSetBuilder; +import io.jsonwebtoken.security.KeyOperationPolicy; + +import java.security.Provider; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +public class DefaultJwkSetBuilder extends AbstractSecurityBuilder + implements JwkSetBuilder { + + private KeyOperationPolicy operationPolicy; + private JwkSetConverter converter; + private ParameterMap map; + + public DefaultJwkSetBuilder() { + this.operationPolicy = AbstractJwkBuilder.DEFAULT_OPERATION_POLICY; + this.converter = new JwkSetConverter(); + this.map = new ParameterMap(Parameters.registry(DefaultJwkSet.KEYS)); + } + + @Override + public JwkSetBuilder delete(String key) { + map.remove(key); + return this; + } + + @Override + public JwkSetBuilder empty() { + map.clear(); + return this; + } + + @Override + public JwkSetBuilder add(String key, Object value) { + map.put(key, value); + return this; + } + + @Override + public JwkSetBuilder add(Map m) { + map.putAll(m); + return this; + } + + private JwkSetBuilder refresh() { + JwkConverter> jwkConverter = new JwkConverter<>(new JwkBuilderSupplier(this.provider, this.operationPolicy)); + this.converter = new JwkSetConverter(jwkConverter, this.converter.isIgnoreUnsupported()); + Parameter>> param = DefaultJwkSet.param(jwkConverter); + this.map = new ParameterMap(Parameters.registry(param), this.map, true); + // a new policy could have been applied, ensure that any existing keys match that policy: + Set> jwks = this.map.get(param); + if (!Collections.isEmpty(jwks)) { + for (Jwk jwk : jwks) { + this.operationPolicy.validate(jwk.getOperations()); + } + } + return this; + } + + @Override + public JwkSetBuilder provider(Provider provider) { + super.provider(provider); + return refresh(); + } + + @Override + public JwkSetBuilder operationPolicy(final KeyOperationPolicy policy) throws IllegalArgumentException { + this.operationPolicy = policy != null ? policy : AbstractJwkBuilder.DEFAULT_OPERATION_POLICY; + return refresh(); + } + + Collection> ensureKeys() { + Collection> keys = map.get(DefaultJwkSet.KEYS); + return Collections.isEmpty(keys) ? new LinkedHashSet>() : keys; + } + + @Override + public JwkSetBuilder add(Jwk jwk) { + if (jwk != null) { + this.operationPolicy.validate(jwk.getOperations()); + Collection> keys = ensureKeys(); + keys.add(jwk); + keys(keys); + } + return this; + } + + @Override + public JwkSetBuilder add(Collection> c) { + if (!Collections.isEmpty(c)) { + for (Jwk jwk : c) { + add(jwk); + } + } + return this; + } + + @Override + public JwkSetBuilder keys(Collection> c) { + return add(DefaultJwkSet.KEYS.getId(), c); + } + + @Override + public JwkSet build() { + return converter.applyFrom(this.map); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkSetParserBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,41 @@ +/* + * 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.impl.io.ConvertingParser; +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.security.JwkSet; +import io.jsonwebtoken.security.JwkSetParserBuilder; + +public class DefaultJwkSetParserBuilder extends AbstractJwkParserBuilder + implements JwkSetParserBuilder { + + private boolean ignoreUnsupported = true; + + @Override + public JwkSetParserBuilder ignoreUnsupported(boolean ignore) { + this.ignoreUnsupported = ignore; + return this; + } + + @Override + public Parser doBuild() { + JwkSetDeserializer deserializer = new JwkSetDeserializer(this.deserializer); + JwkBuilderSupplier supplier = new JwkBuilderSupplier(this.provider, this.operationPolicy); + JwkSetConverter converter = new JwkSetConverter(supplier, this.ignoreUnsupported); + return new ConvertingParser<>(deserializer, converter); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkThumbprint.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkThumbprint.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultJwkThumbprint.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2022 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.io.Encoders; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Objects; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.HashAlgorithm; +import io.jsonwebtoken.security.JwkThumbprint; + +import java.net.URI; +import java.security.MessageDigest; + +class DefaultJwkThumbprint implements JwkThumbprint { + + private static final String URI_PREFIX = "urn:ietf:params:oauth:jwk-thumbprint:"; + + private final byte[] digest; + private final HashAlgorithm alg; + private final URI uri; + private final int hashcode; + private final String sval; + + DefaultJwkThumbprint(byte[] digest, HashAlgorithm alg) { + this.digest = Assert.notEmpty(digest, "Thumbprint digest byte array cannot be null or empty."); + this.alg = Assert.notNull(alg, "Thumbprint HashAlgorithm cannot be null."); + String id = Assert.hasText(Strings.clean(alg.getId()), "Thumbprint HashAlgorithm id cannot be null or empty."); + String base64Url = Encoders.BASE64URL.encode(digest); + String s = URI_PREFIX + id + ":" + base64Url; + this.uri = URI.create(s); + this.hashcode = Objects.nullSafeHashCode(this.digest, this.alg); + this.sval = Encoders.BASE64URL.encode(digest); + } + + @Override + public HashAlgorithm getHashAlgorithm() { + return this.alg; + } + + @Override + public byte[] toByteArray() { + return this.digest.clone(); + } + + @Override + public URI toURI() { + return this.uri; + } + + @Override + public String toString() { + return sval; + } + + @Override + public int hashCode() { + return this.hashcode; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof DefaultJwkThumbprint) { + DefaultJwkThumbprint other = (DefaultJwkThumbprint) obj; + return this.alg.equals(other.alg) && + MessageDigest.isEqual(this.digest, other.digest); + } + + return false; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyOperation.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyOperation.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyOperation.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,89 @@ +/* + * 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.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.KeyOperation; + +import java.util.Set; + +final class DefaultKeyOperation implements KeyOperation { + + private static final String CUSTOM_DESCRIPTION = "Custom key operation"; + + static final KeyOperation SIGN = of("sign", "Compute digital signature or MAC", "verify"); + static final KeyOperation VERIFY = of("verify", "Verify digital signature or MAC", "sign"); + static final KeyOperation ENCRYPT = of("encrypt", "Encrypt content", "decrypt"); + static final KeyOperation DECRYPT = + of("decrypt", "Decrypt content and validate decryption, if applicable", "encrypt"); + static final KeyOperation WRAP = of("wrapKey", "Encrypt key", "unwrapKey"); + static final KeyOperation UNWRAP = + of("unwrapKey", "Decrypt key and validate decryption, if applicable", "wrapKey"); + static final KeyOperation DERIVE_KEY = of("deriveKey", "Derive key", null); + static final KeyOperation DERIVE_BITS = + of("deriveBits", "Derive bits not to be used as a key", null); + + final String id; + final String description; + final Set related; + + static KeyOperation of(String id, String description, String related) { + return new DefaultKeyOperation(id, description, Collections.setOf(related)); + } + + DefaultKeyOperation(String id) { + this(id, null, null); + } + + DefaultKeyOperation(String id, String description, Set related) { + this.id = Assert.hasText(id, "id cannot be null or empty."); + this.description = Strings.hasText(description) ? description : CUSTOM_DESCRIPTION; + this.related = related != null ? Collections.immutable(related) : Collections.emptySet(); + } + + @Override + public String getId() { + return this.id; + } + + @Override + public String getDescription() { + return this.description; + } + + @Override + public boolean isRelated(KeyOperation operation) { + return equals(operation) || (operation != null && this.related.contains(operation.getId())); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj == this || + (obj instanceof KeyOperation && this.id.equals(((KeyOperation) obj).getId())); + } + + @Override + public String toString() { + return "'" + this.id + "' (" + this.description + ")"; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyOperationBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyOperationBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyOperationBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,55 @@ +/* + * 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.lang.Strings; +import io.jsonwebtoken.security.KeyOperation; +import io.jsonwebtoken.security.KeyOperationBuilder; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class DefaultKeyOperationBuilder implements KeyOperationBuilder { + + private String id; + private String description; + private final Set related = new LinkedHashSet<>(); + + @Override + public KeyOperationBuilder id(String id) { + this.id = id; + return this; + } + + @Override + public KeyOperationBuilder description(String description) { + this.description = description; + return this; + } + + @Override + public KeyOperationBuilder related(String related) { + if (Strings.hasText(related)) { + this.related.add(related); + } + return this; + } + + @Override + public KeyOperation build() { + return new DefaultKeyOperation(this.id, this.description, this.related); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyOperationPolicy.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyOperationPolicy.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyOperationPolicy.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,76 @@ +/* + * 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.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Objects; +import io.jsonwebtoken.security.KeyOperation; +import io.jsonwebtoken.security.KeyOperationPolicy; + +import java.util.Collection; + +final class DefaultKeyOperationPolicy implements KeyOperationPolicy { + + private final Collection ops; + + private final boolean allowUnrelated; + + DefaultKeyOperationPolicy(Collection ops, boolean allowUnrelated) { + Assert.notEmpty(ops, "KeyOperation collection cannot be null or empty."); + this.ops = Collections.immutable(ops); + this.allowUnrelated = allowUnrelated; + } + + @Override + public Collection getOperations() { + return this.ops; + } + + @Override + public void validate(Collection ops) { + if (allowUnrelated || Collections.isEmpty(ops)) return; + for (KeyOperation operation : ops) { + for (KeyOperation inner : ops) { + if (!operation.isRelated(inner)) { + String msg = "Unrelated key operations are not allowed. KeyOperation [" + inner + + "] is unrelated to [" + operation + "]."; + throw new IllegalArgumentException(msg); + } + } + } + } + + @Override + public int hashCode() { + int hash = Boolean.valueOf(this.allowUnrelated).hashCode(); + KeyOperation[] ops = this.ops.toArray(new KeyOperation[0]); + hash = 31 * hash + Objects.nullSafeHashCode((Object[]) ops); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (!(obj instanceof DefaultKeyOperationPolicy)) { + return false; + } + DefaultKeyOperationPolicy other = (DefaultKeyOperationPolicy) obj; + return this.allowUnrelated == other.allowUnrelated && + Collections.size(this.ops) == Collections.size(other.ops) && + this.ops.containsAll(other.ops); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyOperationPolicyBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyOperationPolicyBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyOperationPolicyBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,44 @@ +/* + * 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.impl.lang.DefaultCollectionMutator; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.Jwks; +import io.jsonwebtoken.security.KeyOperation; +import io.jsonwebtoken.security.KeyOperationPolicy; +import io.jsonwebtoken.security.KeyOperationPolicyBuilder; + +public class DefaultKeyOperationPolicyBuilder extends DefaultCollectionMutator + implements KeyOperationPolicyBuilder { + + private boolean unrelated = false; + + public DefaultKeyOperationPolicyBuilder() { + super(Jwks.OP.get().values()); + } + + @Override + public KeyOperationPolicyBuilder unrelated() { + this.unrelated = true; + return this; + } + + @Override + public KeyOperationPolicy build() { + return new DefaultKeyOperationPolicy(Collections.immutable(getCollection()), this.unrelated); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyPair.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyPair.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyPair.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 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.lang.Assert; +import io.jsonwebtoken.security.KeyPair; + +import java.security.PrivateKey; +import java.security.PublicKey; + +public class DefaultKeyPair implements KeyPair { + + private final A publicKey; + private final B privateKey; + + private final java.security.KeyPair jdkPair; + + public DefaultKeyPair(A publicKey, B privateKey) { + this.publicKey = Assert.notNull(publicKey, "PublicKey argument cannot be null."); + this.privateKey = Assert.notNull(privateKey, "PrivateKey argument cannot be null."); + this.jdkPair = new java.security.KeyPair(this.publicKey, this.privateKey); + } + + @Override + public A getPublic() { + return this.publicKey; + } + + @Override + public B getPrivate() { + return this.privateKey; + } + + @Override + public java.security.KeyPair toJavaKeyPair() { + return this.jdkPair; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyPairBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyPairBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyPairBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 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.lang.Assert; +import io.jsonwebtoken.security.KeyPairBuilder; + +import java.security.KeyPair; +import java.security.spec.AlgorithmParameterSpec; + +public class DefaultKeyPairBuilder extends AbstractSecurityBuilder implements KeyPairBuilder { + + private final String jcaName; + private final int bitLength; + private final AlgorithmParameterSpec params; + + public DefaultKeyPairBuilder(String jcaName) { + this.jcaName = Assert.hasText(jcaName, "jcaName cannot be null or empty."); + this.bitLength = 0; + this.params = null; + } + + public DefaultKeyPairBuilder(String jcaName, int bitLength) { + this.jcaName = Assert.hasText(jcaName, "jcaName cannot be null or empty."); + this.bitLength = Assert.gt(bitLength, 0, "bitLength must be a positive integer greater than 0"); + this.params = null; + } + + public DefaultKeyPairBuilder(String jcaName, AlgorithmParameterSpec params) { + this.jcaName = Assert.hasText(jcaName, "jcaName cannot be null or empty."); + this.params = Assert.notNull(params, "AlgorithmParameterSpec params cannot be null."); + this.bitLength = 0; + } + + @Override + public KeyPair build() { + JcaTemplate template = new JcaTemplate(this.jcaName, this.provider, this.random); + if (this.params != null) { + return template.generateKeyPair(this.params); + } else if (this.bitLength > 0) { + return template.generateKeyPair(this.bitLength); + } else { + return template.generateKeyPair(); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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.JweHeader; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.KeyRequest; + +import java.security.Provider; +import java.security.SecureRandom; + +public class DefaultKeyRequest extends DefaultRequest implements KeyRequest { + + private final JweHeader header; + private final AeadAlgorithm encryptionAlgorithm; + + public DefaultKeyRequest(T payload, Provider provider, SecureRandom secureRandom, JweHeader header, AeadAlgorithm encryptionAlgorithm) { + super(payload, provider, secureRandom); + this.header = Assert.notNull(header, "JweHeader/Builder cannot be null."); + this.encryptionAlgorithm = Assert.notNull(encryptionAlgorithm, "AeadAlgorithm argument cannot be null."); + } + + @Override + public JweHeader getHeader() { + return this.header; + } + + @Override + public AeadAlgorithm getEncryptionAlgorithm() { + return this.encryptionAlgorithm; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyResult.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyResult.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyResult.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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.impl.lang.Bytes; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.KeyResult; + +import javax.crypto.SecretKey; + +public class DefaultKeyResult extends DefaultMessage implements KeyResult { + + private final SecretKey key; + + public DefaultKeyResult(SecretKey key) { + this(key, Bytes.EMPTY); + } + + public DefaultKeyResult(SecretKey key, byte[] encryptedKey) { + super(encryptedKey); + this.key = Assert.notNull(key, "Content Encryption Key cannot be null."); + } + + @Override + protected void assertBytePayload(byte[] payload) { + Assert.notNull(payload, "encrypted key bytes cannot be null (but may be empty."); + } + + @Override + public SecretKey getKey() { + return this.key; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyUseStrategy.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyUseStrategy.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultKeyUseStrategy.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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; + +public class DefaultKeyUseStrategy implements KeyUseStrategy { + + static final KeyUseStrategy INSTANCE = new DefaultKeyUseStrategy(); + + // values from https://www.rfc-editor.org/rfc/rfc7517.html#section-4.2 + private static final String SIGNATURE = "sig"; + private static final String ENCRYPTION = "enc"; + + @Override + public String toJwkValue(KeyUsage usage) { + + // states 2, 3, 4 + if (usage.isKeyEncipherment() || usage.isDataEncipherment() || usage.isKeyAgreement()) { + return ENCRYPTION; + } + + // states 0, 1, 5, 6 + if (usage.isDigitalSignature() || usage.isNonRepudiation() || usage.isKeyCertSign() || usage.isCRLSign()) { + return SIGNATURE; + } + + // We don't need to check for encipherOnly (7) and decipherOnly (8) because per + // [RFC 5280, Section 4.2.1.3](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3), + // those two states are only relevant when keyAgreement (4) is true, and that is covered in the first + // conditional above + + return null; //can't infer + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2021 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.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.CheckedFunction; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.MacAlgorithm; +import io.jsonwebtoken.security.Password; +import io.jsonwebtoken.security.SecretKeyBuilder; +import io.jsonwebtoken.security.SecureRequest; +import io.jsonwebtoken.security.VerifySecureDigestRequest; +import io.jsonwebtoken.security.WeakKeyException; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import java.io.InputStream; +import java.security.Key; +import java.security.MessageDigest; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * @since 0.12.0 + */ +final class DefaultMacAlgorithm extends AbstractSecureDigestAlgorithm implements MacAlgorithm { + + private static final String HS256_OID = "1.2.840.113549.2.9"; + private static final String HS384_OID = "1.2.840.113549.2.10"; + private static final String HS512_OID = "1.2.840.113549.2.11"; + + private static final Set JWA_STANDARD_IDS = new LinkedHashSet<>(Collections.of("HS256", "HS384", "HS512")); + + static final DefaultMacAlgorithm HS256 = new DefaultMacAlgorithm(256); + static final DefaultMacAlgorithm HS384 = new DefaultMacAlgorithm(384); + static final DefaultMacAlgorithm HS512 = new DefaultMacAlgorithm(512); + + private static final Map JCA_NAME_MAP; + + static { + JCA_NAME_MAP = new LinkedHashMap<>(6); + + // In addition to JCA names, PKCS12 OIDs are added to these per + // https://bugs.openjdk.java.net/browse/JDK-8243551 as well: + JCA_NAME_MAP.put(HS256.getJcaName().toUpperCase(Locale.ENGLISH), HS256); // for case-insensitive lookup + JCA_NAME_MAP.put(HS256_OID, HS256); + + JCA_NAME_MAP.put(HS384.getJcaName().toUpperCase(Locale.ENGLISH), HS384); + JCA_NAME_MAP.put(HS384_OID, HS384); + + JCA_NAME_MAP.put(HS512.getJcaName().toUpperCase(Locale.ENGLISH), HS512); + JCA_NAME_MAP.put(HS512_OID, HS512); + } + + private final int minKeyBitLength; //in bits + + private DefaultMacAlgorithm(int digestBitLength) { + this("HS" + digestBitLength, "HmacSHA" + digestBitLength, digestBitLength); + } + + DefaultMacAlgorithm(String id, String jcaName, int minKeyBitLength) { + super(id, jcaName); + Assert.isTrue(minKeyBitLength > 0, "minKeyLength must be greater than zero."); + this.minKeyBitLength = minKeyBitLength; + } + + @Override + public int getKeyBitLength() { + return this.minKeyBitLength; + } + + private boolean isJwaStandard() { + return JWA_STANDARD_IDS.contains(getId()); + } + + private static boolean isJwaStandardJcaName(String jcaName) { + String key = jcaName.toUpperCase(Locale.ENGLISH); + return JCA_NAME_MAP.containsKey(key); + } + + static DefaultMacAlgorithm findByKey(Key key) { + + String alg = KeysBridge.findAlgorithm(key); + if (!Strings.hasText(alg)) { + return null; + } + + String upper = alg.toUpperCase(Locale.ENGLISH); + DefaultMacAlgorithm mac = JCA_NAME_MAP.get(upper); + if (mac == null) { + return null; + } + + // even though we found a standard alg based on the JCA name, we need to confirm that the key length is + // sufficient if the encoded key bytes are available: + byte[] encoded = KeysBridge.findEncoded(key); + long size = Bytes.bitLength(encoded); + if (size >= mac.getKeyBitLength()) { + return mac; + } + + return null; // couldn't find a suitable match + } + + + @Override + public SecretKeyBuilder key() { + return new DefaultSecretKeyBuilder(getJcaName(), getKeyBitLength()); + } + + private void assertAlgorithmName(SecretKey key, boolean signing) { + + String name = key.getAlgorithm(); + if (!Strings.hasText(name)) { + String msg = "The " + keyType(signing) + " key's algorithm cannot be null or empty."; + throw new InvalidKeyException(msg); + } + + // We can ignore PKCS11 key name assertions because HSM module key algorithm names don't always align with + // JCA standard algorithm names: + boolean pkcs11Key = KeysBridge.isSunPkcs11GenericSecret(key); + + //assert key's jca name is valid if it's a JWA standard algorithm: + if (!pkcs11Key && isJwaStandard() && !isJwaStandardJcaName(name)) { + throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm '" + name + + "' does not equal a valid HmacSHA* algorithm name or PKCS12 OID and cannot be used with " + + getId() + "."); + } + } + + @Override + protected void validateKey(Key k, boolean signing) { + + final String keyType = keyType(signing); + if (k == null) { + throw new IllegalArgumentException("MAC " + keyType + " key cannot be null."); + } + + if (!(k instanceof SecretKey)) { + String msg = "MAC " + keyType + " keys must be SecretKey instances. Specified key is of type " + + k.getClass().getName(); + throw new InvalidKeyException(msg); + } + + if (k instanceof Password) { + String msg = "Passwords are intended for use with key derivation algorithms only."; + throw new InvalidKeyException(msg); + } + + final SecretKey key = (SecretKey) k; + + final String id = getId(); + + assertAlgorithmName(key, signing); + + int size = KeysBridge.findBitLength(key); + + // We can only perform length validation if key bit length is available + // per https://github.com/jwtk/jjwt/issues/478 and https://github.com/jwtk/jjwt/issues/619 + // so return early if we can't: + if (size < 0) return; + + if (size < this.minKeyBitLength) { + String msg = "The " + keyType + " key's size is " + size + " bits which " + + "is not secure enough for the " + id + " algorithm."; + + if (isJwaStandard() && isJwaStandardJcaName(getJcaName())) { //JWA standard algorithm name - reference the spec: + msg += " The JWT " + + "JWA Specification (RFC 7518, Section 3.2) states that keys used with " + id + " MUST have a " + + "size >= " + minKeyBitLength + " bits (the key size must be greater than or equal to the hash " + + "output size). Consider using the Jwts.SIG." + id + ".key() " + + "builder to create a key guaranteed to be secure enough for " + id + ". See " + + "https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; + } else { //custom algorithm - just indicate required key length: + msg += " The " + id + " algorithm requires keys to have a size >= " + minKeyBitLength + " bits."; + } + + throw new WeakKeyException(msg); + } + } + + @Override + public byte[] doDigest(final SecureRequest request) { + return jca(request).withMac(new CheckedFunction() { + @Override + public byte[] apply(Mac mac) throws Exception { + mac.init(request.getKey()); + InputStream payload = request.getPayload(); + byte[] buf = new byte[1024]; + int len = 0; + while (len != -1) { + len = payload.read(buf); + if (len > 0) mac.update(buf, 0, len); + } + return mac.doFinal(); + } + }); + } + + protected boolean doVerify(VerifySecureDigestRequest request) { + byte[] providedSignature = request.getDigest(); + Assert.notEmpty(providedSignature, "Request signature byte array cannot be null or empty."); + byte[] computedSignature = digest(request); + return MessageDigest.isEqual(providedSignature, computedSignature); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultMessage.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultMessage.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultMessage.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 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.lang.Assert; +import io.jsonwebtoken.security.Message; + +class DefaultMessage implements Message { + + private final T payload; + + DefaultMessage(T payload) { + this.payload = Assert.notNull(payload, "payload cannot be null."); + if (payload instanceof byte[]) { + assertBytePayload((byte[])payload); + } + } + protected void assertBytePayload(byte[] payload) { + Assert.notEmpty(payload, "payload byte array cannot be null or empty."); + } + + @Override + public T getPayload() { + return payload; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultOctetPrivateJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultOctetPrivateJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultOctetPrivateJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,49 @@ +/* + * 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.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.OctetPrivateJwk; +import io.jsonwebtoken.security.OctetPublicJwk; +import io.jsonwebtoken.security.PrivateJwk; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Set; + +import static io.jsonwebtoken.impl.security.DefaultOctetPublicJwk.equalsPublic; + +public class DefaultOctetPrivateJwk + extends AbstractPrivateJwk> implements OctetPrivateJwk { + + static final Parameter D = Parameters.bytes("d", "The private key").setSecret(true).build(); + + static final Set> PARAMS = Collections.concat(DefaultOctetPublicJwk.PARAMS, D); + + DefaultOctetPrivateJwk(JwkContext ctx, OctetPublicJwk

    pubJwk) { + super(ctx, + // only public members are included in Private JWK Thumbprints per + // https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 + DefaultOctetPublicJwk.THUMBPRINT_PARAMS, pubJwk); + } + + @Override + protected boolean equals(PrivateJwk jwk) { + return jwk instanceof OctetPrivateJwk && equalsPublic(this, jwk) && Parameters.equals(this, jwk, D); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultOctetPublicJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultOctetPublicJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultOctetPublicJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.OctetPublicJwk; +import io.jsonwebtoken.security.PublicJwk; + +import java.security.PublicKey; +import java.util.List; +import java.util.Set; + +public class DefaultOctetPublicJwk extends AbstractPublicJwk implements OctetPublicJwk { + + static final String TYPE_VALUE = "OKP"; + static final Parameter CRV = DefaultEcPublicJwk.CRV; + static final Parameter X = Parameters.bytes("x", "The public key").build(); + static final Set> PARAMS = Collections.concat(AbstractAsymmetricJwk.PARAMS, CRV, X); + + // https://www.rfc-editor.org/rfc/rfc8037#section-2 (last paragraph): + static final List> THUMBPRINT_PARAMS = Collections.>of(CRV, KTY, X); + + DefaultOctetPublicJwk(JwkContext ctx) { + super(ctx, THUMBPRINT_PARAMS); + } + + static boolean equalsPublic(ParameterReadable self, Object candidate) { + return Parameters.equals(self, candidate, CRV) && Parameters.equals(self, candidate, X); + } + + @Override + protected boolean equals(PublicJwk jwk) { + return jwk instanceof OctetPublicJwk && equalsPublic(this, jwk); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 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.Request; + +import java.security.Provider; +import java.security.SecureRandom; + +public class DefaultRequest extends DefaultMessage implements Request { + + private final Provider provider; + private final SecureRandom secureRandom; + + public DefaultRequest(T payload, Provider provider, SecureRandom secureRandom) { + super(payload); + this.provider = provider; + this.secureRandom = secureRandom; + } + + @Override + public Provider getProvider() { + return this.provider; + } + + @Override + public SecureRandom getSecureRandom() { + return this.secureRandom; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultRsaKeyAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultRsaKeyAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultRsaKeyAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021 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.impl.lang.CheckedFunction; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.DecryptionKeyRequest; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.KeyRequest; +import io.jsonwebtoken.security.KeyResult; +import io.jsonwebtoken.security.SecurityException; +import io.jsonwebtoken.security.WeakKeyException; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.AlgorithmParameterSpec; + +/** + * @since 0.12.0 + */ +public class DefaultRsaKeyAlgorithm extends CryptoAlgorithm implements KeyAlgorithm { + + private final AlgorithmParameterSpec SPEC; //can be null + + private static final int MIN_KEY_BIT_LENGTH = 2048; + + public DefaultRsaKeyAlgorithm(String id, String jcaTransformationString) { + this(id, jcaTransformationString, null); + } + + public DefaultRsaKeyAlgorithm(String id, String jcaTransformationString, AlgorithmParameterSpec spec) { + super(id, jcaTransformationString); + this.SPEC = spec; //can be null + } + + private static String keyType(boolean encryption) { + return encryption ? "encryption" : "decryption"; + } + + protected void validate(Key key, boolean encryption) { // true = encryption, false = decryption + + if (!RsaSignatureAlgorithm.isRsaAlgorithmName(key)) { + throw new InvalidKeyException("Invalid RSA key algorithm name."); + } + + if (RsaSignatureAlgorithm.isPss(key)) { + String msg = "RSASSA-PSS keys may not be used for " + keyType(encryption) + + ", only digital signature algorithms."; + throw new InvalidKeyException(msg); + } + + int size = KeysBridge.findBitLength(key); + if (size < 0) return; // can't validate size: material or length not available (e.g. PKCS11 or HSM) + if (size < MIN_KEY_BIT_LENGTH) { + String id = getId(); + String section = id.startsWith("RSA1") ? "4.2" : "4.3"; + String msg = "The RSA " + keyType(encryption) + " key size (aka modulus bit length) is " + size + + " bits which is not secure enough for the " + id + " algorithm. " + + "The JWT JWA Specification (RFC 7518, Section " + section + ") states that RSA keys MUST " + + "have a size >= " + MIN_KEY_BIT_LENGTH + " bits. See " + + "https://www.rfc-editor.org/rfc/rfc7518.html#section-" + section + " for more information."; + throw new WeakKeyException(msg); + } + } + + @Override + public KeyResult getEncryptionKey(final KeyRequest request) throws SecurityException { + + Assert.notNull(request, "Request cannot be null."); + final PublicKey kek = Assert.notNull(request.getPayload(), "RSA PublicKey encryption key cannot be null."); + validate(kek, true); + final SecretKey cek = generateCek(request); + + byte[] ciphertext = jca(request).withCipher(new CheckedFunction() { + @Override + public byte[] apply(Cipher cipher) throws Exception { + if (SPEC == null) { + cipher.init(Cipher.WRAP_MODE, kek, ensureSecureRandom(request)); + } else { + cipher.init(Cipher.WRAP_MODE, kek, SPEC, ensureSecureRandom(request)); + } + return cipher.wrap(cek); + } + }); + + return new DefaultKeyResult(cek, ciphertext); + } + + @Override + public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { + Assert.notNull(request, "request cannot be null."); + final PrivateKey kek = Assert.notNull(request.getKey(), "RSA PrivateKey decryption key cannot be null."); + validate(kek, false); + final byte[] cekBytes = Assert.notEmpty(request.getPayload(), "Request content (encrypted key) cannot be null or empty."); + + return jca(request).withCipher(new CheckedFunction() { + @Override + public SecretKey apply(Cipher cipher) throws Exception { + if (SPEC == null) { + cipher.init(Cipher.UNWRAP_MODE, kek); + } else { + cipher.init(Cipher.UNWRAP_MODE, kek, SPEC); + } + Key key = cipher.unwrap(cekBytes, AesAlgorithm.KEY_ALG_NAME, Cipher.SECRET_KEY); + return Assert.isInstanceOf(SecretKey.class, key, "Cipher unwrap must return a SecretKey instance."); + } + }); + } +} \ No newline at end of file Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultRsaPrivateJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultRsaPrivateJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultRsaPrivateJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2022 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.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.PrivateJwk; +import io.jsonwebtoken.security.RsaPrivateJwk; +import io.jsonwebtoken.security.RsaPublicJwk; + +import java.math.BigInteger; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.RSAOtherPrimeInfo; +import java.util.List; +import java.util.Set; + +import static io.jsonwebtoken.impl.security.DefaultRsaPublicJwk.equalsPublic; + +class DefaultRsaPrivateJwk extends AbstractPrivateJwk implements RsaPrivateJwk { + + static final Parameter PRIVATE_EXPONENT = Parameters.secretBigInt("d", "Private Exponent"); + static final Parameter FIRST_PRIME = Parameters.secretBigInt("p", "First Prime Factor"); + static final Parameter SECOND_PRIME = Parameters.secretBigInt("q", "Second Prime Factor"); + static final Parameter FIRST_CRT_EXPONENT = Parameters.secretBigInt("dp", "First Factor CRT Exponent"); + static final Parameter SECOND_CRT_EXPONENT = Parameters.secretBigInt("dq", "Second Factor CRT Exponent"); + static final Parameter FIRST_CRT_COEFFICIENT = Parameters.secretBigInt("qi", "First CRT Coefficient"); + static final Parameter> OTHER_PRIMES_INFO = + Parameters.builder(RSAOtherPrimeInfo.class) + .setId("oth").setName("Other Primes Info") + .setConverter(RSAOtherPrimeInfoConverter.INSTANCE).list() + .build(); + + static final Set> PARAMS = Collections.concat(DefaultRsaPublicJwk.PARAMS, + PRIVATE_EXPONENT, FIRST_PRIME, SECOND_PRIME, FIRST_CRT_EXPONENT, + SECOND_CRT_EXPONENT, FIRST_CRT_COEFFICIENT, OTHER_PRIMES_INFO + ); + + DefaultRsaPrivateJwk(JwkContext ctx, RsaPublicJwk pubJwk) { + super(ctx, + // only public members are included in Private JWK Thumbprints per + // https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 + DefaultRsaPublicJwk.THUMBPRINT_PARAMS, + pubJwk); + } + + private static boolean equals(RSAOtherPrimeInfo a, RSAOtherPrimeInfo b) { + if (a == b) return true; + if (a == null || b == null) return false; + return Parameters.bytesEquals(a.getPrime(), b.getPrime()) && + Parameters.bytesEquals(a.getExponent(), b.getExponent()) && + Parameters.bytesEquals(a.getCrtCoefficient(), b.getCrtCoefficient()); + } + + private static boolean equalsOtherPrimes(ParameterReadable a, ParameterReadable b) { + List aOthers = a.get(OTHER_PRIMES_INFO); + List bOthers = b.get(OTHER_PRIMES_INFO); + int aSize = Collections.size(aOthers); + int bSize = Collections.size(bOthers); + if (aSize != bSize) return false; + if (aSize == 0) return true; + RSAOtherPrimeInfo[] aInfos = aOthers.toArray(new RSAOtherPrimeInfo[0]); + RSAOtherPrimeInfo[] bInfos = bOthers.toArray(new RSAOtherPrimeInfo[0]); + for (int i = 0; i < aSize; i++) { + if (!equals(aInfos[i], bInfos[i])) return false; + } + return true; + } + + @Override + protected boolean equals(PrivateJwk jwk) { + return jwk instanceof RsaPrivateJwk && equalsPublic(this, jwk) && + Parameters.equals(this, jwk, PRIVATE_EXPONENT) && + Parameters.equals(this, jwk, FIRST_PRIME) && + Parameters.equals(this, jwk, SECOND_PRIME) && + Parameters.equals(this, jwk, FIRST_CRT_EXPONENT) && + Parameters.equals(this, jwk, SECOND_CRT_EXPONENT) && + Parameters.equals(this, jwk, FIRST_CRT_COEFFICIENT) && + equalsOtherPrimes(this, (ParameterReadable) jwk); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultRsaPublicJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultRsaPublicJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultRsaPublicJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,52 @@ +/* + * Copyright © 2020 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.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.PublicJwk; +import io.jsonwebtoken.security.RsaPublicJwk; + +import java.math.BigInteger; +import java.security.interfaces.RSAPublicKey; +import java.util.List; +import java.util.Set; + +class DefaultRsaPublicJwk extends AbstractPublicJwk implements RsaPublicJwk { + + static final String TYPE_VALUE = "RSA"; + static final Parameter MODULUS = Parameters.bigInt("n", "Modulus").build(); + static final Parameter PUBLIC_EXPONENT = Parameters.bigInt("e", "Public Exponent").build(); + static final Set> PARAMS = Collections.concat(AbstractAsymmetricJwk.PARAMS, MODULUS, PUBLIC_EXPONENT); + + // https://www.rfc-editor.org/rfc/rfc7638#section-3.2 + static final List> THUMBPRINT_PARAMS = Collections.>of(PUBLIC_EXPONENT, KTY, MODULUS); + + DefaultRsaPublicJwk(JwkContext ctx) { + super(ctx, THUMBPRINT_PARAMS); + } + + static boolean equalsPublic(ParameterReadable self, Object candidate) { + return Parameters.equals(self, candidate, MODULUS) && Parameters.equals(self, candidate, PUBLIC_EXPONENT); + } + + @Override + protected boolean equals(PublicJwk jwk) { + return jwk instanceof RsaPublicJwk && equalsPublic(this, jwk); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultSecretJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultSecretJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultSecretJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 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.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.SecretJwk; + +import javax.crypto.SecretKey; +import java.util.List; +import java.util.Set; + +class DefaultSecretJwk extends AbstractJwk implements SecretJwk { + + static final String TYPE_VALUE = "oct"; + static final Parameter K = Parameters.bytes("k", "Key Value").setSecret(true).build(); + static final Set> PARAMS = Collections.concat(AbstractJwk.PARAMS, K); + + // https://www.rfc-editor.org/rfc/rfc7638#section-3.2 + static final List> THUMBPRINT_PARAMS = Collections.>of(K, KTY); + + DefaultSecretJwk(JwkContext ctx) { + super(ctx, THUMBPRINT_PARAMS); + } + + @Override + protected boolean equals(Jwk jwk) { + return jwk instanceof SecretJwk && Parameters.equals(this, jwk, K); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultSecretKeyBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultSecretKeyBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultSecretKeyBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 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.lang.Assert; +import io.jsonwebtoken.security.SecretKeyBuilder; + +import javax.crypto.SecretKey; + +/** + * @since 0.12.0 + */ +public class DefaultSecretKeyBuilder extends AbstractSecurityBuilder + implements SecretKeyBuilder { + + protected final String JCA_NAME; + protected final int BIT_LENGTH; + + public DefaultSecretKeyBuilder(String jcaName, int bitLength) { + this.JCA_NAME = Assert.hasText(jcaName, "jcaName cannot be null or empty."); + if (bitLength % Byte.SIZE != 0) { + String msg = "bitLength must be an even multiple of 8"; + throw new IllegalArgumentException(msg); + } + this.BIT_LENGTH = Assert.gt(bitLength, 0, "bitLength must be > 0"); + random(Randoms.secureRandom()); + } + + @Override + public SecretKey build() { + JcaTemplate template = new JcaTemplate(JCA_NAME, this.provider, this.random); + return template.generateSecretKey(this.BIT_LENGTH); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultSecureRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultSecureRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultSecureRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 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.lang.Assert; +import io.jsonwebtoken.security.SecureRequest; + +import java.security.Key; +import java.security.Provider; +import java.security.SecureRandom; + +public class DefaultSecureRequest extends DefaultRequest implements SecureRequest { + + private final K KEY; + + public DefaultSecureRequest(T payload, Provider provider, SecureRandom secureRandom, K key) { + super(payload, provider, secureRandom); + this.KEY = Assert.notNull(key, "key cannot be null."); + } + + @Override + public K getKey() { + return this.KEY; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultVerifyDigestRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultVerifyDigestRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultVerifyDigestRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 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.lang.Assert; +import io.jsonwebtoken.security.VerifyDigestRequest; + +import java.io.InputStream; +import java.security.Provider; +import java.security.SecureRandom; + +public class DefaultVerifyDigestRequest extends DefaultRequest implements VerifyDigestRequest { + + private final byte[] digest; + + public DefaultVerifyDigestRequest(InputStream payload, Provider provider, SecureRandom secureRandom, byte[] digest) { + super(payload, provider, secureRandom); + this.digest = Assert.notEmpty(digest, "Digest byte array cannot be null or empty."); + } + + @Override + public byte[] getDigest() { + return this.digest; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultVerifySecureDigestRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultVerifySecureDigestRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DefaultVerifySecureDigestRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 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.lang.Assert; +import io.jsonwebtoken.security.VerifySecureDigestRequest; + +import java.io.InputStream; +import java.security.Key; +import java.security.Provider; +import java.security.SecureRandom; + +public class DefaultVerifySecureDigestRequest extends DefaultSecureRequest implements VerifySecureDigestRequest { + + private final byte[] digest; + + public DefaultVerifySecureDigestRequest(InputStream payload, Provider provider, SecureRandom secureRandom, K key, byte[] digest) { + super(payload, provider, secureRandom, key); + this.digest = Assert.notEmpty(digest, "Digest byte array cannot be null or empty."); + } + + @Override + public byte[] getDigest() { + return this.digest; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DirectKeyAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DirectKeyAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DirectKeyAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 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.lang.Assert; +import io.jsonwebtoken.security.DecryptionKeyRequest; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.KeyRequest; +import io.jsonwebtoken.security.KeyResult; +import io.jsonwebtoken.security.SecurityException; + +import javax.crypto.SecretKey; + +/** + * @since 0.12.0 + */ +public class DirectKeyAlgorithm implements KeyAlgorithm { + + static final String ID = "dir"; + + @Override + public String getId() { + return ID; + } + + @Override + public KeyResult getEncryptionKey(final KeyRequest request) throws SecurityException { + Assert.notNull(request, "request cannot be null."); + SecretKey key = Assert.notNull(request.getPayload(), "Encryption key cannot be null."); + return new DefaultKeyResult(key); + } + + @Override + public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { + Assert.notNull(request, "request cannot be null."); + return Assert.notNull(request.getKey(), "Decryption key cannot be null."); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DispatchingJwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DispatchingJwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/DispatchingJwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2021 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.lang.Assert; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.UnsupportedKeyException; + +import java.security.Key; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +class DispatchingJwkFactory implements JwkFactory> { + + @SuppressWarnings({"unchecked", "rawtypes"}) + private static Collection> createDefaultFactories() { + List families = new ArrayList<>(3); + families.add(new SecretJwkFactory()); + families.add(new AsymmetricJwkFactory(EcPublicJwkFactory.INSTANCE, new EcPrivateJwkFactory())); + families.add(new AsymmetricJwkFactory(RsaPublicJwkFactory.INSTANCE, new RsaPrivateJwkFactory())); + families.add(new AsymmetricJwkFactory(OctetPublicJwkFactory.INSTANCE, new OctetPrivateJwkFactory())); + return families; + } + + private static final Collection> DEFAULT_FACTORIES = createDefaultFactories(); + static final JwkFactory> DEFAULT_INSTANCE = new DispatchingJwkFactory(); + + private final Collection> factories; + + DispatchingJwkFactory() { + this(DEFAULT_FACTORIES); + } + + @SuppressWarnings("unchecked") + DispatchingJwkFactory(Collection> factories) { + Assert.notEmpty(factories, "FamilyJwkFactory collection cannot be null or empty."); + this.factories = new ArrayList<>(factories.size()); + for (FamilyJwkFactory factory : factories) { + Assert.hasText(factory.getId(), "FamilyJwkFactory.getFactoryId() cannot return null or empty."); + this.factories.add((FamilyJwkFactory) factory); + } + } + + @Override + public JwkContext newContext(JwkContext src, Key key) { + Assert.notNull(src, "JwkContext cannot be null."); + String kty = src.getType(); + assertKeyOrKeyType(key, kty); + for (FamilyJwkFactory factory : this.factories) { + if (factory.supports(key) || factory.supports(src)) { + JwkContext ctx = factory.newContext(src, key); + return Assert.notNull(ctx, "FamilyJwkFactory implementation cannot return null JwkContexts."); + } + } + throw noFamily(key, kty); + } + + private static void assertKeyOrKeyType(Key key, String kty) { + if (key == null && !Strings.hasText(kty)) { + String msg = "Either a Key instance or a " + AbstractJwk.KTY + " value is required to create a JWK."; + throw new InvalidKeyException(msg); + } + } + + @Override + public Jwk createJwk(JwkContext ctx) { + + Assert.notNull(ctx, "JwkContext cannot be null."); + + final Key key = ctx.getKey(); + final String kty = Strings.clean(ctx.getType()); + assertKeyOrKeyType(key, kty); + + for (FamilyJwkFactory factory : this.factories) { + if (factory.supports(ctx)) { + String algFamilyId = Assert.hasText(factory.getId(), "factory id cannot be null or empty."); + if (kty == null) { + ctx.setType(algFamilyId); //ensure the kty is available for the rest of the creation process + } + return factory.createJwk(ctx); + } + } + + // if nothing has been returned at this point, no factory supported the JwkContext, so that's an error: + throw noFamily(key, kty); + } + + private static UnsupportedKeyException noFamily(Key key, String kty) { + String reason = key != null ? + "key of type " + key.getClass().getName() : + "kty value '" + kty + "'"; + String msg = "Unable to create JWK for unrecognized " + reason + + ": there is no known JWK Factory capable of creating JWKs for this key type."; + return new UnsupportedKeyException(msg); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ECCurve.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ECCurve.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ECCurve.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2021 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.impl.lang.CheckedFunction; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.KeyPairBuilder; + +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.Key; +import java.security.interfaces.ECKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECFieldFp; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.EllipticCurve; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +public class ECCurve extends AbstractCurve { + + private static final BigInteger TWO = BigInteger.valueOf(2); + private static final BigInteger THREE = BigInteger.valueOf(3); + + static final String KEY_PAIR_GENERATOR_JCA_NAME = "EC"; + + public static final ECCurve P256 = new ECCurve("P-256", "secp256r1"); // JDK standard + public static final ECCurve P384 = new ECCurve("P-384", "secp384r1"); // JDK standard + public static final ECCurve P521 = new ECCurve("P-521", "secp521r1"); // JDK standard + + public static final Collection VALUES = Collections.setOf(P256, P384, P521); + private static final Map BY_ID = new LinkedHashMap<>(3); + private static final Map BY_JCA_CURVE = new LinkedHashMap<>(3); + + static { + for (ECCurve curve : VALUES) { + BY_ID.put(curve.getId(), curve); + } + for (ECCurve curve : VALUES) { + BY_JCA_CURVE.put(curve.spec.getCurve(), curve); + } + } + + static EllipticCurve assertJcaCurve(ECKey key) { + Assert.notNull(key, "ECKey cannot be null."); + ECParameterSpec spec = Assert.notNull(key.getParams(), "ECKey params() cannot be null."); + return Assert.notNull(spec.getCurve(), "ECKey params().getCurve() cannot be null."); + } + + static ECCurve findById(String id) { + return BY_ID.get(id); + } + + static ECCurve findByJcaCurve(EllipticCurve curve) { + return BY_JCA_CURVE.get(curve); + } + + static ECCurve findByKey(Key key) { + if (!(key instanceof ECKey)) { + return null; + } + ECKey ecKey = (ECKey) key; + ECParameterSpec spec = ecKey.getParams(); + if (spec == null) { + return null; + } + EllipticCurve jcaCurve = spec.getCurve(); + ECCurve curve = BY_JCA_CURVE.get(jcaCurve); + if (curve != null && key instanceof ECPublicKey) { + ECPublicKey pub = (ECPublicKey) key; + ECPoint w = pub.getW(); + if (w == null || !curve.contains(w)) { // don't support keys with a point not on its indicated curve + curve = null; + } + } + return curve; + } + + static ECPublicKeySpec publicKeySpec(ECPrivateKey key) throws IllegalArgumentException { + EllipticCurve jcaCurve = assertJcaCurve(key); + ECCurve curve = BY_JCA_CURVE.get(jcaCurve); + Assert.notNull(curve, "There is no JWA-standard Elliptic Curve for specified ECPrivateKey."); + final ECPoint w = curve.multiply(key.getS()); + return new ECPublicKeySpec(w, curve.spec); + } + + private final ECParameterSpec spec; + + public ECCurve(String id, String jcaName) { + super(id, jcaName); + JcaTemplate template = new JcaTemplate(KEY_PAIR_GENERATOR_JCA_NAME); + this.spec = template.withAlgorithmParameters(new CheckedFunction() { + @Override + public ECParameterSpec apply(AlgorithmParameters params) throws Exception { + params.init(new ECGenParameterSpec(getJcaName())); + return params.getParameterSpec(ECParameterSpec.class); + } + }); + } + + public ECParameterSpec toParameterSpec() { + return this.spec; + } + + @Override + public KeyPairBuilder keyPair() { + return new DefaultKeyPairBuilder(KEY_PAIR_GENERATOR_JCA_NAME, toParameterSpec()); + } + + @Override + public boolean contains(Key key) { + if (key instanceof ECPublicKey) { + ECPublicKey pub = (ECPublicKey) key; + ECParameterSpec pubSpec = pub.getParams(); + return pubSpec != null && + this.spec.getCurve().equals(pubSpec.getCurve()) && + contains(pub.getW()); + + } + return false; + } + + boolean contains(ECPoint point) { + return contains(this.spec.getCurve(), point); + } + + /** + * Returns {@code true} if the specified curve contains the specified {@code point}, {@code false} otherwise. + * Assumes elliptic curves over finite fields adhering to the reduced (a.k.a short or narrow) + * Weierstrass form: + *

    + * y2 = x3 + ax + b + *

    + * + * @param curve the EllipticCurve to check + * @param point a point that may or may not be defined on this elliptic curve + * @return {@code true} if this curve contains the specified {@code point}, {@code false} otherwise. + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + static boolean contains(EllipticCurve curve, ECPoint point) { + + if (point == null || ECPoint.POINT_INFINITY.equals(point)) { + return false; + } + + final BigInteger a = curve.getA(); + final BigInteger b = curve.getB(); + final BigInteger x = point.getAffineX(); + final BigInteger y = point.getAffineY(); + + // The reduced Weierstrass form y^2 = x^3 + ax + b reflects an elliptic curve E over any field K (e.g. all real + // numbers or all complex numbers, etc). For computational simplicity, cryptographic (e.g. NIST) elliptic curves + // restrict K to be a field of integers modulo a prime number 'p'. As such, we apply modulo p (the field prime) + // to the equation to account for the restricted field. For a nice overview of the math behind EC curves and + // their application in cryptography, see + // https://web.northeastern.edu/dummit/docs/cryptography_5_elliptic_curves_in_cryptography.pdf + + final BigInteger p = ((ECFieldFp) curve.getField()).getP(); + + // Verify the point coordinates are in field range: + if (x.compareTo(BigInteger.ZERO) < 0 || x.compareTo(p) >= 0 || + y.compareTo(BigInteger.ZERO) < 0 || y.compareTo(p) >= 0) { + return false; + } + + // Finally, assert Weierstrass form equality: + final BigInteger lhs = y.modPow(TWO, p); //mod p to account for field prime + final BigInteger rhs = x.modPow(THREE, p).add(a.multiply(x)).add(b).mod(p); //mod p to account for field prime + return lhs.equals(rhs); + } + + /** + * Multiply this curve's generator (aka 'base point') by scalar {@code s} on the curve. + * + * @param s the scalar value to multiply + */ + private ECPoint multiply(BigInteger s) { + return multiply(this.spec.getGenerator(), s); + } + + /** + * Multiply a point {@code p} by scalar {@code s} on the curve. + * + * @param p the Elliptic Curve point to multiply + * @param s the scalar value to multiply + */ + private ECPoint multiply(ECPoint p, BigInteger s) { + + if (ECPoint.POINT_INFINITY.equals(p)) { + return p; + } + + final BigInteger n = this.spec.getOrder(); + final BigInteger k = s.mod(n); + + ECPoint r0 = ECPoint.POINT_INFINITY; + ECPoint r1 = p; + + // Montgomery Ladder implementation to mitigate side-channel attacks (i.e. an 'add' operation and a 'double' + // operation is calculated for every loop iteration, regardless if the 'add'' is needed or not) + // See: https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder +// while (k.compareTo(BigInteger.ZERO) > 0) { +// ECPoint temp = add(r0, r1, curve); +// r0 = k.testBit(0) ? temp : r0; +// r1 = doublePoint(r1, curve); +// k = k.shiftRight(1); +// } + // above implementation (k.compareTo/k.shiftRight) works correctly , but this is a little faster: + for (int i = k.bitLength() - 1; i >= 0; i--) { + if (k.testBit(i)) { // bit == 1 + r0 = add(r0, r1); + r1 = doublePoint(r1); + } else { // bit == 0 + r1 = add(r0, r1); + r0 = doublePoint(r0); + } + } + + return r0; + } + + private ECPoint add(ECPoint P, ECPoint Q) { + + if (ECPoint.POINT_INFINITY.equals(P)) { + return Q; + } else if (ECPoint.POINT_INFINITY.equals(Q)) { + return P; + } else if (P.equals(Q)) { + return doublePoint(P); + } + + final EllipticCurve curve = this.spec.getCurve(); + + final BigInteger Px = P.getAffineX(); + final BigInteger Py = P.getAffineY(); + final BigInteger Qx = Q.getAffineX(); + final BigInteger Qy = Q.getAffineY(); + final BigInteger prime = ((ECFieldFp) curve.getField()).getP(); + final BigInteger slope = Qy.subtract(Py).multiply(Qx.subtract(Px).modInverse(prime)).mod(prime); + final BigInteger Rx = slope.pow(2).subtract(Px).subtract(Qx).mod(prime); + final BigInteger Ry = slope.multiply(Px.subtract(Rx)).subtract(Py).mod(prime); + + return new ECPoint(Rx, Ry); + } + + private ECPoint doublePoint(ECPoint P) { + + if (ECPoint.POINT_INFINITY.equals(P)) { + return P; + } + + final EllipticCurve curve = this.spec.getCurve(); + final BigInteger Px = P.getAffineX(); + final BigInteger Py = P.getAffineY(); + final BigInteger p = ((ECFieldFp) curve.getField()).getP(); + final BigInteger a = curve.getA(); + final BigInteger s = THREE.multiply(Px.pow(2)).add(a).mod(p).multiply(TWO.multiply(Py).modInverse(p)).mod(p); + final BigInteger x = s.pow(2).subtract(TWO.multiply(Px)).mod(p); + final BigInteger y = s.multiply(Px.subtract(x)).subtract(Py).mod(p); + + return new ECPoint(x, y); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EcPrivateJwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EcPrivateJwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EcPrivateJwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2021 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.impl.lang.CheckedFunction; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.RequiredParameterReader; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.EcPrivateJwk; +import io.jsonwebtoken.security.EcPublicJwk; +import io.jsonwebtoken.security.InvalidKeyException; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; + +class EcPrivateJwkFactory extends AbstractEcJwkFactory { + + private static final String ECPUBKEY_ERR_MSG = "JwkContext publicKey must be an " + ECPublicKey.class.getName() + + " instance."; + + private static final EcPublicJwkFactory PUB_FACTORY = EcPublicJwkFactory.INSTANCE; + + EcPrivateJwkFactory() { + super(ECPrivateKey.class, DefaultEcPrivateJwk.PARAMS); + } + + @Override + protected boolean supportsKeyValues(JwkContext ctx) { + return super.supportsKeyValues(ctx) && ctx.containsKey(DefaultEcPrivateJwk.D.getId()); + } + + // visible for testing + protected ECPublicKey derivePublic(KeyFactory keyFactory, ECPublicKeySpec spec) throws InvalidKeySpecException { + return (ECPublicKey) keyFactory.generatePublic(spec); + } + + protected ECPublicKey derivePublic(final JwkContext ctx) { + final ECPrivateKey key = ctx.getKey(); + return generateKey(ctx, ECPublicKey.class, new CheckedFunction() { + @Override + public ECPublicKey apply(KeyFactory kf) { + try { + ECPublicKeySpec spec = ECCurve.publicKeySpec(key); + return derivePublic(kf, spec); + } catch (Exception e) { + String msg = "Unable to derive ECPublicKey from ECPrivateKey: " + e.getMessage(); + throw new InvalidKeyException(msg, e); + } + } + }); + } + + @Override + protected EcPrivateJwk createJwkFromKey(JwkContext ctx) { + + ECPrivateKey key = ctx.getKey(); + ECPublicKey ecPublicKey; + + PublicKey publicKey = ctx.getPublicKey(); + if (publicKey != null) { + ecPublicKey = Assert.isInstanceOf(ECPublicKey.class, publicKey, ECPUBKEY_ERR_MSG); + } else { + ecPublicKey = derivePublic(ctx); + } + + // [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2) + // requires public values to be present in private JWKs, so add them: + + // If a JWK fingerprint has been requested to be the JWK id, ensure we copy over the one computed for the + // public key per https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 + boolean copyId = !Strings.hasText(ctx.getId()) && ctx.getIdThumbprintAlgorithm() != null; + + JwkContext pubCtx = PUB_FACTORY.newContext(ctx, ecPublicKey); + EcPublicJwk pubJwk = PUB_FACTORY.createJwk(pubCtx); + ctx.putAll(pubJwk); // add public values to private key context + if (copyId) { + ctx.setId(pubJwk.getId()); + } + + String d = toOctetString(key.getParams().getCurve(), key.getS()); + ctx.put(DefaultEcPrivateJwk.D.getId(), d); + + return new DefaultEcPrivateJwk(ctx, pubJwk); + } + + @Override + protected EcPrivateJwk createJwkFromValues(final JwkContext ctx) { + + ParameterReadable reader = new RequiredParameterReader(ctx); + String curveId = reader.get(DefaultEcPublicJwk.CRV); + BigInteger d = reader.get(DefaultEcPrivateJwk.D); + + // We don't actually need the public x,y point coordinates for JVM lookup, but the + // [JWA spec](https://tools.ietf.org/html/rfc7518#section-6.2.2) + // requires them to be present and valid for the private key as well, so we assert that here: + JwkContext pubCtx = new DefaultJwkContext<>(DefaultEcPublicJwk.PARAMS, ctx); + EcPublicJwk pubJwk = EcPublicJwkFactory.INSTANCE.createJwk(pubCtx); + + ECCurve curve = getCurveByJwaId(curveId); + final ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(d, curve.toParameterSpec()); + ECPrivateKey key = generateKey(ctx, new CheckedFunction() { + @Override + public ECPrivateKey apply(KeyFactory kf) throws Exception { + return (ECPrivateKey) kf.generatePrivate(privateSpec); + } + }); + + ctx.setKey(key); + + return new DefaultEcPrivateJwk(ctx, pubJwk); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EcPublicJwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EcPublicJwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EcPublicJwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2021 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.impl.lang.CheckedFunction; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.RequiredParameterReader; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.EcPublicJwk; +import io.jsonwebtoken.security.InvalidKeyException; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.EllipticCurve; +import java.util.Map; + +class EcPublicJwkFactory extends AbstractEcJwkFactory { + + private static final String UNSUPPORTED_CURVE_MSG = "The specified ECKey curve does not match a JWA standard curve id."; + + static final EcPublicJwkFactory INSTANCE = new EcPublicJwkFactory(); + + EcPublicJwkFactory() { + super(ECPublicKey.class, DefaultEcPublicJwk.PARAMS); + } + + protected static String keyContainsErrorMessage(String curveId) { + Assert.hasText(curveId, "curveId cannot be null or empty."); + String fmt = "ECPublicKey's ECPoint does not exist on elliptic curve '%s' " + + "and may not be used to create '%s' JWKs."; + return String.format(fmt, curveId, curveId); + } + + protected static String jwkContainsErrorMessage(String curveId, Map jwk) { + Assert.hasText(curveId, "curveId cannot be null or empty."); + String fmt = "EC JWK x,y coordinates do not exist on elliptic curve '%s'. This " + + "could be due simply to an incorrectly-created JWK or possibly an attempted Invalid Curve Attack " + + "(see https://safecurves.cr.yp.to/twist.html for more information)."; + return String.format(fmt, curveId, jwk); + } + + protected static String getJwaIdByCurve(EllipticCurve curve) { + ECCurve c = ECCurve.findByJcaCurve(curve); + if (c == null) { + throw new InvalidKeyException(UNSUPPORTED_CURVE_MSG); + } + return c.getId(); + } + + @Override + protected EcPublicJwk createJwkFromKey(JwkContext ctx) { + + ECPublicKey key = ctx.getKey(); + + ECParameterSpec spec = key.getParams(); + EllipticCurve curve = spec.getCurve(); + ECPoint point = key.getW(); + + String curveId = getJwaIdByCurve(curve); + if (!ECCurve.contains(curve, point)) { + String msg = keyContainsErrorMessage(curveId); + throw new InvalidKeyException(msg); + } + + ctx.put(DefaultEcPublicJwk.CRV.getId(), curveId); + + String x = toOctetString(curve, point.getAffineX()); + ctx.put(DefaultEcPublicJwk.X.getId(), x); + + String y = toOctetString(curve, point.getAffineY()); + ctx.put(DefaultEcPublicJwk.Y.getId(), y); + + return new DefaultEcPublicJwk(ctx); + } + + @Override + protected EcPublicJwk createJwkFromValues(final JwkContext ctx) { + + ParameterReadable reader = new RequiredParameterReader(ctx); + String curveId = reader.get(DefaultEcPublicJwk.CRV); + BigInteger x = reader.get(DefaultEcPublicJwk.X); + BigInteger y = reader.get(DefaultEcPublicJwk.Y); + + ECCurve curve = getCurveByJwaId(curveId); + ECPoint point = new ECPoint(x, y); + + if (!curve.contains(point)) { + String msg = jwkContainsErrorMessage(curveId, ctx); + throw new InvalidKeyException(msg); + } + + final ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, curve.toParameterSpec()); + ECPublicKey key = generateKey(ctx, new CheckedFunction() { + @Override + public ECPublicKey apply(KeyFactory kf) throws Exception { + return (ECPublicKey) kf.generatePublic(pubSpec); + } + }); + + ctx.setKey(key); + + return new DefaultEcPublicJwk(ctx); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EcSignatureAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EcSignatureAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EcSignatureAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2021 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.JwtException; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.CheckedFunction; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.KeyPairBuilder; +import io.jsonwebtoken.security.SecureRequest; +import io.jsonwebtoken.security.SignatureAlgorithm; +import io.jsonwebtoken.security.SignatureException; +import io.jsonwebtoken.security.VerifySecureDigestRequest; + +import java.io.InputStream; +import java.math.BigInteger; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.interfaces.ECKey; +import java.security.spec.ECGenParameterSpec; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +// @since 0.12.0 +final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm { + + private static final String REQD_ORDER_BIT_LENGTH_MSG = "orderBitLength must equal 256, 384, or 521."; + + private static final String DER_ENCODING_SYS_PROPERTY_NAME = "io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator.derEncodingSupported"; + + private static final String ES256_OID = "1.2.840.10045.4.3.2"; + private static final String ES384_OID = "1.2.840.10045.4.3.3"; + private static final String ES512_OID = "1.2.840.10045.4.3.4"; + + private static final Set KEY_ALG_NAMES = Collections.setOf("EC", "ECDSA", ES256_OID, ES384_OID, ES512_OID); + + private final ECGenParameterSpec KEY_PAIR_GEN_PARAMS; + + private final int orderBitLength; + + private final String OID; + + /** + * JWA EC (concat formatted) length in bytes for this instance's {@link #orderBitLength}. + */ + private final int signatureByteLength; + private final int sigFieldByteLength; + + private static int shaSize(int orderBitLength) { + return orderBitLength == 521 ? 512 : orderBitLength; + } + + /** + * Returns {@code true} for Order bit lengths defined in the JWA specification, {@code false} otherwise. + * Specifically, returns {@code true} only for values of {@code 256}, {@code 384} and {@code 521}. See + *
    RFC 7518, Section 3.4 for more. + * + * @param orderBitLength the EC key Order bit length to check + * @return {@code true} for Order bit lengths defined in the JWA specification, {@code false} otherwise. + */ + private static boolean isSupportedOrderBitLength(int orderBitLength) { + // This implementation supports only those defined in the JWA specification. + return orderBitLength == 256 || orderBitLength == 384 || orderBitLength == 521; + } + + static final EcSignatureAlgorithm ES256 = new EcSignatureAlgorithm(256, ES256_OID); + static final EcSignatureAlgorithm ES384 = new EcSignatureAlgorithm(384, ES384_OID); + static final EcSignatureAlgorithm ES512 = new EcSignatureAlgorithm(521, ES512_OID); + + private static final Map BY_OID = new LinkedHashMap<>(3); + + static { + for (EcSignatureAlgorithm alg : Collections.of(ES256, ES384, ES512)) { + BY_OID.put(alg.OID, alg); + } + } + + static SignatureAlgorithm findByKey(Key key) { + + String algName = KeysBridge.findAlgorithm(key); + if (!Strings.hasText(algName)) { + return null; + } + algName = algName.toUpperCase(Locale.ENGLISH); + + SignatureAlgorithm alg = BY_OID.get(algName); + if (alg != null) { + return alg; + } + + if ("EC".equalsIgnoreCase(algName) || "ECDSA".equalsIgnoreCase(algName)) { + // some PKCS11 keystores and HSMs won't expose the RSAKey interface, so we can't assume it: + final int bitLength = KeysBridge.findBitLength(key); // returns -1 if we're unable to find out + if (bitLength == ES512.orderBitLength) { + return ES512; + } else if (bitLength == ES384.orderBitLength) { + return ES384; + } else if (bitLength == ES256.orderBitLength) { + return ES256; + } + } + + return null; + } + + private EcSignatureAlgorithm(int orderBitLength, String oid) { + super("ES" + shaSize(orderBitLength), "SHA" + shaSize(orderBitLength) + "withECDSA"); + Assert.isTrue(isSupportedOrderBitLength(orderBitLength), REQD_ORDER_BIT_LENGTH_MSG); + this.OID = Assert.hasText(oid, "Invalid OID."); + String curveName = "secp" + orderBitLength + "r1"; + this.KEY_PAIR_GEN_PARAMS = new ECGenParameterSpec(curveName); + this.orderBitLength = orderBitLength; + this.sigFieldByteLength = Bytes.length(this.orderBitLength); + this.signatureByteLength = this.sigFieldByteLength * 2; // R bytes + S bytes = concat signature bytes + } + + @Override + public KeyPairBuilder keyPair() { + return new DefaultKeyPairBuilder(ECCurve.KEY_PAIR_GENERATOR_JCA_NAME, this.KEY_PAIR_GEN_PARAMS) + .random(Randoms.secureRandom()); + } + + @Override + protected void validateKey(Key key, boolean signing) { + super.validateKey(key, signing); + if (!KEY_ALG_NAMES.contains(KeysBridge.findAlgorithm(key))) { + throw new InvalidKeyException("Unrecognized EC key algorithm name."); + } + int size = KeysBridge.findBitLength(key); + if (size < 0) return; // likely PKCS11 or HSM key, can't get the data we need + int sigFieldByteLength = Bytes.length(size); + int concatByteLength = sigFieldByteLength * 2; + if (concatByteLength != this.signatureByteLength) { + String msg = "The provided Elliptic Curve " + keyType(signing) + + " key size (aka order bit length) is " + Bytes.bitsMsg(size) + ", but the '" + + getId() + "' algorithm requires EC Keys with " + Bytes.bitsMsg(this.orderBitLength) + + " per [RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4)."; + throw new InvalidKeyException(msg); + } + } + + @Override + protected byte[] doDigest(final SecureRequest request) { + return jca(request).withSignature(new CheckedFunction() { + @Override + public byte[] apply(Signature sig) throws Exception { + sig.initSign(KeysBridge.root(request)); + byte[] signature = sign(sig, request.getPayload()); + return transcodeDERToConcat(signature, signatureByteLength); + } + }); + } + + boolean isValidRAndS(PublicKey key, byte[] concatSignature) { + if (key instanceof ECKey) { //Some PKCS11 providers and HSMs won't expose the ECKey interface, so we have to check first + ECKey ecKey = (ECKey) key; + BigInteger order = ecKey.getParams().getOrder(); + BigInteger r = new BigInteger(1, Arrays.copyOfRange(concatSignature, 0, sigFieldByteLength)); + BigInteger s = new BigInteger(1, Arrays.copyOfRange(concatSignature, sigFieldByteLength, concatSignature.length)); + return r.signum() >= 1 && s.signum() >= 1 && r.compareTo(order) < 0 && s.compareTo(order) < 0; + } + return true; + } + + @Override + protected boolean doVerify(final VerifySecureDigestRequest request) { + + final PublicKey key = request.getKey(); + + return jca(request).withSignature(new CheckedFunction() { + @Override + public Boolean apply(Signature sig) { + byte[] concatSignature = request.getDigest(); + byte[] derSignature; + try { + // mandated per https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4 : + if (signatureByteLength != concatSignature.length) { + /* + * If the expected size is not valid for JOSE, fall back to ASN.1 DER signature IFF the application + * is configured to do so. This fallback is for backwards compatibility ONLY (to support tokens + * generated by early versions of jjwt) and backwards compatibility will be removed in a future + * version of this library. This fallback is only enabled if the system property is set to 'true' due to + * the risk of CVE-2022-21449 attacks on early JVM versions 15, 17 and 18. + */ + // TODO: remove for 1.0 (DER-encoding support is not in the JWT RFCs) + if (concatSignature[0] == 0x30 && + "true".equalsIgnoreCase(System.getProperty(DER_ENCODING_SYS_PROPERTY_NAME))) { + derSignature = concatSignature; + } else { + String msg = "Provided signature is " + Bytes.bytesMsg(concatSignature.length) + " but " + + getId() + " signatures must be exactly " + Bytes.bytesMsg(signatureByteLength) + + " per [RFC 7518, Section 3.4 (validation)]" + + "(https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4)."; + throw new SignatureException(msg); + } + } else { + //guard for JVM security bug CVE-2022-21449: + if (!isValidRAndS(key, concatSignature)) { + return false; + } + + // Convert from concat to DER encoding since + // 1) SHAXXXWithECDSAInP1363Format algorithms are only available on >= JDK 9 and + // 2) the SignatureAlgorithm enum JCA alg names are all SHAXXXwithECDSA (which expects DER formatting) + derSignature = transcodeConcatToDER(concatSignature); + } + + sig.initVerify(key); + return verify(sig, request.getPayload(), derSignature); + + } catch (Exception e) { + String msg = "Unable to verify Elliptic Curve signature using provided ECPublicKey: " + e.getMessage(); + throw new SignatureException(msg, e); + } + } + }); + } + + /** + * Transcodes the JCA ASN.1/DER-encoded signature into the concatenated + * R + S format expected by ECDSA JWS. + * + * @param derSignature The ASN1./DER-encoded. Must not be {@code null}. + * @param outputLength The expected length of the ECDSA JWS signature. + * @return The ECDSA JWS encoded signature. + * @throws JwtException If the ASN.1/DER signature format is invalid. + * @author Martin Treurnicht via 61510dfca58dd40b4b32c708935126785dcff48c + */ + public static byte[] transcodeDERToConcat(final byte[] derSignature, int outputLength) throws JwtException { + + if (derSignature.length < 8 || derSignature[0] != 48) { + throw new JwtException("Invalid ECDSA signature format"); + } + + int offset; + if (derSignature[1] > 0) { + offset = 2; + } else if (derSignature[1] == (byte) 0x81) { + offset = 3; + } else { + throw new JwtException("Invalid ECDSA signature format"); + } + + byte rLength = derSignature[offset + 1]; + + int i = rLength; + while ((i > 0) && (derSignature[(offset + 2 + rLength) - i] == 0)) { + i--; + } + + byte sLength = derSignature[offset + 2 + rLength + 1]; + + int j = sLength; + while ((j > 0) && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0)) { + j--; + } + + int rawLen = Math.max(i, j); + rawLen = Math.max(rawLen, outputLength / 2); + + if ((derSignature[offset - 1] & 0xff) != derSignature.length - offset || + (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength || + derSignature[offset] != 2 || derSignature[offset + 2 + rLength] != 2) { + throw new JwtException("Invalid ECDSA signature format"); + } + + final byte[] concatSignature = new byte[2 * rawLen]; + + System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i); + System.arraycopy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j); + + return concatSignature; + } + + /** + * Transcodes the ECDSA JWS signature into ASN.1/DER format for use by the JCA verifier. + * + * @param jwsSignature The JWS signature, consisting of the concatenated R and S values. Must not be {@code null}. + * @return The ASN.1/DER encoded signature. + * @throws JwtException If the ECDSA JWS signature format is invalid. + */ + public static byte[] transcodeConcatToDER(byte[] jwsSignature) throws JwtException { + try { + return concatToDER(jwsSignature); + } catch (Exception e) { // CVE-2022-21449 guard + String msg = "Invalid ECDSA signature format."; + throw new SignatureException(msg, e); + } + } + + /** + * Converts the specified concat-encoded signature to a DER-encoded signature. + * + * @param jwsSignature concat-encoded signature + * @return correpsonding DER-encoded signature + * @throws ArrayIndexOutOfBoundsException if the signature cannot be converted + * @author Martin Treurnicht via 61510dfca58dd40b4b32c708935126785dcff48c + */ + private static byte[] concatToDER(byte[] jwsSignature) throws ArrayIndexOutOfBoundsException { + + int rawLen = jwsSignature.length / 2; + + int i = rawLen; + + while ((i > 0) && (jwsSignature[rawLen - i] == 0)) { + i--; + } + + int j = i; + + if (jwsSignature[rawLen - i] < 0) { + j += 1; + } + + int k = rawLen; + + while ((k > 0) && (jwsSignature[2 * rawLen - k] == 0)) { + k--; + } + + int l = k; + + if (jwsSignature[2 * rawLen - k] < 0) { + l += 1; + } + + int len = 2 + j + 2 + l; + + if (len > 255) { + throw new JwtException("Invalid ECDSA signature format"); + } + + int offset; + + final byte[] derSignature; + + if (len < 128) { + derSignature = new byte[2 + 2 + j + 2 + l]; + offset = 1; + } else { + derSignature = new byte[3 + 2 + j + 2 + l]; + derSignature[1] = (byte) 0x81; + offset = 2; + } + + derSignature[0] = 48; + derSignature[offset++] = (byte) len; + derSignature[offset++] = 2; + derSignature[offset++] = (byte) j; + + System.arraycopy(jwsSignature, rawLen - i, derSignature, (offset + j) - i, i); + + offset += j; + + derSignature[offset++] = 2; + derSignature[offset++] = (byte) l; + + System.arraycopy(jwsSignature, 2 * rawLen - k, derSignature, (offset + l) - k, k); + + return derSignature; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EcdhKeyAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EcdhKeyAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EcdhKeyAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2021 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.JweHeader; +import io.jsonwebtoken.impl.DefaultJweHeader; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.CheckedFunction; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.RequiredParameterReader; +import io.jsonwebtoken.lang.Arrays; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.Curve; +import io.jsonwebtoken.security.DecryptionKeyRequest; +import io.jsonwebtoken.security.DynamicJwkBuilder; +import io.jsonwebtoken.security.EcPublicJwk; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.Jwks; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.KeyLengthSupplier; +import io.jsonwebtoken.security.KeyRequest; +import io.jsonwebtoken.security.KeyResult; +import io.jsonwebtoken.security.OctetPublicJwk; +import io.jsonwebtoken.security.PublicJwk; +import io.jsonwebtoken.security.Request; +import io.jsonwebtoken.security.SecureRequest; +import io.jsonwebtoken.security.SecurityException; + +import javax.crypto.KeyAgreement; +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.interfaces.ECKey; + +/** + * @since 0.12.0 + */ +class EcdhKeyAlgorithm extends CryptoAlgorithm implements KeyAlgorithm { + + protected static final String JCA_NAME = "ECDH"; + protected static final String XDH_JCA_NAME = "XDH"; + protected static final String DEFAULT_ID = JCA_NAME + "-ES"; + + // Per https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.2, 2nd paragraph: + // Key derivation is performed using the Concat KDF, as defined in + // Section 5.8.1 of [NIST.800-56A](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf), + // where the Digest Method is SHA-256. + private static final String CONCAT_KDF_HASH_ALG_NAME = "SHA-256"; + private static final ConcatKDF CONCAT_KDF = new ConcatKDF(CONCAT_KDF_HASH_ALG_NAME); + + private final KeyAlgorithm WRAP_ALG; + + private static String idFor(KeyAlgorithm wrapAlg) { + return wrapAlg instanceof DirectKeyAlgorithm ? DEFAULT_ID : DEFAULT_ID + "+" + wrapAlg.getId(); + } + + EcdhKeyAlgorithm() { + // default ECDH-ES doesn't do a wrap, so we use DirectKeyAlgorithm which is a no-op. That is, we're using + // the Null Object Design Pattern so we don't have to check for null depending on if key wrapping is used or not + this(new DirectKeyAlgorithm()); + } + + EcdhKeyAlgorithm(KeyAlgorithm wrapAlg) { + super(idFor(wrapAlg), JCA_NAME); + this.WRAP_ALG = Assert.notNull(wrapAlg, "Wrap algorithm cannot be null."); + } + + //visible for testing, for Edwards elliptic curves + protected KeyPair generateKeyPair(Curve curve, Provider provider, SecureRandom random) { + return curve.keyPair().provider(provider).random(random).build(); + } + + protected byte[] generateZ(final KeyRequest request, final PublicKey pub, final PrivateKey priv) { + return jca(request).withKeyAgreement(new CheckedFunction() { + @Override + public byte[] apply(KeyAgreement keyAgreement) throws Exception { + keyAgreement.init(KeysBridge.root(priv), ensureSecureRandom(request)); + keyAgreement.doPhase(pub, true); + return keyAgreement.generateSecret(); + } + }); + } + + protected String getConcatKDFAlgorithmId(AeadAlgorithm enc) { + return this.WRAP_ALG instanceof DirectKeyAlgorithm ? Assert.hasText(enc.getId(), + "AeadAlgorithm id cannot be null or empty.") : getId(); + } + + private byte[] createOtherInfo(int keydatalen, String AlgorithmID, byte[] PartyUInfo, byte[] PartyVInfo) { + + // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.2 "AlgorithmID": + Assert.hasText(AlgorithmID, "AlgorithmId cannot be null or empty."); + byte[] algIdBytes = AlgorithmID.getBytes(StandardCharsets.US_ASCII); + + PartyUInfo = Arrays.length(PartyUInfo) == 0 ? Bytes.EMPTY : PartyUInfo; // ensure not null + PartyVInfo = Arrays.length(PartyVInfo) == 0 ? Bytes.EMPTY : PartyVInfo; // ensure not null + + // Values and order defined in https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.2 and + // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf section 5.8.1.2 : + return Bytes.concat(Bytes.toBytes(algIdBytes.length), algIdBytes, // AlgorithmID + Bytes.toBytes(PartyUInfo.length), PartyUInfo, // PartyUInfo + Bytes.toBytes(PartyVInfo.length), PartyVInfo, // PartyVInfo + Bytes.toBytes(keydatalen), // SuppPubInfo per https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.2 + Bytes.EMPTY // SuppPrivInfo empty per https://www.rfc-editor.org/rfc/rfc7518.html#section-4.6.2 + ); + } + + private int getKeyBitLength(AeadAlgorithm enc) { + int bitLength = this.WRAP_ALG instanceof KeyLengthSupplier ? + ((KeyLengthSupplier) this.WRAP_ALG).getKeyBitLength() : enc.getKeyBitLength(); + return Assert.gt(bitLength, 0, "Algorithm keyBitLength must be > 0"); + } + + private SecretKey deriveKey(KeyRequest request, PublicKey publicKey, PrivateKey privateKey) { + AeadAlgorithm enc = Assert.notNull(request.getEncryptionAlgorithm(), + "Request encryptionAlgorithm cannot be null."); + int requiredCekBitLen = getKeyBitLength(enc); + final String AlgorithmID = getConcatKDFAlgorithmId(enc); + byte[] apu = request.getHeader().getAgreementPartyUInfo(); + byte[] apv = request.getHeader().getAgreementPartyVInfo(); + byte[] OtherInfo = createOtherInfo(requiredCekBitLen, AlgorithmID, apu, apv); + byte[] Z = generateZ(request, publicKey, privateKey); + try { + return CONCAT_KDF.deriveKey(Z, requiredCekBitLen, OtherInfo); + } finally { + Bytes.clear(Z); + } + } + + @Override + protected String getJcaName(Request request) { + if (request instanceof SecureRequest) { + return ((SecureRequest) request).getKey() instanceof ECKey ? super.getJcaName(request) : XDH_JCA_NAME; + } else { + return request.getPayload() instanceof ECKey ? super.getJcaName(request) : XDH_JCA_NAME; + } + } + + private static AbstractCurve assertCurve(Key key) { + Curve curve = StandardCurves.findByKey(key); + if (curve == null) { + String type = key instanceof PublicKey ? "encryption " : "decryption "; + String msg = "Unable to determine JWA-standard Elliptic Curve for " + type + "key [" + + KeysBridge.toString(key) + "]"; + throw new InvalidKeyException(msg); + } + if (curve instanceof EdwardsCurve && ((EdwardsCurve) curve).isSignatureCurve()) { + String msg = curve.getId() + " keys may not be used with ECDH-ES key agreement algorithms per " + + "https://www.rfc-editor.org/rfc/rfc8037#section-3.1."; + throw new InvalidKeyException(msg); + } + return Assert.isInstanceOf(AbstractCurve.class, curve, "AbstractCurve instance expected."); + } + + @Override + public KeyResult getEncryptionKey(KeyRequest request) throws SecurityException { + Assert.notNull(request, "Request cannot be null."); + JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null."); + PublicKey publicKey = Assert.notNull(request.getPayload(), "Encryption PublicKey cannot be null."); + + Curve curve = assertCurve(publicKey); + // note: we don't need to validate if specified key's point is on a supported curve here + // because that will automatically be asserted when using Jwks.builder().... below + Assert.stateNotNull(curve, "Internal implementation state: Curve cannot be null."); + + // Generate our ephemeral key pair: + final SecureRandom random = ensureSecureRandom(request); + DynamicJwkBuilder jwkBuilder = Jwks.builder().random(random); + KeyPair pair = generateKeyPair(curve, null, random); + + Assert.stateNotNull(pair, "Internal implementation state: KeyPair cannot be null."); + + // This asserts that the generated public key (and therefore the request key) is on a JWK-supported curve: + PublicJwk jwk = jwkBuilder.key(pair.getPublic()).build(); + + final SecretKey derived = deriveKey(request, publicKey, pair.getPrivate()); + + KeyRequest wrapReq = new DefaultKeyRequest<>(derived, request.getProvider(), + request.getSecureRandom(), request.getHeader(), request.getEncryptionAlgorithm()); + KeyResult result = WRAP_ALG.getEncryptionKey(wrapReq); + + header.put(DefaultJweHeader.EPK.getId(), jwk); + + return result; + } + + @Override + public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { + + Assert.notNull(request, "Request cannot be null."); + JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null."); + PrivateKey privateKey = Assert.notNull(request.getKey(), "Decryption PrivateKey cannot be null."); + ParameterReadable reader = new RequiredParameterReader(header); + PublicJwk epk = reader.get(DefaultJweHeader.EPK); + + AbstractCurve curve = assertCurve(privateKey); + Assert.stateNotNull(curve, "Internal implementation state: Curve cannot be null."); + Class epkClass = curve instanceof ECCurve ? EcPublicJwk.class : OctetPublicJwk.class; + if (!epkClass.isInstance(epk)) { + String msg = "JWE Header " + DefaultJweHeader.EPK + " value is not an Elliptic Curve " + + "Public JWK. Value: " + epk; + throw new InvalidKeyException(msg); + } + if (!curve.contains(epk.toKey())) { + String msg = "JWE Header " + DefaultJweHeader.EPK + " value does not represent " + + "a point on the expected curve. Value: " + epk; + throw new InvalidKeyException(msg); + } + + final SecretKey derived = deriveKey(request, epk.toKey(), privateKey); + + DecryptionKeyRequest unwrapReq = new DefaultDecryptionKeyRequest<>(request.getPayload(), + null, request.getSecureRandom(), header, request.getEncryptionAlgorithm(), derived); + + return WRAP_ALG.getDecryptionKey(unwrapReq); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EdSignatureAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EdSignatureAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EdSignatureAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,77 @@ +/* + * 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.lang.Assert; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.KeyPairBuilder; +import io.jsonwebtoken.security.Request; +import io.jsonwebtoken.security.SecureRequest; +import io.jsonwebtoken.security.VerifyDigestRequest; + +import java.security.Key; +import java.security.PrivateKey; + +final class EdSignatureAlgorithm extends AbstractSignatureAlgorithm { + + private static final String ID = "EdDSA"; + + private final EdwardsCurve preferredCurve; + + static final EdSignatureAlgorithm INSTANCE = new EdSignatureAlgorithm(); + + static boolean isSigningKey(PrivateKey key) { + EdwardsCurve curve = EdwardsCurve.findByKey(key); + return curve != null && curve.isSignatureCurve(); + } + + private EdSignatureAlgorithm() { + super(ID, ID); + this.preferredCurve = EdwardsCurve.Ed448; + Assert.isTrue(this.preferredCurve.isSignatureCurve(), "Must be signature curve, not key agreement curve."); + } + + @Override + protected String getJcaName(Request request) { + SecureRequest req = Assert.isInstanceOf(SecureRequest.class, request, "SecureRequests are required."); + Key key = Assert.notNull(req.getKey(), "Request key cannot be null."); + // If we're signing, and this instance's algorithm name is the default/generic 'EdDSA', then prefer the + // signing key's curve algorithm ID. This ensures the most specific JCA algorithm is used for signing, + // (while generic 'EdDSA' is fine for validation) + String jcaName = getJcaName(); //default for JCA interaction + if (!(request instanceof VerifyDigestRequest)) { // then we're signing, not verifying + jcaName = EdwardsCurve.forKey(key).getJcaName(); + } + return jcaName; + } + + @Override + public KeyPairBuilder keyPair() { + return this.preferredCurve.keyPair(); + } + + @Override + protected void validateKey(Key key, boolean signing) { + super.validateKey(key, signing); + // should always be non-null due to algorithm name lookup, even without encoded key bytes: + EdwardsCurve curve = EdwardsCurve.forKey(key); + if (!curve.isSignatureCurve()) { + String msg = curve.getId() + " keys may not be used with " + getId() + " digital signatures per " + + "https://www.rfc-editor.org/rfc/rfc8037.html#section-3.2"; + throw new InvalidKeyException(msg); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EdwardsCurve.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EdwardsCurve.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EdwardsCurve.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,379 @@ +/* + * 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.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.Function; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.KeyException; +import io.jsonwebtoken.security.KeyLengthSupplier; +import io.jsonwebtoken.security.KeyPairBuilder; + +import java.security.Key; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +public class EdwardsCurve extends AbstractCurve implements KeyLengthSupplier { + + private static final String OID_PREFIX = "1.3.101."; + + // ASN.1-encoded edwards keys have this exact sequence identifying the type of key that follows. The trailing + // byte is the exact edwards curve subsection OID terminal node id. + private static final byte[] ASN1_OID_PREFIX = new byte[]{0x06, 0x03, 0x2B, 0x65}; + + private static final Function CURVE_NAME_FINDER = new NamedParameterSpecValueFinder(); + + public static final EdwardsCurve X25519 = new EdwardsCurve("X25519", 110); // Requires JDK >= 11 or BC + public static final EdwardsCurve X448 = new EdwardsCurve("X448", 111); // Requires JDK >= 11 or BC + public static final EdwardsCurve Ed25519 = new EdwardsCurve("Ed25519", 112); // Requires JDK >= 15 or BC + public static final EdwardsCurve Ed448 = new EdwardsCurve("Ed448", 113); // Requires JDK >= 15 or BC + + public static final Collection VALUES = Collections.of(X25519, X448, Ed25519, Ed448); + + private static final Map REGISTRY; + + private static final Map BY_OID_TERMINAL_NODE; + + static { + REGISTRY = new LinkedHashMap<>(8); + BY_OID_TERMINAL_NODE = new LinkedHashMap<>(4); + for (EdwardsCurve curve : VALUES) { + int subcategoryId = curve.ASN1_OID[curve.ASN1_OID.length - 1]; + BY_OID_TERMINAL_NODE.put(subcategoryId, curve); + REGISTRY.put(curve.getId(), curve); + REGISTRY.put(curve.OID, curve); // add OID as an alias for alg/id lookups + } + } + + private static byte[] publicKeyAsn1Prefix(int byteLength, byte[] ASN1_OID) { + return Bytes.concat( + new byte[]{ + 0x30, (byte) (byteLength + 10), + 0x30, 0x05}, // ASN.1 SEQUENCE of 5 bytes to follow (i.e. the OID) + ASN1_OID, + new byte[]{ + 0x03, + (byte) (byteLength + 1), + 0x00} + ); + } + + private static byte[] privateKeyPkcs8Prefix(int byteLength, byte[] ASN1_OID, boolean ber) { + + byte[] keyPrefix = ber ? + new byte[]{0x04, (byte) (byteLength + 2), 0x04, (byte) byteLength} : // correct + new byte[]{0x04, (byte) byteLength}; // https://bugs.openjdk.org/browse/JDK-8213363 + + return Bytes.concat( + new byte[]{ + 0x30, + (byte) (5 + ASN1_OID.length + keyPrefix.length + byteLength), + 0x02, 0x01, 0x00, // encoding version 1 (integer, 1 byte, value 0) + 0x30, 0x05}, // ASN.1 SEQUENCE of 5 bytes to follow (i.e. the OID) + ASN1_OID, + keyPrefix + ); + } + + private final String OID; + + /** + * The byte sequence within an ASN.1-encoded key that indicates an Edwards curve encoded key follows. ASN.1 (hex) + * notation: + *
    +     * 06 03       ;   OBJECT IDENTIFIER (3 bytes long)
    +     * |  2B 65 $I ;     "1.3.101.$I" for Edwards alg OID, where $I = 6E, 6F, 70, or 71 (decimal 110, 111, 112, or 113)
    +     * 
    + */ + final byte[] ASN1_OID; + + private final int keyBitLength; + + private final int encodedKeyByteLength; + + /** + * X.509 (ASN.1) encoding of a public key associated with this curve as a prefix (that is, without the + * actual encoded key material at the end). Appending the public key material directly to the end of this value + * results in a complete X.509 (ASN.1) encoded public key. ASN.1 (hex) notation: + *
    +     * 30 $M               ; ASN.1 SEQUENCE ($M bytes long), where $M = encodedKeyByteLength + 10
    +     *    30 05            ;   ASN.1 SEQUENCE (5 bytes long)
    +     *       06 03         ;     OBJECT IDENTIFIER (3 bytes long)
    +     *          2B 65 $I   ;       "1.3.101.$I" for Edwards alg OID, where $I = 6E, 6F, 70, or 71 (110, 111, 112, or 113 decimal)
    +     *    03 $S            ;   ASN.1 BIT STRING ($S bytes long), where $S = encodedKeyByteLength + 1
    +     *       00            ;     ASN.1 bit string marker indicating zero unused bits at the end of the bit string
    +     *       XX XX XX ...  ;     encoded key material (not included in this PREFIX byte array variable)
    +     * 
    + */ + private final byte[] PUBLIC_KEY_ASN1_PREFIX; // https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.7 + + /** + * PKCS8 (ASN.1) Version 1 encoding of a private key associated with this curve, as a prefix (that is, + * without actual encoded key material at the end). Appending the private key material directly to the + * end of this value results in a complete PKCS8 (ASN.1) V1 encoded private key. ASN.1 (hex) notation: + *
    +     * 30 $M                  ; ASN.1 SEQUENCE ($M bytes long), where $M = encodedKeyByteLength + 14
    +     *    02 01               ;   ASN.1 INTEGER (1 byte long)
    +     *       00               ;     zero (private key encoding version V1)
    +     *    30 05               ;   ASN.1 SEQUENCE (5 bytes long)
    +     *       06 03            ;     OBJECT IDENTIFIER (3 bytes long). This is the edwards algorithm ID.
    +     *          2B 65 $I      ;       "1.3.101.$I" for Edwards alg OID, where $I = 6E, 6F, 70, or 71 (110, 111, 112, or 113 decimal)
    +     *    04 $B               ;   ASN.1 SEQUENCE ($B bytes long, where $B = encodedKeyByteLength + 2
    +     *       04 $K            ;     ASN.1 SEQUENCE ($K bytes long), where $K = encodedKeyByteLength
    +     *          XX XX XX ...  ;       encoded key material (not included in this PREFIX byte array variable)
    +     * 
    + */ + private final byte[] PRIVATE_KEY_ASN1_PREFIX; + private final byte[] PRIVATE_KEY_JDK11_PREFIX; // https://bugs.openjdk.org/browse/JDK-8213363 + + /** + * {@code true} IFF the curve is used for digital signatures, {@code false} if used for key agreement + */ + private final boolean signatureCurve; + + EdwardsCurve(final String id, int oidTerminalNode) { + super(id, id); + + if (oidTerminalNode < 110 || oidTerminalNode > 113) { + String msg = "Invalid Edwards Curve ASN.1 OID terminal node value"; + throw new IllegalArgumentException(msg); + } + + // OIDs (with terminal node IDs) defined here: https://www.rfc-editor.org/rfc/rfc8410#section-3 + // X25519 (oid 1.3.101.110) has 255 bytes per https://www.rfc-editor.org/rfc/rfc7748.html#section-5 "Here, the "bits" parameter should be set to 255 for X25519 and 448 for X448" + // X448 (oid 1.3.101.111) have 448 bits per https://www.rfc-editor.org/rfc/rfc7748.html#section-5 + // Ed25519 (oid 1.3.101.112) has 255 bits per https://www.rfc-editor.org/rfc/rfc8032#section-5.1 + // Ed448 (oid 1.3.101.113) has 456 (448 + 8) bits per https://www.rfc-editor.org/rfc/rfc8032#section-5.2 + this.keyBitLength = oidTerminalNode % 2 == 0 ? 255 : 448; + int encodingBitLen = oidTerminalNode == 113 ? + this.keyBitLength + Byte.SIZE : // https://www.rfc-editor.org/rfc/rfc8032#section-5.2.2 + this.keyBitLength; + this.encodedKeyByteLength = Bytes.length(encodingBitLen); + + this.OID = OID_PREFIX + oidTerminalNode; + this.signatureCurve = (oidTerminalNode == 112 || oidTerminalNode == 113); + byte[] suffix = new byte[]{(byte) oidTerminalNode}; + this.ASN1_OID = Bytes.concat(ASN1_OID_PREFIX, suffix); + + this.PUBLIC_KEY_ASN1_PREFIX = publicKeyAsn1Prefix(this.encodedKeyByteLength, this.ASN1_OID); + this.PRIVATE_KEY_ASN1_PREFIX = privateKeyPkcs8Prefix(this.encodedKeyByteLength, this.ASN1_OID, true); + this.PRIVATE_KEY_JDK11_PREFIX = privateKeyPkcs8Prefix(this.encodedKeyByteLength, this.ASN1_OID, false); + } + + @Override + public int getKeyBitLength() { + return this.keyBitLength; + } + + public byte[] getKeyMaterial(Key key) { + try { + return doGetKeyMaterial(key); // can throw assertion and ArrayIndexOutOfBound exception on invalid input + } catch (Throwable t) { + if (t instanceof KeyException) { //propagate + throw (KeyException) t; + } + String msg = "Invalid " + getId() + " ASN.1 encoding: " + t.getMessage(); + throw new InvalidKeyException(msg, t); + } + } + + /** + * Parses the ASN.1-encoding of the specified key + * + * @param key the Edwards curve key + * @return the key value, encoded according to RFC 8032 + * @throws RuntimeException if the key's encoded bytes do not reflect a validly ASN.1-encoded edwards key + */ + protected byte[] doGetKeyMaterial(Key key) { + byte[] encoded = KeysBridge.getEncoded(key); + int i = Bytes.indexOf(encoded, ASN1_OID); + Assert.gt(i, -1, "Missing or incorrect algorithm OID."); + i = i + ASN1_OID.length; + int keyLen = 0; + if (encoded[i] == 0x05) { // NULL terminator, next should be zero byte indicator + int unusedBytes = encoded[++i]; + Assert.eq(unusedBytes, 0, "OID NULL terminator should indicate zero unused bytes."); + i++; + } + if (encoded[i] == 0x03) { // ASN.1 bit stream, Public Key + i++; + keyLen = encoded[i++]; + int unusedBytes = encoded[i++]; + Assert.eq(unusedBytes, 0, "BIT STREAM should not indicate unused bytes."); + keyLen--; + } else if (encoded[i] == 0x04) { // ASN.1 octet sequence, Private Key. Key length follows as next byte. + i++; + keyLen = encoded[i++]; + if (encoded[i] == 0x04) { // ASN.1 octet sequence, key length follows as next byte. + i++; // skip sequence marker + keyLen = encoded[i++]; // next byte is length + } + } + Assert.eq(keyLen, this.encodedKeyByteLength, "Invalid key length."); + byte[] result = Arrays.copyOfRange(encoded, i, i + keyLen); + keyLen = Bytes.length(result); + Assert.eq(keyLen, this.encodedKeyByteLength, "Invalid key length."); + return result; + } + + private void assertLength(byte[] raw, boolean isPublic) { + int len = Bytes.length(raw); + if (len != this.encodedKeyByteLength) { + String msg = "Invalid " + getId() + " encoded " + (isPublic ? "PublicKey" : "PrivateKey") + + " length. Should be " + Bytes.bytesMsg(this.encodedKeyByteLength) + ", found " + + Bytes.bytesMsg(len) + "."; + throw new InvalidKeyException(msg); + } + } + + public PublicKey toPublicKey(byte[] x, Provider provider) { + assertLength(x, true); + final byte[] encoded = Bytes.concat(this.PUBLIC_KEY_ASN1_PREFIX, x); + final X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded); + JcaTemplate template = new JcaTemplate(getJcaName(), provider); + return template.generatePublic(spec); + } + + KeySpec privateKeySpec(byte[] d, boolean standard) { + byte[] prefix = standard ? this.PRIVATE_KEY_ASN1_PREFIX : this.PRIVATE_KEY_JDK11_PREFIX; + byte[] encoded = Bytes.concat(prefix, d); + return new PKCS8EncodedKeySpec(encoded); + } + + public PrivateKey toPrivateKey(final byte[] d, Provider provider) { + assertLength(d, false); + KeySpec spec = privateKeySpec(d, true); + JcaTemplate template = new JcaTemplate(getJcaName(), provider); + return template.generatePrivate(spec); + } + + /** + * Returns {@code true} if this curve is used to compute signatures, {@code false} if used for key agreement. + * + * @return {@code true} if this curve is used to compute signatures, {@code false} if used for key agreement. + */ + public boolean isSignatureCurve() { + return this.signatureCurve; + } + + @Override + public KeyPairBuilder keyPair() { + return new DefaultKeyPairBuilder(getJcaName(), this.keyBitLength); + } + + public static boolean isEdwards(Key key) { + if (key == null) { + return false; + } + String alg = Strings.clean(key.getAlgorithm()); + return "EdDSA".equals(alg) || "XDH".equals(alg) || findByKey(key) != null; + } + + /** + * Computes the PublicKey associated with the specified Edwards-curve PrivateKey. + * + * @param pk the Edwards-curve {@code PrivateKey} to inspect. + * @return the PublicKey associated with the specified Edwards-curve PrivateKey. + * @throws KeyException if the PrivateKey is not an Edwards-curve key or unable to access the PrivateKey's + * material. + */ + public static PublicKey derivePublic(PrivateKey pk) throws KeyException { + return EdwardsPublicKeyDeriver.INSTANCE.apply(pk); + } + + public static EdwardsCurve findById(String id) { + return REGISTRY.get(id); + } + + public static EdwardsCurve findByKey(Key key) { + if (key == null) { + return null; + } + + String alg = key.getAlgorithm(); + EdwardsCurve curve = findById(alg); // try constant time lookup first + if (curve == null) { // Fall back to JDK 11+ NamedParameterSpec access if possible + alg = CURVE_NAME_FINDER.apply(key); + curve = findById(alg); + } + + // try to perform oid and/or length checks: + byte[] encoded = KeysBridge.findEncoded(key); + + if (curve == null && !Bytes.isEmpty(encoded)) { // Try to find the Key ASN.1 algorithm OID: + int oidTerminalNode = findOidTerminalNode(encoded); + curve = BY_OID_TERMINAL_NODE.get(oidTerminalNode); + } + if (curve != null && !Bytes.isEmpty(encoded)) { + // found a curve, and we have encoded bytes, let's make sure that the encoding represents + // the correct key length: + try { + curve.getKeyMaterial(key); + } catch (Throwable ignored) { + curve = null; // key length is invalid for its indicated curve, not a match + } + } + + //TODO: check if key exists on discovered curve via equation + + return curve; + } + + @Override + public boolean contains(Key key) { + EdwardsCurve curve = findByKey(key); + return curve.equals(this); + } + + private static int findOidTerminalNode(byte[] encoded) { + int index = Bytes.indexOf(encoded, ASN1_OID_PREFIX); + if (index > -1) { + index = index + ASN1_OID_PREFIX.length; + if (index < encoded.length) { + return encoded[index]; + } + } + return -1; + } + + public static EdwardsCurve forKey(Key key) { + Assert.notNull(key, "Key cannot be null."); + EdwardsCurve curve = findByKey(key); + if (curve == null) { + String msg = "Unrecognized Edwards Curve key: [" + KeysBridge.toString(key) + "]"; + throw new InvalidKeyException(msg); + } + //TODO: assert key exists on discovered curve via equation + return curve; + } + + @SuppressWarnings("UnusedReturnValue") + static K assertEdwards(K key) { + forKey(key); // will throw UnsupportedKeyException if the key is not an Edwards key + return key; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EdwardsPublicKeyDeriver.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EdwardsPublicKeyDeriver.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/EdwardsPublicKeyDeriver.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,71 @@ +/* + * 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.impl.lang.Function; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.InvalidKeyException; + +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; + +/** + * Derives a PublicKey from an Edwards-curve PrivateKey instance. + */ +final class EdwardsPublicKeyDeriver implements Function { + + public static final Function INSTANCE = new EdwardsPublicKeyDeriver(); + + private EdwardsPublicKeyDeriver() { + // prevent public instantiation. + } + + @Override + public PublicKey apply(PrivateKey privateKey) { + + EdwardsCurve curve = EdwardsCurve.findByKey(privateKey); + if (curve == null) { + String msg = "Unable to derive Edwards-curve PublicKey for specified PrivateKey: " + KeysBridge.toString(privateKey); + throw new InvalidKeyException(msg); + } + + byte[] pkBytes = curve.getKeyMaterial(privateKey); + + // This is a hack that utilizes the JCE implementations' behavior of using an RNG to generate a new private + // key, and from that, the implementation computes a public key from the private key bytes. + // Since we already have a private key, we provide a RNG that 'generates' the existing private key + // instead of a random one, and the corresponding public key will be computed for us automatically. + SecureRandom random = new ConstantRandom(pkBytes); + KeyPair pair = curve.keyPair().random(random).build(); + Assert.stateNotNull(pair, "Edwards curve generated keypair cannot be null."); + return Assert.stateNotNull(pair.getPublic(), "Edwards curve KeyPair must have a PublicKey"); + } + + private static final class ConstantRandom extends SecureRandom { + private final byte[] value; + + public ConstantRandom(byte[] value) { + this.value = value.clone(); + } + + @Override + public void nextBytes(byte[] bytes) { + System.arraycopy(value, 0, bytes, 0, value.length); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/FamilyJwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/FamilyJwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/FamilyJwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 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.Identifiable; +import io.jsonwebtoken.security.Jwk; + +import java.security.Key; + +public interface FamilyJwkFactory> extends JwkFactory, Identifiable { + + boolean supports(Key key); + + boolean supports(JwkContext context); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/FieldElementConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/FieldElementConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/FieldElementConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,69 @@ +/* + * Copyright © 2024 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.impl.io.Codec; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.Converter; +import io.jsonwebtoken.impl.lang.Converters; + +import java.math.BigInteger; + +/** + * Hotfix for JJWT Issue 901. This is currently hard-coded + * expecting field elements for NIST P-256, P-384, or P-521 curves. Ideally this should be refactored to work for + * any curve based on its field size, not just for these NIST curves. However, the + * {@link EcPublicJwkFactory} and {@link EcPrivateJwkFactory} implementations only work with JWA NIST curves, + * so this implementation is acceptable until (and if) different Weierstrass elliptic curves (ever) need to be + * supported. + * + * @since 0.12.4 + */ +final class FieldElementConverter implements Converter { + + static final FieldElementConverter INSTANCE = new FieldElementConverter(); + + static final Converter B64URL_CONVERTER = Converters.forEncoded(BigInteger.class, + Converters.compound(INSTANCE, Codec.BASE64URL)); + + private static int bytelen(ECCurve curve) { + return Bytes.length(curve.toParameterSpec().getCurve().getField().getFieldSize()); + } + + private static final int P256_BYTE_LEN = bytelen(ECCurve.P256); + private static final int P384_BYTE_LEN = bytelen(ECCurve.P384); + private static final int P521_BYTE_LEN = bytelen(ECCurve.P521); + + @Override + public byte[] applyTo(BigInteger bigInteger) { + byte[] bytes = Converters.BIGINT_UBYTES.applyTo(bigInteger); + int len = bytes.length; + if (len == P256_BYTE_LEN || len == P384_BYTE_LEN || len == P521_BYTE_LEN) return bytes; + if (len < P256_BYTE_LEN) { + bytes = Bytes.prepad(bytes, P256_BYTE_LEN); + } else if (len < P384_BYTE_LEN) { + bytes = Bytes.prepad(bytes, P384_BYTE_LEN); + } else { // > P-384, so must be P-521: + bytes = Bytes.prepad(bytes, P521_BYTE_LEN); + } + return bytes; + } + + @Override + public BigInteger applyFrom(byte[] bytes) { + return Converters.BIGINT_UBYTES.applyFrom(bytes); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/GcmAesAeadAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2021 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.impl.io.Streams; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.CheckedFunction; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.AeadRequest; +import io.jsonwebtoken.security.AeadResult; +import io.jsonwebtoken.security.DecryptAeadRequest; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; +import java.security.spec.AlgorithmParameterSpec; + +/** + * @since 0.12.0 + */ +public class GcmAesAeadAlgorithm extends AesAlgorithm implements AeadAlgorithm { + + private static final String TRANSFORMATION_STRING = "AES/GCM/NoPadding"; + + public GcmAesAeadAlgorithm(int keyLength) { + super("A" + keyLength + "GCM", TRANSFORMATION_STRING, keyLength); + } + + @Override + public void encrypt(final AeadRequest req, AeadResult res) throws SecurityException { + + Assert.notNull(req, "Request cannot be null."); + Assert.notNull(res, "Result cannot be null."); + + final SecretKey key = assertKey(req.getKey()); + final InputStream plaintext = Assert.notNull(req.getPayload(), + "Request content (plaintext) InputStream cannot be null."); + final OutputStream out = Assert.notNull(res.getOutputStream(), + "Result ciphertext OutputStream cannot be null."); + final InputStream aad = req.getAssociatedData(); + final byte[] iv = ensureInitializationVector(req); + final AlgorithmParameterSpec ivSpec = getIvSpec(iv); + + final byte[] tag = jca(req).withCipher(new CheckedFunction() { + @Override + public byte[] apply(Cipher cipher) throws Exception { + cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec); + byte[] taggedCiphertext = withCipher(cipher, plaintext, aad, out); + // When using GCM mode, the JDK appends the authentication tag to the ciphertext, so let's extract it: + // (tag has a length of BLOCK_BYTE_SIZE): + int ciphertextLength = Bytes.length(taggedCiphertext) - BLOCK_BYTE_SIZE; + Streams.write(out, taggedCiphertext, 0, ciphertextLength, "Ciphertext write failure."); + byte[] tag = new byte[BLOCK_BYTE_SIZE]; + System.arraycopy(taggedCiphertext, ciphertextLength, tag, 0, BLOCK_BYTE_SIZE); + return tag; + } + }); + Streams.flush(out); + Streams.reset(plaintext); + + res.setTag(tag).setIv(iv); + } + + @Override + public void decrypt(final DecryptAeadRequest req, final OutputStream out) throws SecurityException { + + Assert.notNull(req, "Request cannot be null."); + Assert.notNull(out, "Plaintext OutputStream cannot be null."); + final SecretKey key = assertKey(req.getKey()); + final InputStream ciphertext = Assert.notNull(req.getPayload(), + "Decryption request content (ciphertext) InputStream cannot be null."); + final InputStream aad = req.getAssociatedData(); + final byte[] tag = Assert.notEmpty(req.getDigest(), + "Decryption request authentication tag cannot be null or empty."); + final byte[] iv = assertDecryptionIv(req); + final AlgorithmParameterSpec ivSpec = getIvSpec(iv); + + //for tagged GCM, the JCA spec requires that the tag be appended to the end of the ciphertext byte array: + final InputStream taggedCiphertext = new SequenceInputStream(ciphertext, Streams.of(tag)); + + jca(req).withCipher(new CheckedFunction() { + @Override + public byte[] apply(Cipher cipher) throws Exception { + cipher.init(Cipher.DECRYPT_MODE, key, ivSpec); + byte[] last = withCipher(cipher, taggedCiphertext, aad, out); + Streams.write(out, last, "GcmAesAeadAlgorithm#decrypt plaintext write failure."); + return Bytes.EMPTY; + } + }); + Streams.flush(out); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/HmacAesAeadAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2021 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.impl.io.Streams; +import io.jsonwebtoken.impl.io.TeeOutputStream; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.CheckedFunction; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.AeadRequest; +import io.jsonwebtoken.security.AeadResult; +import io.jsonwebtoken.security.DecryptAeadRequest; +import io.jsonwebtoken.security.SecretKeyBuilder; +import io.jsonwebtoken.security.SecureRequest; +import io.jsonwebtoken.security.SignatureException; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; +import java.security.MessageDigest; +import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +/** + * @since 0.12.0 + */ +public class HmacAesAeadAlgorithm extends AesAlgorithm implements AeadAlgorithm { + + private static final String TRANSFORMATION_STRING = "AES/CBC/PKCS5Padding"; + + private final DefaultMacAlgorithm SIGALG; + + private static int digestLength(int keyLength) { + return keyLength * 2; + } + + private static String id(int keyLength) { + return "A" + keyLength + "CBC-HS" + digestLength(keyLength); + } + + public HmacAesAeadAlgorithm(String id, DefaultMacAlgorithm sigAlg) { + super(id, TRANSFORMATION_STRING, sigAlg.getKeyBitLength()); + this.SIGALG = sigAlg; + } + + public HmacAesAeadAlgorithm(int keyBitLength) { + this(id(keyBitLength), new DefaultMacAlgorithm(id(keyBitLength), "HmacSHA" + digestLength(keyBitLength), keyBitLength)); + } + + @Override + public int getKeyBitLength() { + return super.getKeyBitLength() * 2; + } + + @Override + public SecretKeyBuilder key() { + // The Sun JCE KeyGenerator throws an exception if bitLengths are not standard AES 128, 192 or 256 values. + // Since the JWA HmacAes algorithms require double that, we use secure-random keys instead: + return new RandomSecretKeyBuilder(KEY_ALG_NAME, getKeyBitLength()); + } + + byte[] assertKeyBytes(SecureRequest request) { + SecretKey key = Assert.notNull(request.getKey(), "Request key cannot be null."); + return validateLength(key, this.keyBitLength * 2, true); + } + + @Override + public void encrypt(final AeadRequest req, final AeadResult res) { + + Assert.notNull(req, "Request cannot be null."); + Assert.notNull(res, "Result cannot be null."); + + byte[] compositeKeyBytes = assertKeyBytes(req); + int halfCount = compositeKeyBytes.length / 2; // https://tools.ietf.org/html/rfc7518#section-5.2 + byte[] macKeyBytes = Arrays.copyOfRange(compositeKeyBytes, 0, halfCount); + byte[] encKeyBytes = Arrays.copyOfRange(compositeKeyBytes, halfCount, compositeKeyBytes.length); + final SecretKey encryptionKey; + try { + encryptionKey = new SecretKeySpec(encKeyBytes, KEY_ALG_NAME); + } finally { + Bytes.clear(encKeyBytes); + Bytes.clear(compositeKeyBytes); + } + + final InputStream plaintext = Assert.notNull(req.getPayload(), + "Request content (plaintext) InputStream cannot be null."); + final OutputStream out = Assert.notNull(res.getOutputStream(), "Result ciphertext OutputStream cannot be null."); + final InputStream aad = req.getAssociatedData(); //can be null if there's no associated data + final byte[] iv = ensureInitializationVector(req); + final AlgorithmParameterSpec ivSpec = getIvSpec(iv); + + // we need the ciphertext bytes for message digest calculation, so we'll use a TeeOutputStream to + // aggregate those results while they're being written to the result output stream + final ByteArrayOutputStream copy = new ByteArrayOutputStream(8192); + final OutputStream tee = new TeeOutputStream(out, copy); + + jca(req).withCipher(new CheckedFunction() { + @Override + public Object apply(Cipher cipher) throws Exception { + cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, ivSpec); + withCipher(cipher, plaintext, tee); + return null; // don't need to return anything + } + }); + + byte[] aadBytes = aad == null ? Bytes.EMPTY : Streams.bytes(aad, "Unable to read AAD bytes."); + + byte[] tag; + try { + tag = sign(aadBytes, iv, Streams.of(copy.toByteArray()), macKeyBytes); + res.setTag(tag).setIv(iv); + } finally { + Bytes.clear(macKeyBytes); + } + } + + private byte[] sign(byte[] aad, byte[] iv, InputStream ciphertext, byte[] macKeyBytes) { + + long aadLength = io.jsonwebtoken.lang.Arrays.length(aad); + long aadLengthInBits = aadLength * Byte.SIZE; + long aadLengthInBitsAsUnsignedInt = aadLengthInBits & 0xffffffffL; + byte[] AL = Bytes.toBytes(aadLengthInBitsAsUnsignedInt); + + Collection streams = new ArrayList<>(4); + if (!Bytes.isEmpty(aad)) { // must come first if it exists + streams.add(Streams.of(aad)); + } + streams.add(Streams.of(iv)); + streams.add(ciphertext); + streams.add(Streams.of(AL)); + InputStream in = new SequenceInputStream(Collections.enumeration(streams)); + + SecretKey key = new SecretKeySpec(macKeyBytes, SIGALG.getJcaName()); + SecureRequest request = + new DefaultSecureRequest<>(in, null, null, key); + byte[] digest = SIGALG.digest(request); + + // https://tools.ietf.org/html/rfc7518#section-5.2.2.1 #5 requires truncating the signature + // to be the same length as the macKey/encKey: + return assertTag(Arrays.copyOfRange(digest, 0, macKeyBytes.length)); + } + + @Override + public void decrypt(final DecryptAeadRequest req, final OutputStream plaintext) { + + Assert.notNull(req, "Request cannot be null."); + Assert.notNull(plaintext, "Plaintext OutputStream cannot be null."); + + byte[] compositeKeyBytes = assertKeyBytes(req); + int halfCount = compositeKeyBytes.length / 2; // https://tools.ietf.org/html/rfc7518#section-5.2 + byte[] macKeyBytes = Arrays.copyOfRange(compositeKeyBytes, 0, halfCount); + byte[] encKeyBytes = Arrays.copyOfRange(compositeKeyBytes, halfCount, compositeKeyBytes.length); + final SecretKey decryptionKey; + try { + decryptionKey = new SecretKeySpec(encKeyBytes, KEY_ALG_NAME); + } finally { + Bytes.clear(encKeyBytes); + Bytes.clear(compositeKeyBytes); + } + + InputStream in = Assert.notNull(req.getPayload(), + "Decryption request content (ciphertext) InputStream cannot be null."); + final InputStream aad = req.getAssociatedData(); // can be null if there's no associated data + final byte[] tag = assertTag(req.getDigest()); + final byte[] iv = assertDecryptionIv(req); + final AlgorithmParameterSpec ivSpec = getIvSpec(iv); + + // Assert that the aad + iv + ciphertext provided, when signed, equals the tag provided, + // thereby verifying none of it has been tampered with: + byte[] aadBytes = aad == null ? Bytes.EMPTY : Streams.bytes(aad, "Unable to read AAD bytes."); + byte[] digest; + try { + digest = sign(aadBytes, iv, in, macKeyBytes); + } finally { + Bytes.clear(macKeyBytes); + } + if (!MessageDigest.isEqual(digest, tag)) { //constant time comparison to avoid side-channel attacks + String msg = "Ciphertext decryption failed: Authentication tag verification failed."; + throw new SignatureException(msg); + } + Streams.reset(in); // rewind for decryption + + final InputStream ciphertext = in; + jca(req).withCipher(new CheckedFunction() { + @Override + public byte[] apply(Cipher cipher) throws Exception { + cipher.init(Cipher.DECRYPT_MODE, decryptionKey, ivSpec); + withCipher(cipher, ciphertext, plaintext); + return Bytes.EMPTY; + } + }); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JcaTemplate.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JcaTemplate.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JcaTemplate.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2021 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.Identifiable; +import io.jsonwebtoken.impl.io.Streams; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.CheckedFunction; +import io.jsonwebtoken.impl.lang.CheckedSupplier; +import io.jsonwebtoken.impl.lang.DefaultRegistry; +import io.jsonwebtoken.impl.lang.Function; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Objects; +import io.jsonwebtoken.lang.Registry; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.SecurityException; +import io.jsonwebtoken.security.SignatureException; + +import javax.crypto.Cipher; +import javax.crypto.KeyAgreement; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import java.io.InputStream; +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class JcaTemplate { + + private static final List> FACTORIES = Collections.>of( + new CipherFactory(), + new KeyFactoryFactory(), + new SecretKeyFactoryFactory(), + new KeyGeneratorFactory(), + new KeyPairGeneratorFactory(), + new KeyAgreementFactory(), + new MessageDigestFactory(), + new SignatureFactory(), + new MacFactory(), + new AlgorithmParametersFactory(), + new CertificateFactoryFactory() + ); + + private static final Registry, InstanceFactory> REGISTRY = new DefaultRegistry<>( + "JCA Instance Factory", "instance class", FACTORIES, + new Function, Class>() { + @Override + public Class apply(InstanceFactory factory) { + return factory.getInstanceClass(); + } + }); + + // visible for testing + protected Provider findBouncyCastle() { + return Providers.findBouncyCastle(); + } + + private final String jcaName; + private final Provider provider; + private final SecureRandom secureRandom; + + JcaTemplate(String jcaName) { + this(jcaName, null); + } + + JcaTemplate(String jcaName, Provider provider) { + this(jcaName, provider, null); + } + + JcaTemplate(String jcaName, Provider provider, SecureRandom secureRandom) { + this.jcaName = Assert.hasText(jcaName, "jcaName string cannot be null or empty."); + this.secureRandom = secureRandom != null ? secureRandom : Randoms.secureRandom(); + this.provider = provider; //may be null, meaning to use the JCA subsystem default provider + } + + private R execute(Class clazz, CheckedFunction callback, Provider provider) throws Exception { + InstanceFactory factory = REGISTRY.get(clazz); + Assert.notNull(factory, "Unsupported JCA instance class."); + + Object object = factory.get(this.jcaName, provider); + T instance = Assert.isInstanceOf(clazz, object, "Factory instance does not match expected type."); + + return callback.apply(instance); + } + + private T execute(Class clazz, CheckedSupplier fn) throws SecurityException { + try { + return fn.get(); + } catch (SecurityException se) { + throw se; //propagate + } catch (Throwable t) { + String msg = clazz.getSimpleName() + " callback execution failed: " + t.getMessage(); + throw new SecurityException(msg, t); + } + } + + private R execute(final Class clazz, final CheckedFunction fn) throws SecurityException { + return execute(clazz, new CheckedSupplier() { + @Override + public R get() throws Exception { + return execute(clazz, fn, JcaTemplate.this.provider); + } + }); + } + + protected R fallback(final Class clazz, final CheckedFunction callback) throws SecurityException { + return execute(clazz, new CheckedSupplier() { + @Override + public R get() throws Exception { + try { + return execute(clazz, callback, JcaTemplate.this.provider); + } catch (Exception e) { + try { // fallback + Provider bc = findBouncyCastle(); + if (bc != null) { + return execute(clazz, callback, bc); + } + } catch (Throwable ignored) { // report original exception instead + } + throw e; + } + } + }); + } + + public R withCipher(CheckedFunction fn) throws SecurityException { + return execute(Cipher.class, fn); + } + + public R withKeyFactory(CheckedFunction fn) throws SecurityException { + return execute(KeyFactory.class, fn); + } + + public R withSecretKeyFactory(CheckedFunction fn) throws SecurityException { + return execute(SecretKeyFactory.class, fn); + } + + public R withKeyGenerator(CheckedFunction fn) throws SecurityException { + return execute(KeyGenerator.class, fn); + } + + public R withKeyAgreement(CheckedFunction fn) throws SecurityException { + return execute(KeyAgreement.class, fn); + } + + public R withKeyPairGenerator(CheckedFunction fn) throws SecurityException { + return execute(KeyPairGenerator.class, fn); + } + + public R withMessageDigest(CheckedFunction fn) throws SecurityException { + return execute(MessageDigest.class, fn); + } + + public R withSignature(CheckedFunction fn) throws SecurityException { + return execute(Signature.class, fn); + } + + public R withMac(CheckedFunction fn) throws SecurityException { + return execute(Mac.class, fn); + } + + public R withAlgorithmParameters(CheckedFunction fn) throws SecurityException { + return execute(AlgorithmParameters.class, fn); + } + + public R withCertificateFactory(CheckedFunction fn) throws SecurityException { + return execute(CertificateFactory.class, fn); + } + + public SecretKey generateSecretKey(final int keyBitLength) { + return withKeyGenerator(new CheckedFunction() { + @Override + public SecretKey apply(KeyGenerator generator) { + generator.init(keyBitLength, secureRandom); + return generator.generateKey(); + } + }); + } + + public KeyPair generateKeyPair() { + return withKeyPairGenerator(new CheckedFunction() { + @Override + public KeyPair apply(KeyPairGenerator gen) { + return gen.generateKeyPair(); + } + }); + } + + public KeyPair generateKeyPair(final int keyBitLength) { + return withKeyPairGenerator(new CheckedFunction() { + @Override + public KeyPair apply(KeyPairGenerator generator) { + generator.initialize(keyBitLength, secureRandom); + return generator.generateKeyPair(); + } + }); + } + + public KeyPair generateKeyPair(final AlgorithmParameterSpec params) { + return withKeyPairGenerator(new CheckedFunction() { + @Override + public KeyPair apply(KeyPairGenerator generator) throws InvalidAlgorithmParameterException { + generator.initialize(params, secureRandom); + return generator.generateKeyPair(); + } + }); + } + + public PublicKey generatePublic(final KeySpec spec) { + return fallback(KeyFactory.class, new CheckedFunction() { + @Override + public PublicKey apply(KeyFactory keyFactory) throws Exception { + return keyFactory.generatePublic(spec); + } + }); + } + + protected boolean isJdk11() { + return System.getProperty("java.version").startsWith("11"); + } + + private boolean isJdk8213363Bug(InvalidKeySpecException e) { + return isJdk11() && + ("XDH".equals(this.jcaName) || "X25519".equals(this.jcaName) || "X448".equals(this.jcaName)) && + e.getCause() instanceof InvalidKeyException && + !Objects.isEmpty(e.getStackTrace()) && + "sun.security.ec.XDHKeyFactory".equals(e.getStackTrace()[0].getClassName()) && + "engineGeneratePrivate".equals(e.getStackTrace()[0].getMethodName()); + } + + // visible for testing + private int getJdk8213363BugExpectedSize(InvalidKeyException e) { + String msg = e.getMessage(); + String prefix = "key length must be "; + if (Strings.hasText(msg) && msg.startsWith(prefix)) { + String expectedSizeString = msg.substring(prefix.length()); + try { + return Integer.parseInt(expectedSizeString); + } catch (NumberFormatException ignored) { // return -1 below + } + } + return -1; + } + + private KeySpec respecIfNecessary(InvalidKeySpecException e, KeySpec spec) { + if (!(spec instanceof PKCS8EncodedKeySpec)) { + return null; + } + PKCS8EncodedKeySpec pkcs8Spec = (PKCS8EncodedKeySpec) spec; + byte[] encoded = pkcs8Spec.getEncoded(); + + // Address the [JDK 11 SunCE provider bug](https://bugs.openjdk.org/browse/JDK-8213363) for X25519 + // and X448 encoded keys: Even though the key material might be encoded properly, JDK 11's + // SunCE provider incorrectly expects an ASN.1 OCTET STRING (without the DER tag/length prefix) + // when it should actually be a BER-encoded OCTET STRING (with the tag/length prefix). + // So we get the raw key bytes and use our key factory method: + if (isJdk8213363Bug(e)) { + InvalidKeyException cause = // asserted in isJdk8213363Bug method + Assert.isInstanceOf(InvalidKeyException.class, e.getCause(), "Unexpected argument."); + int size = getJdk8213363BugExpectedSize(cause); + if ((size == 32 || size == 56) && Bytes.length(encoded) >= size) { + byte[] adjusted = new byte[size]; + System.arraycopy(encoded, encoded.length - size, adjusted, 0, size); + EdwardsCurve curve = size == 32 ? EdwardsCurve.X25519 : EdwardsCurve.X448; + return curve.privateKeySpec(adjusted, false); + } + } + + return null; + } + + // visible for testing + protected PrivateKey generatePrivate(KeyFactory factory, KeySpec spec) throws InvalidKeySpecException { + return factory.generatePrivate(spec); + } + + public PrivateKey generatePrivate(final KeySpec spec) { + return fallback(KeyFactory.class, new CheckedFunction() { + @Override + public PrivateKey apply(KeyFactory keyFactory) throws Exception { + try { + return generatePrivate(keyFactory, spec); + } catch (InvalidKeySpecException e) { + KeySpec respec = respecIfNecessary(e, spec); + if (respec != null) { + return generatePrivate(keyFactory, respec); + } + throw e; // could not respec, propagate + } + } + }); + } + + public X509Certificate generateX509Certificate(final byte[] x509DerBytes) { + return fallback(CertificateFactory.class, new CheckedFunction() { + @Override + public X509Certificate apply(CertificateFactory cf) throws CertificateException { + InputStream is = Streams.of(x509DerBytes); + return (X509Certificate) cf.generateCertificate(is); + } + }); + } + + private interface InstanceFactory extends Identifiable { + + Class getInstanceClass(); + + T get(String jcaName, Provider provider) throws Exception; + } + + private static abstract class JcaInstanceFactory implements InstanceFactory { + + private final Class clazz; + + // Boolean value: missing/null = haven't attempted, true = attempted and succeeded, false = attempted and failed + private final ConcurrentMap FALLBACK_ATTEMPTS = new ConcurrentHashMap<>(); + + JcaInstanceFactory(Class clazz) { + this.clazz = Assert.notNull(clazz, "Class argument cannot be null."); + } + + @Override + public Class getInstanceClass() { + return this.clazz; + } + + @Override + public String getId() { + return clazz.getSimpleName(); + } + + // visible for testing + protected Provider findBouncyCastle() { + return Providers.findBouncyCastle(); + } + + @SuppressWarnings("GrazieInspection") + @Override + public final T get(String jcaName, final Provider specifiedProvider) throws Exception { + Assert.hasText(jcaName, "jcaName cannot be null or empty."); + Provider provider = specifiedProvider; + final Boolean attempted = FALLBACK_ATTEMPTS.get(jcaName); + if (provider == null && attempted != null && attempted) { + // We tried with the default provider previously, and needed to fallback, so just + // preemptively load the fallback to avoid the fallback/retry again: + provider = findBouncyCastle(); + } + try { + return doGet(jcaName, provider); + } catch (NoSuchAlgorithmException nsa) { // try to fallback if possible + + if (specifiedProvider == null && attempted == null) { // default provider doesn't support the alg name, + // and we haven't tried BC yet, so try that now: + Provider fallback = findBouncyCastle(); + if (fallback != null) { // BC found, try again: + try { + T value = doGet(jcaName, fallback); + // record the successful attempt so we don't have to do this again: + FALLBACK_ATTEMPTS.putIfAbsent(jcaName, Boolean.TRUE); + return value; + } catch (Throwable ignored) { + // record the failed attempt so we don't keep trying and propagate original exception: + FALLBACK_ATTEMPTS.putIfAbsent(jcaName, Boolean.FALSE); + } + } + } + // otherwise, we tried the fallback, or there isn't a fallback, so no need to try again, so + // propagate the exception: + throw wrap(nsa, jcaName, specifiedProvider, null); + } catch (Exception e) { + throw wrap(e, jcaName, specifiedProvider, null); + } + } + + protected abstract T doGet(String jcaName, Provider provider) throws Exception; + + // visible for testing: + protected Exception wrap(Exception e, String jcaName, Provider specifiedProvider, Provider fallbackProvider) { + String msg = "Unable to obtain '" + jcaName + "' " + getId() + " instance from "; + if (specifiedProvider != null) { + msg += "specified '" + specifiedProvider + "' Provider"; + } else { + msg += "default JCA Provider"; + } + if (fallbackProvider != null) { + msg += " or fallback '" + fallbackProvider + "' Provider"; + } + msg += ": " + e.getMessage(); + return wrap(msg, e); + } + + protected Exception wrap(String msg, Exception cause) { + if (Signature.class.isAssignableFrom(clazz) || Mac.class.isAssignableFrom(clazz)) { + return new SignatureException(msg, cause); + } + return new SecurityException(msg, cause); + } + } + + private static class CipherFactory extends JcaInstanceFactory { + CipherFactory() { + super(Cipher.class); + } + + @Override + public Cipher doGet(String jcaName, Provider provider) throws NoSuchPaddingException, NoSuchAlgorithmException { + return provider != null ? Cipher.getInstance(jcaName, provider) : Cipher.getInstance(jcaName); + } + } + + private static class KeyFactoryFactory extends JcaInstanceFactory { + KeyFactoryFactory() { + super(KeyFactory.class); + } + + @Override + public KeyFactory doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { + return provider != null ? KeyFactory.getInstance(jcaName, provider) : KeyFactory.getInstance(jcaName); + } + } + + private static class SecretKeyFactoryFactory extends JcaInstanceFactory { + SecretKeyFactoryFactory() { + super(SecretKeyFactory.class); + } + + @Override + public SecretKeyFactory doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { + return provider != null ? SecretKeyFactory.getInstance(jcaName, provider) : SecretKeyFactory.getInstance(jcaName); + } + } + + private static class KeyGeneratorFactory extends JcaInstanceFactory { + KeyGeneratorFactory() { + super(KeyGenerator.class); + } + + @Override + public KeyGenerator doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { + return provider != null ? KeyGenerator.getInstance(jcaName, provider) : KeyGenerator.getInstance(jcaName); + } + } + + private static class KeyPairGeneratorFactory extends JcaInstanceFactory { + KeyPairGeneratorFactory() { + super(KeyPairGenerator.class); + } + + @Override + public KeyPairGenerator doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { + return provider != null ? KeyPairGenerator.getInstance(jcaName, provider) : KeyPairGenerator.getInstance(jcaName); + } + } + + private static class KeyAgreementFactory extends JcaInstanceFactory { + KeyAgreementFactory() { + super(KeyAgreement.class); + } + + @Override + public KeyAgreement doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { + return provider != null ? KeyAgreement.getInstance(jcaName, provider) : KeyAgreement.getInstance(jcaName); + } + } + + private static class MessageDigestFactory extends JcaInstanceFactory { + MessageDigestFactory() { + super(MessageDigest.class); + } + + @Override + public MessageDigest doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { + return provider != null ? MessageDigest.getInstance(jcaName, provider) : MessageDigest.getInstance(jcaName); + } + } + + private static class SignatureFactory extends JcaInstanceFactory { + SignatureFactory() { + super(Signature.class); + } + + @Override + public Signature doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { + return provider != null ? Signature.getInstance(jcaName, provider) : Signature.getInstance(jcaName); + } + } + + private static class MacFactory extends JcaInstanceFactory { + MacFactory() { + super(Mac.class); + } + + @Override + public Mac doGet(String jcaName, Provider provider) throws NoSuchAlgorithmException { + return provider != null ? Mac.getInstance(jcaName, provider) : Mac.getInstance(jcaName); + } + } + + private static class AlgorithmParametersFactory extends JcaInstanceFactory { + AlgorithmParametersFactory() { + super(AlgorithmParameters.class); + } + + @Override + protected AlgorithmParameters doGet(String jcaName, Provider provider) throws Exception { + return provider != null ? + AlgorithmParameters.getInstance(jcaName, provider) : + AlgorithmParameters.getInstance(jcaName); + } + } + + private static class CertificateFactoryFactory extends JcaInstanceFactory { + CertificateFactoryFactory() { + super(CertificateFactory.class); + } + + @Override + protected CertificateFactory doGet(String jcaName, Provider provider) throws Exception { + return provider != null ? + CertificateFactory.getInstance(jcaName, provider) : + CertificateFactory.getInstance(jcaName); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkBuilderSupplier.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkBuilderSupplier.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkBuilderSupplier.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,45 @@ +/* + * 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.lang.Supplier; +import io.jsonwebtoken.security.DynamicJwkBuilder; +import io.jsonwebtoken.security.Jwks; +import io.jsonwebtoken.security.KeyOperationPolicy; + +import java.security.Provider; + +public class JwkBuilderSupplier implements Supplier> { + + public static final JwkBuilderSupplier DEFAULT = new JwkBuilderSupplier(null, null); + + private final Provider provider; + private final KeyOperationPolicy operationPolicy; + + public JwkBuilderSupplier(Provider provider, KeyOperationPolicy operationPolicy) { + this.provider = provider; + this.operationPolicy = operationPolicy; + } + + @Override + public DynamicJwkBuilder get() { + DynamicJwkBuilder builder = Jwks.builder().provider(this.provider); + if (this.operationPolicy != null) { + builder.operationPolicy(operationPolicy); + } + return builder; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkContext.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkContext.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkContext.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 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.Identifiable; +import io.jsonwebtoken.impl.X509Context; +import io.jsonwebtoken.impl.lang.Nameable; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.security.HashAlgorithm; +import io.jsonwebtoken.security.KeyOperation; + +import java.security.Key; +import java.security.Provider; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +public interface JwkContext extends Identifiable, Map, ParameterReadable, Nameable, + X509Context> { + + JwkContext parameter(Parameter param); + + JwkContext setId(String id); + + JwkContext setIdThumbprintAlgorithm(HashAlgorithm alg); + + HashAlgorithm getIdThumbprintAlgorithm(); + + String getType(); + + JwkContext setType(String type); + + Set getOperations(); + + JwkContext setOperations(Collection operations); + + String getAlgorithm(); + + JwkContext setAlgorithm(String algorithm); + + String getPublicKeyUse(); + + JwkContext setPublicKeyUse(String use); + + /** + * Returns {@code true} if relevant context values indicate JWK use with MAC or digital signature algorithms, + * {@code false} otherwise. Specifically {@code true} is only returned if either: + *
      + *
    • "sig".equals({@link #getPublicKeyUse()}), OR
    • + *
    • {@link #getOperations()} is not empty and contains either "sign" or "verify"
    • + *
    + *

    otherwise {@code false}.

    + * + * @return {@code true} if relevant context values indicate JWK use with MAC or digital signature algorithms, + * {@code false} otherwise. + */ + boolean isSigUse(); + + K getKey(); + + JwkContext setKey(K key); + + PublicKey getPublicKey(); + + JwkContext setPublicKey(PublicKey publicKey); + + Provider getProvider(); + + JwkContext setProvider(Provider provider); + + SecureRandom getRandom(); + + JwkContext setRandom(SecureRandom random); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2021 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.impl.lang.Converter; +import io.jsonwebtoken.impl.lang.Nameable; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.lang.Supplier; +import io.jsonwebtoken.security.DynamicJwkBuilder; +import io.jsonwebtoken.security.EcPrivateJwk; +import io.jsonwebtoken.security.EcPublicJwk; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.MalformedKeyException; +import io.jsonwebtoken.security.OctetPrivateJwk; +import io.jsonwebtoken.security.OctetPublicJwk; +import io.jsonwebtoken.security.PrivateJwk; +import io.jsonwebtoken.security.PublicJwk; +import io.jsonwebtoken.security.RsaPrivateJwk; +import io.jsonwebtoken.security.RsaPublicJwk; +import io.jsonwebtoken.security.SecretJwk; + +import java.util.Map; + +import static io.jsonwebtoken.lang.Strings.nespace; + +public final class JwkConverter> implements Converter { + + @SuppressWarnings("unchecked") + public static final Class> JWK_CLASS = (Class>) (Class) Jwk.class; + + @SuppressWarnings("unchecked") + public static final Class> PUBLIC_JWK_CLASS = (Class>) (Class) PublicJwk.class; + + public static final JwkConverter> ANY = new JwkConverter<>(JWK_CLASS); + + public static final JwkConverter> PUBLIC_JWK = new JwkConverter<>(PUBLIC_JWK_CLASS); + + private final Class desiredType; + + private final Supplier> supplier; + + public JwkConverter(Class desiredType) { + this(desiredType, JwkBuilderSupplier.DEFAULT); + } + + @SuppressWarnings("unchecked") + public JwkConverter(Supplier> supplier) { + this((Class) JWK_CLASS, supplier); + } + + public JwkConverter(Class desiredType, Supplier> supplier) { + this.desiredType = Assert.notNull(desiredType, "desiredType cannot be null."); + this.supplier = Assert.notNull(supplier, "supplier cannot be null."); + } + + @Override + public Object applyTo(T jwk) { + return desiredType.cast(jwk); + } + + private static String articleFor(String s) { + switch (s.charAt(0)) { + case 'E': // for Elliptic/Edwards Curve + case 'R': // for RSA + return "an"; + default: + return "a"; + } + } + + private static String typeString(Jwk jwk) { + Assert.isInstanceOf(Nameable.class, jwk, "All JWK implementations must implement Nameable."); + return ((Nameable) jwk).getName(); + } + + private static String typeString(Class clazz) { + StringBuilder sb = new StringBuilder(); + if (SecretJwk.class.isAssignableFrom(clazz)) { + sb.append("Secret"); + } else if (RsaPublicJwk.class.isAssignableFrom(clazz) || RsaPrivateJwk.class.isAssignableFrom(clazz)) { + sb.append("RSA"); + } else if (EcPublicJwk.class.isAssignableFrom(clazz) || EcPrivateJwk.class.isAssignableFrom(clazz)) { + sb.append("EC"); + } else if (OctetPublicJwk.class.isAssignableFrom(clazz) || OctetPrivateJwk.class.isAssignableFrom(clazz)) { + sb.append("Edwards Curve"); + } + return typeString(sb, clazz); + } + + private static String typeString(StringBuilder sb, Class clazz) { + if (PublicJwk.class.isAssignableFrom(clazz)) { + nespace(sb).append("Public"); + } else if (PrivateJwk.class.isAssignableFrom(clazz)) { + nespace(sb).append("Private"); + } + nespace(sb).append("JWK"); + return sb.toString(); + } + + private IllegalArgumentException unexpectedIAE(Jwk jwk) { + String desired = typeString(this.desiredType); + String jwkType = typeString(jwk); + String msg = "Value must be " + articleFor(desired) + " " + desired + ", not " + + articleFor(jwkType) + " " + jwkType + "."; + return new IllegalArgumentException(msg); + } + + @Override + public T applyFrom(Object o) { + Assert.notNull(o, "JWK cannot be null."); + if (desiredType.isInstance(o)) { + return desiredType.cast(o); + } else if (o instanceof Jwk) { + throw unexpectedIAE((Jwk) o); + } + if (!(o instanceof Map)) { + String msg = "JWK must be a Map (JSON Object). Type found: " + o.getClass().getName() + "."; + throw new IllegalArgumentException(msg); + } + final Map map = Collections.immutable((Map) o); + + Parameter param = AbstractJwk.KTY; + // mandatory for all JWKs: https://datatracker.ietf.org/doc/html/rfc7517#section-4.1 + // no need for builder param type conversion overhead if this isn't present: + if (Collections.isEmpty(map) || !map.containsKey(param.getId())) { + String msg = "JWK is missing required " + param + " parameter."; + throw new MalformedKeyException(msg); + } + Object val = map.get(param.getId()); + if (val == null) { + String msg = "JWK " + param + " value cannot be null."; + throw new MalformedKeyException(msg); + } + if (!(val instanceof String)) { + String msg = "JWK " + param + " value must be a String. Type found: " + val.getClass().getName(); + throw new MalformedKeyException(msg); + } + String kty = (String) val; + if (!Strings.hasText(kty)) { + String msg = "JWK " + param + " value cannot be empty."; + throw new MalformedKeyException(msg); + } + + DynamicJwkBuilder builder = this.supplier.get(); + for (Map.Entry entry : map.entrySet()) { + Object key = entry.getKey(); + Assert.notNull(key, "JWK map key cannot be null."); + if (!(key instanceof String)) { + String msg = "JWK map keys must be Strings. Encountered key '" + key + "' of type " + + key.getClass().getName() + "."; + throw new IllegalArgumentException(msg); + } + String skey = (String) key; + builder.add(skey, entry.getValue()); + } + Jwk jwk = builder.build(); + + if (desiredType.isInstance(jwk)) { + return desiredType.cast(jwk); + } + throw unexpectedIAE(jwk); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkDeserializer.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkDeserializer.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkDeserializer.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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.impl.security; + +import io.jsonwebtoken.impl.io.JsonObjectDeserializer; +import io.jsonwebtoken.io.Deserializer; +import io.jsonwebtoken.security.MalformedKeyException; + +public class JwkDeserializer extends JsonObjectDeserializer { + + public JwkDeserializer(Deserializer deserializer) { + super(deserializer, "JWK"); + } + + @Override + protected RuntimeException malformed(Throwable t) { + String msg = "Malformed JWK JSON: " + t.getMessage(); + return new MalformedKeyException(msg); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 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.Jwk; + +import java.security.Key; + +public interface JwkFactory> { + + JwkContext newContext(JwkContext src, K key); + + J createJwk(JwkContext ctx); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkSetConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkSetConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkSetConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,146 @@ +/* + * 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.impl.lang.Converter; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Supplier; +import io.jsonwebtoken.security.DynamicJwkBuilder; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.JwkSet; +import io.jsonwebtoken.security.KeyException; +import io.jsonwebtoken.security.MalformedKeySetException; +import io.jsonwebtoken.security.UnsupportedKeyException; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +public class JwkSetConverter implements Converter { + + private final Converter, Object> JWK_CONVERTER; + private final Parameter>> PARAM; + + private final boolean ignoreUnsupported; + + public JwkSetConverter() { + // ignore is true by default per https://www.rfc-editor.org/rfc/rfc7517.html#section-5: + this(JwkBuilderSupplier.DEFAULT, true); + } + + public JwkSetConverter(boolean ignoreUnsupported) { + this(JwkBuilderSupplier.DEFAULT, ignoreUnsupported); + } + + public JwkSetConverter(Supplier> supplier, boolean ignoreUnsupported) { + this(new JwkConverter<>(supplier), ignoreUnsupported); + } + + public JwkSetConverter(Converter, Object> jwkConverter, boolean ignoreUnsupported) { + this.JWK_CONVERTER = Assert.notNull(jwkConverter, "JWK converter cannot be null."); + this.PARAM = DefaultJwkSet.param(jwkConverter); + this.ignoreUnsupported = ignoreUnsupported; + } + + public boolean isIgnoreUnsupported() { + return ignoreUnsupported; + } + + @Override + public Object applyTo(JwkSet jwkSet) { + return jwkSet; + } + + @Override + public JwkSet applyFrom(Object o) { + Assert.notNull(o, "Value cannot be null."); + if (o instanceof JwkSet) { + return (JwkSet) o; + } + if (!(o instanceof Map)) { + String msg = "Value must be a Map (JSON Object). Type found: " + o.getClass().getName() + "."; + throw new IllegalArgumentException(msg); + } + final Map m = Collections.immutable((Map) o); + + // mandatory for all JWK Sets: https://datatracker.ietf.org/doc/html/rfc7517#section-5 + // no need for builder parameter type conversion overhead if this isn't present: + if (Collections.isEmpty(m) || !m.containsKey(PARAM.getId())) { + String msg = "Missing required " + PARAM + " parameter."; + throw new MalformedKeySetException(msg); + } + Object val = m.get(PARAM.getId()); + if (val == null) { + String msg = "JWK Set " + PARAM + " value cannot be null."; + throw new MalformedKeySetException(msg); + } + if (val instanceof Supplier) { + val = ((Supplier) val).get(); + } + if (!(val instanceof Collection)) { + String msg = "JWK Set " + PARAM + " value must be a Collection (JSON Array). Type found: " + + val.getClass().getName(); + throw new MalformedKeySetException(msg); + } + int size = Collections.size((Collection) val); + if (size == 0) { + String msg = "JWK Set " + PARAM + " collection cannot be empty."; + throw new MalformedKeySetException(msg); + } + + // Copy values so we don't mutate the original input + Map src = new LinkedHashMap<>(Collections.size((Map) o)); + for (Map.Entry entry : ((Map) o).entrySet()) { + Object key = Assert.notNull(entry.getKey(), "JWK Set map key cannot be null."); + if (!(key instanceof String)) { + String msg = "JWK Set map keys must be Strings. Encountered key '" + key + "' of type " + + key.getClass().getName(); + throw new IllegalArgumentException(msg); + } + String skey = (String) key; + src.put(skey, entry.getValue()); + } + + Set> jwks = new LinkedHashSet<>(size); + int i = 0; // keep track of which element fails (if any) + for (Object candidate : ((Collection) val)) { + try { + Jwk jwk = JWK_CONVERTER.applyFrom(candidate); + jwks.add(jwk); + } catch (UnsupportedKeyException e) { + if (!ignoreUnsupported) { + String msg = "JWK Set keys[" + i + "]: " + e.getMessage(); + throw new UnsupportedKeyException(msg, e); + } + } catch (IllegalArgumentException | KeyException e) { + if (!ignoreUnsupported) { + String msg = "JWK Set keys[" + i + "]: " + e.getMessage(); + throw new MalformedKeySetException(msg, e); + } + } + i++; + } + + // Replace the `keys` value with validated entries: + src.remove(PARAM.getId()); + src.put(PARAM.getId(), jwks); + return new DefaultJwkSet(PARAM, src); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkSetDeserializer.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkSetDeserializer.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwkSetDeserializer.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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.impl.security; + +import io.jsonwebtoken.impl.io.JsonObjectDeserializer; +import io.jsonwebtoken.io.Deserializer; +import io.jsonwebtoken.security.MalformedKeySetException; + +public class JwkSetDeserializer extends JsonObjectDeserializer { + + public JwkSetDeserializer(Deserializer deserializer) { + super(deserializer, "JWK Set"); + } + + @Override + protected RuntimeException malformed(Throwable t) { + String msg = "Malformed JWK Set JSON: " + t.getMessage(); + throw new MalformedKeySetException(msg, t); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwksBridge.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwksBridge.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwksBridge.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,42 @@ +/* + * 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.impl.io.NamedSerializer; +import io.jsonwebtoken.impl.lang.Services; +import io.jsonwebtoken.io.Serializer; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.Jwk; + +import java.io.ByteArrayOutputStream; +import java.util.Map; + +public final class JwksBridge { + + private JwksBridge() { + } + + @SuppressWarnings({"unchecked", "unused"}) // used via reflection by io.jsonwebtoken.security.Jwks + public static String UNSAFE_JSON(Jwk jwk) { + Serializer> serializer = Services.get(Serializer.class); + Assert.stateNotNull(serializer, "Serializer lookup failed. Ensure JSON impl .jar is in the runtime classpath."); + NamedSerializer ser = new NamedSerializer("JWK", serializer); + ByteArrayOutputStream out = new ByteArrayOutputStream(512); + ser.serialize(jwk, out); + return Strings.utf8(out.toByteArray()); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwtX509StringConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwtX509StringConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/JwtX509StringConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2021 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.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.Converter; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.io.Encoders; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.SecurityException; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +public class JwtX509StringConverter implements Converter { + + public static final JwtX509StringConverter INSTANCE = new JwtX509StringConverter(); + + // Returns a Base64 encoded (NOT Base64Url encoded) string of the cert's encoded byte array per + // https://www.rfc-editor.org/rfc/rfc7515.html#section-4.1.6 + // https://www.rfc-editor.org/rfc/rfc7516.html#section-4.1.8 + // https://www.rfc-editor.org/rfc/rfc7517.html#section-4.7 + @Override + public String applyTo(X509Certificate cert) { + Assert.notNull(cert, "X509Certificate cannot be null."); + byte[] der; + try { + der = cert.getEncoded(); + } catch (CertificateEncodingException e) { + String msg = "Unable to access X509Certificate encoded bytes necessary to perform DER " + + "Base64-encoding. Certificate: {" + cert + "}. Cause: " + e.getMessage(); + throw new IllegalArgumentException(msg, e); + } + if (Bytes.isEmpty(der)) { + String msg = "X509Certificate encoded bytes cannot be null or empty. Certificate: {" + cert + "}."; + throw new IllegalArgumentException(msg); + } + return Encoders.BASE64.encode(der); + } + + // visible for testing + protected X509Certificate toCert(final byte[] der) throws SecurityException { + return new JcaTemplate("X.509").generateX509Certificate(der); + } + + @Override + public X509Certificate applyFrom(CharSequence s) { + Assert.hasText(s, "X.509 Certificate encoded string cannot be null or empty."); + try { + byte[] der = Decoders.BASE64.decode(s); //RFC requires Base64, not Base64Url + return toCert(der); + } catch (Exception e) { + String msg = "Unable to convert Base64 String '" + s + "' to X509Certificate instance. Cause: " + e.getMessage(); + throw new IllegalArgumentException(msg, e); + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeyOperationConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeyOperationConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeyOperationConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,50 @@ +/* + * 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.impl.lang.Converter; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Registry; +import io.jsonwebtoken.security.Jwks; +import io.jsonwebtoken.security.KeyOperation; + +final class KeyOperationConverter implements Converter { + + static final Converter DEFAULT = new KeyOperationConverter(Jwks.OP.get()); + + private final Registry registry; + + KeyOperationConverter(Registry registry) { + this.registry = Assert.notEmpty(registry, "KeyOperation registry cannot be null or empty."); + } + + @Override + public String applyTo(KeyOperation operation) { + Assert.notNull(operation, "KeyOperation cannot be null."); + return operation.getId(); + } + + @Override + public KeyOperation applyFrom(Object o) { + if (o instanceof KeyOperation) { + return (KeyOperation) o; + } + String id = Assert.isInstanceOf(String.class, o, "Argument must be a KeyOperation or String."); + Assert.hasText(id, "KeyOperation string value cannot be null or empty."); + KeyOperation keyOp = this.registry.get(id); + return keyOp != null ? keyOp : Jwks.OP.builder().id(id).build(); // custom operations are allowed + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeyPairs.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeyPairs.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeyPairs.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 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.lang.Assert; +import io.jsonwebtoken.lang.Strings; + +import java.security.Key; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.interfaces.ECKey; +import java.security.interfaces.RSAKey; + +public final class KeyPairs { + + private KeyPairs() { + } + + private static String familyPrefix(Class clazz) { + if (RSAKey.class.isAssignableFrom(clazz)) { + return "RSA "; + } else if (ECKey.class.isAssignableFrom(clazz)) { + return "EC "; + } else { + return Strings.EMPTY; + } + } + + public static K getKey(KeyPair pair, Class clazz) { + Assert.notNull(pair, "KeyPair cannot be null."); + String prefix = familyPrefix(clazz) + "KeyPair "; + boolean isPrivate = PrivateKey.class.isAssignableFrom(clazz); + Key key = isPrivate ? pair.getPrivate() : pair.getPublic(); + return assertKey(key, clazz, prefix); + } + + public static K assertKey(Key key, Class clazz, String msgPrefix) { + Assert.notNull(key, "Key argument cannot be null."); + Assert.notNull(clazz, "Class argument cannot be null."); + String type = key instanceof PrivateKey ? "private" : "public"; + if (!clazz.isInstance(key)) { + String msg = msgPrefix + type + " key must be an instance of " + clazz.getName() + + ". Type found: " + key.getClass().getName(); + throw new IllegalArgumentException(msg); + } + return clazz.cast(key); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeyUsage.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeyUsage.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeyUsage.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 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 java.security.cert.X509Certificate; + +public final class KeyUsage { + + private static final boolean[] NO_FLAGS = new boolean[9]; + + // Direct from X509Certificate#getKeyUsage() JavaDoc. For an understand of when/how to use these + // flags, read https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3 + private static final int + digitalSignature = 0, + nonRepudiation = 1, + keyEncipherment = 2, + dataEncipherment = 3, + keyAgreement = 4, + keyCertSign = 5, + cRLSign = 6, + encipherOnly = 7, //if keyAgreement, then only encipher data during key agreement + decipherOnly = 8; //if keyAgreement, then only decipher data during key agreement + + private final boolean[] is; //for readability: i.e. is[nonRepudiation] simulates isNonRepudiation, etc. + + public KeyUsage(X509Certificate cert) { + boolean[] arr = cert != null ? cert.getKeyUsage() : NO_FLAGS; + this.is = arr != null ? arr : NO_FLAGS; + } + + public boolean isDigitalSignature() { + return is[digitalSignature]; + } + + public boolean isNonRepudiation() { + return is[nonRepudiation]; + } + + public boolean isKeyEncipherment() { + return is[keyEncipherment]; + } + + public boolean isDataEncipherment() { + return is[dataEncipherment]; + } + + public boolean isKeyAgreement() { + return is[keyAgreement]; + } + + public boolean isKeyCertSign() { + return is[keyCertSign]; + } + + public boolean isCRLSign() { + return is[cRLSign]; + } + + public boolean isEncipherOnly() { + return is[encipherOnly]; + } + + public boolean isDecipherOnly() { + return is[decipherOnly]; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeyUseStrategy.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeyUseStrategy.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeyUseStrategy.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 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; + +//TODO: Make a non-impl concept? +public interface KeyUseStrategy { + + //TODO: change argument to have more information? + String toJwkValue(KeyUsage keyUses); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeysBridge.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeysBridge.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/KeysBridge.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2021 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.impl.lang.Bytes; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.KeySupplier; +import io.jsonwebtoken.security.Password; +import io.jsonwebtoken.security.PrivateKeyBuilder; +import io.jsonwebtoken.security.SecretKeyBuilder; + +import javax.crypto.SecretKey; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.ECKey; +import java.security.interfaces.RSAKey; + +@SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.Keys implementation +public final class KeysBridge { + + private static final String SUNPKCS11_GENERIC_SECRET_CLASSNAME = "sun.security.pkcs11.P11Key$P11SecretKey"; + private static final String SUNPKCS11_GENERIC_SECRET_ALGNAME = "Generic Secret"; // https://github.com/openjdk/jdk/blob/4f90abaf17716493bad740dcef76d49f16d69379/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11KeyStore.java#L1292 + + // prevent instantiation + private KeysBridge() { + } + + public static Password password(char[] password) { + return new PasswordSpec(password); + } + + public static SecretKeyBuilder builder(SecretKey key) { + return new ProvidedSecretKeyBuilder(key); + } + + public static PrivateKeyBuilder builder(PrivateKey key) { + return new ProvidedPrivateKeyBuilder(key); + } + + /** + * If the specified {@code key} is a {@link KeySupplier}, the 'root' (lowest level) key that may exist in + * a {@code KeySupplier} chain is returned, otherwise the {@code key} is returned. + * + * @param key the key to check if it is a {@code KeySupplier} + * @param the key type + * @return the lowest-level/root key available. + */ + @SuppressWarnings("unchecked") + public static K root(K key) { + return (key instanceof KeySupplier) ? (K) root((KeySupplier) key) : key; + } + + public static K root(KeySupplier supplier) { + Assert.notNull(supplier, "KeySupplier canot be null."); + return Assert.notNull(root(supplier.getKey()), "KeySupplier key cannot be null."); + } + + public static String findAlgorithm(Key key) { + return key != null ? Strings.clean(key.getAlgorithm()) : null; + } + + /** + * Returns the specified key's available encoded bytes, or {@code null} if not available. + * + *

    Some KeyStore implementations - like Hardware Security Modules, PKCS11 key stores, and later versions + * of Android - will not allow applications or libraries to obtain a key's encoded bytes. In these cases, + * this method will return null.

    + * + * @param key the key to inspect + * @return the specified key's available encoded bytes, or {@code null} if not available. + */ + public static byte[] findEncoded(Key key) { + Assert.notNull(key, "Key cannot be null."); + byte[] encoded = null; + try { + encoded = key.getEncoded(); + } catch (Throwable ignored) { + } + return encoded; + } + + public static boolean isSunPkcs11GenericSecret(Key key) { + return key instanceof SecretKey && + key.getClass().getName().equals(SUNPKCS11_GENERIC_SECRET_CLASSNAME) && + SUNPKCS11_GENERIC_SECRET_ALGNAME.equals(key.getAlgorithm()); + } + + /** + * Returns the specified key's key length (in bits) if possible, or {@code -1} if unable to determine the length. + * + * @param key the key to inspect + * @return the specified key's key length in bits, or {@code -1} if unable to determine length. + */ + public static int findBitLength(Key key) { + + int bitlen = -1; + + // try to parse the length from key specification + if (key instanceof SecretKey) { + SecretKey secretKey = (SecretKey) key; + if ("RAW".equals(secretKey.getFormat())) { + byte[] encoded = findEncoded(secretKey); + if (!Bytes.isEmpty(encoded)) { + bitlen = (int) Bytes.bitLength(encoded); + Bytes.clear(encoded); + } + } + } else if (key instanceof RSAKey) { + RSAKey rsaKey = (RSAKey) key; + bitlen = rsaKey.getModulus().bitLength(); + } else if (key instanceof ECKey) { + ECKey ecKey = (ECKey) key; + bitlen = ecKey.getParams().getOrder().bitLength(); + } else { + // We can check additional logic for EdwardsCurve even if the current JDK version doesn't support it: + EdwardsCurve curve = EdwardsCurve.findByKey(key); + if (curve != null) bitlen = curve.getKeyBitLength(); + } + + return bitlen; + } + + public static byte[] getEncoded(Key key) { + Assert.notNull(key, "Key cannot be null."); + byte[] encoded; + try { + encoded = key.getEncoded(); + } catch (Throwable t) { + String msg = "Cannot obtain required encoded bytes from key [" + KeysBridge.toString(key) + "]: " + + t.getMessage(); + throw new InvalidKeyException(msg, t); + } + if (Bytes.isEmpty(encoded)) { + String msg = "Missing required encoded bytes for key [" + toString(key) + "]."; + throw new InvalidKeyException(msg); + } + return encoded; + } + + public static String toString(Key key) { + if (key == null) { + return "null"; + } + if (key instanceof PublicKey) { + return key.toString(); // safe to show internal key state as it's a public key + } + // else secret or private key, don't show internal key state, just public attributes + return "class: " + key.getClass().getName() + + ", algorithm: " + key.getAlgorithm() + + ", format: " + key.getFormat(); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/LocatingKeyResolver.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/LocatingKeyResolver.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/LocatingKeyResolver.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 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.Claims; +import io.jsonwebtoken.JwsHeader; +import io.jsonwebtoken.Locator; +import io.jsonwebtoken.SigningKeyResolver; +import io.jsonwebtoken.lang.Assert; + +import java.security.Key; + +@SuppressWarnings("deprecation") // TODO: delete this class for 1.0 +public class LocatingKeyResolver implements SigningKeyResolver { + + private final Locator locator; + + public LocatingKeyResolver(Locator locator) { + this.locator = Assert.notNull(locator, "Locator cannot be null."); + } + + @Override + public Key resolveSigningKey(JwsHeader header, Claims claims) { + return this.locator.locate(header); + } + + @Override + public Key resolveSigningKey(JwsHeader header, byte[] content) { + return this.locator.locate(header); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/NamedParameterSpecValueFinder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/NamedParameterSpecValueFinder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/NamedParameterSpecValueFinder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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.impl.lang.Function; +import io.jsonwebtoken.impl.lang.Functions; +import io.jsonwebtoken.impl.lang.OptionalMethodInvoker; + +import java.security.Key; +import java.security.spec.AlgorithmParameterSpec; + +public class NamedParameterSpecValueFinder implements Function { + + private static final Function EDEC_KEY_GET_PARAMS = + new OptionalMethodInvoker<>("java.security.interfaces.EdECKey", "getParams"); // >= JDK 15 + private static final Function XEC_KEY_GET_PARAMS = + new OptionalMethodInvoker<>("java.security.interfaces.XECKey", "getParams"); // >= JDK 11 + private static final Function GET_NAME = + new OptionalMethodInvoker<>("java.security.spec.NamedParameterSpec", "getName"); // >= JDK 11 + + private static final Function COMPOSED = Functions.andThen(Functions.firstResult(EDEC_KEY_GET_PARAMS, XEC_KEY_GET_PARAMS), GET_NAME); + + @Override + public String apply(final Key key) { + return COMPOSED.apply(key); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/NoneSignatureAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/NoneSignatureAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/NoneSignatureAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 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.SecureDigestAlgorithm; +import io.jsonwebtoken.security.SecureRequest; +import io.jsonwebtoken.security.SecurityException; +import io.jsonwebtoken.security.SignatureException; +import io.jsonwebtoken.security.VerifySecureDigestRequest; + +import java.io.InputStream; +import java.security.Key; + +final class NoneSignatureAlgorithm implements SecureDigestAlgorithm { + + private static final String ID = "none"; + + static final SecureDigestAlgorithm INSTANCE = new NoneSignatureAlgorithm(); + + private NoneSignatureAlgorithm() { + } + + @Override + public String getId() { + return ID; + } + + @Override + public byte[] digest(SecureRequest request) throws SecurityException { + throw new SignatureException("The 'none' algorithm cannot be used to create signatures."); + } + + @Override + public boolean verify(VerifySecureDigestRequest request) throws SignatureException { + throw new SignatureException("The 'none' algorithm cannot be used to verify signatures."); + } + + @Override + public boolean equals(Object obj) { + return this == obj || + (obj instanceof SecureDigestAlgorithm && + ID.equalsIgnoreCase(((SecureDigestAlgorithm) obj).getId())); + } + + @Override + public int hashCode() { + return getId().hashCode(); + } + + @Override + public String toString() { + return ID; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/OctetJwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/OctetJwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/OctetJwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.security.Jwk; +import io.jsonwebtoken.security.UnsupportedKeyException; + +import java.security.Key; +import java.util.Set; + +public abstract class OctetJwkFactory> extends AbstractFamilyJwkFactory { + + OctetJwkFactory(Class keyType, Set> params) { + super(DefaultOctetPublicJwk.TYPE_VALUE, keyType, params); + } + + @Override + public boolean supports(Key key) { + return super.supports(key) && EdwardsCurve.isEdwards(key); + } + + protected static EdwardsCurve getCurve(final ParameterReadable reader) throws UnsupportedKeyException { + Parameter param = DefaultOctetPublicJwk.CRV; + String crvId = reader.get(param); + EdwardsCurve curve = EdwardsCurve.findById(crvId); + if (curve == null) { + String msg = "Unrecognized OKP JWK " + param + " value '" + crvId + "'"; + throw new UnsupportedKeyException(msg); + } + return curve; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/OctetPrivateJwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/OctetPrivateJwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/OctetPrivateJwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,90 @@ +/* + * 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.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.RequiredParameterReader; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.OctetPrivateJwk; +import io.jsonwebtoken.security.OctetPublicJwk; + +import java.security.PrivateKey; +import java.security.PublicKey; + +public class OctetPrivateJwkFactory extends OctetJwkFactory> { + + public OctetPrivateJwkFactory() { + super(PrivateKey.class, DefaultOctetPrivateJwk.PARAMS); + } + + @Override + protected boolean supportsKeyValues(JwkContext ctx) { + return super.supportsKeyValues(ctx) && ctx.containsKey(DefaultOctetPrivateJwk.D.getId()); + } + + @Override + protected OctetPrivateJwk createJwkFromKey(JwkContext ctx) { + PrivateKey key = Assert.notNull(ctx.getKey(), "PrivateKey cannot be null."); + EdwardsCurve crv = EdwardsCurve.forKey(key); + + PublicKey pub = ctx.getPublicKey(); + if (pub != null) { + if (!crv.equals(EdwardsCurve.forKey(pub))) { + String msg = "Specified Edwards Curve PublicKey does not match the specified PrivateKey's curve."; + throw new InvalidKeyException(msg); + } + } else { // not supplied - try to generate it: + pub = EdwardsCurve.derivePublic(key); + } + + // If a JWK fingerprint has been requested to be the JWK id, ensure we copy over the one computed for the + // public key per https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 + boolean copyId = !Strings.hasText(ctx.getId()) && ctx.getIdThumbprintAlgorithm() != null; + JwkContext pubCtx = OctetPublicJwkFactory.INSTANCE.newContext(ctx, pub); + OctetPublicJwk pubJwk = OctetPublicJwkFactory.INSTANCE.createJwk(pubCtx); + ctx.putAll(pubJwk); + if (copyId) { + ctx.setId(pubJwk.getId()); + } + + //now add the d value + byte[] d = crv.getKeyMaterial(key); + Assert.notEmpty(d, "Edwards PrivateKey 'd' value cannot be null or empty."); + //TODO: assert that the curve contains the specified key + put(ctx, DefaultOctetPrivateJwk.D, d); + + return new DefaultOctetPrivateJwk<>(ctx, pubJwk); + } + + @Override + protected OctetPrivateJwk createJwkFromValues(JwkContext ctx) { + ParameterReadable reader = new RequiredParameterReader(ctx); + EdwardsCurve curve = getCurve(reader); + //TODO: assert that the curve contains the specified key + + // public values are required, so assert them: + JwkContext pubCtx = new DefaultJwkContext<>(DefaultOctetPublicJwk.PARAMS, ctx); + OctetPublicJwk pubJwk = OctetPublicJwkFactory.INSTANCE.createJwkFromValues(pubCtx); + + byte[] d = reader.get(DefaultOctetPrivateJwk.D); + PrivateKey key = curve.toPrivateKey(d, ctx.getProvider()); + ctx.setKey(key); + + return new DefaultOctetPrivateJwk<>(ctx, pubJwk); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/OctetPublicJwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/OctetPublicJwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/OctetPublicJwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,55 @@ +/* + * 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.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.RequiredParameterReader; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.OctetPublicJwk; + +import java.security.PublicKey; + +public class OctetPublicJwkFactory extends OctetJwkFactory> { + + static final OctetPublicJwkFactory INSTANCE = new OctetPublicJwkFactory(); + + OctetPublicJwkFactory() { + super(PublicKey.class, DefaultOctetPublicJwk.PARAMS); + } + + @Override + protected OctetPublicJwk createJwkFromKey(JwkContext ctx) { + PublicKey key = Assert.notNull(ctx.getKey(), "PublicKey cannot be null."); + EdwardsCurve crv = EdwardsCurve.forKey(key); + byte[] x = crv.getKeyMaterial(key); + Assert.notEmpty(x, "Edwards PublicKey 'x' value cannot be null or empty."); + //TODO: assert that the curve contains the specified key + put(ctx, DefaultOctetPublicJwk.CRV, crv.getId()); + put(ctx, DefaultOctetPublicJwk.X, x); + return new DefaultOctetPublicJwk<>(ctx); + } + + @Override + protected OctetPublicJwk createJwkFromValues(JwkContext ctx) { + ParameterReadable reader = new RequiredParameterReader(ctx); + EdwardsCurve curve = getCurve(reader); + byte[] x = reader.get(DefaultOctetPublicJwk.X); + //TODO: assert that the curve contains the specified key + PublicKey key = curve.toPublicKey(x, ctx.getProvider()); + ctx.setKey(key); + return new DefaultOctetPublicJwk<>(ctx); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/PasswordSpec.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/PasswordSpec.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/PasswordSpec.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2021 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.lang.Assert; +import io.jsonwebtoken.lang.Objects; +import io.jsonwebtoken.security.Password; + +import java.security.spec.KeySpec; + +public class PasswordSpec implements Password, KeySpec { + + private static final String NONE_ALGORITHM = "NONE"; + private static final String DESTROYED_MSG = "Password has been destroyed. Password character array may not be obtained."; + private static final String ENCODED_DISABLED_MSG = + "getEncoded() is disabled for Password instances as they are intended to be used " + + "with key derivation algorithms only. Because passwords rarely have the length or entropy " + + "necessary for secure cryptographic operations such as authenticated hashing or encryption, " + + "they are disabled as direct inputs for these operations to help avoid accidental misuse; if " + + "you see this exception message, it is likely that the associated Password instance is " + + "being used incorrectly."; + + private volatile boolean destroyed; + private final char[] password; + + public PasswordSpec(char[] password) { + this.password = Assert.notEmpty(password, "Password character array cannot be null or empty."); + } + + private void assertActive() { + if (destroyed) { + throw new IllegalStateException(DESTROYED_MSG); + } + } + + @Override + public char[] toCharArray() { + assertActive(); + return this.password.clone(); + } + + @Override + public String getAlgorithm() { + return NONE_ALGORITHM; + } + + @Override + public String getFormat() { + return null; // encoding isn't supported, so we return null per the Key#getFormat() JavaDoc + } + + @Override + public byte[] getEncoded() { + throw new UnsupportedOperationException(ENCODED_DISABLED_MSG); + } + + public void destroy() { + this.destroyed = true; + java.util.Arrays.fill(password, '\u0000'); + } + + public boolean isDestroyed() { + return this.destroyed; + } + + @Override + public int hashCode() { + return Objects.nullSafeHashCode(this.password); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof PasswordSpec) { + PasswordSpec other = (PasswordSpec) obj; + return Objects.nullSafeEquals(this.password, other.password); + } + return false; + } + + @Override + public final String toString() { + return ""; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/Pbes2HsAkwAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/Pbes2HsAkwAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/Pbes2HsAkwAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2021 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.JweHeader; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.impl.DefaultJweHeader; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.CheckedFunction; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.RequiredParameterReader; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.security.DecryptionKeyRequest; +import io.jsonwebtoken.security.KeyAlgorithm; +import io.jsonwebtoken.security.KeyRequest; +import io.jsonwebtoken.security.KeyResult; +import io.jsonwebtoken.security.Password; +import io.jsonwebtoken.security.SecurityException; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; + +/** + * @since 0.12.0 + */ +public class Pbes2HsAkwAlgorithm extends CryptoAlgorithm implements KeyAlgorithm { + + // See https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 : + private static final int DEFAULT_SHA256_ITERATIONS = 310000; + private static final int DEFAULT_SHA384_ITERATIONS = 210000; + private static final int DEFAULT_SHA512_ITERATIONS = 120000; + + private static final int MIN_RECOMMENDED_ITERATIONS = 1000; // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.2 + private static final String MIN_ITERATIONS_MSG_PREFIX = + "[JWA RFC 7518, Section 4.8.1.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.2) " + + "recommends password-based-encryption iterations be greater than or equal to " + + MIN_RECOMMENDED_ITERATIONS + ". Provided: "; + private static final double MAX_ITERATIONS_FACTOR = 2.5; + + private final int HASH_BYTE_LENGTH; + private final int DERIVED_KEY_BIT_LENGTH; + private final byte[] SALT_PREFIX; + private final int DEFAULT_ITERATIONS; + private final int MAX_ITERATIONS; + private final KeyAlgorithm wrapAlg; + + private static byte[] toRfcSaltPrefix(byte[] bytes) { + // last byte must always be zero as it is a delimiter per + // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.1 + // We ensure this by creating a byte array that is one element larger than bytes.length since Java defaults all + // new byte array indices to 0x00, meaning the last one will be our zero delimiter: + byte[] output = new byte[bytes.length + 1]; + System.arraycopy(bytes, 0, output, 0, bytes.length); + return output; + } + + private static int hashBitLength(int keyBitLength) { + return keyBitLength * 2; + } + + private static String idFor(int hashBitLength, KeyAlgorithm wrapAlg) { + Assert.notNull(wrapAlg, "wrapAlg argument cannot be null."); + return "PBES2-HS" + hashBitLength + "+" + wrapAlg.getId(); + } + + public static int assertIterations(int iterations) { + if (iterations < MIN_RECOMMENDED_ITERATIONS) { + String msg = MIN_ITERATIONS_MSG_PREFIX + iterations; + throw new IllegalArgumentException(msg); + } + return iterations; + } + + public Pbes2HsAkwAlgorithm(int keyBitLength) { + this(hashBitLength(keyBitLength), new AesWrapKeyAlgorithm(keyBitLength)); + } + + protected Pbes2HsAkwAlgorithm(int hashBitLength, KeyAlgorithm wrapAlg) { + super(idFor(hashBitLength, wrapAlg), "PBKDF2WithHmacSHA" + hashBitLength); + this.wrapAlg = wrapAlg; // no need to assert non-null due to 'idFor' implementation above + + // There's some white box knowledge here: there is no need to assert the value of hashBitLength + // because that is done implicitly in the constructor when instantiating AesWrapKeyAlgorithm. See that class's + // implementation to see the assertion: + this.HASH_BYTE_LENGTH = hashBitLength / Byte.SIZE; + + // If the JwtBuilder caller doesn't specify an iteration count, fall back to OWASP best-practice recommendations + // per https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 + if (hashBitLength >= 512) { + DEFAULT_ITERATIONS = DEFAULT_SHA512_ITERATIONS; + } else if (hashBitLength >= 384) { + DEFAULT_ITERATIONS = DEFAULT_SHA384_ITERATIONS; + } else { + DEFAULT_ITERATIONS = DEFAULT_SHA256_ITERATIONS; + } + MAX_ITERATIONS = (int) (DEFAULT_ITERATIONS * MAX_ITERATIONS_FACTOR); // upper bound to help mitigate DoS attacks + + // https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8, 2nd paragraph, last sentence: + // "Their derived-key lengths respectively are 16, 24, and 32 octets." : + this.DERIVED_KEY_BIT_LENGTH = hashBitLength / 2; // results in 128, 192, or 256 + + this.SALT_PREFIX = toRfcSaltPrefix(getId().getBytes(StandardCharsets.UTF_8)); + } + + // protected visibility for testing + protected SecretKey deriveKey(SecretKeyFactory factory, final char[] password, final byte[] rfcSalt, int iterations) throws Exception { + PBEKeySpec spec = new PBEKeySpec(password, rfcSalt, iterations, DERIVED_KEY_BIT_LENGTH); + try { + SecretKey derived = factory.generateSecret(spec); + return new SecretKeySpec(derived.getEncoded(), AesAlgorithm.KEY_ALG_NAME); // needed to keep the Sun Provider happy + } finally { + spec.clearPassword(); + } + } + + private SecretKey deriveKey(final KeyRequest request, final char[] password, final byte[] salt, final int iterations) { + try { + Assert.notEmpty(password, "Key password character array cannot be null or empty."); + return jca(request).withSecretKeyFactory(new CheckedFunction() { + @Override + public SecretKey apply(SecretKeyFactory factory) throws Exception { + return deriveKey(factory, password, salt, iterations); + } + }); + } finally { + java.util.Arrays.fill(password, '\u0000'); + } + } + + protected byte[] generateInputSalt(KeyRequest request) { + byte[] inputSalt = new byte[this.HASH_BYTE_LENGTH]; + ensureSecureRandom(request).nextBytes(inputSalt); + return inputSalt; + } + + // protected visibility for testing + protected byte[] toRfcSalt(byte[] inputSalt) { + return Bytes.concat(this.SALT_PREFIX, inputSalt); + } + + @Override + public KeyResult getEncryptionKey(final KeyRequest request) throws SecurityException { + + Assert.notNull(request, "request cannot be null."); + final Password key = Assert.notNull(request.getPayload(), "Encryption Password cannot be null."); + final JweHeader header = Assert.notNull(request.getHeader(), "JweHeader cannot be null."); + Integer p2c = header.getPbes2Count(); + if (p2c == null) { // set a default, and ensure it's available in the header for later decryption: + p2c = DEFAULT_ITERATIONS; + header.put(DefaultJweHeader.P2C.getId(), p2c); + } + final int iterations = assertIterations(p2c); + byte[] inputSalt = generateInputSalt(request); + final byte[] rfcSalt = toRfcSalt(inputSalt); + char[] password = key.toCharArray(); // password will be safely cleaned/zeroed in deriveKey next: + final SecretKey derivedKek = deriveKey(request, password, rfcSalt, iterations); + + // now get a new CEK that is encrypted ('wrapped') with the PBE-derived key: + KeyRequest wrapReq = new DefaultKeyRequest<>(derivedKek, request.getProvider(), + request.getSecureRandom(), request.getHeader(), request.getEncryptionAlgorithm()); + KeyResult result = wrapAlg.getEncryptionKey(wrapReq); + + request.getHeader().put(DefaultJweHeader.P2S.getId(), inputSalt); //retain for recipients + + return result; + } + + @Override + public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { + + JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null."); + final Password key = Assert.notNull(request.getKey(), "Decryption Password cannot be null."); + ParameterReadable reader = new RequiredParameterReader(header); + final byte[] inputSalt = reader.get(DefaultJweHeader.P2S); + + Parameter param = DefaultJweHeader.P2C; + final int iterations = reader.get(param); + if (iterations > MAX_ITERATIONS) { + String msg = "JWE Header " + param + " value " + iterations + " exceeds " + getId() + " maximum " + + "allowed value " + MAX_ITERATIONS + ". The larger value is rejected to help mitigate " + + "potential Denial of Service attacks."; + throw new UnsupportedJwtException(msg); + } + + final byte[] rfcSalt = Bytes.concat(SALT_PREFIX, inputSalt); + final char[] password = key.toCharArray(); // password will be safely cleaned/zeroed in deriveKey next: + final SecretKey derivedKek = deriveKey(request, password, rfcSalt, iterations); + + DecryptionKeyRequest unwrapReq = + new DefaultDecryptionKeyRequest<>(request.getPayload(), request.getProvider(), + request.getSecureRandom(), header, request.getEncryptionAlgorithm(), derivedKek); + + return wrapAlg.getDecryptionKey(unwrapReq); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/PrivateECKey.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/PrivateECKey.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/PrivateECKey.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,62 @@ +/* + * 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.lang.Assert; +import io.jsonwebtoken.security.KeySupplier; + +import java.security.PrivateKey; +import java.security.interfaces.ECKey; +import java.security.spec.ECParameterSpec; + +/** + * @since 0.12.0 + */ +public class PrivateECKey implements PrivateKey, ECKey, KeySupplier { + + private final PrivateKey privateKey; + private final ECParameterSpec params; + + public PrivateECKey(PrivateKey privateKey, ECParameterSpec params) { + this.privateKey = Assert.notNull(privateKey, "PrivateKey cannot be null."); + this.params = Assert.notNull(params, "ECParameterSpec cannot be null."); + } + + @Override + public String getAlgorithm() { + return this.privateKey.getAlgorithm(); + } + + @Override + public String getFormat() { + return this.privateKey.getFormat(); + } + + @Override + public byte[] getEncoded() { + return this.privateKey.getEncoded(); + } + + @Override + public ECParameterSpec getParams() { + return this.params; + } + + @Override + public PrivateKey getKey() { + return this.privateKey; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProvidedKeyBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProvidedKeyBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProvidedKeyBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,41 @@ +/* + * 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.lang.Assert; +import io.jsonwebtoken.security.KeyBuilder; + +import java.security.Key; + +abstract class ProvidedKeyBuilder> extends AbstractSecurityBuilder + implements KeyBuilder { + + protected final K key; + + ProvidedKeyBuilder(K key) { + this.key = Assert.notNull(key, "Key cannot be null."); + } + + @Override + public final K build() { + if (this.key instanceof ProviderKey) { // already wrapped, don't wrap again: + return this.key; + } + return doBuild(); + } + + abstract K doBuild(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProvidedPrivateKeyBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProvidedPrivateKeyBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProvidedPrivateKeyBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,57 @@ +/* + * 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.lang.Strings; +import io.jsonwebtoken.security.PrivateKeyBuilder; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.ECKey; + +public class ProvidedPrivateKeyBuilder extends ProvidedKeyBuilder + implements PrivateKeyBuilder { + + private PublicKey publicKey; + + ProvidedPrivateKeyBuilder(PrivateKey key) { + super(key); + } + + @Override + public PrivateKeyBuilder publicKey(PublicKey publicKey) { + this.publicKey = publicKey; + return this; + } + + @Override + public PrivateKey doBuild() { + + PrivateKey key = this.key; + + // We only need to wrap as an ECKey if: + // 1. The private key is not already an ECKey. If it is, we can validate normally + // 2. The private key indicates via its algorithm that it is intended to be used as an EC key. + // 3. The public key is an ECKey - this must be true to represent EC params for the private key + String privAlg = Strings.clean(this.key.getAlgorithm()); + if (!(key instanceof ECKey) && ("EC".equalsIgnoreCase(privAlg) || "ECDSA".equalsIgnoreCase(privAlg)) && + this.publicKey instanceof ECKey) { + key = new PrivateECKey(key, ((ECKey) this.publicKey).getParams()); + } + + return this.provider != null ? new ProviderPrivateKey(this.provider, key) : key; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProvidedSecretKeyBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProvidedSecretKeyBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProvidedSecretKeyBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,36 @@ +/* + * 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.Password; +import io.jsonwebtoken.security.SecretKeyBuilder; + +import javax.crypto.SecretKey; + +class ProvidedSecretKeyBuilder extends ProvidedKeyBuilder implements SecretKeyBuilder { + + ProvidedSecretKeyBuilder(SecretKey key) { + super(key); + } + + @Override + public SecretKey doBuild() { + if (this.key instanceof Password) { + return this.key; // provider never needed for Password instances. + } + return provider != null ? new ProviderSecretKey(this.provider, this.key) : this.key; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProviderKey.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProviderKey.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProviderKey.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,76 @@ +/* + * 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.lang.Assert; +import io.jsonwebtoken.security.KeySupplier; + +import java.security.Key; +import java.security.Provider; + +public class ProviderKey implements Key, KeySupplier { + + private final T key; + + private final Provider provider; + + public static Provider getProvider(Key key, Provider backup) { + if (key instanceof ProviderKey) { + ProviderKey pkey = (ProviderKey) key; + return Assert.stateNotNull(pkey.getProvider(), "ProviderKey provider can never be null."); + } + return backup; + } + + @SuppressWarnings("unchecked") + public static K getKey(K key) { + return key instanceof ProviderKey ? ((ProviderKey) key).getKey() : key; + } + + ProviderKey(Provider provider, T key) { + this.provider = Assert.notNull(provider, "Provider cannot be null."); + this.key = Assert.notNull(key, "Key argument cannot be null."); + if (key instanceof ProviderKey) { + String msg = "Nesting not permitted."; + throw new IllegalArgumentException(msg); + } + } + + @Override + public T getKey() { + return this.key; + } + + @Override + public String getAlgorithm() { + return this.key.getAlgorithm(); + } + + @Override + public String getFormat() { + return this.key.getFormat(); + } + + @Override + public byte[] getEncoded() { + return this.key.getEncoded(); + } + + public final Provider getProvider() { + return this.provider; + } + +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProviderPrivateKey.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProviderPrivateKey.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProviderPrivateKey.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,26 @@ +/* + * 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 java.security.PrivateKey; +import java.security.Provider; + +public final class ProviderPrivateKey extends ProviderKey implements PrivateKey { + + ProviderPrivateKey(Provider provider, PrivateKey key) { + super(provider, key); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProviderSecretKey.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProviderSecretKey.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/ProviderSecretKey.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,26 @@ +/* + * 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 javax.crypto.SecretKey; +import java.security.Provider; + +public final class ProviderSecretKey extends ProviderKey implements SecretKey { + + ProviderSecretKey(Provider provider, SecretKey key) { + super(provider, key); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/Providers.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/Providers.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/Providers.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 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.lang.Classes; + +import java.security.Provider; +import java.security.Security; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @since 0.12.0 + */ +final class Providers { + + private static final String BC_PROVIDER_CLASS_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider"; + static final boolean BOUNCY_CASTLE_AVAILABLE = Classes.isAvailable(BC_PROVIDER_CLASS_NAME); + private static final AtomicReference BC_PROVIDER = new AtomicReference<>(); + + private Providers() { + } + + /** + * Returns the BouncyCastle provider if and only if BouncyCastle is available, or {@code null} otherwise. + * + *

    If the JVM runtime already has BouncyCastle registered + * (e.g. {@code Security.addProvider(bcProvider)}, that Provider instance will be found and returned. + * If an existing BC provider is not found, a new BC instance will be created, cached for future reference, + * and returned.

    + * + *

    If a new BC provider is created and returned, it is not registered in the JVM via + * {@code Security.addProvider} to ensure JJWT doesn't interfere with the application security provider + * configuration and/or expectations.

    + * + * @return any available BouncyCastle Provider, or {@code null} if BouncyCastle is not available. + */ + public static Provider findBouncyCastle() { + if (!BOUNCY_CASTLE_AVAILABLE) { + return null; + } + Provider provider = BC_PROVIDER.get(); + if (provider == null) { + + Class clazz = Classes.forName(BC_PROVIDER_CLASS_NAME); + + //check to see if the user has already registered the BC provider: + Provider[] providers = Security.getProviders(); + for (Provider aProvider : providers) { + if (clazz.isInstance(aProvider)) { + BC_PROVIDER.set(aProvider); + return aProvider; + } + } + + //user hasn't created the BC provider, so we'll create one just for JJWT's needs: + provider = Classes.newInstance(clazz); + BC_PROVIDER.set(provider); + } + return provider; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RSAOtherPrimeInfoConverter.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RSAOtherPrimeInfoConverter.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RSAOtherPrimeInfoConverter.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2022 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.impl.lang.Converter; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.Parameters; +import io.jsonwebtoken.impl.lang.RequiredParameterReader; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.MalformedKeyException; + +import java.math.BigInteger; +import java.security.spec.RSAOtherPrimeInfo; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +class RSAOtherPrimeInfoConverter implements Converter { + + static final RSAOtherPrimeInfoConverter INSTANCE = new RSAOtherPrimeInfoConverter(); + + static final Parameter PRIME_FACTOR = Parameters.secretBigInt("r", "Prime Factor"); + static final Parameter FACTOR_CRT_EXPONENT = Parameters.secretBigInt("d", "Factor CRT Exponent"); + static final Parameter FACTOR_CRT_COEFFICIENT = Parameters.secretBigInt("t", "Factor CRT Coefficient"); + static final Set> PARAMS = Collections.>setOf(PRIME_FACTOR, FACTOR_CRT_EXPONENT, FACTOR_CRT_COEFFICIENT); + + @Override + public Object applyTo(RSAOtherPrimeInfo info) { + Map m = new LinkedHashMap<>(3); + m.put(PRIME_FACTOR.getId(), PRIME_FACTOR.applyTo(info.getPrime())); + m.put(FACTOR_CRT_EXPONENT.getId(), FACTOR_CRT_EXPONENT.applyTo(info.getExponent())); + m.put(FACTOR_CRT_COEFFICIENT.getId(), FACTOR_CRT_COEFFICIENT.applyTo(info.getCrtCoefficient())); + return m; + } + + @Override + public RSAOtherPrimeInfo applyFrom(Object o) { + if (o == null) { + throw new MalformedKeyException("RSA JWK 'oth' (Other Prime Info) element cannot be null."); + } + if (!(o instanceof Map)) { + String msg = "RSA JWK 'oth' (Other Prime Info) must contain map elements of name/value pairs. " + + "Element type found: " + o.getClass().getName(); + throw new MalformedKeyException(msg); + } + Map m = (Map) o; + if (Collections.isEmpty(m)) { + throw new MalformedKeyException("RSA JWK 'oth' (Other Prime Info) element map cannot be empty."); + } + + // Need a Context instance to satisfy the API contract of the reader.get* methods below. + JwkContext ctx = new DefaultJwkContext<>(PARAMS); + try { + for (Map.Entry entry : m.entrySet()) { + String name = String.valueOf(entry.getKey()); + ctx.put(name, entry.getValue()); + } + } catch (Exception e) { + throw new MalformedKeyException(e.getMessage(), e); + } + + ParameterReadable reader = new RequiredParameterReader(ctx); + BigInteger prime = reader.get(PRIME_FACTOR); + BigInteger primeExponent = reader.get(FACTOR_CRT_EXPONENT); + BigInteger crtCoefficient = reader.get(FACTOR_CRT_COEFFICIENT); + + return new RSAOtherPrimeInfo(prime, primeExponent, crtCoefficient); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RandomSecretKeyBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RandomSecretKeyBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RandomSecretKeyBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 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 javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +/** + * @since 0.12.0 + */ +public class RandomSecretKeyBuilder extends DefaultSecretKeyBuilder { + + public RandomSecretKeyBuilder(String jcaName, int bitLength) { + super(jcaName, bitLength); + } + + @Override + public SecretKey build() { + byte[] bytes = new byte[this.BIT_LENGTH / Byte.SIZE]; + this.random.nextBytes(bytes); + return new SecretKeySpec(bytes, this.JCA_NAME); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/Randoms.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/Randoms.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/Randoms.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 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 java.security.SecureRandom; + +/** + * @since 0.12.0 + */ +public final class Randoms { + + private static final SecureRandom DEFAULT_SECURE_RANDOM; + + static { + DEFAULT_SECURE_RANDOM = new SecureRandom(); + DEFAULT_SECURE_RANDOM.nextBytes(new byte[64]); + } + + private Randoms() { + } + + /** + * Returns JJWT's default SecureRandom number generator - a static singleton which may be cached if desired. + * The RNG is initialized using the JVM default as follows: + * + *
    
    +     * static {
    +     *     DEFAULT_SECURE_RANDOM = new SecureRandom();
    +     *     DEFAULT_SECURE_RANDOM.nextBytes(new byte[64]);
    +     * }
    +     * 
    + * + *

    nextBytes is called to force the RNG to initialize itself if not already initialized. The + * byte array is not used and discarded immediately for garbage collection.

    + * + * @return JJWT's default SecureRandom number generator. + */ + public static SecureRandom secureRandom() { + return DEFAULT_SECURE_RANDOM; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RsaPrivateJwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RsaPrivateJwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RsaPrivateJwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2021 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.impl.lang.CheckedFunction; +import io.jsonwebtoken.impl.lang.Parameter; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.RequiredParameterReader; +import io.jsonwebtoken.lang.Arrays; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.RsaPrivateJwk; +import io.jsonwebtoken.security.RsaPublicJwk; +import io.jsonwebtoken.security.UnsupportedKeyException; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.interfaces.RSAMultiPrimePrivateCrtKey; +import java.security.interfaces.RSAPrivateCrtKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.KeySpec; +import java.security.spec.RSAMultiPrimePrivateCrtKeySpec; +import java.security.spec.RSAOtherPrimeInfo; +import java.security.spec.RSAPrivateCrtKeySpec; +import java.security.spec.RSAPrivateKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.util.List; +import java.util.Set; + +class RsaPrivateJwkFactory extends AbstractFamilyJwkFactory { + + //All RSA Private params _except_ for PRIVATE_EXPONENT. That is always required: + private static final Set> OPTIONAL_PRIVATE_PARAMS = Collections.setOf( + DefaultRsaPrivateJwk.FIRST_PRIME, DefaultRsaPrivateJwk.SECOND_PRIME, + DefaultRsaPrivateJwk.FIRST_CRT_EXPONENT, DefaultRsaPrivateJwk.SECOND_CRT_EXPONENT, + DefaultRsaPrivateJwk.FIRST_CRT_COEFFICIENT + ); + + private static final String PUBKEY_ERR_MSG = "JwkContext publicKey must be an " + RSAPublicKey.class.getName() + " instance."; + private static final String PUB_EXPONENT_EX_MSG = + "Unable to derive RSAPublicKey from RSAPrivateKey [%s]. Supported keys implement the " + + RSAPrivateCrtKey.class.getName() + " or " + RSAMultiPrimePrivateCrtKey.class.getName() + + " interfaces. If the specified RSAPrivateKey cannot be one of these two, you must explicitly " + + "provide an RSAPublicKey in addition to the RSAPrivateKey, as the " + + "[JWA RFC, Section 6.3.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-6.3.2) " + + "requires public values to be present in private RSA JWKs."; + + RsaPrivateJwkFactory() { + super(DefaultRsaPublicJwk.TYPE_VALUE, RSAPrivateKey.class, DefaultRsaPrivateJwk.PARAMS); + } + + @Override + protected boolean supportsKeyValues(JwkContext ctx) { + return super.supportsKeyValues(ctx) && ctx.containsKey(DefaultRsaPrivateJwk.PRIVATE_EXPONENT.getId()); + } + + private static BigInteger getPublicExponent(RSAPrivateKey key) { + if (key instanceof RSAPrivateCrtKey) { + return ((RSAPrivateCrtKey) key).getPublicExponent(); + } else if (key instanceof RSAMultiPrimePrivateCrtKey) { + return ((RSAMultiPrimePrivateCrtKey) key).getPublicExponent(); + } + + String msg = String.format(PUB_EXPONENT_EX_MSG, KeysBridge.toString(key)); + throw new UnsupportedKeyException(msg); + } + + private RSAPublicKey derivePublic(final JwkContext ctx) { + RSAPrivateKey key = ctx.getKey(); + BigInteger modulus = key.getModulus(); + BigInteger publicExponent = getPublicExponent(key); + final RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent); + return generateKey(ctx, RSAPublicKey.class, new CheckedFunction() { + @Override + public RSAPublicKey apply(KeyFactory kf) { + try { + return (RSAPublicKey) kf.generatePublic(spec); + } catch (Exception e) { + String msg = "Unable to derive RSAPublicKey from RSAPrivateKey " + ctx + ". Cause: " + e.getMessage(); + throw new InvalidKeyException(msg); + } + } + }); + } + + @Override + protected RsaPrivateJwk createJwkFromKey(JwkContext ctx) { + + RSAPrivateKey key = ctx.getKey(); + RSAPublicKey rsaPublicKey; + + PublicKey publicKey = ctx.getPublicKey(); + if (publicKey != null) { + rsaPublicKey = Assert.isInstanceOf(RSAPublicKey.class, publicKey, PUBKEY_ERR_MSG); + } else { + rsaPublicKey = derivePublic(ctx); + } + + // The [JWA Spec](https://www.rfc-editor.org/rfc/rfc7518.html#section-6.3.1) + // requires public values to be present in private JWKs, so add them: + + // If a JWK fingerprint has been requested to be the JWK id, ensure we copy over the one computed for the + // public key per https://www.rfc-editor.org/rfc/rfc7638#section-3.2.1 + boolean copyId = !Strings.hasText(ctx.getId()) && ctx.getIdThumbprintAlgorithm() != null; + + JwkContext pubCtx = RsaPublicJwkFactory.INSTANCE.newContext(ctx, rsaPublicKey); + RsaPublicJwk pubJwk = RsaPublicJwkFactory.INSTANCE.createJwk(pubCtx); + ctx.putAll(pubJwk); // add public values to private key context + if (copyId) { + ctx.setId(pubJwk.getId()); + } + + put(ctx, DefaultRsaPrivateJwk.PRIVATE_EXPONENT, key.getPrivateExponent()); + + if (key instanceof RSAPrivateCrtKey) { + RSAPrivateCrtKey ckey = (RSAPrivateCrtKey) key; + //noinspection DuplicatedCode + put(ctx, DefaultRsaPrivateJwk.FIRST_PRIME, ckey.getPrimeP()); + put(ctx, DefaultRsaPrivateJwk.SECOND_PRIME, ckey.getPrimeQ()); + put(ctx, DefaultRsaPrivateJwk.FIRST_CRT_EXPONENT, ckey.getPrimeExponentP()); + put(ctx, DefaultRsaPrivateJwk.SECOND_CRT_EXPONENT, ckey.getPrimeExponentQ()); + put(ctx, DefaultRsaPrivateJwk.FIRST_CRT_COEFFICIENT, ckey.getCrtCoefficient()); + } else if (key instanceof RSAMultiPrimePrivateCrtKey) { + RSAMultiPrimePrivateCrtKey ckey = (RSAMultiPrimePrivateCrtKey) key; + //noinspection DuplicatedCode + put(ctx, DefaultRsaPrivateJwk.FIRST_PRIME, ckey.getPrimeP()); + put(ctx, DefaultRsaPrivateJwk.SECOND_PRIME, ckey.getPrimeQ()); + put(ctx, DefaultRsaPrivateJwk.FIRST_CRT_EXPONENT, ckey.getPrimeExponentP()); + put(ctx, DefaultRsaPrivateJwk.SECOND_CRT_EXPONENT, ckey.getPrimeExponentQ()); + put(ctx, DefaultRsaPrivateJwk.FIRST_CRT_COEFFICIENT, ckey.getCrtCoefficient()); + List infos = Arrays.asList(ckey.getOtherPrimeInfo()); + if (!Collections.isEmpty(infos)) { + put(ctx, DefaultRsaPrivateJwk.OTHER_PRIMES_INFO, infos); + } + } + + return new DefaultRsaPrivateJwk(ctx, pubJwk); + } + + @Override + protected RsaPrivateJwk createJwkFromValues(JwkContext ctx) { + + final ParameterReadable reader = new RequiredParameterReader(ctx); + + final BigInteger privateExponent = reader.get(DefaultRsaPrivateJwk.PRIVATE_EXPONENT); + + //The [JWA Spec, Section 6.3.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-6.3.2) requires + //RSA Private Keys to also encode the public key values, so we assert that we can acquire it successfully: + JwkContext pubCtx = new DefaultJwkContext<>(DefaultRsaPublicJwk.PARAMS, ctx); + RsaPublicJwk pubJwk = RsaPublicJwkFactory.INSTANCE.createJwkFromValues(pubCtx); + RSAPublicKey pubKey = pubJwk.toKey(); + final BigInteger modulus = pubKey.getModulus(); + final BigInteger publicExponent = pubKey.getPublicExponent(); + + // JWA Section 6.3.2 also indicates that if any of the optional private names are present, then *all* of those + // optional values must be present (except 'oth', which is handled separately next). Quote: + // + // If the producer includes any of the other private key parameters, then all of the others MUST + // be present, with the exception of "oth", which MUST only be present when more than two prime + // factors were used + // + boolean containsOptional = false; + for (Parameter param : OPTIONAL_PRIVATE_PARAMS) { + if (ctx.containsKey(param.getId())) { + containsOptional = true; + break; + } + } + + KeySpec spec; + + if (containsOptional) { //if any one optional parameter exists, they are all required per JWA Section 6.3.2: + BigInteger firstPrime = reader.get(DefaultRsaPrivateJwk.FIRST_PRIME); + BigInteger secondPrime = reader.get(DefaultRsaPrivateJwk.SECOND_PRIME); + BigInteger firstCrtExponent = reader.get(DefaultRsaPrivateJwk.FIRST_CRT_EXPONENT); + BigInteger secondCrtExponent = reader.get(DefaultRsaPrivateJwk.SECOND_CRT_EXPONENT); + BigInteger firstCrtCoefficient = reader.get(DefaultRsaPrivateJwk.FIRST_CRT_COEFFICIENT); + + // Other Primes Info is actually optional even if the above ones are required: + if (ctx.containsKey(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO.getId())) { + List otherPrimes = reader.get(DefaultRsaPrivateJwk.OTHER_PRIMES_INFO); + RSAOtherPrimeInfo[] arr = new RSAOtherPrimeInfo[Collections.size(otherPrimes)]; + arr = otherPrimes.toArray(arr); + spec = new RSAMultiPrimePrivateCrtKeySpec(modulus, publicExponent, privateExponent, firstPrime, + secondPrime, firstCrtExponent, secondCrtExponent, firstCrtCoefficient, arr); + } else { + spec = new RSAPrivateCrtKeySpec(modulus, publicExponent, privateExponent, firstPrime, secondPrime, + firstCrtExponent, secondCrtExponent, firstCrtCoefficient); + } + } else { + spec = new RSAPrivateKeySpec(modulus, privateExponent); + } + + RSAPrivateKey key = generateFromSpec(ctx, spec); + ctx.setKey(key); + + return new DefaultRsaPrivateJwk(ctx, pubJwk); + } + + protected RSAPrivateKey generateFromSpec(JwkContext ctx, final KeySpec keySpec) { + return generateKey(ctx, new CheckedFunction() { + @Override + public RSAPrivateKey apply(KeyFactory kf) throws Exception { + return (RSAPrivateKey) kf.generatePrivate(keySpec); + } + }); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RsaPublicJwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RsaPublicJwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RsaPublicJwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 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.impl.lang.CheckedFunction; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.RequiredParameterReader; +import io.jsonwebtoken.security.RsaPublicJwk; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.RSAPublicKeySpec; + +class RsaPublicJwkFactory extends AbstractFamilyJwkFactory { + + static final RsaPublicJwkFactory INSTANCE = new RsaPublicJwkFactory(); + + RsaPublicJwkFactory() { + super(DefaultRsaPublicJwk.TYPE_VALUE, RSAPublicKey.class, DefaultRsaPublicJwk.PARAMS); + } + + @Override + protected RsaPublicJwk createJwkFromKey(JwkContext ctx) { + RSAPublicKey key = ctx.getKey(); + ctx.put(DefaultRsaPublicJwk.MODULUS.getId(), DefaultRsaPublicJwk.MODULUS.applyTo(key.getModulus())); + ctx.put(DefaultRsaPublicJwk.PUBLIC_EXPONENT.getId(), DefaultRsaPublicJwk.PUBLIC_EXPONENT.applyTo(key.getPublicExponent())); + return new DefaultRsaPublicJwk(ctx); + } + + @Override + protected RsaPublicJwk createJwkFromValues(JwkContext ctx) { + ParameterReadable reader = new RequiredParameterReader(ctx); + BigInteger modulus = reader.get(DefaultRsaPublicJwk.MODULUS); + BigInteger publicExponent = reader.get(DefaultRsaPublicJwk.PUBLIC_EXPONENT); + final RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent); + + RSAPublicKey key = generateKey(ctx, new CheckedFunction() { + @Override + public RSAPublicKey apply(KeyFactory keyFactory) throws Exception { + return (RSAPublicKey) keyFactory.generatePublic(spec); + } + }); + + ctx.setKey(key); + + return new DefaultRsaPublicJwk(ctx); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RsaSignatureAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RsaSignatureAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/RsaSignatureAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2019 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.impl.lang.CheckedFunction; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.KeyPairBuilder; +import io.jsonwebtoken.security.SecureRequest; +import io.jsonwebtoken.security.SignatureAlgorithm; +import io.jsonwebtoken.security.VerifySecureDigestRequest; +import io.jsonwebtoken.security.WeakKeyException; + +import java.io.InputStream; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.PSSParameterSpec; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * @since 0.12.0 + */ +final class RsaSignatureAlgorithm extends AbstractSignatureAlgorithm { + + // Defined in https://www.rfc-editor.org/rfc/rfc8017#appendix-A.1: + //private static final String RSA_ENC_OID = "1.2.840.113549.1.1.1"; // RFC 8017's "rsaEncryption" + + // Defined in https://www.rfc-editor.org/rfc/rfc8017#appendix-A.2.3: + static final String PSS_JCA_NAME = "RSASSA-PSS"; + static final String PSS_OID = "1.2.840.113549.1.1.10"; // RFC 8017's "id-RSASSA-PSS" + + // Defined in https://www.rfc-editor.org/rfc/rfc8017#appendix-A.2.4: + private static final String RS256_OID = "1.2.840.113549.1.1.11"; // RFC 8017's "sha256WithRSAEncryption" + private static final String RS384_OID = "1.2.840.113549.1.1.12"; // RFC 8017's "sha384WithRSAEncryption" + private static final String RS512_OID = "1.2.840.113549.1.1.13"; // RFC 8017's "sha512WithRSAEncryption" + + private static final Set PSS_ALG_NAMES = Collections.setOf(PSS_JCA_NAME, PSS_OID); + + private static final Set KEY_ALG_NAMES = + Collections.setOf("RSA", PSS_JCA_NAME, PSS_OID, RS256_OID, RS384_OID, RS512_OID); + + private static final int MIN_KEY_BIT_LENGTH = 2048; + + private static AlgorithmParameterSpec pssParamSpec(int digestBitLength) { + MGF1ParameterSpec ps = new MGF1ParameterSpec("SHA-" + digestBitLength); + int saltByteLength = digestBitLength / Byte.SIZE; + return new PSSParameterSpec(ps.getDigestAlgorithm(), "MGF1", ps, saltByteLength, 1); + } + + private static SignatureAlgorithm rsaSsaPss(int digestBitLength) { + return new RsaSignatureAlgorithm(digestBitLength, pssParamSpec(digestBitLength)); + } + + static final SignatureAlgorithm RS256 = new RsaSignatureAlgorithm(256); + static final SignatureAlgorithm RS384 = new RsaSignatureAlgorithm(384); + static final SignatureAlgorithm RS512 = new RsaSignatureAlgorithm(512); + static final SignatureAlgorithm PS256 = rsaSsaPss(256); + static final SignatureAlgorithm PS384 = rsaSsaPss(384); + static final SignatureAlgorithm PS512 = rsaSsaPss(512); + + private static final Map PKCSv15_ALGS; + + static { + PKCSv15_ALGS = new LinkedHashMap<>(); + PKCSv15_ALGS.put(RS256_OID, RS256); + PKCSv15_ALGS.put(RS384_OID, RS384); + PKCSv15_ALGS.put(RS512_OID, RS512); + } + + private final int preferredKeyBitLength; + + private final AlgorithmParameterSpec algorithmParameterSpec; + + private RsaSignatureAlgorithm(String name, String jcaName, int digestBitLength, AlgorithmParameterSpec paramSpec) { + super(name, jcaName); + this.preferredKeyBitLength = digestBitLength * Byte.SIZE; // RSA invariant + // invariant since this is a protected constructor: + Assert.state(this.preferredKeyBitLength >= MIN_KEY_BIT_LENGTH); + this.algorithmParameterSpec = paramSpec; + } + + private RsaSignatureAlgorithm(int digestBitLength) { + this("RS" + digestBitLength, "SHA" + digestBitLength + "withRSA", digestBitLength, null); + } + + // RSASSA-PSS constructor + private RsaSignatureAlgorithm(int digestBitLength, AlgorithmParameterSpec paramSpec) { + this("PS" + digestBitLength, PSS_JCA_NAME, digestBitLength, paramSpec); + } + + static SignatureAlgorithm findByKey(Key key) { + + String algName = KeysBridge.findAlgorithm(key); + if (!Strings.hasText(algName)) { + return null; + } + algName = algName.toUpperCase(Locale.ENGLISH); // for checking against name Sets + + // some PKCS11 keystores and HSMs won't expose the RSAKey interface, so we can't assume it: + final int bitLength = KeysBridge.findBitLength(key); // returns -1 if we're unable to find out + + if (PSS_ALG_NAMES.contains(algName)) { // generic RSASSA-PSS names, check for key lengths: + // even though we found an RSASSA-PSS key, we need to confirm that the key length is + // sufficient if the encoded key bytes are available: + if (bitLength >= 4096) { + return PS512; + } else if (bitLength >= 3072) { + return PS384; + } else if (bitLength >= MIN_KEY_BIT_LENGTH) { + return PS256; + } + } + + // unable to resolve/recommend an RSASSA-PSS alg, so try PKCS v 1.5 algs by OID: + SignatureAlgorithm alg = PKCSv15_ALGS.get(algName); + if (alg != null) { + return alg; + } + + if ("RSA".equals(algName)) { + if (bitLength >= 4096) { + return RS512; + } else if (bitLength >= 3072) { + return RS384; + } else if (bitLength >= MIN_KEY_BIT_LENGTH) { + return RS256; + } + } + + return null; + } + + static boolean isPss(Key key) { + String alg = KeysBridge.findAlgorithm(key); + return PSS_ALG_NAMES.contains(alg); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + static boolean isRsaAlgorithmName(Key key) { + String alg = KeysBridge.findAlgorithm(key); + return KEY_ALG_NAMES.contains(alg); + } + + @Override + public KeyPairBuilder keyPair() { + final String jcaName = this.algorithmParameterSpec != null ? PSS_JCA_NAME : "RSA"; + + //TODO: JDK 8 or later, for RSASSA-PSS, use the following instead of what is below: + // + // AlgorithmParameterSpec keyGenSpec = new RSAKeyGenParameterSpec(this.preferredKeyBitLength, + // RSAKeyGenParameterSpec.F4, this.algorithmParameterSpec); + // return new DefaultKeyPairBuilder(jcaName, keyGenSpec).provider(getProvider()).random(Randoms.secureRandom()); + // + + return new DefaultKeyPairBuilder(jcaName, this.preferredKeyBitLength).random(Randoms.secureRandom()); + } + + @Override + protected void validateKey(Key key, boolean signing) { + super.validateKey(key, signing); + if (!isRsaAlgorithmName(key)) { + throw new InvalidKeyException("Unrecognized RSA or RSASSA-PSS key algorithm name."); + } + int size = KeysBridge.findBitLength(key); + if (size < 0) return; // https://github.com/jwtk/jjwt/issues/68 + if (size < MIN_KEY_BIT_LENGTH) { + String id = getId(); + String section = id.startsWith("PS") ? "3.5" : "3.3"; + String msg = "The RSA " + keyType(signing) + " key size (aka modulus bit length) is " + size + " bits " + + "which is not secure enough for the " + id + " algorithm. The JWT JWA Specification " + + "(RFC 7518, Section " + section + ") states that RSA keys MUST have a size >= " + + MIN_KEY_BIT_LENGTH + " bits. Consider using the Jwts.SIG." + id + + ".keyPair() builder to create a KeyPair guaranteed to be secure enough for " + id + ". See " + + "https://tools.ietf.org/html/rfc7518#section-" + section + " for more information."; + throw new WeakKeyException(msg); + } + } + + @Override + protected byte[] doDigest(final SecureRequest request) { + return jca(request).withSignature(new CheckedFunction() { + @Override + public byte[] apply(Signature sig) throws Exception { + if (algorithmParameterSpec != null) { + sig.setParameter(algorithmParameterSpec); + } + sig.initSign(request.getKey()); + return sign(sig, request.getPayload()); + } + }); + } + + @Override + protected boolean doVerify(final VerifySecureDigestRequest request) { + return jca(request).withSignature(new CheckedFunction() { + @Override + public Boolean apply(Signature sig) throws Exception { + if (algorithmParameterSpec != null) { + sig.setParameter(algorithmParameterSpec); + } + sig.initVerify(request.getKey()); + return verify(sig, request.getPayload(), request.getDigest()); + } + }); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/SecretJwkFactory.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/SecretJwkFactory.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/SecretJwkFactory.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2021 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.Identifiable; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.impl.lang.Bytes; +import io.jsonwebtoken.impl.lang.ParameterReadable; +import io.jsonwebtoken.impl.lang.RequiredParameterReader; +import io.jsonwebtoken.io.Encoders; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Strings; +import io.jsonwebtoken.security.AeadAlgorithm; +import io.jsonwebtoken.security.InvalidKeyException; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.MacAlgorithm; +import io.jsonwebtoken.security.MalformedKeyException; +import io.jsonwebtoken.security.SecretJwk; +import io.jsonwebtoken.security.SecretKeyAlgorithm; +import io.jsonwebtoken.security.WeakKeyException; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +/** + * @since 0.12.0 + */ +class SecretJwkFactory extends AbstractFamilyJwkFactory { + + SecretJwkFactory() { + super(DefaultSecretJwk.TYPE_VALUE, SecretKey.class, DefaultSecretJwk.PARAMS); + } + + @Override + protected SecretJwk createJwkFromKey(JwkContext ctx) { + SecretKey key = Assert.notNull(ctx.getKey(), "JwkContext key cannot be null."); + String k; + byte[] encoded = null; + try { + encoded = KeysBridge.getEncoded(key); + k = Encoders.BASE64URL.encode(encoded); + Assert.hasText(k, "k value cannot be null or empty."); + } catch (Throwable t) { + String msg = "Unable to encode SecretKey to JWK: " + t.getMessage(); + throw new InvalidKeyException(msg, t); + } finally { + Bytes.clear(encoded); + } + + MacAlgorithm mac = DefaultMacAlgorithm.findByKey(key); + if (mac != null) { + ctx.put(AbstractJwk.ALG.getId(), mac.getId()); + } + + ctx.put(DefaultSecretJwk.K.getId(), k); + + return createJwkFromValues(ctx); + } + + private static void assertKeyBitLength(byte[] bytes, MacAlgorithm alg) { + long bitLen = Bytes.bitLength(bytes); + long requiredBitLen = alg.getKeyBitLength(); + if (bitLen < requiredBitLen) { + // Implementors note: Don't print out any information about the `bytes` value itself - size, + // content, etc., as it is considered secret material: + String msg = "Secret JWK " + AbstractJwk.ALG + " value is '" + alg.getId() + + "', but the " + DefaultSecretJwk.K + " length is smaller than the " + alg.getId() + + " minimum length of " + Bytes.bitsMsg(requiredBitLen) + + " required by " + + "[JWA RFC 7518, Section 3.2](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.2), " + + "2nd paragraph: 'A key of the same size as the hash output or larger MUST be used with this " + + "algorithm.'"; + throw new WeakKeyException(msg); + } + } + + private static void assertSymmetric(Identifiable alg) { + if (alg instanceof MacAlgorithm || alg instanceof SecretKeyAlgorithm || alg instanceof AeadAlgorithm) + return; // valid + String msg = "Invalid Secret JWK " + AbstractJwk.ALG + " value '" + alg.getId() + "'. Secret JWKs " + + "may only be used with symmetric (secret) key algorithms."; + throw new MalformedKeyException(msg); + } + + @Override + protected SecretJwk createJwkFromValues(JwkContext ctx) { + ParameterReadable reader = new RequiredParameterReader(ctx); + final byte[] bytes = reader.get(DefaultSecretJwk.K); + SecretKey key; + + String algId = ctx.getAlgorithm(); + if (!Strings.hasText(algId)) { // optional per https://www.rfc-editor.org/rfc/rfc7517.html#section-4.4 + + // Here we try to infer the best type of key to create based on siguse and/or key length. + // + // AES requires 128, 192, or 256 bits, so anything larger than 256 cannot be AES, so we'll need to assume + // HMAC. + // + // Also, 256 bits works for either HMAC or AES, so we just have to choose one as there is no other + // RFC-based criteria for determining. Historically, we've chosen AES due to the larger number of + // KeyAlgorithm and AeadAlgorithm use cases, so that's our default. + int kBitLen = (int) Bytes.bitLength(bytes); + + if (ctx.isSigUse() || kBitLen > Jwts.SIG.HS256.getKeyBitLength()) { + // The only JWA SecretKey signature algorithms are HS256, HS384, HS512, so choose based on bit length: + key = Keys.hmacShaKeyFor(bytes); + } else { + key = AesAlgorithm.keyFor(bytes); + } + ctx.setKey(key); + return new DefaultSecretJwk(ctx); + } + + //otherwise 'alg' was specified, ensure it's valid for secret key use: + Identifiable alg = Jwts.SIG.get().get(algId); + if (alg == null) alg = Jwts.KEY.get().get(algId); + if (alg == null) alg = Jwts.ENC.get().get(algId); + if (alg != null) assertSymmetric(alg); // if we found a standard alg, it must be a symmetric key algorithm + + if (alg instanceof MacAlgorithm) { + assertKeyBitLength(bytes, ((MacAlgorithm) alg)); + String jcaName = ((CryptoAlgorithm) alg).getJcaName(); + Assert.hasText(jcaName, "Algorithm jcaName cannot be null or empty."); + key = new SecretKeySpec(bytes, jcaName); + } else { + // all other remaining JWA-standard symmetric algs use AES: + key = AesAlgorithm.keyFor(bytes); + } + ctx.setKey(key); + return new DefaultSecretJwk(ctx); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardCurves.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardCurves.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardCurves.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,48 @@ +/* + * 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.impl.lang.IdRegistry; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.Curve; + +import java.security.Key; + +public final class StandardCurves extends IdRegistry { + + public StandardCurves() { + super("Elliptic Curve", Collections.of( + ECCurve.P256, + ECCurve.P384, + ECCurve.P521, + EdwardsCurve.X25519, + EdwardsCurve.X448, + EdwardsCurve.Ed25519, + EdwardsCurve.Ed448 + )); + } + + public static Curve findByKey(Key key) { + if (key == null) { + return null; + } + Curve curve = ECCurve.findByKey(key); + if (curve == null) { + curve = EdwardsCurve.findByKey(key); + } + return curve; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardEncryptionAlgorithms.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardEncryptionAlgorithms.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardEncryptionAlgorithms.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 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.impl.lang.IdRegistry; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.AeadAlgorithm; + +@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.ENC +public final class StandardEncryptionAlgorithms extends IdRegistry { + + public static final String NAME = "JWE Encryption Algorithm"; + + public StandardEncryptionAlgorithms() { + super(NAME, Collections.of( + (AeadAlgorithm) new HmacAesAeadAlgorithm(128), + new HmacAesAeadAlgorithm(192), + new HmacAesAeadAlgorithm(256), + new GcmAesAeadAlgorithm(128), + new GcmAesAeadAlgorithm(192), + new GcmAesAeadAlgorithm(256))); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardHashAlgorithms.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardHashAlgorithms.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardHashAlgorithms.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,44 @@ +/* + * 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.impl.lang.IdRegistry; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.HashAlgorithm; + +/** + * Backing implementation for the {@link io.jsonwebtoken.security.Jwks.HASH} implementation. + * + * @since 0.12.0 + */ +@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.security.Jwks.HASH +public final class StandardHashAlgorithms extends IdRegistry { + + public StandardHashAlgorithms() { + super("IANA Hash Algorithm", Collections.of( + // 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. + // The SHA1 instance only exists in JJWT's codebase to support RFC-required `x5t` + // (X.509 SHA-1 Thumbprint) computation - we don't use it anywhere else. + new DefaultHashAlgorithm("sha-256"), + new DefaultHashAlgorithm("sha-384"), + new DefaultHashAlgorithm("sha-512"), + new DefaultHashAlgorithm("sha3-256"), + new DefaultHashAlgorithm("sha3-384"), + new DefaultHashAlgorithm("sha3-512") + )); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardKeyAlgorithms.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardKeyAlgorithms.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardKeyAlgorithms.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2021 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.impl.lang.IdRegistry; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.KeyAlgorithm; + +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; + +/** + * Static class definitions for standard {@link KeyAlgorithm} instances. + * + * @since 0.12.0 + */ +public final class StandardKeyAlgorithms extends IdRegistry> { + + public static final String NAME = "JWE Key Management Algorithm"; + + private static final String RSA1_5_ID = "RSA1_5"; + private static final String RSA1_5_TRANSFORMATION = "RSA/ECB/PKCS1Padding"; + private static final String RSA_OAEP_ID = "RSA-OAEP"; + private static final String RSA_OAEP_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"; + private static final String RSA_OAEP_256_ID = "RSA-OAEP-256"; + private static final String RSA_OAEP_256_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"; + private static final AlgorithmParameterSpec RSA_OAEP_256_SPEC = + new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT); + + public StandardKeyAlgorithms() { + super(NAME, Collections.>of( + new DirectKeyAlgorithm(), + new AesWrapKeyAlgorithm(128), + new AesWrapKeyAlgorithm(192), + new AesWrapKeyAlgorithm(256), + new AesGcmKeyAlgorithm(128), + new AesGcmKeyAlgorithm(192), + new AesGcmKeyAlgorithm(256), + new Pbes2HsAkwAlgorithm(128), + new Pbes2HsAkwAlgorithm(192), + new Pbes2HsAkwAlgorithm(256), + new EcdhKeyAlgorithm(), + new EcdhKeyAlgorithm(new AesWrapKeyAlgorithm(128)), + new EcdhKeyAlgorithm(new AesWrapKeyAlgorithm(192)), + new EcdhKeyAlgorithm(new AesWrapKeyAlgorithm(256)), + new DefaultRsaKeyAlgorithm(RSA1_5_ID, RSA1_5_TRANSFORMATION), + new DefaultRsaKeyAlgorithm(RSA_OAEP_ID, RSA_OAEP_TRANSFORMATION), + new DefaultRsaKeyAlgorithm(RSA_OAEP_256_ID, RSA_OAEP_256_TRANSFORMATION, RSA_OAEP_256_SPEC) + )); + } + + /* + private static KeyAlgorithm lean(final Pbes2HsAkwAlgorithm alg) { + + // ensure we use the same key factory over and over so that time spent acquiring one is not repeated: + JcaTemplate template = new JcaTemplate(alg.getJcaName(), null, Randoms.secureRandom()); + final SecretKeyFactory factory = template.execute(SecretKeyFactory.class, new CheckedFunction() { + @Override + public SecretKeyFactory apply(SecretKeyFactory secretKeyFactory) { + return secretKeyFactory; + } + }); + + // pre-compute the salt so we don't spend time doing that on each iteration. Doesn't need to be random for a + // computation-only test: + final byte[] rfcSalt = alg.toRfcSalt(alg.generateInputSalt(null)); + + // ensure that the bare minimum steps are performed to hash, ensuring our time sampling pertains only to + // hashing and not ancillary steps needed to setup the hashing/derivation + return new KeyAlgorithm() { + @Override + public KeyResult getEncryptionKey(KeyRequest request) throws SecurityException { + int iterations = request.getHeader().getPbes2Count(); + char[] password = request.getKey().getPassword(); + try { + alg.deriveKey(factory, password, rfcSalt, iterations); + } catch (Exception e) { + throw new SecurityException("Unable to derive key", e); + } + return null; + } + + @Override + public SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException { + throw new UnsupportedOperationException("Not intended to be called."); + } + + @Override + public String getId() { + return alg.getId(); + } + }; + } + + private static char randomChar() { + return (char) Randoms.secureRandom().nextInt(Character.MAX_VALUE); + } + + private static char[] randomChars(@SuppressWarnings("SameParameterValue") int length) { + char[] chars = new char[length]; + for (int i = 0; i < length; i++) { + chars[i] = randomChar(); + } + return chars; + } + + public static int estimateIterations(KeyAlgorithm alg, long desiredMillis) { + + // The number of computational samples that land in our 'sweet spot' timing range matching desiredMillis. + // These samples will be averaged and the final average will be the return value of this method + // representing the number of iterations that should be taken for any given PBE hashing attempt to get + // reasonably close to desiredMillis: + final int NUM_SAMPLES = 30; + final int SKIP = 3; + // More important than the actual password (or characters) is the password length. + // 8 characters is a commonly-found minimum required length in many systems circa 2021. + final int PASSWORD_LENGTH = 8; + + final JweHeader HEADER = new DefaultJweHeader(); + final AeadAlgorithm ENC_ALG = Jwts.ENC.A128GCM; // not used, needed to satisfy API + + if (alg instanceof Pbes2HsAkwAlgorithm) { + // Strip away all things that cause time during computation except for the actual hashing algorithm: + alg = lean((Pbes2HsAkwAlgorithm) alg); + } + + int workFactor = 1000; // same as iterations for PBKDF2. Different concept for Bcrypt/Scrypt + int minWorkFactor = workFactor; + List points = new ArrayList<>(NUM_SAMPLES); + for (int i = 0; points.size() < NUM_SAMPLES; i++) { + + char[] password = randomChars(PASSWORD_LENGTH); + Password key = Keys.password(password); + HEADER.pbes2Count(workFactor); + KeyRequest request = new DefaultKeyRequest<>(null, null, key, HEADER, ENC_ALG); + + long start = System.currentTimeMillis(); + alg.getEncryptionKey(request); // <-- Computation occurs here. Don't need the result, just need to exec + long end = System.currentTimeMillis(); + long duration = end - start; + + // Exclude the first SKIP number of attempts from the average due to initial JIT optimization/slowness. + // After a few attempts, the JVM should be relatively optimized and the subsequent + // PBE hashing times are the ones we want to include in our analysis + boolean warmedUp = i >= SKIP; + + // how close we were on this hashing attempt to reach our desiredMillis target: + // A number under 1 means we weren't slow enough, a number greater than 1 means we were too slow: + double durationPercentAchieved = (double) duration / (double) desiredMillis; + + // we only want to collect timing samples if : + // 1. we're warmed up (to account for JIT optimization) + // 2. The attempt time at least met (>=) the desiredMillis target + boolean collectSample = warmedUp && duration >= desiredMillis; + if (collectSample) { + // For each attempt, the x axis is the workFactor, and the y axis is how long it took to compute: + points.add(new Point(workFactor, duration)); + //System.out.println("Collected point: workFactor=" + workFactor + ", duration=" + duration + " ms, %achieved=" + durationPercentAchieved); + } else { + minWorkFactor = Math.max(minWorkFactor, workFactor); + //System.out.println(" Excluding sample: workFactor=" + workFactor + ", duration=" + duration + " ms, %achieved=" + durationPercentAchieved); + } + + // amount to increase or decrease the workFactor for the next hashing iteration. We increase if + // we haven't met the desired millisecond time, and decrease if we're over it a little too much, always + // trying to stay in that desired timing sweet spot + double percentAdjust = workFactor * 0.0075; // 3/4ths of a percent + if (durationPercentAchieved < 1d) { + // Under target. Let's increase by the amount that should get right at (or near) 100%: + double ratio = desiredMillis / (double) duration; + if (ratio > 1) { + double result = workFactor * ratio; + workFactor = (int) result; + } else { + double difference = workFactor * (1 - durationPercentAchieved); + workFactor += Math.max(percentAdjust, difference); + } + } else if (durationPercentAchieved > 1.01d) { + // Over target. Let's decrease gently to get closer. + double difference = workFactor * (durationPercentAchieved - 1.01); + difference = Math.min(percentAdjust, difference); + // math.max here because the min allowed is 1000 per the JWA RFC, so we never want to go below that. + workFactor = (int) Math.max(1000, workFactor - difference); + } else { + // we're at our target (desiredMillis); let's increase by a teeny bit to see where we get + // (and the JVM might optimize with the same inputs, so we want to prevent that here) + workFactor += 100; + } + } + + // We've collected all of our samples, now let's find the workFactor average number + // That average is the best estimate for ensuring PBE hashes for the specified algorithm meet the + // desiredMillis target on the current JVM/CPU platform: + double sumX = 0; + for (Point p : points) { + sumX += p.x; + } + double average = sumX / points.size(); + //ensure our average is at least as much as the smallest work factor that got us closest to desiredMillis: + return (int) Math.max(average, minWorkFactor); + } + + private static class Point { + long x; + long y; + double lnY; + + public Point(long x, long y) { + this.x = x; + this.y = y; + this.lnY = Math.log((double) y); + } + } + */ +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardKeyOperations.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardKeyOperations.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardKeyOperations.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,36 @@ +/* + * 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.impl.lang.IdRegistry; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.KeyOperation; + +public final class StandardKeyOperations extends IdRegistry { + + public StandardKeyOperations() { + super("JSON Web Key Operation", Collections.of( + DefaultKeyOperation.SIGN, + DefaultKeyOperation.VERIFY, + DefaultKeyOperation.ENCRYPT, + DefaultKeyOperation.DECRYPT, + DefaultKeyOperation.WRAP, + DefaultKeyOperation.UNWRAP, + DefaultKeyOperation.DERIVE_KEY, + DefaultKeyOperation.DERIVE_BITS + )); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardSecureDigestAlgorithms.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardSecureDigestAlgorithms.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/StandardSecureDigestAlgorithms.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 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.impl.lang.IdRegistry; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.security.Password; +import io.jsonwebtoken.security.SecureDigestAlgorithm; + +import javax.crypto.SecretKey; +import java.security.Key; +import java.security.PrivateKey; + +@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwts.SIG +public final class StandardSecureDigestAlgorithms extends IdRegistry> { + + public static final String NAME = "JWS Digital Signature or MAC"; + + public StandardSecureDigestAlgorithms() { + super(NAME, Collections.of( + NoneSignatureAlgorithm.INSTANCE, + DefaultMacAlgorithm.HS256, + DefaultMacAlgorithm.HS384, + DefaultMacAlgorithm.HS512, + RsaSignatureAlgorithm.RS256, + RsaSignatureAlgorithm.RS384, + RsaSignatureAlgorithm.RS512, + RsaSignatureAlgorithm.PS256, + RsaSignatureAlgorithm.PS384, + RsaSignatureAlgorithm.PS512, + EcSignatureAlgorithm.ES256, + EcSignatureAlgorithm.ES384, + EcSignatureAlgorithm.ES512, + EdSignatureAlgorithm.INSTANCE + )); + } + + @SuppressWarnings("unchecked") + public static SecureDigestAlgorithm findBySigningKey(K key) { + + SecureDigestAlgorithm alg = null; // null value means no suitable match + + if (key instanceof SecretKey && !(key instanceof Password)) { + + alg = DefaultMacAlgorithm.findByKey(key); + + } else if (key instanceof PrivateKey) { + + PrivateKey pk = (PrivateKey) key; + + alg = RsaSignatureAlgorithm.findByKey(pk); + if (alg == null) { + alg = EcSignatureAlgorithm.findByKey(pk); + } + if (alg == null && EdSignatureAlgorithm.isSigningKey(pk)) { + alg = EdSignatureAlgorithm.INSTANCE; + } + } + + return (SecureDigestAlgorithm) alg; + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/X509BuilderSupport.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/X509BuilderSupport.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/impl/security/X509BuilderSupport.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2021 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.impl.ParameterMap; +import io.jsonwebtoken.impl.io.Streams; +import io.jsonwebtoken.impl.lang.CheckedFunction; +import io.jsonwebtoken.impl.lang.Function; +import io.jsonwebtoken.impl.lang.Functions; +import io.jsonwebtoken.lang.Assert; +import io.jsonwebtoken.lang.Collections; +import io.jsonwebtoken.lang.Objects; +import io.jsonwebtoken.security.HashAlgorithm; +import io.jsonwebtoken.security.Jwks; +import io.jsonwebtoken.security.Request; +import io.jsonwebtoken.security.X509Builder; + +import java.io.InputStream; +import java.net.URI; +import java.security.cert.X509Certificate; +import java.util.List; + +//Consolidates logic between DefaultJwtHeaderBuilder and AbstractAsymmetricJwkBuilder +public class X509BuilderSupport implements X509Builder { + + private final ParameterMap map; + + protected boolean computeX509Sha1Thumbprint; + + /** + * Boolean object indicates 3 states: 1) not configured 2) configured as true, 3) configured as false + */ + protected Boolean computeX509Sha256Thumbprint = null; + + private static Function createGetBytesFunction(Class clazz) { + return Functions.wrapFmt(new CheckedFunction() { + @Override + public byte[] apply(X509Certificate cert) throws Exception { + return cert.getEncoded(); + } + }, clazz, "Unable to access X509Certificate encoded bytes necessary to compute thumbprint. Certificate: %s"); + } + + private final Function GET_X509_BYTES; + + public X509BuilderSupport(ParameterMap map, Class getBytesFailedException) { + this.map = Assert.notNull(map, "ParameterMap cannot be null."); + this.GET_X509_BYTES = createGetBytesFunction(getBytesFailedException); + } + + @Override + public X509BuilderSupport x509Url(URI uri) { + this.map.put(AbstractAsymmetricJwk.X5U.getId(), uri); + return this; + } + + @Override + public X509BuilderSupport x509Chain(List chain) { + this.map.put(AbstractAsymmetricJwk.X5C.getId(), chain); + return this; + } + + @Override + public X509BuilderSupport x509Sha1Thumbprint(byte[] thumbprint) { + this.map.put(AbstractAsymmetricJwk.X5T.getId(), thumbprint); + return this; + } + + @Override + public X509BuilderSupport x509Sha1Thumbprint(boolean enable) { + this.computeX509Sha1Thumbprint = enable; + return this; + } + + @Override + public X509BuilderSupport x509Sha256Thumbprint(byte[] thumbprint) { + this.map.put(AbstractAsymmetricJwk.X5T_S256.getId(), thumbprint); + return this; + } + + @Override + public X509BuilderSupport x509Sha256Thumbprint(boolean enable) { + this.computeX509Sha256Thumbprint = enable; + return this; + } + + private byte[] computeThumbprint(final X509Certificate cert, HashAlgorithm alg) { + byte[] encoded = GET_X509_BYTES.apply(cert); + InputStream in = Streams.of(encoded); + Request request = new DefaultRequest<>(in, null, null); + return alg.digest(request); + } + + public void apply() { + List chain = this.map.get(AbstractAsymmetricJwk.X5C); + X509Certificate firstCert = null; + if (!Collections.isEmpty(chain)) { + firstCert = chain.get(0); + } + + Boolean computeX509Sha256 = this.computeX509Sha256Thumbprint; + if (computeX509Sha256 == null) { //if not specified, enable by default if possible: + computeX509Sha256 = firstCert != null && + !computeX509Sha1Thumbprint && // no need if at least one thumbprint will be set + Objects.isEmpty(this.map.get(AbstractAsymmetricJwk.X5T_S256)); // no need if already set + } + + if (firstCert != null) { + if (computeX509Sha1Thumbprint) { + byte[] thumbprint = computeThumbprint(firstCert, DefaultHashAlgorithm.SHA1); + x509Sha1Thumbprint(thumbprint); + } + if (computeX509Sha256) { + byte[] thumbprint = computeThumbprint(firstCert, Jwks.HASH.SHA256); + x509Sha256Thumbprint(thumbprint); + } + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/AbstractDeserializer.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/AbstractDeserializer.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/AbstractDeserializer.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,84 @@ +/* + * 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.io; + +import io.jsonwebtoken.lang.Assert; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; + +/** + * Convenient base class to use to implement {@link Deserializer}s, with subclasses only needing to implement + * {@link #doDeserialize(Reader)}. + * + * @param the type of object returned after deserialization + * @since 0.12.0 + */ +public abstract class AbstractDeserializer implements Deserializer { + + /** + * EOF (End of File) marker, equal to {@code -1}. + */ + protected static final int EOF = -1; + + private static final byte[] EMPTY_BYTES = new byte[0]; + + /** + * Default constructor, does not initialize any internal state. + */ + protected AbstractDeserializer() { + } + + /** + * {@inheritDoc} + */ + @Override + public final T deserialize(byte[] bytes) throws DeserializationException { + bytes = bytes == null ? EMPTY_BYTES : bytes; // null safe + InputStream in = new ByteArrayInputStream(bytes); + Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8); + return deserialize(reader); + } + + /** + * {@inheritDoc} + */ + @Override + public final T deserialize(Reader reader) throws DeserializationException { + Assert.notNull(reader, "Reader argument cannot be null."); + try { + return doDeserialize(reader); + } catch (Throwable t) { + if (t instanceof DeserializationException) { + throw (DeserializationException) t; + } + String msg = "Unable to deserialize: " + t.getMessage(); + throw new DeserializationException(msg, t); + } + } + + /** + * Reads the specified character stream and returns the corresponding Java object. + * + * @param reader the reader to use to read the character stream + * @return the deserialized Java object + * @throws Exception if there is a problem reading the stream or creating the expected Java object + */ + protected abstract T doDeserialize(Reader reader) throws Exception; +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/AbstractSerializer.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/AbstractSerializer.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/AbstractSerializer.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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.io; + +import io.jsonwebtoken.lang.Objects; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; + +/** + * Convenient base class to use to implement {@link Serializer}s, with subclasses only needing to implement + * * {@link #doSerialize(Object, OutputStream)}. + * + * @param the type of object to serialize + * @since 0.12.0 + */ +public abstract class AbstractSerializer implements Serializer { + + /** + * Default constructor, does not initialize any internal state. + */ + protected AbstractSerializer() { + } + + /** + * {@inheritDoc} + */ + @Override + public final byte[] serialize(T t) throws SerializationException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + serialize(t, out); + return out.toByteArray(); + } + + /** + * {@inheritDoc} + */ + @Override + public final void serialize(T t, OutputStream out) throws SerializationException { + try { + doSerialize(t, out); + } catch (Throwable e) { + if (e instanceof SerializationException) { + throw (SerializationException) e; + } + String msg = "Unable to serialize object of type " + Objects.nullSafeClassName(t) + ": " + e.getMessage(); + throw new SerializationException(msg, e); + } + } + + /** + * Converts the specified Java object into a formatted data byte stream, writing the bytes to the specified + * {@code out}put stream. + * + * @param t the object to convert to a byte stream + * @param out the stream to write to + * @throws Exception if there is a problem converting the object to a byte stream or writing the + * bytes to the {@code out}put stream. + * @since 0.12.0 + */ + protected abstract void doSerialize(T t, OutputStream out) throws Exception; +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64.java (.../Base64.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64.java (.../Base64.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -111,7 +111,7 @@ // Encode even 24-bits for (int s = 0, d = 0, cc = 0; s < eLen; ) { - // Copy next three bytes into lower 24 bits of int, paying attension to sign. + // Copy next three bytes into lower 24 bits of int, paying attention to sign. int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); // Encode the int into four chars @@ -224,40 +224,41 @@ } /** - * Decodes a BASE64 encoded char array that is known to be reasonably well formatted. The preconditions are:
    - * + The array must have a line length of 76 chars OR no line separators at all (one line).
    + * Decodes a BASE64-encoded {@code CharSequence} that is known to be reasonably well formatted. The preconditions + * are:
    + * + The sequence must have a line length of 76 chars OR no line separators at all (one line).
    * + Line separator must be "\r\n", as specified in RFC 2045 - * + The array must not contain illegal characters within the encoded string
    - * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
    + * + The sequence must not contain illegal characters within the encoded string
    + * + The sequence CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
    * - * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. + * @param seq The source sequence. Length 0 will return an empty array. null will throw an exception. * @return The decoded array of bytes. May be of length 0. * @throws DecodingException on illegal input */ - final byte[] decodeFast(char[] sArr) throws DecodingException { + byte[] decodeFast(CharSequence seq) throws DecodingException { // Check special case - int sLen = sArr != null ? sArr.length : 0; + int sLen = seq != null ? seq.length() : 0; if (sLen == 0) { return new byte[0]; } int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. // Trim illegal chars from start - while (sIx < eIx && IALPHABET[sArr[sIx]] < 0) { + while (sIx < eIx && IALPHABET[seq.charAt(sIx)] < 0) { sIx++; } // Trim illegal chars from end - while (eIx > 0 && IALPHABET[sArr[eIx]] < 0) { + while (eIx > 0 && IALPHABET[seq.charAt(eIx)] < 0) { eIx--; } // get the padding count (=) (0, 1 or 2) - int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. + int pad = seq.charAt(eIx) == '=' ? (seq.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end. int cCnt = eIx - sIx + 1; // Content count including possible separators - int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; + int sepCnt = sLen > 76 ? (seq.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes byte[] dArr = new byte[len]; // Preallocate byte[] of exact length @@ -267,7 +268,7 @@ for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { // Assemble three bytes into an int from four "valid" characters. - int i = ctoi(sArr[sIx++]) << 18 | ctoi(sArr[sIx++]) << 12 | ctoi(sArr[sIx++]) << 6 | ctoi(sArr[sIx++]); + int i = ctoi(seq.charAt(sIx++)) << 18 | ctoi(seq.charAt(sIx++)) << 12 | ctoi(seq.charAt(sIx++)) << 6 | ctoi(seq.charAt(sIx++)); // Add the bytes dArr[d++] = (byte) (i >> 16); @@ -285,7 +286,7 @@ // Decode last 1-3 bytes (incl '=') into 1-3 bytes int i = 0; for (int j = 0; sIx <= eIx - pad; j++) { - i |= ctoi(sArr[sIx++]) << (18 - j * 6); + i |= ctoi(seq.charAt(sIx++)) << (18 - j * 6); } for (int r = 16; d < len; r -= 8) { @@ -339,7 +340,7 @@ // Encode even 24-bits for (int s = sOff, d = 0, cc = 0; s < sOff + eLen; ) { - // Copy next three bytes into lower 24 bits of int, paying attension to sign. + // Copy next three bytes into lower 24 bits of int, paying attention to sign. int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); // Encode the int into four chars @@ -404,7 +405,7 @@ } } - // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. + // Check so that legal chars (including '=') are evenly divisible by 4 as specified in RFC 2045. if ((sLen - sepCnt) % 4 != 0) { return null; } @@ -447,7 +448,7 @@ /* - * Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as + * Decodes a BASE64 encoded byte array that is known to be reasonably well formatted. The method is about twice as * fast as {@link #decode(byte[])}. The preconditions are:
    * + The array must have a line length of 76 chars OR no line separators at all (one line).
    * + Line separator must be "\r\n", as specified in RFC 2045 @@ -533,7 +534,7 @@ * little faster. * @return A BASE64 encoded array. Never null. */ - final String encodeToString(byte[] sArr, boolean lineSep) { + String encodeToString(byte[] sArr, boolean lineSep) { // Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower. return new String(encodeToChar(sArr, lineSep)); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64Decoder.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64Decoder.java (.../Base64Decoder.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64Decoder.java (.../Base64Decoder.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -18,9 +18,12 @@ import io.jsonwebtoken.lang.Assert; /** + * Very fast Base64 decoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + * * @since 0.10.0 */ -class Base64Decoder extends Base64Support implements Decoder { +class Base64Decoder extends Base64Support implements Decoder { Base64Decoder() { super(Base64.DEFAULT); @@ -31,8 +34,8 @@ } @Override - public byte[] decode(String s) throws DecodingException { + public byte[] decode(CharSequence s) throws DecodingException { Assert.notNull(s, "String argument cannot be null"); - return this.base64.decodeFast(s.toCharArray()); + return this.base64.decodeFast(s); } } \ No newline at end of file Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64Encoder.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64Encoder.java (.../Base64Encoder.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64Encoder.java (.../Base64Encoder.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -18,12 +18,15 @@ import io.jsonwebtoken.lang.Assert; /** + * Very fast Base64 encoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + * * @since 0.10.0 */ class Base64Encoder extends Base64Support implements Encoder { Base64Encoder() { - super(Base64.DEFAULT); + this(Base64.DEFAULT); } Base64Encoder(Base64 base64) { Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64Support.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64Support.java (.../Base64Support.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64Support.java (.../Base64Support.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -18,6 +18,8 @@ import io.jsonwebtoken.lang.Assert; /** + * Parent class for Base64 encoders and decoders. + * * @since 0.10.0 */ class Base64Support { Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64UrlDecoder.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64UrlDecoder.java (.../Base64UrlDecoder.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64UrlDecoder.java (.../Base64UrlDecoder.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,6 +16,9 @@ package io.jsonwebtoken.io; /** + * Very fast Base64Url decoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + * * @since 0.10.0 */ class Base64UrlDecoder extends Base64Decoder { Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64UrlEncoder.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64UrlEncoder.java (.../Base64UrlEncoder.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Base64UrlEncoder.java (.../Base64UrlEncoder.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,6 +16,9 @@ package io.jsonwebtoken.io; /** + * Very fast Base64Url encoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + * * @since 0.10.0 */ class Base64UrlEncoder extends Base64Encoder { Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/CodecException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/CodecException.java (.../CodecException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/CodecException.java (.../CodecException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,14 +16,27 @@ package io.jsonwebtoken.io; /** + * An exception thrown when encountering a problem during encoding or decoding. + * * @since 0.10.0 */ public class CodecException extends IOException { + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ public CodecException(String message) { super(message); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public CodecException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/CompressionAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/CompressionAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/CompressionAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,65 @@ +/* + * 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.io; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.JwtParserBuilder; +import io.jsonwebtoken.Jwts; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Compresses and decompresses byte streams. + * + *

    "zip" identifier

    + * + *

    {@code CompressionAlgorithm} extends {@code Identifiable}; the value returned from + * {@link Identifiable#getId() getId()} will be used as the JWT + * zip header value.

    + * + *

    Custom Implementations

    + * + *

    A custom implementation of this interface may be used when creating a JWT by calling the + * {@link JwtBuilder#compressWith(CompressionAlgorithm)} method.

    + * + *

    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.

    + * + * @see Jwts.ZIP#DEF + * @see Jwts.ZIP#GZIP + * @see JSON Web Encryption Compression Algorithms Registry + * @since 0.12.0 + */ +public interface CompressionAlgorithm extends Identifiable { + + /** + * Wraps the specified {@code OutputStream} to ensure any stream bytes are compressed as they are written. + * + * @param out the stream to wrap for compression + * @return the stream to use for writing + */ + OutputStream compress(OutputStream out); + + /** + * Wraps the specified {@code InputStream} to ensure any stream bytes are decompressed as they are read. + * + * @param in the stream to wrap for decompression + * @return the stream to use for reading + */ + InputStream decompress(InputStream in); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Decoder.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Decoder.java (.../Decoder.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Decoder.java (.../Decoder.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,9 +16,20 @@ package io.jsonwebtoken.io; /** + * A decoder converts an already-encoded data value to a desired data type. + * + * @param decoding input type + * @param decoding output type * @since 0.10.0 */ public interface Decoder { + /** + * Convert the specified encoded data value into the desired data type. + * + * @param t the encoded data + * @return the resulting expected data + * @throws DecodingException if there is a problem during decoding. + */ R decode(T t) throws DecodingException; } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Decoders.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Decoders.java (.../Decoders.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Decoders.java (.../Decoders.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,13 +16,26 @@ package io.jsonwebtoken.io; /** + * Constant definitions for various decoding algorithms. + * + * @see #BASE64 + * @see #BASE64URL * @since 0.10.0 */ public final class Decoders { - public static final Decoder BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder()); - public static final Decoder BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder()); + /** + * Very fast Base64 decoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + */ + public static final Decoder BASE64 = new ExceptionPropagatingDecoder<>(new Base64Decoder()); + /** + * Very fast Base64Url decoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + */ + public static final Decoder BASE64URL = new ExceptionPropagatingDecoder<>(new Base64UrlDecoder()); + private Decoders() { //prevent instantiation } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/DecodingException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/DecodingException.java (.../DecodingException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/DecodingException.java (.../DecodingException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,14 +16,27 @@ package io.jsonwebtoken.io; /** + * An exception thrown when encountering a problem during decoding. + * * @since 0.10.0 */ public class DecodingException extends CodecException { + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ public DecodingException(String message) { super(message); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public DecodingException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/DeserializationException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/DeserializationException.java (.../DeserializationException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/DeserializationException.java (.../DeserializationException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,14 +16,27 @@ package io.jsonwebtoken.io; /** + * Exception thrown when reconstituting a serialized byte array into a Java object. + * * @since 0.10.0 */ public class DeserializationException extends SerialException { + /** + * Creates a new instance with the specified explanation message. + * + * @param msg the message explaining why the exception is thrown. + */ public DeserializationException(String msg) { super(msg); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public DeserializationException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Deserializer.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Deserializer.java (.../Deserializer.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Deserializer.java (.../Deserializer.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,10 +15,34 @@ */ package io.jsonwebtoken.io; +import java.io.Reader; + /** + * A {@code Deserializer} is able to convert serialized byte streams into Java objects. + * + * @param the type of object to be returned as a result of deserialization. * @since 0.10.0 */ public interface Deserializer { + /** + * Convert the specified formatted data byte array into a Java object. + * + * @param bytes the formatted data byte array to convert + * @return the reconstituted Java object + * @throws DeserializationException if there is a problem converting the byte array to an object. + * @deprecated since 0.12.0 in favor of {@link #deserialize(Reader)} + */ + @Deprecated T deserialize(byte[] bytes) throws DeserializationException; + + /** + * Reads the specified character stream and returns the corresponding Java object. + * + * @param reader the reader to use to read the character stream + * @return the deserialized Java object + * @throws DeserializationException if there is a problem reading the stream or creating the expected Java object + * @since 0.12.0 + */ + T deserialize(Reader reader) throws DeserializationException; } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Encoder.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Encoder.java (.../Encoder.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Encoder.java (.../Encoder.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,9 +16,20 @@ package io.jsonwebtoken.io; /** + * An encoder converts data of one type into another formatted data value. + * + * @param the type of data to convert + * @param the type of the resulting formatted data * @since 0.10.0 */ public interface Encoder { + /** + * Convert the specified data into another formatted data value. + * + * @param t the data to convert + * @return the resulting formatted data value + * @throws EncodingException if there is a problem during encoding + */ R encode(T t) throws EncodingException; } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Encoders.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Encoders.java (.../Encoders.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Encoders.java (.../Encoders.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,11 +16,24 @@ package io.jsonwebtoken.io; /** + * Constant definitions for various encoding algorithms. + * + * @see #BASE64 + * @see #BASE64URL * @since 0.10.0 */ public final class Encoders { + /** + * Very fast Base64 encoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + */ public static final Encoder BASE64 = new ExceptionPropagatingEncoder<>(new Base64Encoder()); + + /** + * Very fast Base64Url encoder guaranteed to + * work in all >= Java 7 JDK and Android environments. + */ public static final Encoder BASE64URL = new ExceptionPropagatingEncoder<>(new Base64UrlEncoder()); private Encoders() { //prevent instantiation Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/EncodingException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/EncodingException.java (.../EncodingException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/EncodingException.java (.../EncodingException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,10 +16,18 @@ package io.jsonwebtoken.io; /** + * An exception thrown when encountering a problem during encoding. + * * @since 0.10.0 */ public class EncodingException extends CodecException { + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public EncodingException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/ExceptionPropagatingDecoder.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/ExceptionPropagatingDecoder.java (.../ExceptionPropagatingDecoder.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/ExceptionPropagatingDecoder.java (.../ExceptionPropagatingDecoder.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -18,17 +18,33 @@ import io.jsonwebtoken.lang.Assert; /** + * Decoder that ensures any exceptions thrown that are not {@link DecodingException}s are wrapped + * and re-thrown as a {@code DecodingException}. + * * @since 0.10.0 */ class ExceptionPropagatingDecoder implements Decoder { private final Decoder decoder; + /** + * Creates a new instance, wrapping the specified {@code decoder} to invoke during {@link #decode(Object)}. + * + * @param decoder the decoder to wrap and call during {@link #decode(Object)} + */ ExceptionPropagatingDecoder(Decoder decoder) { Assert.notNull(decoder, "Decoder cannot be null."); this.decoder = decoder; } + /** + * Decode the specified encoded data, delegating to the wrapped Decoder, wrapping any + * non-{@link DecodingException} as a {@code DecodingException}. + * + * @param t the encoded data + * @return the decoded data + * @throws DecodingException if there is an unexpected problem during decoding. + */ @Override public R decode(T t) throws DecodingException { Assert.notNull(t, "Decode argument cannot be null."); Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/ExceptionPropagatingEncoder.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/ExceptionPropagatingEncoder.java (.../ExceptionPropagatingEncoder.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/ExceptionPropagatingEncoder.java (.../ExceptionPropagatingEncoder.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -18,17 +18,33 @@ import io.jsonwebtoken.lang.Assert; /** + * Encoder that ensures any exceptions thrown that are not {@link EncodingException}s are wrapped + * and re-thrown as a {@code EncodingException}. + * * @since 0.10.0 */ class ExceptionPropagatingEncoder implements Encoder { private final Encoder encoder; + /** + * Creates a new instance, wrapping the specified {@code encoder} to invoke during {@link #encode(Object)}. + * + * @param encoder the encoder to wrap and call during {@link #encode(Object)} + */ ExceptionPropagatingEncoder(Encoder encoder) { Assert.notNull(encoder, "Encoder cannot be null."); this.encoder = encoder; } + /** + * Encoded the specified data, delegating to the wrapped Encoder, wrapping any + * non-{@link EncodingException} as an {@code EncodingException}. + * + * @param t the data to encode + * @return the encoded data + * @throws EncodingException if there is an unexpected problem during encoding. + */ @Override public R encode(T t) throws EncodingException { Assert.notNull(t, "Encode argument cannot be null."); Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/IOException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/IOException.java (.../IOException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/IOException.java (.../IOException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -18,14 +18,28 @@ import io.jsonwebtoken.JwtException; /** + * JJWT's base exception for problems during data input or output operations, such as serialization, + * deserialization, marshalling, unmarshalling, etc. + * * @since 0.10.0 */ public class IOException extends JwtException { + /** + * Creates a new instance with the specified explanation message. + * + * @param msg the message explaining why the exception is thrown. + */ public IOException(String msg) { super(msg); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public IOException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Parser.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Parser.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Parser.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,68 @@ +/* + * 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.io; + +import java.io.InputStream; +import java.io.Reader; + +/** + * A Parser converts a character stream into a Java object. + * + * @param the instance type created after parsing + * @since 0.12.0 + */ +public interface Parser { + + /** + * Parse the specified character sequence into a Java object. + * + * @param input the character sequence to parse into a Java object. + * @return the Java object represented by the specified {@code input} stream. + */ + T parse(CharSequence input); + + /** + * Parse the specified character sequence with the specified bounds into a Java object. + * + * @param input The character sequence, may be {@code null} + * @param start The start index in the character sequence, inclusive + * @param end The end index in the character sequence, exclusive + * @return the Java object represented by the specified sequence bounds + * @throws IllegalArgumentException if the start index is negative, or if the end index is smaller than the start index + */ + T parse(CharSequence input, int start, int end); + + /** + * Parse the specified character sequence into a Java object. + * + * @param reader the reader to use to parse a Java object. + * @return the Java object represented by the specified {@code input} stream. + */ + T parse(Reader reader); + + /** + * Parses the specified {@link InputStream} assuming {@link java.nio.charset.StandardCharsets#UTF_8 UTF_8} encoding. + * This is a convenience alias for: + * + *
    {@link #parse(Reader) parse}(new {@link java.io.InputStreamReader
    +     * InputStreamReader}(in, {@link java.nio.charset.StandardCharsets#UTF_8
    +     * StandardCharsets.UTF_8});
    + * + * @param in the UTF-8 InputStream. + * @return the Java object represented by the specified {@link InputStream}. + */ + T parse(InputStream in); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/ParserBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/ParserBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/ParserBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,54 @@ +/* + * 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.io; + +import io.jsonwebtoken.lang.Builder; + +import java.security.Provider; +import java.util.Map; + +/** + * A {@code ParserBuilder} configures and creates new {@link Parser} instances. + * + * @param The resulting parser's {@link Parser#parse parse} output type + * @param builder type used for method chaining + * @since 0.12.0 + */ +public interface ParserBuilder> extends Builder> { + + /** + * Sets the JCA Provider to use during cryptographic operations, or {@code null} if the + * JCA subsystem preferred provider should be used. + * + * @param provider the JCA Provider to use during cryptographic key factory operations, or {@code null} + * if the JCA subsystem preferred provider should be used. + * @return the builder for method chaining. + */ + B provider(Provider provider); + + /** + * Uses the specified {@code Deserializer} to convert JSON Strings (UTF-8 byte streams) into Java Map objects. The + * resulting Maps are then used to construct respective JWT objects (JWTs, JWKs, etc). + * + *

    If this method is not called, JJWT will use whatever Deserializer it can find at runtime, checking for the + * presence of well-known implementations such as Jackson, Gson, and org.json. If one of these is not found + * in the runtime classpath, an exception will be thrown when the {@link #build()} method is called. + * + * @param deserializer the Deserializer to use when converting JSON Strings (UTF-8 byte streams) into Map objects. + * @return the builder for method chaining. + */ + B json(Deserializer> deserializer); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/SerialException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/SerialException.java (.../SerialException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/SerialException.java (.../SerialException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,14 +16,27 @@ package io.jsonwebtoken.io; /** + * An exception thrown during serialization or deserialization. + * * @since 0.10.0 */ public class SerialException extends IOException { + /** + * Creates a new instance with the specified explanation message. + * + * @param msg the message explaining why the exception is thrown. + */ public SerialException(String msg) { super(msg); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public SerialException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/SerializationException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/SerializationException.java (.../SerializationException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/SerializationException.java (.../SerializationException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,14 +16,27 @@ package io.jsonwebtoken.io; /** + * Exception thrown when converting a Java object to a formatted byte array. + * * @since 0.10.0 */ public class SerializationException extends SerialException { + /** + * Creates a new instance with the specified explanation message. + * + * @param msg the message explaining why the exception is thrown. + */ public SerializationException(String msg) { super(msg); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public SerializationException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Serializer.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Serializer.java (.../Serializer.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/io/Serializer.java (.../Serializer.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,11 +15,37 @@ */ package io.jsonwebtoken.io; +import java.io.OutputStream; + /** + * A {@code Serializer} is able to convert a Java object into a formatted byte stream. It is expected this byte stream + * can be reconstituted back into a Java object with a matching {@link Deserializer}. + * + * @param The type of object to serialize. * @since 0.10.0 */ public interface Serializer { + /** + * Converts the specified Java object into a formatted data byte array. + * + * @param t the object to serialize + * @return the serialized byte array representing the specified object. + * @throws SerializationException if there is a problem converting the object to a byte array. + * @deprecated since 0.12.0 in favor of {@link #serialize(Object, OutputStream)} + */ + @Deprecated byte[] serialize(T t) throws SerializationException; + /** + * Converts the specified Java object into a formatted data byte stream, writing the bytes to the specified + * {@code out}put stream. + * + * @param t the object to convert to a byte stream + * @param out the stream to write to + * @throws SerializationException if there is a problem converting the object to a byte stream or writing the + * bytes to the {@code out}put stream. + * @since 0.12.0 + */ + void serialize(T t, OutputStream out) throws SerializationException; } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/jackson/io/JacksonDeserializer.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/jackson/io/JacksonDeserializer.java (.../JacksonDeserializer.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/jackson/io/JacksonDeserializer.java (.../JacksonDeserializer.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -18,26 +18,30 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.ObjectMapper; - import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; -import io.jsonwebtoken.io.DeserializationException; -import io.jsonwebtoken.io.Deserializer; +import io.jsonwebtoken.io.AbstractDeserializer; import io.jsonwebtoken.lang.Assert; import java.io.IOException; +import java.io.Reader; import java.util.Collections; import java.util.Map; /** + * Deserializer using a Jackson {@link ObjectMapper}. + * * @since 0.10.0 */ -public class JacksonDeserializer implements Deserializer { +public class JacksonDeserializer extends AbstractDeserializer { private final Class returnType; + private final ObjectMapper objectMapper; - @SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator + /** + * Constructor using JJWT's default {@link ObjectMapper} singleton for deserialization. + */ public JacksonDeserializer() { this(JacksonSerializer.DEFAULT_OBJECT_MAPPER); } @@ -64,25 +68,64 @@ * If you would like to use your own {@code ObjectMapper} instance that also supports custom types for * JWT {@code Claims}, you will need to first customize your {@code ObjectMapper} instance by registering * your custom types and then use the {@link #JacksonDeserializer(ObjectMapper)} constructor instead. - * + * * @param claimTypeMap The claim name-to-class map used to deserialize claims into the given type */ - public JacksonDeserializer(Map claimTypeMap) { - // DO NOT reuse JacksonSerializer.DEFAULT_OBJECT_MAPPER as this could result in sharing the custom deserializer - // between instances - this(new ObjectMapper()); - Assert.notNull(claimTypeMap, "Claim type map cannot be null."); - // register a new Deserializer - SimpleModule module = new SimpleModule(); - module.addDeserializer(Object.class, new MappedTypeDeserializer(Collections.unmodifiableMap(claimTypeMap))); - objectMapper.registerModule(module); + public JacksonDeserializer(Map> claimTypeMap) { + // DO NOT specify JacksonSerializer.DEFAULT_OBJECT_MAPPER here as that would modify the shared instance + this(JacksonSerializer.newObjectMapper(), claimTypeMap); } - @SuppressWarnings({"unchecked", "WeakerAccess", "unused"}) // for end-users providing a custom ObjectMapper + /** + * Constructor using the specified Jackson {@link ObjectMapper}. + * + * @param objectMapper the ObjectMapper to use for deserialization. + */ + @SuppressWarnings("unchecked") public JacksonDeserializer(ObjectMapper objectMapper) { this(objectMapper, (Class) Object.class); } + /** + * Creates a new JacksonDeserializer where the values of the claims can be parsed into given types by registering + * a type-converting {@link com.fasterxml.jackson.databind.Module Module} on the specified {@link ObjectMapper}. + * A common usage example is to parse custom User object out of a claim, for example the claims: + *

    {@code
    +     * {
    +     *     "issuer": "https://issuer.example.com",
    +     *     "user": {
    +     *         "firstName": "Jill",
    +     *         "lastName": "Coder"
    +     *     }
    +     * }}
    + * Passing a map of {@code ["user": User.class]} to this constructor would result in the {@code user} claim being + * transformed to an instance of your custom {@code User} class, instead of the default of {@code Map}. + *

    + * Because custom type parsing requires modifying the state of a Jackson {@code ObjectMapper}, this + * constructor modifies the specified {@code objectMapper} argument and customizes it to support the + * specified {@code claimTypeMap}. + *

    + * If you do not want your {@code ObjectMapper} instance modified, but also want to support custom types for + * JWT {@code Claims}, you will need to first customize your {@code ObjectMapper} instance by registering + * your custom types separately and then use the {@link #JacksonDeserializer(ObjectMapper)} constructor instead + * (which does not modify the {@code objectMapper} argument). + * + * @param objectMapper the objectMapper to modify by registering a custom type-converting + * {@link com.fasterxml.jackson.databind.Module Module} + * @param claimTypeMap The claim name-to-class map used to deserialize claims into the given type + * @since 0.12.4 + */ + //TODO: Make this public on a minor release + // (cannot do that on a point release as that would violate semver) + private JacksonDeserializer(ObjectMapper objectMapper, Map> claimTypeMap) { + this(objectMapper); + Assert.notNull(claimTypeMap, "Claim type map cannot be null."); + // register a new Deserializer on the ObjectMapper instance: + SimpleModule module = new SimpleModule(); + module.addDeserializer(Object.class, new MappedTypeDeserializer(Collections.unmodifiableMap(claimTypeMap))); + objectMapper.registerModule(module); + } + private JacksonDeserializer(ObjectMapper objectMapper, Class returnType) { Assert.notNull(objectMapper, "ObjectMapper cannot be null."); Assert.notNull(returnType, "Return type cannot be null."); @@ -91,28 +134,19 @@ } @Override - public T deserialize(byte[] bytes) throws DeserializationException { - try { - return readValue(bytes); - } catch (IOException e) { - String msg = "Unable to deserialize bytes into a " + returnType.getName() + " instance: " + e.getMessage(); - throw new DeserializationException(msg, e); - } + protected T doDeserialize(Reader reader) throws Exception { + return objectMapper.readValue(reader, returnType); } - protected T readValue(byte[] bytes) throws IOException { - return objectMapper.readValue(bytes, returnType); - } - /** * A Jackson {@link com.fasterxml.jackson.databind.JsonDeserializer JsonDeserializer}, that will convert claim * values to types based on {@code claimTypeMap}. */ private static class MappedTypeDeserializer extends UntypedObjectDeserializer { - private final Map claimTypeMap; + private final Map> claimTypeMap; - private MappedTypeDeserializer(Map claimTypeMap) { + private MappedTypeDeserializer(Map> claimTypeMap) { super(null, null); this.claimTypeMap = claimTypeMap; } @@ -122,7 +156,8 @@ // check if the current claim key is mapped, if so traverse it's value String name = parser.currentName(); if (claimTypeMap != null && name != null && claimTypeMap.containsKey(name)) { - Class type = claimTypeMap.get(name); + Class type = claimTypeMap.get(name); + //noinspection resource return parser.readValueAsTree().traverse(parser.getCodec()).readValueAs(type); } // otherwise default to super Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/jackson/io/JacksonSerializer.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/jackson/io/JacksonSerializer.java (.../JacksonSerializer.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/jackson/io/JacksonSerializer.java (.../JacksonSerializer.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,45 +15,78 @@ */ package io.jsonwebtoken.jackson.io; -import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; -import io.jsonwebtoken.io.SerializationException; -import io.jsonwebtoken.io.Serializer; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.module.SimpleModule; +import io.jsonwebtoken.io.AbstractSerializer; import io.jsonwebtoken.lang.Assert; +import java.io.OutputStream; + /** + * Serializer using a Jackson {@link ObjectMapper}. + * * @since 0.10.0 */ -public class JacksonSerializer implements Serializer { +public class JacksonSerializer extends AbstractSerializer { - static final ObjectMapper DEFAULT_OBJECT_MAPPER = new ObjectMapper(); + static final String MODULE_ID = "jjwt-jackson"; + static final Module MODULE; - private final ObjectMapper objectMapper; + static { + SimpleModule module = new SimpleModule(MODULE_ID); + module.addSerializer(JacksonSupplierSerializer.INSTANCE); + MODULE = module; + } - @SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator + static final ObjectMapper DEFAULT_OBJECT_MAPPER = newObjectMapper(); + + /** + * Creates and returns a new ObjectMapper with the {@code jjwt-jackson} module registered and + * {@code JsonParser.Feature.STRICT_DUPLICATE_DETECTION} enabled (set to true) and + * {@code DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES} disabled (set to false). + * + * @return a new ObjectMapper with the {@code jjwt-jackson} module registered and + * {@code JsonParser.Feature.STRICT_DUPLICATE_DETECTION} enabled (set to true) and + * {@code DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES} disabled (set to false). + * + * @since 0.12.4 + */ + // package protected on purpose, do not expose to the public API + static ObjectMapper newObjectMapper() { + return new ObjectMapper() + .registerModule(MODULE) + .configure(JsonParser.Feature.STRICT_DUPLICATE_DETECTION, true) // https://github.com/jwtk/jjwt/issues/877 + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // https://github.com/jwtk/jjwt/issues/893 + } + + protected final ObjectMapper objectMapper; + + /** + * Constructor using JJWT's default {@link ObjectMapper} singleton for serialization. + */ public JacksonSerializer() { this(DEFAULT_OBJECT_MAPPER); } - @SuppressWarnings("WeakerAccess") //intended for end-users to use when providing a custom ObjectMapper + /** + * Creates a new Jackson Serializer that uses the specified {@link ObjectMapper} for serialization. + * + * @param objectMapper the ObjectMapper to use for serialization. + */ public JacksonSerializer(ObjectMapper objectMapper) { Assert.notNull(objectMapper, "ObjectMapper cannot be null."); - this.objectMapper = objectMapper; + this.objectMapper = objectMapper.registerModule(MODULE); } @Override - public byte[] serialize(T t) throws SerializationException { - Assert.notNull(t, "Object to serialize cannot be null."); - try { - return writeValueAsBytes(t); - } catch (JsonProcessingException e) { - String msg = "Unable to serialize object: " + e.getMessage(); - throw new SerializationException(msg, e); - } + protected void doSerialize(T t, OutputStream out) throws Exception { + Assert.notNull(out, "OutputStream cannot be null."); + ObjectWriter writer = this.objectMapper.writer().without(JsonGenerator.Feature.AUTO_CLOSE_TARGET); + writer.writeValue(out, t); } - - @SuppressWarnings("WeakerAccess") //for testing - protected byte[] writeValueAsBytes(T t) throws JsonProcessingException { - return this.objectMapper.writeValueAsBytes(t); - } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/jackson/io/JacksonSupplierSerializer.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/jackson/io/JacksonSupplierSerializer.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/jackson/io/JacksonSupplierSerializer.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 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.jackson.io; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import io.jsonwebtoken.lang.Supplier; + +import java.io.IOException; + +final class JacksonSupplierSerializer extends StdSerializer> { + + static final JacksonSupplierSerializer INSTANCE = new JacksonSupplierSerializer(); + + public JacksonSupplierSerializer() { + super(Supplier.class, false); + } + + @Override + public void serialize(Supplier supplier, JsonGenerator generator, SerializerProvider provider) throws IOException { + Object value = supplier.get(); + + if (value == null) { + provider.defaultSerializeNull(generator); + return; + } + + Class clazz = value.getClass(); + JsonSerializer ser = provider.findTypedValueSerializer(clazz, true, null); + ser.serialize(value, generator, provider); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Arrays.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Arrays.java (.../Arrays.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Arrays.java (.../Arrays.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,18 +15,105 @@ */ package io.jsonwebtoken.lang; +import java.lang.reflect.Array; +import java.util.List; + /** + * Utility methods to work with array instances. + * * @since 0.6 */ public final class Arrays { - private Arrays(){} //prevent instantiation + private Arrays() { + } //prevent instantiation + /** + * Returns the length of the array, or {@code 0} if the array is {@code null}. + * + * @param a the possibly-null array + * @param the type of elements in the array + * @return the length of the array, or zero if the array is null. + */ + public static int length(T[] a) { + return a == null ? 0 : a.length; + } + + /** + * Converts the specified array to a {@link List}. If the array is empty, an empty list will be returned. + * + * @param a the array to represent as a list + * @param the type of elements in the array + * @return the array as a list, or an empty list if the array is empty. + */ + public static List asList(T[] a) { + return Objects.isEmpty(a) ? Collections.emptyList() : java.util.Arrays.asList(a); + } + + /** + * Returns the length of the specified byte array, or {@code 0} if the byte array is {@code null}. + * + * @param bytes the array to check + * @return the length of the specified byte array, or {@code 0} if the byte array is {@code null}. + */ public static int length(byte[] bytes) { return bytes != null ? bytes.length : 0; } + /** + * Returns the byte array unaltered if it is non-null and has a positive length, otherwise {@code null}. + * + * @param bytes the byte array to check. + * @return the byte array unaltered if it is non-null and has a positive length, otherwise {@code null}. + */ public static byte[] clean(byte[] bytes) { return length(bytes) > 0 ? bytes : null; } + + /** + * Creates a shallow copy of the specified object or array. + * + * @param obj the object to copy + * @return a shallow copy of the specified object or array. + */ + public static Object copy(Object obj) { + if (obj == null) { + return null; + } + Assert.isTrue(Objects.isArray(obj), "Argument must be an array."); + if (obj instanceof Object[]) { + return ((Object[]) obj).clone(); + } + if (obj instanceof boolean[]) { + return ((boolean[]) obj).clone(); + } + if (obj instanceof byte[]) { + return ((byte[]) obj).clone(); + } + if (obj instanceof char[]) { + return ((char[]) obj).clone(); + } + if (obj instanceof double[]) { + return ((double[]) obj).clone(); + } + if (obj instanceof float[]) { + return ((float[]) obj).clone(); + } + if (obj instanceof int[]) { + return ((int[]) obj).clone(); + } + if (obj instanceof long[]) { + return ((long[]) obj).clone(); + } + if (obj instanceof short[]) { + return ((short[]) obj).clone(); + } + Class componentType = obj.getClass().getComponentType(); + int length = Array.getLength(obj); + Object[] copy = (Object[]) Array.newInstance(componentType, length); + for (int i = 0; i < length; i++) { + copy[i] = Array.get(obj, i); + } + return copy; + } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Assert.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Assert.java (.../Assert.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Assert.java (.../Assert.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -18,16 +18,22 @@ import java.util.Collection; import java.util.Map; +/** + * Utility methods for providing argument and state assertions to reduce repeating these patterns and otherwise + * increasing cyclomatic complexity. + */ public final class Assert { - private Assert(){} //prevent instantiation + private Assert() { + } //prevent instantiation /** * Assert a boolean expression, throwing IllegalArgumentException * if the test result is false. *
    Assert.isTrue(i > 0, "The value must be greater than zero");
    + * * @param expression a boolean expression - * @param message the exception message to use if the assertion fails + * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if expression is false */ public static void isTrue(boolean expression, String message) { @@ -40,6 +46,7 @@ * Assert a boolean expression, throwing IllegalArgumentException * if the test result is false. *
    Assert.isTrue(i > 0);
    + * * @param expression a boolean expression * @throws IllegalArgumentException if expression is false */ @@ -50,7 +57,8 @@ /** * Assert that an object is null . *
    Assert.isNull(value, "The value must be null");
    - * @param object the object to check + * + * @param object the object to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object is not null */ @@ -63,6 +71,7 @@ /** * Assert that an object is null . *
    Assert.isNull(value);
    + * * @param object the object to check * @throws IllegalArgumentException if the object is not null */ @@ -73,19 +82,24 @@ /** * Assert that an object is not null . *
    Assert.notNull(clazz, "The class must not be null");
    - * @param object the object to check + * + * @param object the object to check + * @param the type of object * @param message the exception message to use if the assertion fails + * @return the non-null object * @throws IllegalArgumentException if the object is null */ - public static void notNull(Object object, String message) { + public static T notNull(T object, String message) { if (object == null) { throw new IllegalArgumentException(message); } + return object; } /** * Assert that an object is not null . *
    Assert.notNull(clazz);
    + * * @param object the object to check * @throws IllegalArgumentException if the object is null */ @@ -97,7 +111,8 @@ * Assert that the given String is not empty; that is, * it must not be null and not the empty String. *
    Assert.hasLength(name, "Name must not be empty");
    - * @param text the String to check + * + * @param text the String to check * @param message the exception message to use if the assertion fails * @see Strings#hasLength */ @@ -111,32 +126,38 @@ * Assert that the given String is not empty; that is, * it must not be null and not the empty String. *
    Assert.hasLength(name);
    + * * @param text the String to check * @see Strings#hasLength */ public static void hasLength(String text) { hasLength(text, - "[Assertion failed] - this String argument must have length; it must not be null or empty"); + "[Assertion failed] - this String argument must have length; it must not be null or empty"); } /** * Assert that the given String has valid text content; that is, it must not * be null and must contain at least one non-whitespace character. *
    Assert.hasText(name, "'name' must not be empty");
    - * @param text the String to check + * + * @param the type of CharSequence + * @param text the CharSequence to check * @param message the exception message to use if the assertion fails + * @return the CharSequence if it has text * @see Strings#hasText */ - public static void hasText(String text, String message) { + public static T hasText(T text, String message) { if (!Strings.hasText(text)) { throw new IllegalArgumentException(message); } + return text; } /** * Assert that the given String has valid text content; that is, it must not * be null and must contain at least one non-whitespace character. *
    Assert.hasText(name, "'name' must not be empty");
    + * * @param text the String to check * @see Strings#hasText */ @@ -148,65 +169,98 @@ /** * Assert that the given text does not contain the given substring. *
    Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");
    + * * @param textToSearch the text to search - * @param substring the substring to find within the text - * @param message the exception message to use if the assertion fails + * @param substring the substring to find within the text + * @param message the exception message to use if the assertion fails */ public static void doesNotContain(String textToSearch, String substring, String message) { if (Strings.hasLength(textToSearch) && Strings.hasLength(substring) && - textToSearch.indexOf(substring) != -1) { + textToSearch.indexOf(substring) != -1) { throw new IllegalArgumentException(message); } } /** * Assert that the given text does not contain the given substring. *
    Assert.doesNotContain(name, "rod");
    + * * @param textToSearch the text to search - * @param substring the substring to find within the text + * @param substring the substring to find within the text */ public static void doesNotContain(String textToSearch, String substring) { doesNotContain(textToSearch, substring, - "[Assertion failed] - this String argument must not contain the substring [" + substring + "]"); + "[Assertion failed] - this String argument must not contain the substring [" + substring + "]"); } /** * Assert that an array has elements; that is, it must not be * null and must have at least one element. *
    Assert.notEmpty(array, "The array must have elements");
    - * @param array the array to check + * + * @param array the array to check * @param message the exception message to use if the assertion fails + * @return the non-empty array for immediate use * @throws IllegalArgumentException if the object array is null or has no elements */ - public static void notEmpty(Object[] array, String message) { + public static Object[] notEmpty(Object[] array, String message) { if (Objects.isEmpty(array)) { throw new IllegalArgumentException(message); } + return array; } /** * Assert that an array has elements; that is, it must not be * null and must have at least one element. *
    Assert.notEmpty(array);
    + * * @param array the array to check * @throws IllegalArgumentException if the object array is null or has no elements */ public static void notEmpty(Object[] array) { notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element"); } - public static void notEmpty(byte[] array, String msg) { + /** + * Assert that the specified byte array is not null and has at least one byte element. + * + * @param array the byte array to check + * @param msg the exception message to use if the assertion fails + * @return the byte array if the assertion passes + * @throws IllegalArgumentException if the byte array is null or empty + * @since 0.12.0 + */ + public static byte[] notEmpty(byte[] array, String msg) { if (Objects.isEmpty(array)) { throw new IllegalArgumentException(msg); } + return array; } /** + * Assert that the specified character array is not null and has at least one byte element. + * + * @param chars the character array to check + * @param msg the exception message to use if the assertion fails + * @return the character array if the assertion passes + * @throws IllegalArgumentException if the character array is null or empty + * @since 0.12.0 + */ + public static char[] notEmpty(char[] chars, String msg) { + if (Objects.isEmpty(chars)) { + throw new IllegalArgumentException(msg); + } + return chars; + } + + /** * Assert that an array has no null elements. * Note: Does not complain if the array is empty! *
    Assert.noNullElements(array, "The array must have non-null elements");
    - * @param array the array to check + * + * @param array the array to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object array contains a null element */ @@ -224,6 +278,7 @@ * Assert that an array has no null elements. * Note: Does not complain if the array is empty! *
    Assert.noNullElements(array);
    + * * @param array the array to check * @throws IllegalArgumentException if the object array contains a null element */ @@ -235,46 +290,56 @@ * Assert that a collection has elements; that is, it must not be * null and must have at least one element. *
    Assert.notEmpty(collection, "Collection must have elements");
    + * * @param collection the collection to check - * @param message the exception message to use if the assertion fails + * @param the type of collection + * @param message the exception message to use if the assertion fails + * @return the non-null, non-empty collection * @throws IllegalArgumentException if the collection is null or has no elements */ - public static void notEmpty(Collection collection, String message) { + public static > T notEmpty(T collection, String message) { if (Collections.isEmpty(collection)) { throw new IllegalArgumentException(message); } + return collection; } /** * Assert that a collection has elements; that is, it must not be * null and must have at least one element. *
    Assert.notEmpty(collection, "Collection must have elements");
    + * * @param collection the collection to check * @throws IllegalArgumentException if the collection is null or has no elements */ - public static void notEmpty(Collection collection) { + public static void notEmpty(Collection collection) { notEmpty(collection, - "[Assertion failed] - this collection must not be empty: it must contain at least 1 element"); + "[Assertion failed] - this collection must not be empty: it must contain at least 1 element"); } /** * Assert that a Map has entries; that is, it must not be null * and must have at least one entry. *
    Assert.notEmpty(map, "Map must have entries");
    - * @param map the map to check + * + * @param map the map to check + * @param the type of Map to check * @param message the exception message to use if the assertion fails + * @return the non-null, non-empty map * @throws IllegalArgumentException if the map is null or has no entries */ - public static void notEmpty(Map map, String message) { + public static > T notEmpty(T map, String message) { if (Collections.isEmpty(map)) { throw new IllegalArgumentException(message); } + return map; } /** * Assert that a Map has entries; that is, it must not be null * and must have at least one entry. *
    Assert.notEmpty(map);
    + * * @param map the map to check * @throws IllegalArgumentException if the map is null or has no entries */ @@ -286,41 +351,75 @@ /** * Assert that the provided object is an instance of the provided class. *
    Assert.instanceOf(Foo.class, foo);
    + * + * @param the type of instance expected * @param clazz the required class - * @param obj the object to check + * @param obj the object to check + * @return the expected instance of type {@code T} * @throws IllegalArgumentException if the object is not an instance of clazz * @see Class#isInstance */ - public static void isInstanceOf(Class clazz, Object obj) { - isInstanceOf(clazz, obj, ""); + public static T isInstanceOf(Class clazz, Object obj) { + return isInstanceOf(clazz, obj, ""); } /** * Assert that the provided object is an instance of the provided class. *
    Assert.instanceOf(Foo.class, foo);
    - * @param type the type to check against - * @param obj the object to check + * + * @param type the type to check against + * @param the object's expected type + * @param obj the object to check * @param message a message which will be prepended to the message produced by - * the function itself, and which may be used to provide context. It should - * normally end in a ": " or ". " so that the function generate message looks - * ok when prepended to it. + * the function itself, and which may be used to provide context. It should + * normally end in a ": " or ". " so that the function generate message looks + * ok when prepended to it. + * @return the non-null object IFF it is an instance of the specified {@code type}. * @throws IllegalArgumentException if the object is not an instance of clazz * @see Class#isInstance */ - public static void isInstanceOf(Class type, Object obj, String message) { + public static T isInstanceOf(Class type, Object obj, String message) { notNull(type, "Type to check against must not be null"); if (!type.isInstance(obj)) { throw new IllegalArgumentException(message + - "Object of class [" + (obj != null ? obj.getClass().getName() : "null") + - "] must be an instance of " + type); + "Object of class [" + (obj != null ? obj.getClass().getName() : "null") + + "] must be an instance of " + type); } + return type.cast(obj); } /** + * Asserts that the provided object is an instance of the provided class, throwing an + * {@link IllegalStateException} otherwise. + *
    Assert.stateIsInstance(Foo.class, foo);
    + * + * @param type the type to check against + * @param the object's expected type + * @param obj the object to check + * @param message a message which will be prepended to the message produced by + * the function itself, and which may be used to provide context. It should + * normally end in a ": " or ". " so that the function generate message looks + * ok when prepended to it. + * @return the non-null object IFF it is an instance of the specified {@code type}. + * @throws IllegalStateException if the object is not an instance of clazz + * @see Class#isInstance + */ + public static T stateIsInstance(Class type, Object obj, String message) { + notNull(type, "Type to check cannot be null."); + if (!type.isInstance(obj)) { + String msg = message + "Object of class [" + Objects.nullSafeClassName(obj) + + "] must be an instance of " + type; + throw new IllegalStateException(msg); + } + return type.cast(obj); + } + + /** * Assert that superType.isAssignableFrom(subType) is true. *
    Assert.isAssignable(Number.class, myClass);
    + * * @param superType the super type to check - * @param subType the sub type to check + * @param subType the sub type to check * @throws IllegalArgumentException if the classes are not assignable */ public static void isAssignable(Class superType, Class subType) { @@ -330,12 +429,13 @@ /** * Assert that superType.isAssignableFrom(subType) is true. *
    Assert.isAssignable(Number.class, myClass);
    + * * @param superType the super type to check against - * @param subType the sub type to check - * @param message a message which will be prepended to the message produced by - * the function itself, and which may be used to provide context. It should - * normally end in a ": " or ". " so that the function generate message looks - * ok when prepended to it. + * @param subType the sub type to check + * @param message a message which will be prepended to the message produced by + * the function itself, and which may be used to provide context. It should + * normally end in a ": " or ". " so that the function generate message looks + * ok when prepended to it. * @throws IllegalArgumentException if the classes are not assignable */ public static void isAssignable(Class superType, Class subType, String message) { @@ -345,14 +445,75 @@ } } + /** + * Asserts that a specified {@code value} is equal to the given {@code requirement}, throwing + * an {@link IllegalArgumentException} with the given message if not. + * + * @param the type of argument + * @param value the value to check + * @param requirement the requirement that {@code value} must be greater than + * @param msg the message to use for the {@code IllegalArgumentException} if thrown. + * @return {@code value} if greater than the specified {@code requirement}. + * @since 0.12.0 + */ + public static > T eq(T value, T requirement, String msg) { + if (compareTo(value, requirement) != 0) { + throw new IllegalArgumentException(msg); + } + return value; + } + private static > int compareTo(T value, T requirement) { + notNull(value, "value cannot be null."); + notNull(requirement, "requirement cannot be null."); + return value.compareTo(requirement); + } + /** + * Asserts that a specified {@code value} is greater than the given {@code requirement}, throwing + * an {@link IllegalArgumentException} with the given message if not. + * + * @param the type of value to check and return if the requirement is met + * @param value the value to check + * @param requirement the requirement that {@code value} must be greater than + * @param msg the message to use for the {@code IllegalArgumentException} if thrown. + * @return {@code value} if greater than the specified {@code requirement}. + * @since 0.12.0 + */ + public static > T gt(T value, T requirement, String msg) { + if (!(compareTo(value, requirement) > 0)) { + throw new IllegalArgumentException(msg); + } + return value; + } + + /** + * Asserts that a specified {@code value} is less than or equal to the given {@code requirement}, throwing + * an {@link IllegalArgumentException} with the given message if not. + * + * @param the type of value to check and return if the requirement is met + * @param value the value to check + * @param requirement the requirement that {@code value} must be greater than + * @param msg the message to use for the {@code IllegalArgumentException} if thrown. + * @return {@code value} if greater than the specified {@code requirement}. + * @since 0.12.0 + */ + public static > T lte(T value, T requirement, String msg) { + if (compareTo(value, requirement) > 0) { + throw new IllegalArgumentException(msg); + } + return value; + } + + + /** * Assert a boolean expression, throwing IllegalStateException * if the test result is false. Call isTrue if you wish to * throw IllegalArgumentException on an assertion failure. *
    Assert.state(id == null, "The id property must not already be initialized");
    + * * @param expression a boolean expression - * @param message the exception message to use if the assertion fails + * @param message the exception message to use if the assertion fails * @throws IllegalStateException if expression is false */ public static void state(boolean expression, String message) { @@ -367,11 +528,31 @@ *

    Call {@link #isTrue(boolean)} if you wish to * throw {@link IllegalArgumentException} on an assertion failure. *

    Assert.state(id == null);
    + * * @param expression a boolean expression * @throws IllegalStateException if the supplied expression is false */ public static void state(boolean expression) { state(expression, "[Assertion failed] - this state invariant must be true"); } + /** + * Asserts that the specified {@code value} is not null, otherwise throws an + * {@link IllegalStateException} with the specified {@code msg}. Intended to be used with + * code invariants (as opposed to method arguments, like {@link #notNull(Object)}). + * + * @param value value to assert is not null + * @param msg exception message to use if {@code value} is null + * @param value type + * @return the non-null value + * @throws IllegalStateException with the specified {@code msg} if {@code value} is null. + * @since 0.12.0 + */ + public static T stateNotNull(T value, String msg) throws IllegalStateException { + if (value == null) { + throw new IllegalStateException(msg); + } + return value; + } + } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Builder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Builder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Builder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2022 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; + +/** + * Type-safe interface that reflects the Builder pattern. + * + * @param The type of object that will be created when {@link #build()} is invoked. + * @since 0.12.0 + */ +public interface Builder { + + /** + * Creates and returns a new instance of type {@code T}. + * + * @return a new instance of type {@code T}. + */ + T build(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Classes.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Classes.java (.../Classes.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Classes.java (.../Classes.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -17,41 +17,38 @@ import java.io.InputStream; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.net.URL; /** + * Utility methods for working with {@link Class}es. + * * @since 0.1 */ public final class Classes { - private Classes() {} //prevent instantiation + private Classes() { + } //prevent instantiation - /** - * @since 0.1 - */ private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() { @Override - protected ClassLoader doGetClassLoader() throws Throwable { + protected ClassLoader doGetClassLoader() { return Thread.currentThread().getContextClassLoader(); } }; - /** - * @since 0.1 - */ private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() { @Override - protected ClassLoader doGetClassLoader() throws Throwable { + protected ClassLoader doGetClassLoader() { return Classes.class.getClassLoader(); } }; - /** - * @since 0.1 - */ private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() { @Override - protected ClassLoader doGetClassLoader() throws Throwable { + protected ClassLoader doGetClassLoader() { return ClassLoader.getSystemClassLoader(); } }; @@ -65,13 +62,14 @@ * the JRE's ClassNotFoundException. * * @param fqcn the fully qualified class name to load + * @param The type of Class returned * @return the located class * @throws UnknownClassException if the class cannot be found. */ @SuppressWarnings("unchecked") public static Class forName(String fqcn) throws UnknownClassException { - Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn); + Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn); if (clazz == null) { clazz = CLASS_CL_ACCESSOR.loadClass(fqcn); @@ -92,7 +90,7 @@ throw new UnknownClassException(msg); } - return clazz; + return (Class) clazz; } /** @@ -122,6 +120,37 @@ return is; } + /** + * Returns the specified resource URL by checking the current thread's + * {@link Thread#getContextClassLoader() context class loader}, then the + * current ClassLoader (Classes.class.getClassLoader()), then the system/application + * ClassLoader (ClassLoader.getSystemClassLoader(), in that order, using + * {@link ClassLoader#getResource(String) getResource(name)}. + * + * @param name the name of the resource to acquire from the classloader(s). + * @return the URL of the resource found, or null if the resource cannot be found from any + * of the three mentioned ClassLoaders. + * @since 0.12.0 + */ + private static URL getResource(String name) { + URL url = THREAD_CL_ACCESSOR.getResource(name); + if (url == null) { + url = CLASS_CL_ACCESSOR.getResource(name); + } + if (url == null) { + return SYSTEM_CL_ACCESSOR.getResource(name); + } + return url; + } + + /** + * Returns {@code true} if the specified {@code fullyQualifiedClassName} can be found in any of the thread + * context, class, or system classloaders, or {@code false} otherwise. + * + * @param fullyQualifiedClassName the fully qualified class name to check + * @return {@code true} if the specified {@code fullyQualifiedClassName} can be found in any of the thread + * context, class, or system classloaders, or {@code false} otherwise. + */ public static boolean isAvailable(String fullyQualifiedClassName) { try { forName(fullyQualifiedClassName); @@ -131,22 +160,56 @@ } } + /** + * Creates and returns a new instance of the class with the specified fully qualified class name using the + * classes default no-argument constructor. + * + * @param fqcn the fully qualified class name + * @param the type of object created + * @return a new instance of the specified class name + */ @SuppressWarnings("unchecked") public static T newInstance(String fqcn) { - return (T)newInstance(forName(fqcn)); + return (T) newInstance(forName(fqcn)); } - public static T newInstance(String fqcn, Class[] ctorArgTypes, Object... args) { + /** + * Creates and returns a new instance of the specified fully qualified class name using the + * specified {@code args} arguments provided to the constructor with {@code ctorArgTypes} + * + * @param fqcn the fully qualified class name + * @param ctorArgTypes the argument types of the constructor to invoke + * @param args the arguments to supply when invoking the constructor + * @param the type of object created + * @return the newly created object + */ + public static T newInstance(String fqcn, Class[] ctorArgTypes, Object... args) { Class clazz = forName(fqcn); Constructor ctor = getConstructor(clazz, ctorArgTypes); return instantiate(ctor, args); } + /** + * Creates and returns a new instance of the specified fully qualified class name using a constructor that matches + * the specified {@code args} arguments. + * + * @param fqcn fully qualified class name + * @param args the arguments to supply to the constructor + * @param the type of the object created + * @return the newly created object + */ @SuppressWarnings("unchecked") public static T newInstance(String fqcn, Object... args) { - return (T)newInstance(forName(fqcn), args); + return (T) newInstance(forName(fqcn), args); } + /** + * Creates a new instance of the specified {@code clazz} via {@code clazz.newInstance()}. + * + * @param clazz the class to invoke + * @param the type of the object created + * @return the newly created object + */ public static T newInstance(Class clazz) { if (clazz == null) { String msg = "Class method parameter cannot be null."; @@ -159,16 +222,35 @@ } } + /** + * Returns a new instance of the specified {@code clazz}, invoking the associated constructor with the specified + * {@code args} arguments. + * + * @param clazz the class to invoke + * @param args the arguments matching an associated class constructor + * @param the type of the created object + * @return the newly created object + */ public static T newInstance(Class clazz, Object... args) { - Class[] argTypes = new Class[args.length]; + Class[] argTypes = new Class[args.length]; for (int i = 0; i < args.length; i++) { argTypes[i] = args[i].getClass(); } Constructor ctor = getConstructor(clazz, argTypes); return instantiate(ctor, args); } - public static Constructor getConstructor(Class clazz, Class... argTypes) { + /** + * Returns the {@link Constructor} for the specified {@code Class} with arguments matching the specified + * {@code argTypes}. + * + * @param clazz the class to inspect + * @param argTypes the argument types for the desired constructor + * @param the type of object to create + * @return the constructor matching the specified argument types + * @throws IllegalStateException if the constructor for the specified {@code argTypes} does not exist. + */ + public static Constructor getConstructor(Class clazz, Class... argTypes) throws IllegalStateException { try { return clazz.getConstructor(argTypes); } catch (NoSuchMethodException e) { @@ -177,6 +259,16 @@ } + /** + * Creates a new object using the specified {@link Constructor}, invoking it with the specified constructor + * {@code args} arguments. + * + * @param ctor the constructor to invoke + * @param args the arguments to supply to the constructor + * @param the type of object to create + * @return the new object instance + * @throws InstantiationException if the constructor cannot be invoked successfully + */ public static T instantiate(Constructor ctor, Object... args) { try { return ctor.newInstance(args); @@ -187,28 +279,88 @@ } /** + * Invokes the fully qualified class name's method named {@code methodName} with parameters of type {@code argTypes} + * using the {@code args} as the method arguments. + * + * @param fqcn fully qualified class name to locate + * @param methodName name of the method to invoke on the class + * @param argTypes the method argument types supported by the {@code methodName} method + * @param args the runtime arguments to use when invoking the located class method + * @param the expected type of the object returned from the invoked method. + * @return the result returned by the invoked method * @since 0.10.0 */ + public static T invokeStatic(String fqcn, String methodName, Class[] argTypes, Object... args) { + try { + Class clazz = Classes.forName(fqcn); + return invokeStatic(clazz, methodName, argTypes, args); + } catch (Exception e) { + String msg = "Unable to invoke class method " + fqcn + "#" + methodName + ". Ensure the necessary " + + "implementation is in the runtime classpath."; + throw new IllegalStateException(msg, e); + } + } + + /** + * Invokes the {@code clazz}'s matching static method (named {@code methodName} with exact argument types + * of {@code argTypes}) with the given {@code args} arguments, and returns the method return value. + * + * @param clazz the class to invoke + * @param methodName the name of the static method on {@code clazz} to invoke + * @param argTypes the types of the arguments accepted by the method + * @param args the actual runtime arguments to use when invoking the method + * @param the type of object expected to be returned from the method + * @return the result returned by the invoked method. + * @since 0.12.0 + */ @SuppressWarnings("unchecked") - public static T invokeStatic(String fqcn, String methodName, Class[] argTypes, Object... args) { + public static T invokeStatic(Class clazz, String methodName, Class[] argTypes, Object... args) { try { - Class clazz = Classes.forName(fqcn); Method method = clazz.getDeclaredMethod(methodName, argTypes); method.setAccessible(true); - return(T)method.invoke(null, args); - } catch (Exception e) { - String msg = "Unable to invoke class method " + fqcn + "#" + methodName + ". Ensure the necessary " + - "implementation is in the runtime classpath."; + return (T) method.invoke(null, args); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw ((RuntimeException) cause); //propagate + } + String msg = "Unable to invoke class method " + clazz.getName() + "#" + methodName + + ". Ensure the necessary implementation is in the runtime classpath."; throw new IllegalStateException(msg, e); } } /** + * 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 field instance value type + * @return the field value + */ + public static T getFieldValue(Object instance, String fieldName, Class fieldType) { + if (instance == null) return null; + try { + Field field = instance.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + Object o = field.get(instance); + return fieldType.cast(o); + } catch (Throwable t) { + String msg = "Unable to read field " + instance.getClass().getName() + + "#" + fieldName + ": " + t.getMessage(); + throw new IllegalStateException(msg, t); + } + } + + /** * @since 1.0 */ - private static interface ClassLoaderAccessor { - Class loadClass(String fqcn); + private interface ClassLoaderAccessor { + Class loadClass(String fqcn); + URL getResource(String name); + InputStream getResourceStream(String name); } @@ -217,8 +369,8 @@ */ private static abstract class ExceptionIgnoringAccessor implements ClassLoaderAccessor { - public Class loadClass(String fqcn) { - Class clazz = null; + public Class loadClass(String fqcn) { + Class clazz = null; ClassLoader cl = getClassLoader(); if (cl != null) { try { @@ -230,6 +382,16 @@ return clazz; } + @Override + public URL getResource(String name) { + URL url = null; + ClassLoader cl = getClassLoader(); + if (cl != null) { + url = cl.getResource(name); + } + return url; + } + public InputStream getResourceStream(String name) { InputStream is = null; ClassLoader cl = getClassLoader(); Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/CollectionMutator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/CollectionMutator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/CollectionMutator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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 the type of elements in the collection + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface CollectionMutator> { + + /** + * 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 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); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Collections.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Collections.java (.../Collections.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Collections.java (.../Collections.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -20,22 +20,197 @@ import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; +/** + * Utility methods for working with {@link Collection}s, {@link List}s, {@link Set}s, and {@link Maps}. + */ +@SuppressWarnings({"unused", "rawtypes"}) public final class Collections { - private Collections(){} //prevent instantiation + private Collections() { + } //prevent instantiation /** + * Returns a type-safe immutable empty {@code List}. + * + * @param list element type + * @return a type-safe immutable empty {@code List}. + */ + public static List emptyList() { + return java.util.Collections.emptyList(); + } + + /** + * Returns a type-safe immutable empty {@code Set}. + * + * @param set element type + * @return a type-safe immutable empty {@code Set}. + */ + @SuppressWarnings("unused") + public static Set emptySet() { + return java.util.Collections.emptySet(); + } + + /** + * Returns a type-safe immutable empty {@code Map}. + * + * @param map key type + * @param map value type + * @return a type-safe immutable empty {@code Map}. + */ + @SuppressWarnings("unused") + public static Map emptyMap() { + return java.util.Collections.emptyMap(); + } + + /** + * Returns a type-safe immutable {@code List} containing the specified array elements. + * + * @param elements array elements to include in the list + * @param list element type + * @return a type-safe immutable {@code List} containing the specified array elements. + */ + @SafeVarargs + public static List of(T... elements) { + if (elements == null || elements.length == 0) { + return java.util.Collections.emptyList(); + } + return java.util.Collections.unmodifiableList(Arrays.asList(elements)); + } + + /** + * Returns the specified collection as a {@link Set} instance. + * + * @param c the collection to represent as a set + * @param collection element type + * @return a type-safe immutable {@code Set} containing the specified collection elements. + * @since 0.12.0 + */ + public static Set asSet(Collection c) { + if (c instanceof Set) { + return (Set) c; + } + if (isEmpty(c)) { + return java.util.Collections.emptySet(); + } + return java.util.Collections.unmodifiableSet(new LinkedHashSet<>(c)); + } + + /** + * Returns a type-safe immutable {@code Set} containing the specified array elements. + * + * @param elements array elements to include in the set + * @param set element type + * @return a type-safe immutable {@code Set} containing the specified array elements. + */ + @SafeVarargs + public static Set setOf(T... elements) { + if (elements == null || elements.length == 0) { + return java.util.Collections.emptySet(); + } + Set set = new LinkedHashSet<>(Arrays.asList(elements)); + return immutable(set); + } + + /** + * Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableList(List)} so both classes + * don't need to be imported. + * + * @param m map to wrap in an immutable/unmodifiable collection + * @param map key type + * @param map value type + * @return an immutable wrapper for {@code m}. + * @since 0.12.0 + */ + public static Map immutable(Map m) { + return m != null ? java.util.Collections.unmodifiableMap(m) : null; + } + + /** + * Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableSet(Set)} so both classes don't + * need to be imported. + * + * @param set set to wrap in an immutable Set + * @param set element type + * @return an immutable wrapper for {@code set} + */ + public static Set immutable(Set set) { + return set != null ? java.util.Collections.unmodifiableSet(set) : null; + } + + /** + * Shorter null-safe convenience alias for {@link java.util.Collections#unmodifiableList(List)} so both classes + * don't need to be imported. + * + * @param list list to wrap in an immutable List + * @param list element type + * @return an immutable wrapper for {@code list} + */ + public static List immutable(List list) { + return list != null ? java.util.Collections.unmodifiableList(list) : null; + } + + /** + * Null-safe factory method that returns an immutable/unmodifiable view of the specified collection instance. + * Works for {@link List}, {@link Set} and {@link Collection} arguments. + * + * @param c collection to wrap in an immutable/unmodifiable collection + * @param type of collection + * @param type of elements in the collection + * @return an immutable wrapper for {@code l}. + * @since 0.12.0 + */ + @SuppressWarnings("unchecked") + public static > C immutable(C c) { + if (c == null) { + return null; + } else if (c instanceof Set) { + return (C) java.util.Collections.unmodifiableSet((Set) c); + } else if (c instanceof List) { + return (C) java.util.Collections.unmodifiableList((List) c); + } else { + return (C) java.util.Collections.unmodifiableCollection(c); + } + } + + /** + * Returns a non-null set, either {@code s} if it is not null, or {@link #emptySet()} otherwise. + * + * @param s the set to check for null + * @param type of elements in the set + * @return a non-null set, either {@code s} if it is not null, or {@link #emptySet()} otherwise. + * @since 0.12.0 + */ + public static Set nullSafe(Set s) { + return s == null ? Collections.emptySet() : s; + } + + /** + * Returns a non-null collection, either {@code c} if it is not null, or {@link #emptyList()} otherwise. + * + * @param c the collection to check for null + * @param type of elements in the collection + * @return a non-null collection, either {@code c} if it is not null, or {@link #emptyList()} otherwise. + * @since 0.12.0 + */ + public static Collection nullSafe(Collection c) { + return c == null ? Collections.emptyList() : c; + } + + /** * Return true if the supplied Collection is null * or empty. Otherwise, return false. + * * @param collection the Collection to check * @return whether the given Collection is empty */ - public static boolean isEmpty(Collection collection) { - return (collection == null || collection.isEmpty()); + public static boolean isEmpty(Collection collection) { + return size(collection) == 0; } /** @@ -45,7 +220,7 @@ * @return the collection's size or {@code 0} if the collection is {@code null}. * @since 0.9.2 */ - public static int size(Collection collection) { + public static int size(Collection collection) { return collection == null ? 0 : collection.size(); } @@ -56,25 +231,27 @@ * @return the map's size or {@code 0} if the map is {@code null}. * @since 0.9.2 */ - public static int size(Map map) { + public static int size(Map map) { return map == null ? 0 : map.size(); } /** * Return true if the supplied Map is null * or empty. Otherwise, return false. + * * @param map the Map to check * @return whether the given Map is empty */ - public static boolean isEmpty(Map map) { - return (map == null || map.isEmpty()); + public static boolean isEmpty(Map map) { + return size(map) == 0; } /** * Convert the supplied array into a List. A primitive array gets * converted into a List of the appropriate wrapper type. *

    A null source value will be converted to an * empty List. + * * @param source the (potentially primitive) array * @return the converted List result * @see Objects#toObjectArray(Object) @@ -84,8 +261,27 @@ } /** + * Concatenate the specified set with the specified array elements, resulting in a new {@link LinkedHashSet} with + * the array elements appended to the end of the existing Set. + * + * @param c the set to append to + * @param elements the array elements to append to the end of the set + * @param set element type + * @return a new {@link LinkedHashSet} with the array elements appended to the end of the original set. + */ + @SafeVarargs + public static Set concat(Set c, T... elements) { + int size = Math.max(1, Collections.size(c) + io.jsonwebtoken.lang.Arrays.length(elements)); + Set set = new LinkedHashSet<>(size); + set.addAll(c); + java.util.Collections.addAll(set, elements); + return immutable(set); + } + + /** * Merge the given array into the given Collection. - * @param array the array to merge (may be null) + * + * @param array the array to merge (may be null) * @param collection the target Collection to merge the array into */ @SuppressWarnings("unchecked") @@ -94,26 +290,25 @@ throw new IllegalArgumentException("Collection must not be null"); } Object[] arr = Objects.toObjectArray(array); - for (Object elem : arr) { - collection.add(elem); - } + java.util.Collections.addAll(collection, arr); } /** * Merge the given Properties instance into the given Map, * copying all properties (key-value pairs) over. *

    Uses Properties.propertyNames() to even catch * default properties linked into the original Properties instance. + * * @param props the Properties instance to merge (may be null) - * @param map the target Map to merge the properties into + * @param map the target Map to merge the properties into */ @SuppressWarnings("unchecked") public static void mergePropertiesIntoMap(Properties props, Map map) { if (map == null) { throw new IllegalArgumentException("Map must not be null"); } if (props != null) { - for (Enumeration en = props.propertyNames(); en.hasMoreElements();) { + for (Enumeration en = props.propertyNames(); en.hasMoreElements(); ) { String key = (String) en.nextElement(); Object value = props.getProperty(key); if (value == null) { @@ -128,8 +323,9 @@ /** * Check whether the given Iterator contains the given element. + * * @param iterator the Iterator to check - * @param element the element to look for + * @param element the element to look for * @return true if found, false else */ public static boolean contains(Iterator iterator, Object element) { @@ -146,8 +342,9 @@ /** * Check whether the given Enumeration contains the given element. + * * @param enumeration the Enumeration to check - * @param element the element to look for + * @param element the element to look for * @return true if found, false else */ public static boolean contains(Enumeration enumeration, Object element) { @@ -166,8 +363,9 @@ * Check whether the given Collection contains the given element instance. *

    Enforces the given instance to be present, rather than returning * true for an equal element as well. + * * @param collection the Collection to check - * @param element the element to look for + * @param element the element to look for * @return true if found, false else */ public static boolean containsInstance(Collection collection, Object element) { @@ -184,7 +382,8 @@ /** * Return true if any element in 'candidates' is * contained in 'source'; otherwise returns false. - * @param source the source Collection + * + * @param source the source Collection * @param candidates the candidates to search for * @return whether any of the candidates has been found */ @@ -205,7 +404,8 @@ * 'source'. If no element in 'candidates' is present in * 'source' returns null. Iteration order is * {@link Collection} implementation specific. - * @param source the source Collection + * + * @param source the source Collection * @param candidates the candidates to search for * @return the first present object, or null if not found */ @@ -223,8 +423,10 @@ /** * Find a single value of the given type in the given Collection. + * * @param collection the Collection to search - * @param type the type to look for + * @param type the type to look for + * @param the generic type parameter for {@code type} * @return a value of the given type found if there is a clear match, * or null if none or more than one such value found */ @@ -250,8 +452,9 @@ * Find a single value of one of the given types in the given Collection: * searching the Collection for a value of the first type, then * searching for a value of the second type, etc. + * * @param collection the collection to search - * @param types the types to look for, in prioritized order + * @param types the types to look for, in prioritized order * @return a value of one of the given types found if there is a clear match, * or null if none or more than one such value found */ @@ -270,6 +473,7 @@ /** * Determine whether the given Collection only contains a single unique object. + * * @param collection the Collection to check * @return true if the collection contains a single reference or * multiple references to the same instance, false else @@ -284,8 +488,7 @@ if (!hasCandidate) { hasCandidate = true; candidate = elem; - } - else if (candidate != elem) { + } else if (candidate != elem) { return false; } } @@ -294,6 +497,7 @@ /** * Find the common element type of the given Collection, if any. + * * @param collection the Collection to check * @return the common element type, or null if no clear * common type has been found (or the collection was empty) @@ -307,8 +511,7 @@ if (val != null) { if (candidate == null) { candidate = val.getClass(); - } - else if (candidate != val.getClass()) { + } else if (candidate != val.getClass()) { return null; } } @@ -320,9 +523,15 @@ * Marshal the elements from the given enumeration into an array of the given type. * Enumeration elements must be assignable to the type of the given array. The array * returned will be a different instance than the array given. + * + * @param enumeration the collection to convert to an array + * @param array an array instance that matches the type of array to return + * @param the element type of the array that will be created + * @param the element type contained within the enumeration. + * @return a new array of type {@code A} that contains the elements in the specified {@code enumeration}. */ - public static A[] toArray(Enumeration enumeration, A[] array) { - ArrayList elements = new ArrayList(); + public static A[] toArray(Enumeration enumeration, A[] array) { + ArrayList elements = new ArrayList<>(); while (enumeration.hasMoreElements()) { elements.add(enumeration.nextElement()); } @@ -331,19 +540,21 @@ /** * Adapt an enumeration to an iterator. + * * @param enumeration the enumeration + * @param the type of elements in the enumeration * @return the iterator */ public static Iterator toIterator(Enumeration enumeration) { - return new EnumerationIterator(enumeration); + return new EnumerationIterator<>(enumeration); } /** * Iterator wrapping an Enumeration. */ private static class EnumerationIterator implements Iterator { - private Enumeration enumeration; + private final Enumeration enumeration; public EnumerationIterator(Enumeration enumeration) { this.enumeration = enumeration; Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Conjunctor.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Conjunctor.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Conjunctor.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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 the type of joined object to return. + * @since 0.12.0 + */ +public interface Conjunctor { + + /** + * Returns the joined object. + * + * @return the joined object. + */ + T and(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/DateFormats.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/DateFormats.java (.../DateFormats.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/DateFormats.java (.../DateFormats.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -22,10 +22,15 @@ import java.util.TimeZone; /** + * Utility methods to format and parse date strings. + * * @since 0.10.0 */ -public class DateFormats { +public final class DateFormats { + private DateFormats() { + } // prevent instantiation + private static final String ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; private static final String ISO_8601_MILLIS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; @@ -48,17 +53,40 @@ } }; + /** + * Return an ISO-8601-formatted string with millisecond precision representing the + * specified {@code date}. + * + * @param date the date for which to create an ISO-8601-formatted string + * @return the date represented as an ISO-8601-formatted string with millisecond precision. + */ public static String formatIso8601(Date date) { return formatIso8601(date, true); } + /** + * Returns an ISO-8601-formatted string with optional millisecond precision for the specified + * {@code date}. + * + * @param date the date for which to create an ISO-8601-formatted string + * @param includeMillis whether to include millisecond notation within the string. + * @return the date represented as an ISO-8601-formatted string with optional millisecond precision. + */ public static String formatIso8601(Date date, boolean includeMillis) { if (includeMillis) { return ISO_8601_MILLIS.get().format(date); } return ISO_8601.get().format(date); } + /** + * Parse the specified ISO-8601-formatted date string and return the corresponding {@link Date} instance. The + * date string may optionally contain millisecond notation, and those milliseconds will be represented accordingly. + * + * @param s the ISO-8601-formatted string to parse + * @return the string's corresponding {@link Date} instance. + * @throws ParseException if the specified date string is not a validly-formatted ISO-8601 string. + */ public static Date parseIso8601Date(String s) throws ParseException { Assert.notNull(s, "String argument cannot be null."); if (s.lastIndexOf('.') > -1) { //assume ISO-8601 with milliseconds Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/InstantiationException.java =================================================================== diff -u -rdd64f16fdf89f789b8c2179d421290dcabf15835 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/InstantiationException.java (.../InstantiationException.java) (revision dd64f16fdf89f789b8c2179d421290dcabf15835) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/InstantiationException.java (.../InstantiationException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,11 +16,19 @@ package io.jsonwebtoken.lang; /** + * {@link RuntimeException} equivalent of {@link java.lang.InstantiationException}. + * * @since 0.1 */ public class InstantiationException extends RuntimeException { - public InstantiationException(String s, Throwable t) { - super(s, t); + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public InstantiationException(String message, Throwable cause) { + super(message, cause); } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/MapMutator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/MapMutator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/MapMutator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 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.Map; + +/** + * Mutation (modifications) to a {@link Map} instance while also supporting method chaining. The Map interface's + * {@link Map#put(Object, Object)}, {@link Map#remove(Object)}, {@link Map#putAll(Map)}, and {@link Map#clear()} + * mutation methods do not support method chaining, so this interface enables that behavior. + * + * @param map key type + * @param map value type + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface MapMutator> { + + /** + * Removes the map entry with the specified key. + *

    This method is the same as {@link Map#remove Map.remove}, but instead returns the mutator instance for + * method chaining.

    + * + * @param key the key for the map entry to remove. + * @return the mutator/builder for method chaining. + */ + T delete(K key); + + /** + * Removes all entries from the map. The map will be empty after this call returns. + *

    This method is the same as {@link Map#clear Map.clear}, but instead returns the mutator instance for + * method chaining.

    + * + * @return the mutator/builder for method chaining. + */ + T empty(); + + /** + * Sets the specified key/value pair in the map, overwriting any existing entry with the same key. + * A {@code null} or empty value will remove the entry from the map entirely. + * + *

    This method is the same as {@link Map#put Map.put}, but instead returns the mutator instance for + * method chaining.

    + * + * @param key the map key + * @param value the value to set for the specified header parameter name + * @return the mutator/builder for method chaining. + */ + T add(K key, V value); + + /** + * Sets the specified key/value pairs in the map, overwriting any existing entries with the same keys. + * If any pair has a {@code null} or empty value, that pair will be removed from the map entirely. + * + *

    This method is the same as {@link Map#putAll Map.putAll}, but instead returns the mutator instance for + * method chaining.

    + * + * @param m the map to add + * @return the mutator/builder for method chaining. + */ + T add(Map m); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Maps.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Maps.java (.../Maps.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Maps.java (.../Maps.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -21,11 +21,13 @@ /** * Utility class to help with the manipulation of working with Maps. + * * @since 0.11.0 */ public final class Maps { - private Maps() {} //prevent instantiation + private Maps() { + } //prevent instantiation /** * Creates a new map builder with a single entry. @@ -35,11 +37,12 @@ * // ... * .build(); * } - * @param key the key of an map entry to be added + * + * @param key the key of an map entry to be added * @param value the value of map entry to be added * @param the maps key type * @param the maps value type - * Creates a new map builder with a single entry. + * @return a new map builder with a single entry. */ public static MapBuilder of(K key, V value) { return new HashMapBuilder().and(key, value); @@ -53,21 +56,24 @@ * // ... * .build(); * } + * * @param the maps key type * @param the maps value type */ - public interface MapBuilder { + public interface MapBuilder extends Builder> { /** * Add a new entry to this map builder - * @param key the key of an map entry to be added + * + * @param key the key of an map entry to be added * @param value the value of map entry to be added * @return the current MapBuilder to allow for method chaining. */ MapBuilder and(K key, V value); /** - * Returns a the resulting Map object from this MapBuilder. - * @return Returns a the resulting Map object from this MapBuilder. + * Returns the resulting Map object from this MapBuilder. + * + * @return the resulting Map object from this MapBuilder. */ Map build(); } @@ -80,6 +86,7 @@ data.put(key, value); return this; } + public Map build() { return Collections.unmodifiableMap(data); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/NestedCollection.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/NestedCollection.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/NestedCollection.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,32 @@ +/* + * 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. For example: + *
    + * builder
    + *     .aNestedCollection()// etc...
    + *     .and() // return parent
    + * // resume parent configuration...
    + * + * @param the type of elements in the collection + * @param

    the parent to return + * @since 0.12.0 + */ +public interface NestedCollection extends CollectionMutator>, Conjunctor

    { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Objects.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Objects.java (.../Objects.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Objects.java (.../Objects.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,22 +16,30 @@ package io.jsonwebtoken.lang; import java.io.Closeable; +import java.io.Flushable; import java.io.IOException; import java.lang.reflect.Array; import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +/** + * Utility methods for working with object instances to reduce pattern repetition and otherwise + * increased cyclomatic complexity. + */ public final class Objects { - private Objects(){} //prevent instantiation + private Objects() { + } //prevent instantiation private static final int INITIAL_HASH = 7; - private static final int MULTIPLIER = 31; + private static final int MULTIPLIER = 31; - private static final String EMPTY_STRING = ""; - private static final String NULL_STRING = "null"; - private static final String ARRAY_START = "{"; - private static final String ARRAY_END = "}"; - private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END; + private static final String EMPTY_STRING = ""; + private static final String NULL_STRING = "null"; + private static final String ARRAY_START = "{"; + private static final String ARRAY_END = "}"; + private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END; private static final String ARRAY_ELEMENT_SEPARATOR = ", "; /** @@ -73,36 +81,68 @@ } /** - * Determine whether the given object is an array: - * either an Object array or a primitive array. + * Returns {@code true} if the specified argument is an Object or primitive array, {@code false} otherwise. * - * @param obj the object to check + * @param obj the object instance to check + * @return {@code true} if the specified argument is an Object or primitive array, {@code false} otherwise. */ public static boolean isArray(Object obj) { return (obj != null && obj.getClass().isArray()); } /** - * Determine whether the given array is empty: - * i.e. null or of zero length. + * Returns {@code true} if the specified argument: + *

      + *
    1. is {@code null}, or
    2. + *
    3. is a CharSequence and {@link Strings#hasText(CharSequence)} is {@code false}, or
    4. + *
    5. is a Collection or Map with zero size, or
    6. + *
    7. is an empty array
    8. + *
    + *

    or {@code false} otherwise.

    * + * @param v object to check + * @return {@code true} if the specified argument is empty, {@code false} otherwise. + * @since 0.12.0 + */ + public static boolean isEmpty(Object v) { + return v == null || + (v instanceof CharSequence && !Strings.hasText((CharSequence) v)) || + (v instanceof Collection && Collections.isEmpty((Collection) v)) || + (v instanceof Map && Collections.isEmpty((Map) v)) || + (v.getClass().isArray() && Array.getLength(v) == 0); + } + + /** + * {@code true} if the specified array is null or zero length, {@code false} if populated. + * * @param array the array to check + * @return {@code true} if the specified array is null or zero length, {@code false} if populated. */ public static boolean isEmpty(Object[] array) { return (array == null || array.length == 0); } /** - * Returns {@code true} if the specified byte array is null or of zero length, {@code false} otherwise. + * Returns {@code true} if the specified byte array is null or of zero length, {@code false} if populated. * * @param array the byte array to check - * @return {@code true} if the specified byte array is null or of zero length, {@code false} otherwise. + * @return {@code true} if the specified byte array is null or of zero length, {@code false} if populated. */ public static boolean isEmpty(byte[] array) { return array == null || array.length == 0; } /** + * Returns {@code true} if the specified character array is null or of zero length, {@code false} otherwise. + * + * @param chars the character array to check + * @return {@code true} if the specified character array is null or of zero length, {@code false} otherwise. + */ + public static boolean isEmpty(char[] chars) { + return chars == null || chars.length == 0; + } + + /** * Check whether the given array contains the given element. * * @param array the array to check (may be null, @@ -145,8 +185,8 @@ public static boolean containsConstant(Enum[] enumValues, String constant, boolean caseSensitive) { for (Enum candidate : enumValues) { if (caseSensitive ? - candidate.toString().equals(constant) : - candidate.toString().equalsIgnoreCase(constant)) { + candidate.toString().equals(constant) : + candidate.toString().equalsIgnoreCase(constant)) { return true; } } @@ -159,6 +199,7 @@ * @param the concrete Enum type * @param enumValues the array of all Enum constants in question, usually per Enum.values() * @param constant the constant to get the enum value of + * @return the enum constant of the specified enum type with the specified case-insensitive name * @throws IllegalArgumentException if the given constant is not found in the given array * of enum values. Use {@link #containsConstant(Enum[], String)} as a guard to * avoid this exception. @@ -170,16 +211,18 @@ } } throw new IllegalArgumentException( - String.format("constant [%s] does not exist in enum type %s", - constant, enumValues.getClass().getComponentType().getName())); + String.format("constant [%s] does not exist in enum type %s", + constant, enumValues.getClass().getComponentType().getName())); } /** * Append the given object to the given array, returning a new array * consisting of the input array contents plus the given object. * * @param array the array to append to (can be null) + * @param
    the type of each element in the specified {@code array} * @param obj the object to append + * @param the type of the specified object, which must be equal to or extend the <A> type. * @return the new array (of the same component type; never null) */ public static A[] addObjectToArray(A[] array, O obj) { @@ -297,6 +340,8 @@ * methods for arrays in this class. If the object is null, * this method returns 0. * + * @param obj the object to use for obtaining a hashcode + * @return the object's hashcode, which could be 0 if the object is null. * @see #nullSafeHashCode(Object[]) * @see #nullSafeHashCode(boolean[]) * @see #nullSafeHashCode(byte[]) @@ -346,8 +391,11 @@ /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. + * + * @param array the array to obtain a hashcode + * @return the array's hashcode, which could be 0 if the array is null. */ - public static int nullSafeHashCode(Object[] array) { + public static int nullSafeHashCode(Object... array) { if (array == null) { return 0; } @@ -362,6 +410,9 @@ /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. + * + * @param array the boolean array to obtain a hashcode + * @return the boolean array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(boolean[] array) { if (array == null) { @@ -378,6 +429,9 @@ /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. + * + * @param array the byte array to obtain a hashcode + * @return the byte array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(byte[] array) { if (array == null) { @@ -394,6 +448,9 @@ /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. + * + * @param array the char array to obtain a hashcode + * @return the char array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(char[] array) { if (array == null) { @@ -410,6 +467,9 @@ /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. + * + * @param array the double array to obtain a hashcode + * @return the double array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(double[] array) { if (array == null) { @@ -426,6 +486,9 @@ /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. + * + * @param array the float array to obtain a hashcode + * @return the float array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(float[] array) { if (array == null) { @@ -442,6 +505,9 @@ /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. + * + * @param array the int array to obtain a hashcode + * @return the int array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(int[] array) { if (array == null) { @@ -458,6 +524,9 @@ /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. + * + * @param array the long array to obtain a hashcode + * @return the long array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(long[] array) { if (array == null) { @@ -474,6 +543,9 @@ /** * Return a hash code based on the contents of the specified array. * If array is null, this method returns 0. + * + * @param array the short array to obtain a hashcode + * @return the short array's hashcode, which could be 0 if the array is null. */ public static int nullSafeHashCode(short[] array) { if (array == null) { @@ -490,6 +562,8 @@ /** * Return the same value as {@link Boolean#hashCode()}. * + * @param bool the boolean to get a hashcode + * @return the same value as {@link Boolean#hashCode()}. * @see Boolean#hashCode() */ public static int hashCode(boolean bool) { @@ -499,6 +573,8 @@ /** * Return the same value as {@link Double#hashCode()}. * + * @param dbl the double to get a hashcode + * @return the same value as {@link Double#hashCode()}. * @see Double#hashCode() */ public static int hashCode(double dbl) { @@ -509,6 +585,8 @@ /** * Return the same value as {@link Float#hashCode()}. * + * @param flt the float to get a hashcode + * @return the same value as {@link Float#hashCode()}. * @see Float#hashCode() */ public static int hashCode(float flt) { @@ -518,6 +596,8 @@ /** * Return the same value as {@link Long#hashCode()}. * + * @param lng the long to get a hashcode + * @return the same value as {@link Long#hashCode()}. * @see Long#hashCode() */ public static int hashCode(long lng) { @@ -532,9 +612,8 @@ /** * Return a String representation of an object's overall identity. * - * @param obj the object (may be null) - * @return the object's identity as String representation, - * or an empty String if the object was null + * @param obj the object (which may be null). + * @return the object's identity as String representation, or an empty String if the object was null. */ public static String identityToString(Object obj) { if (obj == null) { @@ -909,6 +988,12 @@ return sb.toString(); } + /** + * Iterate over the specified {@link Closeable} instances, invoking + * {@link Closeable#close()} on each one, ignoring any potential {@link IOException}s. + * + * @param closeables the closeables to close. + */ public static void nullSafeClose(Closeable... closeables) { if (closeables == null) { return; @@ -924,4 +1009,23 @@ } } } + + /** + * Iterate over the specified {@link Flushable} instances, invoking + * {@link Flushable#flush()} on each one, ignoring any potential {@link IOException}s. + * + * @param flushables the flushables to flush. + * @since 0.12.0 + */ + public static void nullSafeFlush(Flushable... flushables) { + if (flushables == null) return; + for (Flushable flushable : flushables) { + if (flushable != null) { + try { + flushable.flush(); + } catch (IOException ignored) { + } + } + } + } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Registry.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Registry.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Registry.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,51 @@ +/* + * Copyright © 2020 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.Map; + +/** + * An immutable (read-only) repository of key-value pairs. In addition to {@link Map} read methods, this interface also + * provides guaranteed/expected lookup via the {@link #forKey(Object)} method. + * + *

    Immutability

    + * + *

    Registries are immutable and cannot be changed. {@code Registry} extends the + * {@link Map} interface purely out of convenience: to allow easy key/value + * pair access and iteration, and other conveniences provided by the Map interface, as well as for seamless use with + * existing Map-based APIs. Attempting to call any of + * the {@link Map} interface's mutation methods however (such as {@link Map#put(Object, Object) put}, + * {@link Map#remove(Object) remove}, {@link Map#clear() clear}, etc) will throw an + * {@link UnsupportedOperationException}.

    + * + * @param key type + * @param value type + * @since 0.12.0 + */ +public interface Registry extends Map { + + /** + * Returns the value assigned the specified key or throws an {@code IllegalArgumentException} if there is no + * associated value. If a value is not required, consider using the {@link #get(Object)} method instead. + * + * @param key the registry key assigned to the required value + * @return the value assigned the specified key + * @throws IllegalArgumentException if there is no value assigned the specified key + * @see #get(Object) + */ + V forKey(K key) throws IllegalArgumentException; + +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/RuntimeEnvironment.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/RuntimeEnvironment.java (.../RuntimeEnvironment.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/RuntimeEnvironment.java (.../RuntimeEnvironment.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -19,38 +19,59 @@ import java.security.Security; import java.util.concurrent.atomic.AtomicBoolean; +/** + * No longer used by JJWT. Will be removed before the 1.0 final release. + * + * @deprecated since 0.12.0. will be removed before the 1.0 final release. + */ +@Deprecated public final class RuntimeEnvironment { - private RuntimeEnvironment(){} //prevent instantiation + private RuntimeEnvironment() { + } //prevent instantiation private static final String BC_PROVIDER_CLASS_NAME = "org.bouncycastle.jce.provider.BouncyCastleProvider"; private static final AtomicBoolean bcLoaded = new AtomicBoolean(false); + /** + * {@code true} if BouncyCastle is in the runtime classpath, {@code false} otherwise. + * + * @deprecated since 0.12.0. will be removed before the 1.0 final release. + */ + @Deprecated public static final boolean BOUNCY_CASTLE_AVAILABLE = Classes.isAvailable(BC_PROVIDER_CLASS_NAME); + /** + * Register BouncyCastle as a JCA provider in the system's {@link Security#getProviders() Security Providers} list + * if BouncyCastle is in the runtime classpath. + * + * @deprecated since 0.12.0. will be removed before the 1.0 final release. + */ + @Deprecated public static void enableBouncyCastleIfPossible() { if (!BOUNCY_CASTLE_AVAILABLE || bcLoaded.get()) { return; } try { - Class clazz = Classes.forName(BC_PROVIDER_CLASS_NAME); + Class clazz = Classes.forName(BC_PROVIDER_CLASS_NAME); //check to see if the user has already registered the BC provider: Provider[] providers = Security.getProviders(); - for(Provider provider : providers) { + for (Provider provider : providers) { if (clazz.isInstance(provider)) { bcLoaded.set(true); return; } } //bc provider not enabled - add it: - Security.addProvider((Provider)Classes.newInstance(clazz)); + Provider provider = Classes.newInstance(clazz); + Security.addProvider(provider); bcLoaded.set(true); } catch (UnknownClassException e) { Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Strings.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Strings.java (.../Strings.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Strings.java (.../Strings.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,7 +15,10 @@ */ package io.jsonwebtoken.lang; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -29,8 +32,19 @@ import java.util.StringTokenizer; import java.util.TreeSet; +/** + * Utility methods for working with Strings to reduce pattern repetition and otherwise + * increased cyclomatic complexity. + */ public final class Strings { + /** + * Empty String, equal to "". + */ + public static final String EMPTY = ""; + + private static final CharBuffer EMPTY_BUF = CharBuffer.wrap(EMPTY); + private static final String FOLDER_SEPARATOR = "/"; private static final String WINDOWS_FOLDER_SEPARATOR = "\\"; @@ -41,9 +55,13 @@ private static final char EXTENSION_SEPARATOR = '.'; - public static final Charset UTF_8 = Charset.forName("UTF-8"); + /** + * Convenience alias for {@link StandardCharsets#UTF_8}. + */ + public static final Charset UTF_8 = StandardCharsets.UTF_8; - private Strings(){} //prevent instantiation + private Strings() { + } //prevent instantiation //--------------------------------------------------------------------- // General convenience methods for working with Strings @@ -52,12 +70,13 @@ /** * Check that the given CharSequence is neither null nor of length 0. * Note: Will return true for a CharSequence that purely consists of whitespace. - *

    +     * 
          * Strings.hasLength(null) = false
          * Strings.hasLength("") = false
          * Strings.hasLength(" ") = true
          * Strings.hasLength("Hello") = true
          * 
    + * * @param str the CharSequence to check (may be null) * @return true if the CharSequence is not null and has length * @see #hasText(String) @@ -69,6 +88,7 @@ /** * Check that the given String is neither null nor of length 0. * Note: Will return true for a String that purely consists of whitespace. + * * @param str the String to check (may be null) * @return true if the String is not null and has length * @see #hasLength(CharSequence) @@ -81,13 +101,14 @@ * Check whether the given CharSequence has actual text. * More specifically, returns true if the string not null, * its length is greater than 0, and it contains at least one non-whitespace character. - *

    +     * 
          * Strings.hasText(null) = false
          * Strings.hasText("") = false
          * Strings.hasText(" ") = false
          * Strings.hasText("12345") = true
          * Strings.hasText(" 12345 ") = true
          * 
    + * * @param str the CharSequence to check (may be null) * @return true if the CharSequence is not null, * its length is greater than 0, and it does not contain whitespace only @@ -110,6 +131,7 @@ * Check whether the given String has actual text. * More specifically, returns true if the string not null, * its length is greater than 0, and it contains at least one non-whitespace character. + * * @param str the String to check (may be null) * @return true if the String is not null, its length is * greater than 0, and it does not contain whitespace only @@ -121,6 +143,7 @@ /** * Check whether the given CharSequence contains any whitespace characters. + * * @param str the CharSequence to check (may be null) * @return true if the CharSequence is not empty and * contains at least 1 whitespace character @@ -141,6 +164,7 @@ /** * Check whether the given String contains any whitespace characters. + * * @param str the String to check (may be null) * @return true if the String is not empty and * contains at least 1 whitespace character @@ -152,40 +176,57 @@ /** * Trim leading and trailing whitespace from the given String. + * * @param str the String to check * @return the trimmed String * @see java.lang.Character#isWhitespace */ public static String trimWhitespace(String str) { - return (String) trimWhitespace((CharSequence)str); + return (String) trimWhitespace((CharSequence) str); } - - + + private static CharSequence trimWhitespace(CharSequence str) { if (!hasLength(str)) { return str; } final int length = str.length(); int start = 0; - while (start < length && Character.isWhitespace(str.charAt(start))) { + while (start < length && Character.isWhitespace(str.charAt(start))) { start++; } - - int end = length; + + int end = length; while (start < length && Character.isWhitespace(str.charAt(end - 1))) { end--; } - + return ((start > 0) || (end < length)) ? str.subSequence(start, end) : str; } + /** + * Returns the specified string without leading or trailing whitespace, or {@code null} if there are no remaining + * characters. + * + * @param str the string to clean + * @return the specified string without leading or trailing whitespace, or {@code null} if there are no remaining + * characters. + */ public static String clean(String str) { - CharSequence result = clean((CharSequence) str); - - return result!=null?result.toString():null; + CharSequence result = clean((CharSequence) str); + + return result != null ? result.toString() : null; } - + + /** + * Returns the specified {@code CharSequence} without leading or trailing whitespace, or {@code null} if there are + * no remaining characters. + * + * @param str the {@code CharSequence} to clean + * @return the specified string without leading or trailing whitespace, or {@code null} if there are no remaining + * characters. + */ public static CharSequence clean(CharSequence str) { str = trimWhitespace(str); if (!hasLength(str)) { @@ -195,8 +236,125 @@ } /** + * Returns the specified string's UTF-8 bytes, or {@code null} if the string is {@code null}. + * + * @param s the string to obtain UTF-8 bytes + * @return the specified string's UTF-8 bytes, or {@code null} if the string is {@code null}. + * @since 0.12.0 + */ + public static byte[] utf8(CharSequence s) { + if (s == null) return null; + CharBuffer cb = s instanceof CharBuffer ? (CharBuffer) s : CharBuffer.wrap(s); + cb.mark(); + ByteBuffer buf = UTF_8.encode(cb); + byte[] bytes = new byte[buf.remaining()]; + buf.get(bytes); + cb.reset(); + return bytes; + } + + /** + * Returns {@code new String(utf8Bytes, StandardCharsets.UTF_8)}. + * + * @param utf8Bytes UTF-8 bytes to use with the {@code String} constructor. + * @return {@code new String(utf8Bytes, StandardCharsets.UTF_8)}. + * @since 0.12.0 + */ + public static String utf8(byte[] utf8Bytes) { + return new String(utf8Bytes, UTF_8); + } + + /** + * Returns {@code new String(asciiBytes, StandardCharsets.US_ASCII)}. + * + * @param asciiBytes US_ASCII bytes to use with the {@code String} constructor. + * @return {@code new String(asciiBytes, StandardCharsets.US_ASCII)}. + * @since 0.12.0 + */ + public static String ascii(byte[] asciiBytes) { + 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) { + byte[] bytes = null; + if (s != null) { + CharBuffer cb = s instanceof CharBuffer ? (CharBuffer) s : CharBuffer.wrap(s); + ByteBuffer buf = StandardCharsets.US_ASCII.encode(cb); + bytes = new byte[buf.remaining()]; + buf.get(bytes); + } + return bytes; + } + + /** + * Returns a {@code CharBuffer} that wraps {@code seq}, or an empty buffer if {@code seq} is null. If + * {@code seq} is already a {@code CharBuffer}, it is returned unmodified. + * + * @param seq the {@code CharSequence} to wrap. + * @return a {@code CharBuffer} that wraps {@code seq}, or an empty buffer if {@code seq} is null. + */ + public static CharBuffer wrap(CharSequence seq) { + if (!hasLength(seq)) return EMPTY_BUF; + if (seq instanceof CharBuffer) return (CharBuffer) seq; + return CharBuffer.wrap(seq); + } + + /** + * Returns a String representation (1s and 0s) of the specified byte. + * + * @param b the byte to represent as 1s and 0s. + * @return a String representation (1s and 0s) of the specified byte. + */ + public static String toBinary(byte b) { + String bString = Integer.toBinaryString(b & 0xFF); + return String.format("%8s", bString).replace((char) Character.SPACE_SEPARATOR, '0'); + } + + /** + * Returns a String representation (1s and 0s) of the specified byte array. + * + * @param bytes the bytes to represent as 1s and 0s. + * @return a String representation (1s and 0s) of the specified byte array. + */ + public static String toBinary(byte[] bytes) { + StringBuilder sb = new StringBuilder(19); //16 characters + 3 space characters + for (byte b : bytes) { + if (sb.length() > 0) { + sb.append((char) Character.SPACE_SEPARATOR); + } + String val = toBinary(b); + sb.append(val); + } + return sb.toString(); + } + + /** + * Returns a hexadecimal String representation of the specified byte array. + * + * @param bytes the bytes to represent as a hexidecimal string. + * @return a hexadecimal String representation of the specified byte array. + */ + public static String toHex(byte[] bytes) { + StringBuilder result = new StringBuilder(); + for (byte temp : bytes) { + if (result.length() > 0) { + result.append((char) Character.SPACE_SEPARATOR); + } + result.append(String.format("%02x", temp)); + } + return result.toString(); + } + + /** * Trim all whitespace from the given String: - * leading, trailing, and inbetween characters. + * leading, trailing, and intermediate characters. + * * @param str the String to check * @return the trimmed String * @see java.lang.Character#isWhitespace @@ -210,8 +368,7 @@ while (sb.length() > index) { if (Character.isWhitespace(sb.charAt(index))) { sb.deleteCharAt(index); - } - else { + } else { index++; } } @@ -220,6 +377,7 @@ /** * Trim leading whitespace from the given String. + * * @param str the String to check * @return the trimmed String * @see java.lang.Character#isWhitespace @@ -237,6 +395,7 @@ /** * Trim trailing whitespace from the given String. + * * @param str the String to check * @return the trimmed String * @see java.lang.Character#isWhitespace @@ -253,8 +412,9 @@ } /** - * Trim all occurences of the supplied leading character from the given String. - * @param str the String to check + * Trim all occurrences of the supplied leading character from the given String. + * + * @param str the String to check * @param leadingCharacter the leading character to be trimmed * @return the trimmed String */ @@ -270,8 +430,9 @@ } /** - * Trim all occurences of the supplied trailing character from the given String. - * @param str the String to check + * Trim all occurrences of the supplied trailing character from the given String. + * + * @param str the String to check * @param trailingCharacter the trailing character to be trimmed * @return the trimmed String */ @@ -288,32 +449,34 @@ /** - * Test if the given String starts with the specified prefix, - * ignoring upper/lower case. - * @param str the String to check + * Returns {@code true} if the given string starts with the specified case-insensitive prefix, {@code false} otherwise. + * + * @param str the String to check * @param prefix the prefix to look for + * @return {@code true} if the given string starts with the specified case-insensitive prefix, {@code false} otherwise. * @see java.lang.String#startsWith */ public static boolean startsWithIgnoreCase(String str, String prefix) { if (str == null || prefix == null) { return false; } - if (str.startsWith(prefix)) { - return true; - } if (str.length() < prefix.length()) { return false; } + if (str.startsWith(prefix)) { + return true; + } String lcStr = str.substring(0, prefix.length()).toLowerCase(); String lcPrefix = prefix.toLowerCase(); return lcStr.equals(lcPrefix); } /** - * Test if the given String ends with the specified suffix, - * ignoring upper/lower case. - * @param str the String to check + * Returns {@code true} if the given string ends with the specified case-insensitive suffix, {@code false} otherwise. + * + * @param str the String to check * @param suffix the suffix to look for + * @return {@code true} if the given string ends with the specified case-insensitive suffix, {@code false} otherwise. * @see java.lang.String#endsWith */ public static boolean endsWithIgnoreCase(String str, String suffix) { @@ -333,11 +496,12 @@ } /** - * Test whether the given string matches the given substring - * at the given index. - * @param str the original string (or StringBuilder) - * @param index the index in the original string to start matching against + * Returns {@code true} if the given string matches the given substring at the given index, {@code false} otherwise. + * + * @param str the original string (or StringBuilder) + * @param index the index in the original string to start matching against * @param substring the substring to match at the given index + * @return {@code true} if the given string matches the given substring at the given index, {@code false} otherwise. */ public static boolean substringMatch(CharSequence str, int index, CharSequence substring) { for (int j = 0; j < substring.length(); j++) { @@ -350,9 +514,11 @@ } /** - * Count the occurrences of the substring in string s. + * Returns the number of occurrences the substring {@code sub} appears in string {@code str}. + * * @param str string to search in. Return 0 if this is null. * @param sub string to search for. Return 0 if this is null. + * @return the number of occurrences the substring {@code sub} appears in string {@code str}. */ public static int countOccurrencesOf(String str, String sub) { if (str == null || sub == null || str.length() == 0 || sub.length() == 0) { @@ -369,9 +535,10 @@ } /** - * Replace all occurences of a substring within a string with + * Replace all occurrences of a substring within a string with * another string. - * @param inString String to examine + * + * @param inString String to examine * @param oldPattern String to replace * @param newPattern String to insert * @return a String with the replacements @@ -398,8 +565,9 @@ /** * Delete all occurrences of the given substring. + * * @param inString the original String - * @param pattern the pattern to delete all occurrences of + * @param pattern the pattern to delete all occurrences of * @return the resulting String */ public static String delete(String inString, String pattern) { @@ -408,9 +576,10 @@ /** * Delete any character in a given String. - * @param inString the original String + * + * @param inString the original String * @param charsToDelete a set of characters to delete. - * E.g. "az\n" will delete 'a's, 'z's and new lines. + * E.g. "az\n" will delete 'a's, 'z's and new lines. * @return the resulting String */ public static String deleteAny(String inString, String charsToDelete) { @@ -434,6 +603,7 @@ /** * Quote the given String with single quotes. + * * @param str the input String (e.g. "myString") * @return the quoted String (e.g. "'myString'"), * or null if the input was null @@ -445,6 +615,7 @@ /** * Turn the given Object into a String with single quotes * if it is a String; keeping the Object as-is else. + * * @param obj the input Object (e.g. "myString") * @return the quoted String (e.g. "'myString'"), * or the input object as-is if not a String @@ -456,7 +627,9 @@ /** * Unqualify a string qualified by a '.' dot character. For example, * "this.name.is.qualified", returns "qualified". + * * @param qualifiedName the qualified name + * @return an unqualified string by stripping all previous text before (and including) the last period character. */ public static String unqualify(String qualifiedName) { return unqualify(qualifiedName, '.'); @@ -465,8 +638,10 @@ /** * Unqualify a string qualified by a separator character. For example, * "this:name:is:qualified" returns "qualified" if using a ':' separator. + * * @param qualifiedName the qualified name - * @param separator the separator + * @param separator the separator + * @return an unqualified string by stripping all previous text before and including the last {@code separator} character. */ public static String unqualify(String qualifiedName, char separator) { return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1); @@ -476,6 +651,7 @@ * Capitalize a String, changing the first letter to * upper case as per {@link Character#toUpperCase(char)}. * No other letters are changed. + * * @param str the String to capitalize, may be null * @return the capitalized String, null if null */ @@ -487,6 +663,7 @@ * Uncapitalize a String, changing the first letter to * lower case as per {@link Character#toLowerCase(char)}. * No other letters are changed. + * * @param str the String to uncapitalize, may be null * @return the uncapitalized String, null if null */ @@ -501,8 +678,7 @@ StringBuilder sb = new StringBuilder(str.length()); if (capitalize) { sb.append(Character.toUpperCase(str.charAt(0))); - } - else { + } else { sb.append(Character.toLowerCase(str.charAt(0))); } sb.append(str.substring(1)); @@ -512,6 +688,7 @@ /** * Extract the filename from the given path, * e.g. "mypath/myfile.txt" -> "myfile.txt". + * * @param path the file path (may be null) * @return the extracted filename, or null if none */ @@ -526,6 +703,7 @@ /** * Extract the filename extension from the given path, * e.g. "mypath/myfile.txt" -> "txt". + * * @param path the file path (may be null) * @return the extracted filename extension, or null if none */ @@ -547,6 +725,7 @@ /** * Strip the filename extension from the given path, * e.g. "mypath/myfile.txt" -> "mypath/myfile". + * * @param path the file path (may be null) * @return the path with stripped filename extension, * or null if none @@ -569,9 +748,10 @@ /** * Apply the given relative path to the given path, * assuming standard Java folder separation (i.e. "/" separators). - * @param path the path to start from (usually a full file path) + * + * @param path the path to start from (usually a full file path) * @param relativePath the relative path to apply - * (relative to the full file path above) + * (relative to the full file path above) * @return the full file path that results from applying the relative path */ public static String applyRelativePath(String path, String relativePath) { @@ -582,8 +762,7 @@ newPath += FOLDER_SEPARATOR; } return newPath + relativePath; - } - else { + } else { return relativePath; } } @@ -593,6 +772,7 @@ * inner simple dots. *

    The result is convenient for path comparison. For other uses, * notice that Windows separators ("\") are replaced by simple slashes. + * * @param path the original path * @return the normalized path */ @@ -625,17 +805,14 @@ String element = pathArray[i]; if (CURRENT_PATH.equals(element)) { // Points to current directory - drop it. - } - else if (TOP_PATH.equals(element)) { + } else if (TOP_PATH.equals(element)) { // Registering top path found. tops++; - } - else { + } else { if (tops > 0) { // Merging path element with element corresponding to top path. tops--; - } - else { + } else { // Normal path element found. pathElements.add(0, element); } @@ -652,6 +829,7 @@ /** * Compare two paths after normalization of them. + * * @param path1 first path for comparison * @param path2 second path for comparison * @return whether the two paths are equivalent after normalization @@ -663,9 +841,10 @@ /** * Parse the given localeString value into a {@link java.util.Locale}. *

    This is the inverse operation of {@link java.util.Locale#toString Locale's toString}. + * * @param localeString the locale string, following Locale's - * toString() format ("en", "en_UK", etc); - * also accepts spaces as separators, as an alternative to underscores + * toString() format ("en", "en_UK", etc); + * also accepts spaces as separators, as an alternative to underscores * @return a corresponding Locale instance */ public static Locale parseLocaleString(String localeString) { @@ -692,15 +871,15 @@ for (int i = 0; i < localePart.length(); i++) { char ch = localePart.charAt(i); if (ch != '_' && ch != ' ' && !Character.isLetterOrDigit(ch)) { - throw new IllegalArgumentException( - "Locale part \"" + localePart + "\" contains invalid characters"); + throw new IllegalArgumentException("Locale part \"" + localePart + "\" contains invalid characters"); } } } /** * Determine the RFC 3066 compliant language tag, * as used for the HTTP "Accept-Language" header. + * * @param locale the Locale to transform to a language tag * @return the RFC 3066 compliant language tag as String */ @@ -716,13 +895,14 @@ /** * Append the given String to the given String array, returning a new array * consisting of the input array contents plus the given String. + * * @param array the array to append to (can be null) - * @param str the String to append + * @param str the String to append * @return the new array (never null) */ public static String[] addStringToArray(String[] array, String str) { if (Objects.isEmpty(array)) { - return new String[] {str}; + return new String[]{str}; } String[] newArr = new String[array.length + 1]; System.arraycopy(array, 0, newArr, 0, array.length); @@ -734,6 +914,7 @@ * Concatenate the given String arrays into one, * with overlapping array elements included twice. *

    The order of elements in the original arrays is preserved. + * * @param array1 the first array (can be null) * @param array2 the second array (can be null) * @return the new array (null if both given arrays were null) @@ -757,6 +938,7 @@ *

    The order of elements in the original arrays is preserved * (with the exception of overlapping elements, which are only * included on their first occurrence). + * * @param array1 the first array (can be null) * @param array2 the second array (can be null) * @return the new array (null if both given arrays were null) @@ -780,6 +962,7 @@ /** * Turn given source String array into sorted array. + * * @param array the source array * @return the sorted array (never null) */ @@ -794,6 +977,7 @@ /** * Copy the given Collection into a String array. * The Collection must contain String elements only. + * * @param collection the Collection to copy * @return the String array (null if the passed-in * Collection was null) @@ -808,6 +992,7 @@ /** * Copy the given Enumeration into a String array. * The Enumeration must contain String elements only. + * * @param enumeration the Enumeration to copy * @return the String array (null if the passed-in * Enumeration was null) @@ -823,6 +1008,7 @@ /** * Trim the elements of the given String array, * calling String.trim() on each of them. + * * @param array the original String array * @return the resulting array (of the same size) with trimmed elements */ @@ -841,6 +1027,7 @@ /** * Remove duplicate Strings from the given array. * Also sorts the array, as it uses a TreeSet. + * * @param array the String array * @return an array without duplicates, in natural sort order */ @@ -858,7 +1045,8 @@ /** * Split a String at the first occurrence of the delimiter. * Does not include the delimiter in the result. - * @param toSplit the string to split + * + * @param toSplit the string to split * @param delimiter to split the string up with * @return a two element array with index 0 being before the delimiter, and * index 1 being after the delimiter (neither element includes the delimiter); @@ -874,7 +1062,7 @@ } String beforeDelimiter = toSplit.substring(0, offset); String afterDelimiter = toSplit.substring(offset + delimiter.length()); - return new String[] {beforeDelimiter, afterDelimiter}; + return new String[]{beforeDelimiter, afterDelimiter}; } /** @@ -883,7 +1071,8 @@ * delimiter providing the key, and the right of the delimiter providing the value. *

    Will trim both the key and value before adding them to the * Properties instance. - * @param array the array to process + * + * @param array the array to process * @param delimiter to split each element using (typically the equals symbol) * @return a Properties instance representing the array contents, * or null if the array to process was null or empty @@ -898,16 +1087,16 @@ * delimiter providing the key, and the right of the delimiter providing the value. *

    Will trim both the key and value before adding them to the * Properties instance. - * @param array the array to process - * @param delimiter to split each element using (typically the equals symbol) + * + * @param array the array to process + * @param delimiter to split each element using (typically the equals symbol) * @param charsToDelete one or more characters to remove from each element - * prior to attempting the split operation (typically the quotation mark - * symbol), or null if no removal should occur + * prior to attempting the split operation (typically the quotation mark + * symbol), or null if no removal should occur * @return a Properties instance representing the array contents, * or null if the array to process was null or empty */ - public static Properties splitArrayElementsIntoProperties( - String[] array, String delimiter, String charsToDelete) { + public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter, String charsToDelete) { if (Objects.isEmpty(array)) { return null; @@ -933,9 +1122,10 @@ * delimiter characters. Each of those characters can be used to separate * tokens. A delimiter is always a single character; for multi-character * delimiters, consider using delimitedListToStringArray - * @param str the String to tokenize + * + * @param str the String to tokenize * @param delimiters the delimiter characters, assembled as String - * (each of those characters is individually considered as delimiter). + * (each of those characters is individually considered as delimiter). * @return an array of the tokens * @see java.util.StringTokenizer * @see java.lang.String#trim() @@ -951,21 +1141,21 @@ * delimiter characters. Each of those characters can be used to separate * tokens. A delimiter is always a single character; for multi-character * delimiters, consider using delimitedListToStringArray - * @param str the String to tokenize - * @param delimiters the delimiter characters, assembled as String - * (each of those characters is individually considered as delimiter) - * @param trimTokens trim the tokens via String's trim + * + * @param str the String to tokenize + * @param delimiters the delimiter characters, assembled as String + * (each of those characters is individually considered as delimiter) + * @param trimTokens trim the tokens via String's trim * @param ignoreEmptyTokens omit empty tokens from the result array - * (only applies to tokens that are empty after trimming; StringTokenizer - * will not consider subsequent delimiters as token in the first place). + * (only applies to tokens that are empty after trimming; StringTokenizer + * will not consider subsequent delimiters as token in the first place). * @return an array of the tokens (null if the input String * was null) * @see java.util.StringTokenizer * @see java.lang.String#trim() * @see #delimitedListToStringArray */ - public static String[] tokenizeToStringArray( - String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { + public static String[] tokenizeToStringArray(String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { if (str == null) { return null; @@ -989,9 +1179,10 @@ *

    A single delimiter can consists of more than one character: It will still * be considered as single delimiter string, rather than as bunch of potential * delimiter characters - in contrast to tokenizeToStringArray. - * @param str the input String + * + * @param str the input String * @param delimiter the delimiter between elements (this is a single delimiter, - * rather than a bunch individual delimiter characters) + * rather than a bunch individual delimiter characters) * @return an array of the tokens in the list * @see #tokenizeToStringArray */ @@ -1004,11 +1195,12 @@ *

    A single delimiter can consists of more than one character: It will still * be considered as single delimiter string, rather than as bunch of potential * delimiter characters - in contrast to tokenizeToStringArray. - * @param str the input String - * @param delimiter the delimiter between elements (this is a single delimiter, - * rather than a bunch individual delimiter characters) + * + * @param str the input String + * @param delimiter the delimiter between elements (this is a single delimiter, + * rather than a bunch individual delimiter characters) * @param charsToDelete a set of characters to delete. Useful for deleting unwanted - * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a String. + * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a String. * @return an array of the tokens in the list * @see #tokenizeToStringArray */ @@ -1017,15 +1209,14 @@ return new String[0]; } if (delimiter == null) { - return new String[] {str}; + return new String[]{str}; } List result = new ArrayList(); if ("".equals(delimiter)) { for (int i = 0; i < str.length(); i++) { result.add(deleteAny(str.substring(i, i + 1), charsToDelete)); } - } - else { + } else { int pos = 0; int delPos; while ((delPos = str.indexOf(delimiter, pos)) != -1) { @@ -1042,6 +1233,7 @@ /** * Convert a CSV list into an array of Strings. + * * @param str the input String * @return an array of Strings, or the empty array in case of empty input */ @@ -1052,6 +1244,7 @@ /** * Convenience method to convert a CSV string list to a set. * Note that this will suppress duplicates. + * * @param str the input String * @return a Set of String entries in the list */ @@ -1067,8 +1260,9 @@ /** * Convenience method to return a Collection as a delimited (e.g. CSV) * String. E.g. useful for toString() implementations. - * @param coll the Collection to display - * @param delim the delimiter to use (probably a ",") + * + * @param coll the Collection to display + * @param delim the delimiter to use (probably a ",") * @param prefix the String to start each element with * @param suffix the String to end each element with * @return the delimited String @@ -1091,7 +1285,8 @@ /** * Convenience method to return a Collection as a delimited (e.g. CSV) * String. E.g. useful for toString() implementations. - * @param coll the Collection to display + * + * @param coll the Collection to display * @param delim the delimiter to use (probably a ",") * @return the delimited String */ @@ -1102,6 +1297,7 @@ /** * Convenience method to return a Collection as a CSV String. * E.g. useful for toString() implementations. + * * @param coll the Collection to display * @return the delimited String */ @@ -1112,7 +1308,8 @@ /** * Convenience method to return a String array as a delimited (e.g. CSV) * String. E.g. useful for toString() implementations. - * @param arr the array to display + * + * @param arr the array to display * @param delim the delimiter to use (probably a ",") * @return the delimited String */ @@ -1136,12 +1333,39 @@ /** * Convenience method to return a String array as a CSV String. * E.g. useful for toString() implementations. + * * @param arr the array to display * @return the delimited String */ public static String arrayToCommaDelimitedString(Object[] arr) { return arrayToDelimitedString(arr, ","); } + /** + * Appends a space character (' ') if the argument is not empty, otherwise does nothing. This method + * can be thought of as "non-empty space". Using this method allows reduction of this: + *

    +     * if (sb.length != 0) {
    +     *     sb.append(' ');
    +     * }
    +     * sb.append(nextWord);
    + *

    To this:

    + *
    +     * nespace(sb).append(nextWord);
    + * + * @param sb the string builder to append a space to if non-empty + * @return the string builder argument for method chaining. + * @since 0.12.0 + */ + public static StringBuilder nespace(StringBuilder sb) { + if (sb == null) { + return null; + } + if (sb.length() != 0) { + sb.append(' '); + } + return sb; + } + } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Supplier.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Supplier.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/lang/Supplier.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 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; + +/** + * Represents a supplier of results. + * + *

    There is no requirement that a new or distinct result be returned each time the supplier is invoked.

    + * + *

    This interface is the equivalent of a JDK 8 {@code java.util.function.Supplier}, backported for JJWT's use in + * JDK 7 environments.

    + * + * @param the type of object returned by this supplier + * @since 0.12.0 + */ +public interface Supplier { + + /** + * Returns a result. + * + * @return a result. + */ + T get(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AeadAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AeadAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AeadAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.Jwts; + +import javax.crypto.SecretKey; +import java.io.OutputStream; + +/** + * A cryptographic algorithm that performs + *
    Authenticated encryption with additional data. + * Per JWE RFC 7516, Section 4.1.2, all JWEs + * MUST use an AEAD algorithm to encrypt or decrypt the JWE payload/content. Consequently, all + * JWA "enc" algorithms are AEAD + * algorithms, and they are accessible as concrete instances via {@link Jwts.ENC}. + * + *

    "enc" identifier

    + * + *

    {@code AeadAlgorithm} extends {@code Identifiable}: the value returned from {@link Identifiable#getId() getId()} + * will be used as the JWE "enc" protected header value.

    + * + *

    Key Strength

    + * + *

    Encryption strength is in part attributed to how difficult it is to discover the encryption key. As such, + * cryptographic algorithms often require keys of a minimum length to ensure the keys are difficult to discover + * and the algorithm's security properties are maintained.

    + * + *

    The {@code AeadAlgorithm} interface extends the {@link KeyLengthSupplier} interface to represent the length + * in bits a key must have to be used with its implementation. If you do not want to worry about lengths and + * parameters of keys required for an algorithm, it is often easier to automatically generate a key that adheres + * to the algorithms requirements, as discussed below.

    + * + *

    Key Generation

    + * + *

    {@code AeadAlgorithm} extends {@link KeyBuilderSupplier} to enable {@link SecretKey} generation. Each AEAD + * algorithm instance will return a {@link KeyBuilder} that ensures any created keys will have a sufficient length + * and algorithm parameters required by that algorithm. For example:

    + * + *
    
    + *     SecretKey key = aeadAlgorithm.key().build();
    + * 
    + * + *

    The resulting {@code key} is guaranteed to have the correct algorithm parameters and strength/length necessary for + * that exact {@code aeadAlgorithm} instance.

    + * + * @see Jwts.ENC + * @see Identifiable#getId() + * @see KeyLengthSupplier + * @see KeyBuilderSupplier + * @see KeyBuilder + * @since 0.12.0 + */ +public interface AeadAlgorithm extends Identifiable, KeyLengthSupplier, KeyBuilderSupplier { + + /** + * Encrypts plaintext and signs any {@link AeadRequest#getAssociatedData() associated data}, placing the resulting + * ciphertext, initialization vector and authentication tag in the provided {@code result}. + * + * @param req the encryption request representing the plaintext to be encrypted, any additional + * integrity-protected data and the encryption key. + * @param res the result to write ciphertext, initialization vector and AAD authentication tag (aka digest) + * @throws SecurityException if there is an encryption problem or AAD authenticity cannot be guaranteed. + */ + void encrypt(AeadRequest req, AeadResult res) throws SecurityException; + + /** + * Decrypts ciphertext and authenticates any {@link DecryptAeadRequest#getAssociatedData() associated data}, + * writing the decrypted plaintext to the provided {@code out}put stream. + * + * @param request the decryption request representing the ciphertext to be decrypted, any additional + * integrity-protected data, authentication tag, initialization vector, and decryption key + * @param out the OutputStream for writing decrypted plaintext + * @throws SecurityException if there is a decryption problem or authenticity assertions fail. + */ + void decrypt(DecryptAeadRequest request, OutputStream out) throws SecurityException; +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AeadRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AeadRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AeadRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; +import java.io.InputStream; + +/** + * A request to an {@link AeadAlgorithm} to perform authenticated encryption with a supplied symmetric + * {@link SecretKey}, allowing for additional data to be authenticated and integrity-protected. + * + * @see SecureRequest + * @see AssociatedDataSupplier + * @since 0.12.0 + */ +public interface AeadRequest extends SecureRequest, AssociatedDataSupplier { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AeadResult.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AeadResult.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AeadResult.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.io.OutputStream; + +/** + * The result of authenticated encryption, providing access to the ciphertext {@link #getOutputStream() output stream} + * and resulting {@link #setTag(byte[]) AAD tag} and {@link #setIv(byte[]) initialization vector}. + * The AAD tag and initialization vector must be supplied with the ciphertext to decrypt. + * + * @since 0.12.0 + */ +public interface AeadResult { + + /** + * Returns the {@code OutputStream} the AeadAlgorithm will use to write the resulting ciphertext during + * encryption or plaintext during decryption. + * + * @return the {@code OutputStream} the AeadAlgorithm will use to write the resulting ciphertext during + * encryption or plaintext during decryption. + */ + OutputStream getOutputStream(); + + /** + * Sets the AEAD authentication tag. + * + * @param tag the AEAD authentication tag. + * @return the AeadResult for method chaining. + */ + AeadResult setTag(byte[] tag); + + /** + * Sets the initialization vector used during encryption. + * + * @param iv the initialization vector used during encryption. + * @return the AeadResult for method chaining. + */ + AeadResult setIv(byte[] iv); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AssociatedDataSupplier.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AssociatedDataSupplier.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AssociatedDataSupplier.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.io.InputStream; + +/** + * Provides any "associated data" that must be integrity protected (but not encrypted) when performing + * AEAD encryption or decryption. + * + * @see #getAssociatedData() + * @since 0.12.0 + */ +public interface AssociatedDataSupplier { + + /** + * Returns any data that must be integrity protected (but not encrypted) when performing + * AEAD encryption or decryption, or + * {@code null} if no additional data must be integrity protected. + * + * @return any data that must be integrity protected (but not encrypted) when performing + * AEAD encryption or decryption, or + * {@code null} if no additional data must be integrity protected. + */ + InputStream getAssociatedData(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AsymmetricJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AsymmetricJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AsymmetricJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * JWK representation of an asymmetric (public or private) cryptographic key. + * + * @param the type of {@link java.security.PublicKey} or {@link java.security.PrivateKey} represented by this JWK. + * @since 0.12.0 + */ +public interface AsymmetricJwk extends Jwk, X509Accessor { + + /** + * Returns the JWK + * {@code use} (Public Key Use) + * parameter value or {@code null} if not present. {@code use} values are CaSe-SeNsItIvE. + * + *

    The JWK specification defines the + * following {@code use} values:

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    JWK Key Use Values
    ValueKey Use
    {@code sig}signature
    {@code enc}encryption
    + * + *

    Other values MAY be used. For best interoperability with other applications however, it is + * recommended to use only the values above.

    + * + *

    When a key is used to wrap another key and a public key use designation for the first key is desired, the + * {@code enc} (encryption) key use value is used, since key wrapping is a kind of encryption. The + * {@code enc} value is also to be used for public keys used for key agreement operations.

    + * + *

    Public Key Use vs Key Operations

    + * + *

    Per + * JWK RFC 7517, Section 4.3, last paragraph, + * the {@code use} (Public Key Use) and {@link #getOperations() key_ops (Key Operations)} members + * SHOULD NOT be used together; however, if both are used, the information they convey MUST be + * consistent. Applications should specify which of these members they use, if either is to be used by the + * application.

    + * + * @return the JWK {@code use} value or {@code null} if not present. + */ + String getPublicKeyUse(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AsymmetricJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AsymmetricJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/AsymmetricJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * A {@link JwkBuilder} that builds asymmetric (public or private) JWKs. + * + * @param the type of Java key provided by the JWK. + * @param the type of asymmetric JWK created + * @param the type of the builder, for subtype method chaining + * @since 0.12.0 + */ +public interface AsymmetricJwkBuilder, T extends AsymmetricJwkBuilder> + extends JwkBuilder, X509Builder { + + /** + * Sets the JWK + * {@code use} (Public Key Use) + * parameter value. {@code use} values are CaSe-SeNsItIvE. A {@code null} value will remove the property + * from the JWK. + * + *

    The JWK specification defines the + * following {@code use} values:

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    JWK Key Use Values
    ValueKey Use
    {@code sig}signature
    {@code enc}encryption
    + * + *

    Other values MAY be used. For best interoperability with other applications however, it is + * recommended to use only the values above.

    + * + *

    When a key is used to wrap another key and a public key use designation for the first key is desired, the + * {@code enc} (encryption) key use value is used, since key wrapping is a kind of encryption. The + * {@code enc} value is also to be used for public keys used for key agreement operations.

    + * + *

    Public Key Use vs Key Operations

    + * + *

    Per + * JWK RFC 7517, Section 4.3, last paragraph, + * the use (Public Key Use) and {@link #operations() key_ops (Key Operations)} members + * SHOULD NOT be used together; however, if both are used, the information they convey MUST be + * consistent. Applications should specify which of these members they use, if either is to be used by the + * application.

    + * + * @param use the JWK {@code use} value. + * @return the builder for method chaining. + * @throws IllegalArgumentException if the {@code use} value is {@code null} or empty. + */ + T publicKeyUse(String use) throws IllegalArgumentException; +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Curve.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Curve.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Curve.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,41 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; + +/** + * A cryptographic Elliptic Curve for use with digital signature or key agreement algorithms. + * + *

    Curve Identifier

    + * + *

    This interface extends {@link Identifiable}; the value returned from {@link #getId()} will + * be used as the JWK + * crv value.

    + * + *

    KeyPair Generation

    + * + *

    A secure-random KeyPair of sufficient strength on the curve may be obtained with its {@link #keyPair()} builder.

    + * + *

    Standard Implementations

    + * + *

    Constants for all JWA standard Curves are available via the {@link Jwks.CRV} registry.

    + * + * @see Jwks.CRV + * @since 0.12.0 + */ +public interface Curve extends Identifiable, KeyPairBuilderSupplier { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DecryptAeadRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DecryptAeadRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DecryptAeadRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; + +/** + * A request to an {@link AeadAlgorithm} to decrypt ciphertext and perform integrity-protection with a supplied + * decryption {@link SecretKey}. Extends both {@link IvSupplier} and {@link DigestSupplier} to + * ensure the respective required IV and AAD tag returned from an {@link AeadResult} are available for decryption. + * + * @since 0.12.0 + */ +public interface DecryptAeadRequest extends AeadRequest, IvSupplier, DigestSupplier { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DecryptionKeyRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DecryptionKeyRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DecryptionKeyRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * A {@link KeyRequest} to obtain a decryption key that will be used to decrypt a JWE using an {@link AeadAlgorithm}. + * The AEAD algorithm used for decryption is accessible via {@link #getEncryptionAlgorithm()}. + * + *

    The key used to perform cryptographic operations, for example a direct shared key, or a + * JWE "key decryption key" will be accessible via {@link #getKey()}. This is always required and + * never {@code null}.

    + * + *

    Any encrypted key material (what the JWE specification calls the + * JWE Encrypted Key) will + * be accessible via {@link #getPayload()}. If present, the {@link KeyAlgorithm} will decrypt it to obtain the resulting + * Content Encryption Key (CEK). + * This may be empty however depending on which {@link KeyAlgorithm} was used during JWE encryption.

    + * + *

    Finally, any public information necessary by the called {@link KeyAlgorithm} to decrypt any + * {@code JWE Encrypted Key} (such as an initialization vector, authentication tag, ephemeral key, etc) is expected + * to be available in the JWE protected header, accessible via {@link #getHeader()}.

    + * + * @param the type of {@link Key} used during the request to obtain the resulting decryption key. + * @since 0.12.0 + */ +public interface DecryptionKeyRequest extends SecureRequest, KeyRequest { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DigestAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DigestAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DigestAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,101 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.lang.Registry; + +import javax.crypto.SecretKey; +import java.io.InputStream; +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * A {@code DigestAlgorithm} is a + * Cryptographic Hash Function + * that computes and verifies cryptographic digests. There are three types of {@code DigestAlgorithm}s represented + * by subtypes, and RFC-standard implementations are available as constants in {@link Registry} singletons: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Types of {@code DigestAlgorithm}s
    SubtypeStandard Implementation RegistrySecurity Model
    {@link HashAlgorithm}{@link Jwks.HASH}Unsecured (unkeyed), does not require a key to compute or verify digests.
    {@link MacAlgorithm}{@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}Requires a {@link SecretKey} to both compute and verify digests (aka + * "Message Authentication Codes").
    {@link SignatureAlgorithm}{@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}Requires a {@link PrivateKey} to compute and {@link PublicKey} to verify digests + * (aka "Digital Signatures").
    + * + *

    Standard Identifier

    + * + *

    {@code DigestAlgorithm} extends {@link Identifiable}: the value returned from + * {@link Identifiable#getId() getId()} will be used as the JWT standard identifier where required.

    + * + *

    For example, + * when a {@link MacAlgorithm} or {@link SignatureAlgorithm} is used to secure a JWS, the value returned from + * {@code algorithm.getId()} will be used as the JWS "alg" protected header value. Or when a + * {@link HashAlgorithm} is used to compute a {@link JwkThumbprint}, it's {@code algorithm.getId()} value will be + * used within the thumbprint's {@link JwkThumbprint#toURI() URI} per JWT RFC requirements.

    + * + * @param the type of {@link Request} used when computing a digest. + * @param the type of {@link VerifyDigestRequest} used when verifying a digest. + * @see Jwks.HASH + * @see io.jsonwebtoken.Jwts.SIG Jwts.SIG + * @since 0.12.0 + */ +public interface DigestAlgorithm, V extends VerifyDigestRequest> extends Identifiable { + + /** + * Returns a cryptographic digest of the request {@link Request#getPayload() payload}. + * + * @param request the request containing the data to be hashed, mac'd or signed. + * @return a cryptographic digest of the request {@link Request#getPayload() payload}. + * @throws SecurityException if there is invalid key input or a problem during digest creation. + */ + byte[] digest(R request) throws SecurityException; + + /** + * Returns {@code true} if the provided {@link VerifyDigestRequest#getDigest() digest} matches the expected value + * for the given {@link VerifyDigestRequest#getPayload() payload}, {@code false} otherwise. + * + * @param request the request containing the {@link VerifyDigestRequest#getDigest() digest} to verify for the + * associated {@link VerifyDigestRequest#getPayload() payload}. + * @return {@code true} if the provided {@link VerifyDigestRequest#getDigest() digest} matches the expected value + * for the given {@link VerifyDigestRequest#getPayload() payload}, {@code false} otherwise. + * @throws SecurityException if there is an invalid key input or a problem that won't allow digest verification. + */ + boolean verify(V request) throws SecurityException; +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DigestSupplier.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DigestSupplier.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DigestSupplier.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * A {@code DigestSupplier} provides access to the result of a cryptographic digest algorithm, such as a + * Message Digest, MAC, Signature, or Authentication Tag. + * + * @since 0.12.0 + */ +public interface DigestSupplier { + + /** + * Returns a cryptographic digest result, such as a Message Digest, MAC, Signature, or Authentication Tag + * depending on the cryptographic algorithm that produced it. + * + * @return a cryptographic digest result, such as a Message Digest, MAC, Signature, or Authentication Tag + * * depending on the cryptographic algorithm that produced it. + */ + byte[] getDigest(); + +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DynamicJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DynamicJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/DynamicJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; +import java.security.Key; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.List; + +/** + * A {@link JwkBuilder} that coerces to a more type-specific builder based on the {@link Key} that will be + * represented as a JWK. + * + * @param the type of Java {@link Key} represented by the created {@link Jwk}. + * @param the type of {@link Jwk} created by the builder + * @since 0.12.0 + */ +public interface DynamicJwkBuilder> extends JwkBuilder> { + + /** + * Ensures the builder will create a {@link PublicJwk} for the specified Java {@link X509Certificate} chain. + * The first {@code X509Certificate} in the chain (at array index 0) MUST contain a {@link PublicKey} + * instance when calling the certificate's {@link X509Certificate#getPublicKey() getPublicKey()} method. + * + *

    This method is provided for congruence with the other {@code chain} methods and is expected to be used when + * the calling code has a variable {@code PublicKey} reference. Based on the argument type, it will + * delegate to one of the following methods if possible: + *

      + *
    • {@link #rsaChain(List)}
    • + *
    • {@link #ecChain(List)}
    • + *
    • {@link #octetChain(List)}
    • + *
    + * + *

    If the specified {@code chain} argument is not capable of being supported by one of those methods, an + * {@link UnsupportedKeyException} will be thrown.

    + * + *

    Type Parameters

    + * + *

    In addition to the public key type A, the public key's associated private key type + * B is parameterized as well. This ensures that any subsequent call to the builder's + * {@link PublicJwkBuilder#privateKey(PrivateKey) privateKey} method will be type-safe. For example:

    + * + *
    Jwks.builder().<EdECPublicKey, EdECPrivateKey>chain(edECPublicKeyX509CertificateChain)
    +     *     .privateKey(aPrivateKey) // <-- must be an EdECPrivateKey instance
    +     *     ... etc ...
    +     *     .build();
    + * + * @param the type of {@link PublicKey} provided by the created public JWK. + * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce a + * {@link PrivateJwk} if desired. + * @param chain the {@link X509Certificate} chain to inspect to find the {@link PublicKey} to represent as a + * {@link PublicJwk}. + * @return the builder coerced as a {@link PublicJwkBuilder} for continued method chaining. + * @throws UnsupportedKeyException if the specified key is not a supported type and cannot be used to delegate to + * other {@code key} methods. + * @see PublicJwk + * @see PrivateJwk + */ + PublicJwkBuilder chain(List chain) + throws UnsupportedKeyException; + + /** + * Ensures the builder will create a {@link SecretJwk} for the specified Java {@link SecretKey}. + * + * @param key the {@link SecretKey} to represent as a {@link SecretJwk}. + * @return the builder coerced as a {@link SecretJwkBuilder}. + */ + SecretJwkBuilder key(SecretKey key); + + /** + * Ensures the builder will create an {@link RsaPublicJwk} for the specified Java {@link RSAPublicKey}. + * + * @param key the {@link RSAPublicKey} to represent as a {@link RsaPublicJwk}. + * @return the builder coerced as an {@link RsaPublicJwkBuilder}. + */ + RsaPublicJwkBuilder key(RSAPublicKey key); + + /** + * Ensures the builder will create an {@link RsaPrivateJwk} for the specified Java {@link RSAPrivateKey}. If + * possible, it is recommended to also call the resulting builder's + * {@link RsaPrivateJwkBuilder#publicKey(PublicKey) publicKey} method with the private key's matching + * {@link PublicKey} for better performance. See the + * {@link RsaPrivateJwkBuilder#publicKey(PublicKey) publicKey} and {@link PrivateJwk} JavaDoc for more + * information. + * + * @param key the {@link RSAPublicKey} to represent as a {@link RsaPublicJwk}. + * @return the builder coerced as an {@link RsaPrivateJwkBuilder}. + */ + RsaPrivateJwkBuilder key(RSAPrivateKey key); + + /** + * Ensures the builder will create an {@link EcPublicJwk} for the specified Java {@link ECPublicKey}. + * + * @param key the {@link ECPublicKey} to represent as a {@link EcPublicJwk}. + * @return the builder coerced as an {@link EcPublicJwkBuilder}. + */ + EcPublicJwkBuilder key(ECPublicKey key); + + /** + * Ensures the builder will create an {@link EcPrivateJwk} for the specified Java {@link ECPrivateKey}. If + * possible, it is recommended to also call the resulting builder's + * {@link EcPrivateJwkBuilder#publicKey(PublicKey) publicKey} method with the private key's matching + * {@link PublicKey} for better performance. See the + * {@link EcPrivateJwkBuilder#publicKey(PublicKey) publicKey} and {@link PrivateJwk} JavaDoc for more + * information. + * + * @param key the {@link ECPublicKey} to represent as an {@link EcPublicJwk}. + * @return the builder coerced as a {@link EcPrivateJwkBuilder}. + */ + EcPrivateJwkBuilder key(ECPrivateKey key); + + /** + * Ensures the builder will create a {@link PublicJwk} for the specified Java {@link PublicKey} argument. This + * method is provided for congruence with the other {@code key} methods and is expected to be used when + * the calling code has an untyped {@code PublicKey} reference. Based on the argument type, it will delegate to one + * of the following methods if possible: + *
      + *
    • {@link #key(RSAPublicKey)}
    • + *
    • {@link #key(ECPublicKey)}
    • + *
    • {@link #octetKey(PublicKey)}
    • + *
    + * + *

    If the specified {@code key} argument is not capable of being supported by one of those methods, an + * {@link UnsupportedKeyException} will be thrown.

    + * + *

    Type Parameters

    + * + *

    In addition to the public key type A, the public key's associated private key type + * B is parameterized as well. This ensures that any subsequent call to the builder's + * {@link PublicJwkBuilder#privateKey(PrivateKey) privateKey} method will be type-safe. For example:

    + * + *
    Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPublicKey)
    +     *     .privateKey(aPrivateKey) // <-- must be an EdECPrivateKey instance
    +     *     ... etc ...
    +     *     .build();
    + * + * @param
    the type of {@link PublicKey} provided by the created public JWK. + * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce a + * {@link PrivateJwk} if desired. + * @param key the {@link PublicKey} to represent as a {@link PublicJwk}. + * @return the builder coerced as a {@link PublicJwkBuilder} for continued method chaining. + * @throws UnsupportedKeyException if the specified key is not a supported type and cannot be used to delegate to + * other {@code key} methods. + * @see PublicJwk + * @see PrivateJwk + */ + PublicJwkBuilder key(A key) throws UnsupportedKeyException; + + /** + * Ensures the builder will create a {@link PrivateJwk} for the specified Java {@link PrivateKey} argument. This + * method is provided for congruence with the other {@code key} methods and is expected to be used when + * the calling code has an untyped {@code PrivateKey} reference. Based on the argument type, it will delegate to one + * of the following methods if possible: + *
      + *
    • {@link #key(RSAPrivateKey)}
    • + *
    • {@link #key(ECPrivateKey)}
    • + *
    • {@link #octetKey(PrivateKey)}
    • + *
    + * + *

    If the specified {@code key} argument is not capable of being supported by one of those methods, an + * {@link UnsupportedKeyException} will be thrown.

    + * + *

    Type Parameters

    + * + *

    In addition to the private key type B, the private key's associated public key type + * A is parameterized as well. This ensures that any subsequent call to the builder's + * {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} method will be type-safe. For example:

    + * + *
    Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPrivateKey)
    +     *     .publicKey(aPublicKey) // <-- must be an EdECPublicKey instance
    +     *     ... etc ...
    +     *     .build();
    + * + * @param
    the type of {@link PublicKey} paired with the {@code key} argument to produce the {@link PrivateJwk}. + * @param the type of the {@link PrivateKey} argument. + * @param key the {@link PrivateKey} to represent as a {@link PrivateJwk}. + * @return the builder coerced as a {@link PrivateJwkBuilder} for continued method chaining. + * @throws UnsupportedKeyException if the specified key is not a supported type and cannot be used to delegate to + * other {@code key} methods. + * @see PublicJwk + * @see PrivateJwk + */ + PrivateJwkBuilder key(B key) throws UnsupportedKeyException; + + /** + * Ensures the builder will create a {@link PrivateJwk} for the specified Java {@link KeyPair} argument. This + * method is provided for congruence with the other {@code keyPair} methods and is expected to be used when + * the calling code has a variable {@code PrivateKey} reference. Based on the argument's {@code PrivateKey} type, + * it will delegate to one of the following methods if possible: + *
      + *
    • {@link #key(RSAPrivateKey)}
    • + *
    • {@link #key(ECPrivateKey)}
    • + *
    • {@link #octetKey(PrivateKey)}
    • + *
    + *

    and automatically set the resulting builder's {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} with + * the pair's {@code PublicKey}.

    + * + *

    If the specified {@code key} argument is not capable of being supported by one of those methods, an + * {@link UnsupportedKeyException} will be thrown.

    + * + *

    Type Parameters

    + * + *

    In addition to the private key type B, the private key's associated public key type + * A is parameterized as well. This ensures that any subsequent call to the builder's + * {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} method will be type-safe. For example:

    + * + *
    Jwks.builder().<EdECPublicKey, EdECPrivateKey>keyPair(anEdECKeyPair)
    +     *     .publicKey(aPublicKey) // <-- must be an EdECPublicKey instance
    +     *     ... etc ...
    +     *     .build();
    + * + * @param
    the {@code keyPair} argument's {@link PublicKey} type + * @param the {@code keyPair} argument's {@link PrivateKey} type + * @param keyPair the {@code KeyPair} containing the public and private key + * @return the builder coerced as a {@link PrivateJwkBuilder} for continued method chaining. + * @throws UnsupportedKeyException if the specified {@code KeyPair}'s keys are not supported and cannot be used to + * delegate to other {@code key} methods. + * @see PublicJwk + * @see PrivateJwk + */ + PrivateJwkBuilder keyPair(KeyPair keyPair) + throws UnsupportedKeyException; + + /** + * Ensures the builder will create an {@link OctetPublicJwk} for the specified Edwards-curve {@code PublicKey} + * argument. The {@code PublicKey} must be an instance of one of the following: + * + * + *

    Type Parameters

    + * + *

    In addition to the public key type A, the public key's associated private key type + * B is parameterized as well. This ensures that any subsequent call to the builder's + * {@link PublicJwkBuilder#privateKey(PrivateKey) privateKey} method will be type-safe. For example:

    + * + *
    Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPublicKey)
    +     *     .privateKey(aPrivateKey) // <-- must be an EdECPrivateKey instance
    +     *     ... etc ...
    +     *     .build();
    + * + * @param the type of Edwards-curve {@link PublicKey} provided by the created public JWK. + * @param the type of Edwards-curve {@link PrivateKey} that may be paired with the {@link PublicKey} to produce + * an {@link OctetPrivateJwk} if desired. + * @param key the Edwards-curve {@link PublicKey} to represent as an {@link OctetPublicJwk}. + * @return the builder coerced as a {@link OctetPublicJwkBuilder} for continued method chaining. + * @throws UnsupportedKeyException if the specified key is not a supported Edwards-curve key. + * @see java.security.interfaces.XECPublicKey + * @see java.security.interfaces.EdECPublicKey + */ + OctetPublicJwkBuilder octetKey(A key); + + /** + * Ensures the builder will create an {@link OctetPrivateJwk} for the specified Edwards-curve {@code PrivateKey} + * argument. The {@code PrivateKey} must be an instance of one of the following: + * + * + *

    Type Parameters

    + * + *

    In addition to the private key type B, the private key's associated public key type + * A is parameterized as well. This ensures that any subsequent call to the builder's + * {@link PrivateJwkBuilder#publicKey(PublicKey) publicKey} method will be type-safe. For example:

    + * + *
    Jwks.builder().<EdECPublicKey, EdECPrivateKey>key(anEdECPrivateKey)
    +     *     .publicKey(aPublicKey) // <-- must be an EdECPublicKey instance
    +     *     ... etc ...
    +     *     .build();
    + * + * @param the type of the Edwards-curve {@link PrivateKey} argument. + * @param the type of Edwards-curve {@link PublicKey} paired with the {@code key} argument to produce the + * {@link OctetPrivateJwk}. + * @param key the Edwards-curve {@link PrivateKey} to represent as an {@link OctetPrivateJwk}. + * @return the builder coerced as an {@link OctetPrivateJwkBuilder} for continued method chaining. + * @throws UnsupportedKeyException if the specified key is not a supported Edwards-curve key. + * @see java.security.interfaces.XECPrivateKey + * @see java.security.interfaces.EdECPrivateKey + */ + OctetPrivateJwkBuilder octetKey(A key); + + /** + * Ensures the builder will create an {@link OctetPublicJwk} for the specified Java {@link X509Certificate} chain. + * The first {@code X509Certificate} in the chain (at list index 0) MUST + * {@link X509Certificate#getPublicKey() contain} an Edwards-curve public key as defined by + * {@link #octetKey(PublicKey)}. + * + * @param the type of Edwards-curve {@link PublicKey} contained in the first {@code X509Certificate}. + * @param the type of Edwards-curve {@link PrivateKey} that may be paired with the {@link PublicKey} to produce + * an {@link OctetPrivateJwk} if desired. + * @param chain the {@link X509Certificate} chain to inspect to find the Edwards-curve {@code PublicKey} to + * represent as an {@link OctetPublicJwk}. + * @return the builder coerced as an {@link OctetPublicJwkBuilder} for continued method chaining. + */ + OctetPublicJwkBuilder octetChain(List chain); + + /** + * Ensures the builder will create an {@link OctetPrivateJwk} for the specified Java Edwards-curve + * {@link KeyPair}. The pair's {@link KeyPair#getPublic() public key} MUST be an + * Edwards-curve public key as defined by {@link #octetKey(PublicKey)}. The pair's + * {@link KeyPair#getPrivate() private key} MUST be an Edwards-curve private key as defined by + * {@link #octetKey(PrivateKey)}. + * + * @param the type of Edwards-curve {@link PublicKey} contained in the key pair. + * @param the type of the Edwards-curve {@link PrivateKey} contained in the key pair. + * @param keyPair the Edwards-curve {@link KeyPair} to represent as an {@link OctetPrivateJwk}. + * @return the builder coerced as an {@link OctetPrivateJwkBuilder} for continued method chaining. + * @throws IllegalArgumentException if the {@code keyPair} does not contain Edwards-curve public and private key + * instances. + */ + OctetPrivateJwkBuilder octetKeyPair(KeyPair keyPair); + + /** + * Ensures the builder will create an {@link EcPublicJwk} for the specified Java {@link X509Certificate} chain. + * The first {@code X509Certificate} in the chain (at list index 0) MUST contain an {@link ECPublicKey} + * instance when calling the certificate's {@link X509Certificate#getPublicKey() getPublicKey()} method. + * + * @param chain the {@link X509Certificate} chain to inspect to find the {@link ECPublicKey} to represent as a + * {@link EcPublicJwk}. + * @return the builder coerced as an {@link EcPublicJwkBuilder}. + */ + EcPublicJwkBuilder ecChain(List chain); + + /** + * Ensures the builder will create an {@link EcPrivateJwk} for the specified Java Elliptic Curve + * {@link KeyPair}. The pair's {@link KeyPair#getPublic() public key} MUST be an + * {@link ECPublicKey} instance. The pair's {@link KeyPair#getPrivate() private key} MUST be an + * {@link ECPrivateKey} instance. + * + * @param keyPair the EC {@link KeyPair} to represent as an {@link EcPrivateJwk}. + * @return the builder coerced as an {@link EcPrivateJwkBuilder}. + * @throws IllegalArgumentException if the {@code keyPair} does not contain {@link ECPublicKey} and + * {@link ECPrivateKey} instances. + */ + EcPrivateJwkBuilder ecKeyPair(KeyPair keyPair) throws IllegalArgumentException; + + /** + * Ensures the builder will create an {@link RsaPublicJwk} for the specified Java {@link X509Certificate} chain. + * The first {@code X509Certificate} in the chain (at list index 0) MUST contain an {@link RSAPublicKey} + * instance when calling the certificate's {@link X509Certificate#getPublicKey() getPublicKey()} method. + * + * @param chain the {@link X509Certificate} chain to inspect to find the {@link RSAPublicKey} to represent as a + * {@link RsaPublicJwk}. + * @return the builder coerced as an {@link RsaPublicJwkBuilder}. + */ + RsaPublicJwkBuilder rsaChain(List chain); + + /** + * Ensures the builder will create an {@link RsaPrivateJwk} for the specified Java RSA + * {@link KeyPair}. The pair's {@link KeyPair#getPublic() public key} MUST be an + * {@link RSAPublicKey} instance. The pair's {@link KeyPair#getPrivate() private key} MUST be an + * {@link RSAPrivateKey} instance. + * + * @param keyPair the RSA {@link KeyPair} to represent as an {@link RsaPrivateJwk}. + * @return the builder coerced as an {@link RsaPrivateJwkBuilder}. + * @throws IllegalArgumentException if the {@code keyPair} does not contain {@link RSAPublicKey} and + * {@link RSAPrivateKey} instances. + */ + RsaPrivateJwkBuilder rsaKeyPair(KeyPair keyPair) throws IllegalArgumentException; +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/EcPrivateJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/EcPrivateJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/EcPrivateJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; + +/** + * JWK representation of an {@link ECPrivateKey} as defined by the JWA (RFC 7518) specification sections on + * Parameters for Elliptic Curve Keys and + * Parameters for Elliptic Curve Private Keys. + * + *

    Note that the various EC-specific properties are not available as separate dedicated getter methods, as most Java + * applications should rarely, if ever, need to access these individual key properties since they typically represent + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link ECPrivateKey} instance returned by {@link #toKey()} and + * query that instead.

    + * + *

    Even so, because these properties exist and are readable by nature of every JWK being a + * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method + * using an appropriate JWK parameter id, for example:

    + *
    + * jwk.get("x");
    + * jwk.get("y");
    + * // ... etc ...
    + * + * @since 0.12.0 + */ +public interface EcPrivateJwk extends PrivateJwk { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/EcPrivateJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/EcPrivateJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/EcPrivateJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; + +/** + * A {@link PrivateJwkBuilder} that creates {@link EcPrivateJwk}s. + * + * @since 0.12.0 + */ +public interface EcPrivateJwkBuilder extends PrivateJwkBuilder { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/EcPublicJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/EcPublicJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/EcPublicJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.ECPublicKey; + +/** + * JWK representation of an {@link ECPublicKey} as defined by the JWA (RFC 7518) specification sections on + * Parameters for Elliptic Curve Keys and + * Parameters for Elliptic Curve Public Keys. + * + *

    Note that the various EC-specific properties are not available as separate dedicated getter methods, as most Java + * applications should rarely, if ever, need to access these individual key properties since they typically represent + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link ECPublicKey} instance returned by {@link #toKey()} and + * query that instead.

    + * + *

    Even so, because these properties exist and are readable by nature of every JWK being a + * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method + * using an appropriate JWK parameter id, for example:

    + *
    + * jwk.get("x");
    + * jwk.get("y");
    + * // ... etc ...
    + * + * @since 0.12.0 + */ +public interface EcPublicJwk extends PublicJwk { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/EcPublicJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/EcPublicJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/EcPublicJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; + +/** + * A {@link PublicJwkBuilder} that creates {@link EcPublicJwk}s. + * + * @since 0.12.0 + */ +public interface EcPublicJwkBuilder extends PublicJwkBuilder { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/HashAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/HashAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/HashAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,45 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; + +import java.io.InputStream; + +/** + * A {@link DigestAlgorithm} that computes and verifies digests without the use of a cryptographic key, such as for + * thumbprints and digital fingerprints. + * + *

    Standard Identifier

    + * + *

    {@code HashAlgorithm} extends {@link Identifiable}: the value returned from + * {@link Identifiable#getId() getId()} in all JWT standard hash algorithms will return one of the + * "{@code Hash Name String}" values defined in the IANA + * Named Information Hash + * Algorithm Registry. This is to ensure the correct algorithm ID is used within other JWT-standard identifiers, + * such as within JWK Thumbprint URIs.

    + * + *

    IANA Standard Implementations

    + * + *

    Constant definitions and utility methods for common (but not all) + * IANA Hash + * Algorithms are available via {@link Jwks.HASH}.

    + * + * @see Jwks.HASH + * @since 0.12.0 + */ +public interface HashAlgorithm extends DigestAlgorithm, VerifyDigestRequest> { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/InvalidKeyException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/InvalidKeyException.java (.../InvalidKeyException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/InvalidKeyException.java (.../InvalidKeyException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,11 +16,30 @@ package io.jsonwebtoken.security; /** + * A {@code KeyException} thrown when encountering a key that is not suitable for the required functionality, or + * when attempting to use a Key in an incorrect or prohibited manner. + * * @since 0.10.0 */ public class InvalidKeyException extends KeyException { + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ public InvalidKeyException(String message) { super(message); } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + * @since 0.12.0 + */ + public InvalidKeyException(String message, Throwable cause) { + super(message, cause); + } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/IvSupplier.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/IvSupplier.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/IvSupplier.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * An {@code IvSupplier} provides access to the secure-random Initialization Vector used during + * encryption, which must in turn be presented for use during decryption. To maintain the security integrity of cryptographic + * algorithms, a new secure-random Initialization Vector MUST be generated for every individual + * encryption attempt. + * + * @since 0.12.0 + */ +public interface IvSupplier { + + /** + * Returns the secure-random Initialization Vector used during encryption, which must in turn be presented for + * use during decryption. + * + * @return the secure-random Initialization Vector used during encryption, which must in turn be presented for + * use during decryption. + */ + byte[] getIv(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Jwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Jwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Jwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.lang.Supplier; + +import java.security.Key; +import java.util.Map; +import java.util.Set; + +/** + * A JWK is an immutable set of name/value pairs that represent a cryptographic key as defined by + * RFC 7517: JSON Web Key (JWK). The {@code Jwk} + * interface represents properties common to all JWKs. Subtypes will have additional properties specific to + * different types of cryptographic keys (e.g. Secret, Asymmetric, RSA, Elliptic Curve, etc). + * + *

    Immutability

    + * + *

    JWKs are immutable and cannot be changed after they are created. {@code Jwk} extends the + * {@link Map} interface purely out of convenience: to allow easy marshalling to JSON as well as name/value + * pair access and key/value iteration, and other conveniences provided by the Map interface. Attempting to call any of + * the {@link Map} interface's mutation methods however (such as {@link Map#put(Object, Object) put}, + * {@link Map#remove(Object) remove}, {@link Map#clear() clear}, etc) will throw an + * {@link UnsupportedOperationException}.

    + * + *

    Identification

    + * + *

    {@code Jwk} extends {@link Identifiable} to support the + * JWK {@code kid} parameter. Calling + * {@link #getId() aJwk.getId()} is the type-safe idiomatic approach to the alternative equivalent of + * {@code aJwk.get("kid")}. Either approach will return an id if one was originally set on the JWK, or {@code null} if + * an id does not exist.

    + * + *

    Private and Secret Value Safety

    + * + *

    JWKs often represent secret or private key data which should never be exposed publicly, nor mistakenly printed + * to application logs or {@code System.out.println} calls. As a result, all JJWT JWK + * private or secret values are 'wrapped' in a {@link io.jsonwebtoken.lang.Supplier Supplier} instance to ensure + * any attempt to call {@link String#toString() toString()} on the value will print a redacted value instead of an + * actual private or secret value.

    + * + *

    For example, a {@link SecretJwk} will have an internal "{@code k}" member whose value reflects raw + * key material that should always be kept secret. If the following is called:

    + *
    + * System.out.println(aSecretJwk.get("k"));
    + *

    You would see the following:

    + *
    + * <redacted>
    + *

    instead of the actual/raw {@code k} value.

    + * + *

    Similarly, if attempting to print the entire JWK:

    + *
    + * System.out.println(aSecretJwk);
    + *

    You would see the following substring in the output:

    + *
    + * k=<redacted>
    + *

    instead of the actual/raw {@code k} value.

    + * + *

    Finally, because all private or secret values are wrapped as {@link io.jsonwebtoken.lang.Supplier} + * instances, if you really wanted the real internal value, you could just call the supplier's + * {@link Supplier#get() get()} method:

    + *
    + * String k = ((Supplier<String>)aSecretJwk.get("k")).get();
    + *

    but BE CAREFUL: obtaining the raw value in your application code exposes greater security + * risk - you must ensure to keep that value safe and out of console or log output. It is almost always better to + * interact with the JWK's {@link #toKey() toKey()} instance directly instead of accessing + * JWK internal serialization parameters.

    + * + * @param The type of Java {@link Key} represented by this JWK + * @since 0.12.0 + */ +public interface Jwk extends Identifiable, Map { + + /** + * Returns the JWK + * {@code alg} (Algorithm) value + * or {@code null} if not present. + * + * @return the JWK {@code alg} value or {@code null} if not present. + */ + String getAlgorithm(); + + /** + * Returns the JWK {@code key_ops} + * (Key Operations) parameter values or {@code null} if not present. All JWK standard Key Operations are + * available via the {@link Jwks.OP} registry, but other (custom) values MAY be present in the returned + * set. + * + * @return the JWK {@code key_ops} value or {@code null} if not present. + * @see key_ops(Key Operations) Parameter + */ + Set getOperations(); + + /** + * Returns the required JWK + * {@code kty} (Key Type) + * parameter value. A value is required and may not be {@code null}. + * + *

    The JWA specification defines the + * following {@code kty} values:

    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    JWK Key Types
    ValueKey Type
    {@code EC}Elliptic Curve [DSS]
    {@code RSA}RSA [RFC 3447]
    {@code oct}Octet sequence (used to represent symmetric keys)
    {@code OKP}Octet Key Pair (used to represent Edwards + * Elliptic Curve keys)
    + * + * @return the JWK {@code kty} (Key Type) value. + */ + String getType(); + + /** + * Computes and returns the canonical JWK Thumbprint of this + * JWK using the {@code SHA-256} hash algorithm. This is a convenience method that delegates to + * {@link #thumbprint(HashAlgorithm)} with a {@code SHA-256} {@link HashAlgorithm} instance. + * + * @return the canonical JWK Thumbprint of this + * JWK using the {@code SHA-256} hash algorithm. + * @see #thumbprint(HashAlgorithm) + */ + JwkThumbprint thumbprint(); + + /** + * Computes and returns the canonical JWK Thumbprint of this + * JWK using the specified hash algorithm. + * + * @param alg the hash algorithm to use to compute the digest of the canonical JWK Thumbprint JSON form of this JWK. + * @return the canonical JWK Thumbprint of this + * JWK using the specified hash algorithm. + */ + JwkThumbprint thumbprint(HashAlgorithm alg); + + /** + * Represents the JWK as its corresponding Java {@link Key} instance for use with Java cryptographic + * APIs. + * + * @return the JWK's corresponding Java {@link Key} instance for use with Java cryptographic APIs. + */ + K toKey(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.lang.Conjunctor; +import io.jsonwebtoken.lang.MapMutator; +import io.jsonwebtoken.lang.NestedCollection; + +import java.security.Key; + +/** + * A {@link SecurityBuilder} that produces a JWK. A JWK is an immutable set of name/value pairs that represent a + * cryptographic key as defined by + * RFC 7517: JSON Web Key (JWK). + * The {@code JwkBuilder} interface represents common JWK properties that may be specified for any type of JWK. + * Builder subtypes support additional JWK properties specific to different types of cryptographic keys + * (e.g. Secret, Asymmetric, RSA, Elliptic Curve, etc). + * + * @param the type of Java {@link Key} represented by the constructed JWK. + * @param the type of {@link Jwk} created by the builder + * @param the type of the builder, for subtype method chaining + * @see SecretJwkBuilder + * @see RsaPublicJwkBuilder + * @see RsaPrivateJwkBuilder + * @see EcPublicJwkBuilder + * @see EcPrivateJwkBuilder + * @see OctetPublicJwkBuilder + * @see OctetPrivateJwkBuilder + * @since 0.12.0 + */ +public interface JwkBuilder, T extends JwkBuilder> + extends MapMutator, SecurityBuilder, KeyOperationPolicied { + + /** + * Sets the JWK {@code alg} (Algorithm) + * Parameter. + * + *

    The {@code alg} (algorithm) parameter identifies the algorithm intended for use with the key. The + * value specified should either be one of the values in the IANA + * JSON Web Signature and Encryption + * Algorithms registry or be a value that contains a {@code Collision-Resistant Name}. The {@code alg} + * must be a CaSe-SeNsItIvE ASCII string.

    + * + * @param alg the JWK {@code alg} value. + * @return the builder for method chaining. + * @throws IllegalArgumentException if {@code alg} is {@code null} or empty. + */ + T algorithm(String alg) throws IllegalArgumentException; + + /** + * Sets the JWK {@code kid} (Key ID) + * Parameter. + * + *

    The {@code kid} (key ID) parameter is used to match a specific key. This is used, for instance, + * to choose among a set of keys within a {@code JWK Set} during key rollover. The structure of the + * {@code kid} value is unspecified. When {@code kid} values are used within a JWK Set, different keys + * within the {@code JWK Set} SHOULD use distinct {@code kid} values. (One example in which + * different keys might use the same {@code kid} value is if they have different {@code kty} (key type) + * values but are considered to be equivalent alternatives by the application using them.)

    + * + *

    The {@code kid} value is a CaSe-SeNsItIvE string, and it is optional. When used with JWS or JWE, + * the {@code kid} value is used to match a JWS or JWE {@code kid} Header Parameter value.

    + * + * @param kid the JWK {@code kid} value. + * @return the builder for method chaining. + * @throws IllegalArgumentException if the argument is {@code null} or empty. + */ + T id(String kid) throws IllegalArgumentException; + + /** + * Sets the JWK's {@link #id(String) kid} value to be the Base64URL-encoding of its {@code SHA-256} + * {@link Jwk#thumbprint(HashAlgorithm) thumbprint}. That is, the constructed JWK's {@code kid} value will equal + * jwk.{@link Jwk#thumbprint(HashAlgorithm) thumbprint}({@link Jwks.HASH}.{@link Jwks.HASH#SHA256 SHA256}).{@link JwkThumbprint#toString() toString()}. + * + *

    This is a convenience method that delegates to {@link #idFromThumbprint(HashAlgorithm)} using + * {@link Jwks.HASH}{@code .}{@link Jwks.HASH#SHA256 SHA256}.

    + * + * @return the builder for method chaining. + */ + T idFromThumbprint(); + + /** + * Sets the JWK's {@link #id(String) kid} value to be the Base64URL-encoding of its + * {@link Jwk#thumbprint(HashAlgorithm) thumbprint} using the specified {@link HashAlgorithm}. That is, the + * constructed JWK's {@code kid} value will equal + * {@link Jwk#thumbprint(HashAlgorithm) thumbprint}(alg).{@link JwkThumbprint#toString() toString()}. + * + * @param alg the hash algorithm to use to compute the thumbprint. + * @return the builder for method chaining. + * @see Jwks.HASH + */ + T idFromThumbprint(HashAlgorithm alg); + + /** + * Configures the key operations for which + * the key is intended to be used. When finished, use the collection's {@link Conjunctor#and() and()} method to + * return to the JWK builder, for example: + *
    +     * jwkBuilder.operations().add(aKeyOperation).{@link Conjunctor#and() and()} // etc...
    + * + *

    The {@code add()} method(s) will throw an {@link IllegalArgumentException} if any of the specified + * {@code KeyOperation}s are not permitted by the JWK's + * {@link #operationPolicy(KeyOperationPolicy) operationPolicy}. See that documentation for more + * information on security vulnerabilities when using the same key with multiple algorithms.

    + * + *

    Standard {@code KeyOperation}s and Overrides

    + * + *

    All RFC-standard JWK Key Operations in the {@link Jwks.OP} registry are supported via the builder's default + * {@link #operationPolicy(KeyOperationPolicy) operationPolicy}, but other (custom) values + * MAY be specified (for example, using a {@link Jwks.OP#builder()}).

    + * + *

    If the {@code JwkBuilder} is being used to rebuild or parse an existing JWK however, any custom operations + * should be enabled by configuring an {@link #operationPolicy(KeyOperationPolicy) operationPolicy} + * that includes the custom values (e.g. via + * {@link Jwks.OP#policy()}.{@link KeyOperationPolicyBuilder#add(KeyOperation) add(customKeyOperation)}).

    + * + *

    For best interoperability with other applications however, it is recommended to use only the {@link Jwks.OP} + * constants.

    + * + * @return the {@link NestedCollection} to use for {@code key_ops} configuration. + * @see Jwks.OP + * @see RFC 7517: key_ops (Key Operations) Parameter + */ + NestedCollection operations(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkParserBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkParserBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkParserBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.io.ParserBuilder; + +/** + * A builder to construct a {@link Parser} that can parse {@link Jwk}s. + * Example usage: + *
    + * Jwk<?> jwk = Jwks.parser()
    + *         .provider(aJcaProvider)     // optional
    + *         .deserializer(deserializer) // optional
    + *         .operationPolicy(policy)    // optional
    + *         .build()
    + *         .parse(jwkString);
    + * + * @since 0.12.0 + */ +public interface JwkParserBuilder extends ParserBuilder, JwkParserBuilder>, KeyOperationPolicied { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkSet.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkSet.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkSet.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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.security; + +import java.util.Map; +import java.util.Set; + +/** + * A JWK Set is an immutable JSON Object that represents a Set of {@link Jwk}s as defined by + * RFC 7517 JWK Set Format. Per that specification, + * any number of name/value pairs may be present in a {@code JwkSet}, but only a non-empty {@link #getKeys() keys} + * set MUST be present. + * + *

    Immutability

    + * + *

    JWK Sets are immutable and cannot be changed after they are created. {@code JwkSet} extends the + * {@link Map} interface purely out of convenience: to allow easy marshalling to JSON as well as name/value + * pair access and key/value iteration, and other conveniences provided by the Map interface. Attempting to call any of + * the {@link Map} interface's mutation methods however (such as {@link Map#put(Object, Object) put}, + * {@link Map#remove(Object) remove}, {@link Map#clear() clear}, etc) will throw an + * {@link UnsupportedOperationException}.

    + * + * @since 0.12.0 + */ +public interface JwkSet extends Map, Iterable> { + + /** + * Returns the non-null, non-empty set of JWKs contained within the {@code JwkSet}. + * + * @return the non-null, non-empty set of JWKs contained within the {@code JwkSet}. + */ + Set> getKeys(); + +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkSetBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkSetBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkSetBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,66 @@ +/* + * 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.security; + +import io.jsonwebtoken.lang.MapMutator; + +import java.security.Provider; +import java.util.Collection; + +/** + * A builder that produces {@link JwkSet}s containing {@link Jwk}s. {@code Jwk}s with any key + * {@link Jwk#getOperations() operations} will be validated by + * the {@link #operationPolicy(KeyOperationPolicy) operationPolicy} first before being added. + * + * @see #operationPolicy(KeyOperationPolicy) + * @see #provider(Provider) + * @since 0.12.0 + */ +public interface JwkSetBuilder extends MapMutator, + SecurityBuilder, KeyOperationPolicied { + + /** + * Appends the specified {@code jwk} to the set. If the {@code jwk} has any key + * {@link Jwk#getOperations() operations}, it will be validated with the + * {@link #operationPolicy(KeyOperationPolicy) operationPolicy} first before being added. + * + * @param jwk the jwk to add to the JWK Set. A {@code null} {@code jwk} is ignored. + * @return the builder for method chaining + */ + JwkSetBuilder add(Jwk jwk); + + /** + * Appends the specified {@code Jwk} collection to the JWK Set. If any {@code Jwk} in the collection has + * any key {@link Jwk#getOperations() operations}, it will be validated with the + * {@link #operationPolicy(KeyOperationPolicy) operationPolicy} first before being added. + * + * @param c the collection of {@code Jwk}s to add to the JWK Set. A {@code null} or empty collection is ignored. + * @return the builder for method chaining + */ + JwkSetBuilder add(Collection> c); + + /** + * Sets the {@code JwkSet} {@code keys} parameter value; per standard Java setter idioms, this is a + * full replacement operation, removing any previous keys from the set. A {@code null} or empty + * collection removes all keys from the set. + * + * @param c the (possibly null or empty) collection of {@code Jwk}s to set as the JWK set {@code keys} parameter + * value. + * @return the builder for method chaining + */ + JwkSetBuilder keys(Collection> c); + +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkSetParserBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkSetParserBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkSetParserBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,57 @@ +/* + * 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.security; + +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.io.ParserBuilder; + +/** + * A builder to construct a {@link Parser} that can parse {@link JwkSet}s. + * Example usage: + *
    + * JwkSet jwkSet = Jwks.setParser()
    + *         .provider(aJcaProvider)      // optional
    + *         .json(deserializer)          // optional
    + *         .operationPolicy(policy)     // optional
    + *         .ignoreUnsupported(aBoolean) // optional
    + *         .build()
    + *         .parse(jwkSetString);
    + * + * @since 0.12.0 + */ +public interface JwkSetParserBuilder extends ParserBuilder, KeyOperationPolicied { + + /** + * Sets whether the parser should ignore any encountered JWK it does not support, either because the JWK has an + * unrecognized {@link Jwk#getType() key type} or the JWK was malformed (missing required parameters, etc). + * The default value is {@code true} per + * RFC 7517, Section 5, last paragraph: + *
    +     *    Implementations SHOULD ignore JWKs within a JWK Set that use "kty"
    +     *    (key type) values that are not understood by them, that are missing
    +     *    required members, or for which values are out of the supported
    +     *    ranges.
    +     * 
    + * + *

    This value may be set to {@code false} for applications that prefer stricter parsing constraints + * and wish to react to any {@link MalformedKeyException}s or {@link UnsupportedKeyException}s that could + * occur.

    + * + * @param ignore whether to ignore unsupported or malformed JWKs encountered during parsing. + * @return the builder for method chaining. + */ + JwkSetParserBuilder ignoreUnsupported(boolean ignore); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkThumbprint.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkThumbprint.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/JwkThumbprint.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,54 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.net.URI; + +/** + * A canonical cryptographic digest of a JWK as defined by the + * JSON Web Key (JWK) Thumbprint specification. + * + * @since 0.12.0 + */ +public interface JwkThumbprint { + + /** + * Returns the {@link HashAlgorithm} used to compute the thumbprint. + * + * @return the {@link HashAlgorithm} used to compute the thumbprint. + */ + HashAlgorithm getHashAlgorithm(); + + /** + * Returns the actual thumbprint (aka digest) byte array value. + * + * @return the actual thumbprint (aka digest) byte array value. + */ + byte[] toByteArray(); + + /** + * Returns the canonical URI representation of this thumbprint as defined by the + * JWK Thumbprint URI specification. + * + * @return a canonical JWK Thumbprint URI + */ + URI toURI(); + + /** + * Returns the {@link #toByteArray()} value as a Base64URL-encoded string. + */ + String toString(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Jwks.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Jwks.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Jwks.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.io.Parser; +import io.jsonwebtoken.lang.Classes; +import io.jsonwebtoken.lang.Registry; + +/** + * Utility methods for creating + * JWKs (JSON Web Keys) with a type-safe builder. + * + *

    Standard JWK Thumbprint Algorithm References

    + *

    Standard IANA Hash + * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid + * JWK Thumbprint URIs + * are available via the {@link Jwks.HASH} registry constants to allow for easy code-completion in IDEs. For example, when + * typing:

    + *
    + * Jwks.{@link Jwks.HASH HASH}.// press hotkeys to suggest individual hash algorithms or utility methods
    + * + * @see #builder() + * @since 0.12.0 + */ +public final class Jwks { + + private Jwks() { + } //prevent instantiation + + private static final String JWKS_BRIDGE_FQCN = "io.jsonwebtoken.impl.security.JwksBridge"; + private static final String BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultDynamicJwkBuilder"; + private static final String PARSER_BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultJwkParserBuilder"; + private static final String SET_BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultJwkSetBuilder"; + private static final String SET_PARSER_BUILDER_FQCN = "io.jsonwebtoken.impl.security.DefaultJwkSetParserBuilder"; + + /** + * Return a new JWK builder instance, allowing for type-safe JWK builder coercion based on a specified key or key pair. + * + * @return a new JWK builder instance, allowing for type-safe JWK builder coercion based on a specified key or key pair. + */ + public static DynamicJwkBuilder builder() { + return Classes.newInstance(BUILDER_FQCN); + } + + /** + * Returns a new builder used to create {@link Parser}s that parse JSON into {@link Jwk} instances. For example: + *
    +     * Jwk<?> jwk = Jwks.parser()
    +     *         //.provider(aJcaProvider)     // optional
    +     *         //.deserializer(deserializer) // optional
    +     *         //.operationPolicy(policy)    // optional
    +     *         .build()
    +     *         .parse(jwkString);
    + * + * @return a new builder used to create {@link Parser}s that parse JSON into {@link Jwk} instances. + */ + public static JwkParserBuilder parser() { + return Classes.newInstance(PARSER_BUILDER_FQCN); + } + + /** + * Return a new builder used to create {@link JwkSet}s. For example: + *
    +     * JwkSet jwkSet = Jwks.set()
    +     *     //.provider(aJcaProvider)     // optional
    +     *     //.operationPolicy(policy)    // optional
    +     *     .add(aJwk)                    // appends a key
    +     *     .add(aCollection)             // appends multiple keys
    +     *     //.keys(allJwks)              // sets/replaces all keys
    +     *     .build()
    +     * 
    + * + * @return a new builder used to create {@link JwkSet}s + */ + public static JwkSetBuilder set() { + return Classes.newInstance(SET_BUILDER_FQCN); + } + + /** + * Returns a new builder used to create {@link Parser}s that parse JSON into {@link JwkSet} instances. For example: + *
    +     * JwkSet jwkSet = Jwks.setParser()
    +     *         //.provider(aJcaProvider)     // optional
    +     *         //.deserializer(deserializer) // optional
    +     *         //.operationPolicy(policy)    // optional
    +     *         .build()
    +     *         .parse(jwkSetString);
    + * + * @return a new builder used to create {@link Parser}s that parse JSON into {@link JwkSet} instances. + */ + public static JwkSetParserBuilder setParser() { + return Classes.newInstance(SET_PARSER_BUILDER_FQCN); + } + + /** + * Converts the specified {@link PublicJwk} into JSON. Because {@link PublicJwk}s do not contain secret or private + * key material, they are safe to be printed to application logs or {@code System.out}. + * + * @param publicJwk the {@code PublicJwk} to convert to JSON + * @return the JWK's canonical JSON value + */ + public static String json(PublicJwk publicJwk) { + return UNSAFE_JSON(publicJwk); // safe by nature of it being a Public JWK + } + + /** + * WARNING - UNSAFE OPERATION - RETURN VALUES CONTAIN RAW KEY MATERIAL, DO NOT LOG OR PRINT TO SYSTEM.OUT. + * Converts the specified JWK into JSON, including raw key material. If the specified JWK + * is a {@link SecretJwk} or a {@link PrivateJwk}, be very careful with the return value, ensuring it is not + * printed to application logs or system.out. + * + * @param jwk the JWK to convert to JSON + * @return the JWK's canonical JSON value + */ + public static String UNSAFE_JSON(Jwk jwk) { + return Classes.invokeStatic(JWKS_BRIDGE_FQCN, "UNSAFE_JSON", new Class[]{Jwk.class}, jwk); + } + + /** + * Constants for all standard JWK + * crv (Curve) parameter values + * defined in the JSON Web Key Elliptic + * Curve Registry (including its + * Edwards Elliptic Curve additions). + * Each standard algorithm is available as a ({@code public static final}) constant for direct type-safe + * reference in application code. For example: + *
    +     * Jwks.CRV.P256.keyPair().build();
    + *

    They are also available together as a {@link Registry} instance via the {@link #get()} method.

    + * + * @see #get() + * @since 0.12.0 + */ + public static final class CRV { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardCurves"; + private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + /** + * Returns a registry of all standard Elliptic Curves in the {@code JSON Web Key Elliptic Curve Registry} + * defined by RFC 7518, Section 7.6 + * (for Weierstrass Elliptic Curves) and + * RFC 8037, Section 5 (for Edwards Elliptic Curves). + * + * @return a registry of all standard Elliptic Curves in the {@code JSON Web Key Elliptic Curve Registry}. + */ + public static Registry get() { + return REGISTRY; + } + + /** + * {@code P-256} Elliptic Curve defined by + * RFC 7518, Section 6.2.1.1 + * using the native Java JCA {@code secp256r1} algorithm. + * + * @see Java Security Standard Algorithm Names + */ + public static final Curve P256 = get().forKey("P-256"); + + /** + * {@code P-384} Elliptic Curve defined by + * RFC 7518, Section 6.2.1.1 + * using the native Java JCA {@code secp384r1} algorithm. + * + * @see Java Security Standard Algorithm Names + */ + public static final Curve P384 = get().forKey("P-384"); + + /** + * {@code P-521} Elliptic Curve defined by + * RFC 7518, Section 6.2.1.1 + * using the native Java JCA {@code secp521r1} algorithm. + * + * @see Java Security Standard Algorithm Names + */ + public static final Curve P521 = get().forKey("P-521"); + + /** + * {@code Ed25519} Elliptic Curve defined by + * RFC 8037, Section 3.1 + * using the native Java JCA {@code Ed25519}1 algorithm. + * + *

    1 Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 14 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

    + * + * @see Java Security Standard Algorithm Names + */ + public static final Curve Ed25519 = get().forKey("Ed25519"); + + /** + * {@code Ed448} Elliptic Curve defined by + * RFC 8037, Section 3.1 + * using the native Java JCA {@code Ed448}1 algorithm. + * + *

    1 Requires Java 15 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 14 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

    + * + * @see Java Security Standard Algorithm Names + */ + public static final Curve Ed448 = get().forKey("Ed448"); + + /** + * {@code X25519} Elliptic Curve defined by + * RFC 8037, Section 3.2 + * using the native Java JCA {@code X25519}1 algorithm. + * + *

    1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

    + * + * @see Java Security Standard Algorithm Names + */ + public static final Curve X25519 = get().forKey("X25519"); + + /** + * {@code X448} Elliptic Curve defined by + * RFC 8037, Section 3.2 + * using the native Java JCA {@code X448}1 algorithm. + * + *

    1 Requires Java 11 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath. If on Java 10 or earlier, BouncyCastle will be used automatically if found in the runtime + * classpath.

    + * + * @see Java Security Standard Algorithm Names + */ + public static final Curve X448 = get().forKey("X448"); + + //prevent instantiation + private CRV() { + } + } + + /** + * Various (but not all) + * IANA Hash + * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid + * JWK Thumbprint URIs. + * Each algorithm is made available as a ({@code public static final}) constant for direct type-safe + * reference in application code. For example: + *
    +     * Jwks.{@link Jwks#builder}()
    +     *     // ... etc ...
    +     *     .{@link JwkBuilder#idFromThumbprint(HashAlgorithm) idFromThumbprint}(Jwts.HASH.{@link Jwks.HASH#SHA256 SHA256}) // <---
    +     *     .build()
    + *

    or

    + *
    +     * HashAlgorithm hashAlg = Jwks.HASH.{@link Jwks.HASH#SHA256 SHA256};
    +     * {@link JwkThumbprint} thumbprint = aJwk.{@link Jwk#thumbprint(HashAlgorithm) thumbprint}(hashAlg);
    +     * String rfcMandatoryPrefix = "urn:ietf:params:oauth:jwk-thumbprint:" + hashAlg.getId();
    +     * assert thumbprint.toURI().toString().startsWith(rfcMandatoryPrefix);
    +     * 
    + *

    They are also available together as a {@link Registry} instance via the {@link #get()} method.

    + * + * @see #get() + * @since 0.12.0 + */ + public static final class HASH { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardHashAlgorithms"; + private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + /** + * Returns a registry of various (but not all) + * IANA Hash + * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid + * JWK Thumbprint URIs. + * + * @return a registry of various (but not all) + * IANA Hash + * Algorithms commonly used to compute {@link JwkThumbprint JWK Thumbprint}s and ensure valid + * JWK Thumbprint URIs. + */ + public static Registry get() { + return REGISTRY; + } + + /** + * IANA + * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") + * value of {@code sha-256}. It is a {@code HashAlgorithm} alias for the native + * Java JCA {@code SHA-256} {@code MessageDigest} algorithm. + */ + public static final HashAlgorithm SHA256 = get().forKey("sha-256"); + + /** + * IANA + * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") + * value of {@code sha-384}. It is a {@code HashAlgorithm} alias for the native + * Java JCA {@code SHA-384} {@code MessageDigest} algorithm. + */ + public static final HashAlgorithm SHA384 = get().forKey("sha-384"); + + /** + * IANA + * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") + * value of {@code sha-512}. It is a {@code HashAlgorithm} alias for the native + * Java JCA {@code SHA-512} {@code MessageDigest} algorithm. + */ + public static final HashAlgorithm SHA512 = get().forKey("sha-512"); + + /** + * IANA + * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") + * value of {@code sha3-256}. It is a {@code HashAlgorithm} alias for the native + * Java JCA {@code SHA3-256} {@code MessageDigest} algorithm. + *

    This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath.

    + */ + public static final HashAlgorithm SHA3_256 = get().forKey("sha3-256"); + + /** + * IANA + * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") + * value of {@code sha3-384}. It is a {@code HashAlgorithm} alias for the native + * Java JCA {@code SHA3-384} {@code MessageDigest} algorithm. + *

    This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath.

    + */ + public static final HashAlgorithm SHA3_384 = get().forKey("sha3-384"); + + /** + * IANA + * hash algorithm with an {@link Identifiable#getId() id} (aka IANA "{@code Hash Name String}") + * value of {@code sha3-512}. It is a {@code HashAlgorithm} alias for the native + * Java JCA {@code SHA3-512} {@code MessageDigest} algorithm. + *

    This algorithm requires at least JDK 9 or a compatible JCA Provider (like BouncyCastle) in the runtime + * classpath.

    + */ + public static final HashAlgorithm SHA3_512 = get().forKey("sha3-512"); + + //prevent instantiation + private HASH() { + } + } + + /** + * Constants for all standard JWK + * key_ops (Key Operations) parameter values + * defined in the JSON Web Key Operations + * Registry. Each standard key operation is available as a ({@code public static final}) constant for + * direct type-safe reference in application code. For example: + *
    +     * Jwks.builder()
    +     *     .operations(Jwks.OP.SIGN)
    +     *     // ... etc ...
    +     *     .build();
    + *

    They are also available together as a {@link Registry} instance via the {@link #get()} method.

    + * + * @see #get() + * @since 0.12.0 + */ + public static final class OP { + + private static final String IMPL_CLASSNAME = "io.jsonwebtoken.impl.security.StandardKeyOperations"; + private static final Registry REGISTRY = Classes.newInstance(IMPL_CLASSNAME); + + private static final String BUILDER_CLASSNAME = "io.jsonwebtoken.impl.security.DefaultKeyOperationBuilder"; + + + private static final String POLICY_BUILDER_CLASSNAME = + "io.jsonwebtoken.impl.security.DefaultKeyOperationPolicyBuilder"; + + /** + * Creates a new {@link KeyOperationBuilder} for creating custom {@link KeyOperation} instances. + * + * @return a new {@link KeyOperationBuilder} for creating custom {@link KeyOperation} instances. + */ + public static KeyOperationBuilder builder() { + return Classes.newInstance(BUILDER_CLASSNAME); + } + + /** + * Creates a new {@link KeyOperationPolicyBuilder} for creating custom {@link KeyOperationPolicy} instances. + * + * @return a new {@link KeyOperationPolicyBuilder} for creating custom {@link KeyOperationPolicy} instances. + */ + public static KeyOperationPolicyBuilder policy() { + return Classes.newInstance(POLICY_BUILDER_CLASSNAME); + } + + /** + * Returns a registry of all standard Key Operations in the {@code JSON Web Key Operations Registry} + * defined by RFC 7517, Section 8.3. + * + * @return a registry of all standard Key Operations in the {@code JSON Web Key Operations Registry}. + */ + public static Registry get() { + return REGISTRY; + } + + /** + * {@code sign} operation indicating a key is intended to be used to compute digital signatures or + * MACs. It's related operation is {@link #VERIFY}. + * + * @see #VERIFY + * @see Key Operation Registry Contents + */ + public static final KeyOperation SIGN = get().forKey("sign"); + + /** + * {@code verify} operation indicating a key is intended to be used to verify digital signatures or + * MACs. It's related operation is {@link #SIGN}. + * + * @see #SIGN + * @see Key Operation Registry Contents + */ + public static final KeyOperation VERIFY = get().forKey("verify"); + + /** + * {@code encrypt} operation indicating a key is intended to be used to encrypt content. It's + * related operation is {@link #DECRYPT}. + * + * @see #DECRYPT + * @see Key Operation Registry Contents + */ + public static final KeyOperation ENCRYPT = get().forKey("encrypt"); + + /** + * {@code decrypt} operation indicating a key is intended to be used to decrypt content. It's + * related operation is {@link #ENCRYPT}. + * + * @see #ENCRYPT + * @see Key Operation Registry Contents + */ + public static final KeyOperation DECRYPT = get().forKey("decrypt"); + + /** + * {@code wrapKey} operation indicating a key is intended to be used to encrypt another key. It's + * related operation is {@link #UNWRAP_KEY}. + * + * @see #UNWRAP_KEY + * @see Key Operation Registry Contents + */ + public static final KeyOperation WRAP_KEY = get().forKey("wrapKey"); + + /** + * {@code unwrapKey} operation indicating a key is intended to be used to decrypt another key and validate + * decryption, if applicable. It's related operation is + * {@link #WRAP_KEY}. + * + * @see #WRAP_KEY + * @see Key Operation Registry Contents + */ + public static final KeyOperation UNWRAP_KEY = get().forKey("unwrapKey"); + + /** + * {@code deriveKey} operation indicating a key is intended to be used to derive another key. It does not have + * a related operation. + * + * @see Key Operation Registry Contents + */ + public static final KeyOperation DERIVE_KEY = get().forKey("deriveKey"); + + /** + * {@code deriveBits} operation indicating a key is intended to be used to derive bits that are not to be + * used as key. It does not have a related operation. + * + * @see Key Operation Registry Contents + */ + public static final KeyOperation DERIVE_BITS = get().forKey("deriveBits"); + + //prevent instantiation + private OP() { + } + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.Jwts; + +import javax.crypto.SecretKey; +import java.security.Key; + +/** + * A {@code KeyAlgorithm} produces the {@link SecretKey} used to encrypt or decrypt a JWE. The {@code KeyAlgorithm} + * used for a particular JWE is {@link #getId() identified} in the JWE's + * {@code alg} header. The {@code KeyAlgorithm} + * interface is JJWT's idiomatic approach to the JWE specification's + * {@code Key Management Mode} concept. + * + *

    All standard Key Algorithms are defined in + * JWA (RFC 7518), Section 4.1, + * and they are all available as concrete instances via {@link Jwts.KEY}.

    + * + *

    "alg" identifier

    + * + *

    {@code KeyAlgorithm} extends {@code Identifiable}: the value returned from + * {@link Identifiable#getId() keyAlgorithm.getId()} will be used as the + * JWE "alg" protected header value.

    + * + * @param The type of key to use to obtain the AEAD encryption key + * @param The type of key to use to obtain the AEAD decryption key + * @see Jwts.KEY + * @see RFC 7561, Section 2: JWE Key (Management) Algorithms + * @since 0.12.0 + */ +@SuppressWarnings("JavadocLinkAsPlainText") +public interface KeyAlgorithm extends Identifiable { + + /** + * Return the {@link SecretKey} that should be used to encrypt a JWE via the request's specified + * {@link KeyRequest#getEncryptionAlgorithm() AeadAlgorithm}. The encryption key will + * be available via the result's {@link KeyResult#getKey() result.getKey()} method. + * + *

    If the key algorithm uses key encryption or key agreement to produce an encrypted key value that must be + * included in the JWE, the encrypted key ciphertext will be available via the result's + * {@link KeyResult#getPayload() result.getPayload()} method. If the key algorithm does not produce encrypted + * key ciphertext, {@link KeyResult#getPayload() result.getPayload()} will be a non-null empty byte array.

    + * + * @param request the {@code KeyRequest} containing information necessary to produce a {@code SecretKey} for + * {@link AeadAlgorithm AEAD} encryption. + * @return the {@link SecretKey} that should be used to encrypt a JWE via the request's specified + * {@link KeyRequest#getEncryptionAlgorithm() AeadAlgorithm}, along with any optional encrypted key ciphertext. + * @throws SecurityException if there is a problem obtaining or encrypting the AEAD {@code SecretKey}. + */ + KeyResult getEncryptionKey(KeyRequest request) throws SecurityException; + + /** + * Return the {@link SecretKey} that should be used to decrypt a JWE via the request's specified + * {@link DecryptionKeyRequest#getEncryptionAlgorithm() AeadAlgorithm}. + * + *

    If the key algorithm used key encryption or key agreement to produce an encrypted key value, the encrypted + * key ciphertext will be available via the request's {@link DecryptionKeyRequest#getPayload() result.getPayload()} + * method. If the key algorithm did not produce encrypted key ciphertext, + * {@link DecryptionKeyRequest#getPayload() request.getPayload()} will return a non-null empty byte array.

    + * + * @param request the {@code DecryptionKeyRequest} containing information necessary to obtain a + * {@code SecretKey} for {@link AeadAlgorithm AEAD} decryption. + * @return the {@link SecretKey} that should be used to decrypt a JWE via the request's specified + * {@link DecryptionKeyRequest#getEncryptionAlgorithm() AeadAlgorithm}. + * @throws SecurityException if there is a problem obtaining or decrypting the AEAD {@code SecretKey}. + */ + SecretKey getDecryptionKey(DecryptionKeyRequest request) throws SecurityException; +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; +import java.security.Key; + +/** + * A {@code KeyBuilder} produces new {@link Key}s suitable for use with an associated cryptographic algorithm. + * A new {@link Key} is created each time the builder's {@link #build()} method is called. + * + *

    {@code KeyBuilder}s are provided by components that implement the {@link KeyBuilderSupplier} interface, + * ensuring the resulting {@link SecretKey}s are compatible with their associated cryptographic algorithm.

    + * + * @param the type of key to build + * @param the type of the builder, for subtype method chaining + * @see KeyBuilderSupplier + * @since 0.12.0 + */ +public interface KeyBuilder> extends SecurityBuilder { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyBuilderSupplier.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyBuilderSupplier.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyBuilderSupplier.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * Interface implemented by components that support building/creating new {@link Key}s suitable for use with + * their associated cryptographic algorithm implementation. + * + * @param type of {@link Key} created by the builder + * @param type of builder to create each time {@link #key()} is called. + * @see #key() + * @see KeyBuilder + * @since 0.12.0 + */ +public interface KeyBuilderSupplier> { + + /** + * Returns a new {@link KeyBuilder} instance that will produce new secure-random keys with a length sufficient + * to be used by the component's associated cryptographic algorithm. + * + * @return a new {@link KeyBuilder} instance that will produce new secure-random keys with a length sufficient + * to be used by the component's associated cryptographic algorithm. + */ + B key(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyException.java (.../KeyException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyException.java (.../KeyException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,11 +16,29 @@ package io.jsonwebtoken.security; /** + * General-purpose exception when encountering a problem with a cryptographic {@link java.security.Key} + * or {@link Jwk}. + * * @since 0.10.0 */ public class KeyException extends SecurityException { + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ public KeyException(String message) { super(message); } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param msg the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public KeyException(String msg, Throwable cause) { + super(msg, cause); + } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyLengthSupplier.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyLengthSupplier.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyLengthSupplier.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * Provides access to the required length in bits (not bytes) of keys usable with the associated algorithm. + * + * @since 0.12.0 + */ +public interface KeyLengthSupplier { + + /** + * Returns the required length in bits (not bytes) of keys usable with the associated algorithm. + * + * @return the required length in bits (not bytes) of keys usable with the associated algorithm. + */ + int getKeyBitLength(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperation.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperation.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperation.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,55 @@ +/* + * 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.security; + +import io.jsonwebtoken.Identifiable; + +/** + * A {@code KeyOperation} identifies a behavior for which a key may be used. Key validation + * algorithms may inspect a key's operations and reject the key if it is being used in a manner inconsistent + * with its indicated operations. + * + *

    KeyOperation Identifier

    + * + *

    This interface extends {@link Identifiable}; the value returned from {@link #getId()} is a + * CaSe-SeNsItIvE value that uniquely identifies the operation among other KeyOperation instances.

    + * + * @see JWK key_ops (Key Operations) Parameter + * @see JSON Web Key Operations Registry + * @since 0.12.0 + */ +public interface KeyOperation extends Identifiable { + + /** + * Returns a brief description of the key operation behavior. + * + * @return a brief description of the key operation behavior. + */ + String getDescription(); + + /** + * Returns {@code true} if the specified {@code operation} is an acceptable use case for the key already assigned + * this operation, {@code false} otherwise. As described in the + * JWK key_ops (Key Operations) Parameter + * specification, Key validation algorithms will likely reject keys with inconsistent or unrelated operations + * because of the security vulnerabilities that could occur otherwise. + * + * @param operation the key operation to check if it is related to (consistent or compatible with) this operation. + * @return {@code true} if the specified {@code operation} is an acceptable use case for the key already assigned + * this operation, {@code false} otherwise. + */ + boolean isRelated(KeyOperation operation); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperationBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperationBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperationBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,73 @@ +/* + * 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.security; + +import io.jsonwebtoken.lang.Builder; + +/** + * A {@code KeyOperationBuilder} produces {@link KeyOperation} instances that may be added to a JWK's + * {@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 + * are available already via the {@link Jwks.OP} registry singleton. + * + * @see Jwks.OP#builder() + * @see Jwks.OP#policy() + * @see JwkBuilder#operationPolicy(KeyOperationPolicy) + * @since 0.12.0 + */ +public interface KeyOperationBuilder extends Builder { + + /** + * Sets the CaSe-SeNsItIvE {@link KeyOperation#getId() id} expected to be unique compared to all other + * {@code KeyOperation}s. + * + * @param id the key operation id + * @return the builder for method chaining + */ + KeyOperationBuilder id(String id); + + /** + * Sets the key operation {@link KeyOperation#getDescription() description}. + * + * @param description the key operation description + * @return the builder for method chaining + */ + KeyOperationBuilder description(String description); + + /** + * Indicates that the {@code KeyOperation} with the given {@link KeyOperation#getId() id} is cryptographically + * related (and complementary) to this one, and may be specified together in a JWK's + * {@link Jwk#getOperations() operations} set. + * + *

    More concretely, calling this method will ensure the following:

    + *
    +     *     KeyOperation built = Jwks.operation()/*...*/.related(otherId).build();
    +     *     KeyOperation other = getKeyOperation(otherId);
    +     *     assert built.isRelated(other);
    + * + *

    A {@link JwkBuilder}'s key operation {@link JwkBuilder#operationPolicy(KeyOperationPolicy) policy} is likely + * to {@link KeyOperationPolicyBuilder#unrelated() reject} any unrelated operations specified + * together due to the potential security vulnerabilities that could occur.

    + * + *

    This method may be called multiple times to add/append a related {@code id} to the constructed + * {@code KeyOperation}'s total set of related ids.

    + * + * @param id the id of a KeyOperation that will be considered cryptographically related to this one. + * @return the builder for method chaining. + * @see JwkBuilder#operationPolicy(KeyOperationPolicy) + */ + KeyOperationBuilder related(String id); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperationPolicied.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperationPolicied.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperationPolicied.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -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.security; + +/** + * A marker interface that indicates the implementing instance supports the ability to configure a + * {@link KeyOperationPolicy} used to validate JWK instances. + * + * @param the implementing instance for method chaining + */ +public interface KeyOperationPolicied> { + + /** + * Sets the key operation policy that determines which {@link KeyOperation}s may be assigned to a + * JWK. Unless overridden by this method, the default RFC-recommended policy is used where: + *
      + *
    • All {@link Jwks.OP RFC-standard key operations} are supported.
    • + *
    • Multiple unrelated operations may not be assigned to the JWK per the + * RFC 7517, Section 4.3 recommendation: + *
      +     * Multiple unrelated key operations SHOULD NOT 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 SHOULD NOT be used.
      + *
    • + *
    + * + *

    If you wish to enable a different policy, perhaps to support additional custom {@code KeyOperation} values, + * one can be created by using the {@link Jwks.OP#policy()} builder, or by implementing the + * {@link KeyOperationPolicy} interface directly.

    + * + * @param policy the policy that determines which {@link KeyOperation}s may be assigned to a JWK. + * @return the builder for method chaining. + * @throws IllegalArgumentException if {@code policy} is null + */ + T operationPolicy(KeyOperationPolicy policy) throws IllegalArgumentException; +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperationPolicy.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperationPolicy.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperationPolicy.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,43 @@ +/* + * 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.security; + +import java.util.Collection; + +/** + * A key operation policy determines which {@link KeyOperation}s may be assigned to a JWK. + * + * @see JwkBuilder#operationPolicy(KeyOperationPolicy) + * @since 0.12.0 + */ +public interface KeyOperationPolicy { + + /** + * Returns all supported {@code KeyOperation}s that may be assigned to a JWK. + * + * @return all supported {@code KeyOperation}s that may be assigned to a JWK. + */ + Collection getOperations(); + + /** + * Returns quietly if all of the specified key operations are allowed to be assigned to a JWK, + * or throws an {@link IllegalArgumentException} otherwise. + * + * @param ops the operations to validate + */ + @SuppressWarnings("GrazieInspection") + void validate(Collection ops) throws IllegalArgumentException; +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperationPolicyBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperationPolicyBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyOperationPolicyBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,114 @@ +/* + * 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.security; + +import io.jsonwebtoken.Identifiable; +import io.jsonwebtoken.lang.Builder; +import io.jsonwebtoken.lang.CollectionMutator; + +import java.util.Collection; + + +/** + * A {@code KeyOperationPolicyBuilder} produces a {@link KeyOperationPolicy} that determines + * which {@link KeyOperation}s may be assigned to a JWK. Custom {@code KeyOperation}s (such as those created by a + * {@link Jwks.OP#builder()}) may be added to a policy via the {@link #add(KeyOperation)} or {@link #add(Collection)} + * methods. + * + * @see Jwks.OP#policy() + * @see JwkBuilder#operationPolicy(KeyOperationPolicy) + * @see Jwks.OP#builder() + * @since 0.12.0 + */ +public interface KeyOperationPolicyBuilder extends CollectionMutator, + Builder { + + /** + * Allows a JWK to have unrelated {@link KeyOperation}s in its {@code key_ops} parameter values. Be careful + * when calling this method - one should fully understand the security implications of using the same key + * with multiple algorithms in your application. + *

    If this method is not called, unrelated key operations are disabled by default per the recommendations in + * RFC 7517, Section 4.3:

    + *
    +     * Multiple unrelated key operations SHOULD NOT be specified for a key
    +     * because of the potential vulnerabilities associated with using the
    +     * same key with multiple algorithms.
    + * + * @return the builder for method chaining + * @see "key_ops" (Key Operations) + * Parameter + */ + KeyOperationPolicyBuilder unrelated(); + + /** + * Adds the specified key operation to the policy's total set of supported key operations + * used to validate a key's intended usage, replacing any existing one with an identical (CaSe-SeNsItIvE) + * {@link Identifiable#getId() id}. + * + *

    Standard {@code KeyOperation}s and Overrides

    + * + *

    The RFC standard {@link Jwks.OP} key operations are supported by default and do not need + * to be added via this method, but beware: If the {@code op} argument has a JWK standard + * {@link Identifiable#getId() id}, it will replace the JJWT standard operation implementation. + * 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).

    + * + *

    If a custom {@code KeyOperation} is desired, one may be easily created with a {@link Jwks.OP#builder()}.

    + * + * @param op a key operation to add to the policy's total set of supported operations, replacing any + * existing one with the same exact (CaSe-SeNsItIvE) {@link KeyOperation#getId() id}. + * @return the builder for method chaining. + * @see Jwks.OP + * @see Jwks.OP#builder() + * @see JwkBuilder#operationPolicy(KeyOperationPolicy) + * @see JwkBuilder#operations() + */ + @Override + // for better JavaDoc + KeyOperationPolicyBuilder add(KeyOperation op); + + /** + * Adds the specified key operations to the policy's total set of supported key operations + * used to validate a key's intended usage, replacing any existing ones with identical + * {@link Identifiable#getId() id}s. + * + *

    There may be only one registered {@code KeyOperation} per CaSe-SeNsItIvE {@code id}, and the + * {@code ops} collection is added in iteration order; if a duplicate id is found when iterating the {@code ops} + * collection, the later operation will evict any existing operation with the same {@code id}.

    + * + *

    Standard {@code KeyOperation}s and Overrides

    + * + *

    The RFC standard {@link Jwks.OP} key operations are supported by default and do not need + * to be added via this method, but beware: any operation in the {@code ops} argument with a + * JWK standard {@link Identifiable#getId() id} will replace the JJWT standard operation implementation. + * 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).

    + * + *

    If custom {@code KeyOperation}s are desired, they may be easily created with a {@link Jwks.OP#builder()}.

    + * + * @param ops collection of key operations to add to the policy's total set of supported operations, replacing any + * existing ones with the same exact (CaSe-SeNsItIvE) {@link KeyOperation#getId() id}s. + * @return the builder for method chaining. + * @see Jwks.OP + * @see Jwks.OP#builder() + * @see JwkBuilder#operationPolicy(KeyOperationPolicy) + * @see JwkBuilder#operations() + */ + @Override + // for better JavaDoc + KeyOperationPolicyBuilder add(Collection ops); + +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyPair.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyPair.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyPair.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * Generics-capable and type-safe alternative to {@link java.security.KeyPair}. Instances may be + * converted to {@link java.security.KeyPair} if desired via {@link #toJavaKeyPair()}. + * + * @param The type of {@link PublicKey} in the key pair. + * @param The type of {@link PrivateKey} in the key pair. + * @since 0.12.0 + */ +public interface KeyPair { + + /** + * Returns the pair's public key. + * + * @return the pair's public key. + */ + A getPublic(); + + /** + * Returns the pair's private key. + * + * @return the pair's private key. + */ + B getPrivate(); + + /** + * Returns this instance as a {@link java.security.KeyPair} instance. + * + * @return this instance as a {@link java.security.KeyPair} instance. + */ + java.security.KeyPair toJavaKeyPair(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyPairBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyPairBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyPairBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.KeyPair; + +/** + * A {@code KeyPairBuilder} produces new {@link KeyPair}s suitable for use with an associated cryptographic algorithm. + * A new {@link KeyPair} is created each time the builder's {@link #build()} method is called. + * + *

    {@code KeyPairBuilder}s are provided by components that implement the {@link KeyPairBuilderSupplier} interface, + * ensuring the resulting {@link KeyPair}s are compatible with their associated cryptographic algorithm.

    + * + * @see KeyPairBuilderSupplier + * @since 0.12.0 + */ +public interface KeyPairBuilder extends SecurityBuilder { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyPairBuilderSupplier.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyPairBuilderSupplier.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyPairBuilderSupplier.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.KeyPair; + +/** + * Interface implemented by components that support building/creating new {@link KeyPair}s suitable for use with their + * associated cryptographic algorithm implementation. + * + * @see #keyPair() + * @see KeyPairBuilder + * @since 0.12.0 + */ +public interface KeyPairBuilderSupplier { + + /** + * Returns a new {@link KeyPairBuilder} that will create new secure-random {@link KeyPair}s with a length and + * parameters sufficient for use with the component's associated cryptographic algorithm. + * + * @return a new {@link KeyPairBuilder} that will create new secure-random {@link KeyPair}s with a length and + * parameters sufficient for use with the component's associated cryptographic algorithm. + */ + KeyPairBuilder keyPair(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.JweHeader; + +/** + * A request to a {@link KeyAlgorithm} to obtain the key necessary for AEAD encryption or decryption. The exact + * {@link AeadAlgorithm} that will be used is accessible via {@link #getEncryptionAlgorithm()}. + * + *

    Encryption Requests

    + *

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

    + * + *

    Decryption Requests

    + *

    For a decryption request, the {@code KeyRequest} instance will be + * a {@link DecryptionKeyRequest} instance, {@link #getPayload()} will return the encrypted key ciphertext (a + * byte array), and the decryption key will be available via {@link DecryptionKeyRequest#getKey()}. Additionally, + * any public information necessary by the called {@link KeyAlgorithm} (such as an initialization vector, + * authentication tag, ephemeral key, etc) is expected to be available in the JWE protected header, accessible + * via {@link #getHeader()}.

    + * + * @param the type of object relevant during key algorithm cryptographic operations. + * @see DecryptionKeyRequest + * @since 0.12.0 + */ +public interface KeyRequest extends Request { + + /** + * Returns the {@link AeadAlgorithm} that will be called for encryption or decryption after processing the + * {@code KeyRequest}. {@link KeyAlgorithm} implementations that generate an ephemeral {@code SecretKey} to use + * as what the
    JWE specification calls a + * "Content Encryption Key (CEK)" should call the {@code AeadAlgorithm}'s + * {@link AeadAlgorithm#key() key()} builder to create a key suitable for that exact {@code AeadAlgorithm}. + * + * @return the {@link AeadAlgorithm} that will be called for encryption or decryption after processing the + * {@code KeyRequest}. + */ + AeadAlgorithm getEncryptionAlgorithm(); + + /** + * Returns the {@link JweHeader} that will be used to construct the final JWE header, available for + * reading or writing any {@link KeyAlgorithm}-specific information. + * + *

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

    + * + *

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

    + * + * @return the {@link JweHeader} that will be used to construct the final JWE header, available for + * reading or writing any {@link KeyAlgorithm}-specific information. + */ + JweHeader getHeader(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyResult.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyResult.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeyResult.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; + +/** + * The result of a {@link KeyAlgorithm} encryption key request, containing the resulting + * {@code JWE encrypted key} and {@code JWE Content Encryption Key (CEK)}, concepts defined in + * JWE Terminology. + * + *

    The result {@link #getPayload() payload} is the {@code JWE encrypted key}, which will be Base64URL-encoded + * and embedded in the resulting compact JWE string.

    + * + *

    The result {@link #getKey() key} is the {@code JWE Content Encryption Key (CEK)} which will be used to encrypt + * the JWE.

    + * + * @since 0.12.0 + */ +public interface KeyResult extends Message, KeySupplier { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeySupplier.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeySupplier.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/KeySupplier.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * Provides access to a cryptographic {@link Key} necessary for signing, wrapping, encryption or decryption algorithms. + * + * @param the type of key provided by this supplier. + * @since 0.12.0 + */ +public interface KeySupplier { + + /** + * Returns the key to use for signing, wrapping, encryption or decryption depending on the type of operation. + * + * @return the key to use for signing, wrapping, encryption or decryption depending on the type of operation. + */ + K getKey(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Keys.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Keys.java (.../Keys.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Keys.java (.../Keys.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -15,16 +15,16 @@ */ package io.jsonwebtoken.security; -import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.Jwts; import io.jsonwebtoken.lang.Assert; import io.jsonwebtoken.lang.Classes; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.security.KeyPair; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; /** * Utility class for securely generating {@link SecretKey}s and {@link KeyPair}s. @@ -33,36 +33,20 @@ */ public final class Keys { - private static final String MAC = "io.jsonwebtoken.impl.crypto.MacProvider"; - private static final String RSA = "io.jsonwebtoken.impl.crypto.RsaProvider"; - private static final String EC = "io.jsonwebtoken.impl.crypto.EllipticCurveProvider"; + private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.KeysBridge"; + private static final Class BRIDGE_CLASS = Classes.forName(BRIDGE_CLASSNAME); + private static final Class[] FOR_PASSWORD_ARG_TYPES = new Class[]{char[].class}; + private static final Class[] SECRET_BUILDER_ARG_TYPES = new Class[]{SecretKey.class}; + private static final Class[] PRIVATE_BUILDER_ARG_TYPES = new Class[]{PrivateKey.class}; - private static final Class[] SIG_ARG_TYPES = new Class[]{SignatureAlgorithm.class}; + private static T invokeStatic(String method, Class[] argTypes, Object... args) { + return Classes.invokeStatic(BRIDGE_CLASS, method, argTypes, args); + } - //purposefully ordered higher to lower: - private static final List PREFERRED_HMAC_ALGS = Collections.unmodifiableList(Arrays.asList( - SignatureAlgorithm.HS512, SignatureAlgorithm.HS384, SignatureAlgorithm.HS256)); - //prevent instantiation private Keys() { } - /* - public static final int bitLength(Key key) throws IllegalArgumentException { - Assert.notNull(key, "Key cannot be null."); - if (key instanceof SecretKey) { - byte[] encoded = key.getEncoded(); - return Arrays.length(encoded) * 8; - } else if (key instanceof RSAKey) { - return ((RSAKey)key).getModulus().bitLength(); - } else if (key instanceof ECKey) { - return ((ECKey)key).getParams().getOrder().bitLength(); - } - - throw new IllegalArgumentException("Unsupported key type: " + key.getClass().getName()); - } - */ - /** * Creates a new SecretKey instance for use with HMAC-SHA algorithms based on the specified key byte array. * @@ -80,30 +64,52 @@ int bitLength = bytes.length * 8; - for (SignatureAlgorithm alg : PREFERRED_HMAC_ALGS) { - if (bitLength >= alg.getMinKeyLength()) { - return new SecretKeySpec(bytes, alg.getJcaName()); - } + //Purposefully ordered higher to lower to ensure the strongest key possible can be generated. + if (bitLength >= 512) { + return new SecretKeySpec(bytes, "HmacSHA512"); + } else if (bitLength >= 384) { + return new SecretKeySpec(bytes, "HmacSHA384"); + } else if (bitLength >= 256) { + return new SecretKeySpec(bytes, "HmacSHA256"); } String msg = "The specified key byte array is " + bitLength + " bits which " + - "is not secure enough for any JWT HMAC-SHA algorithm. The JWT " + - "JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a " + - "size >= 256 bits (the key size must be greater than or equal to the hash " + - "output size). Consider using the " + Keys.class.getName() + "#secretKeyFor(SignatureAlgorithm) method " + - "to create a key guaranteed to be secure enough for your preferred HMAC-SHA algorithm. See " + - "https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; + "is not secure enough for any JWT HMAC-SHA algorithm. The JWT " + + "JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a " + + "size >= 256 bits (the key size must be greater than or equal to the hash " + + "output size). Consider using the Jwts.SIG.HS256.key() builder (or HS384.key() " + + "or HS512.key()) to create a key guaranteed to be secure enough for your preferred HMAC-SHA " + + "algorithm. See https://tools.ietf.org/html/rfc7518#section-3.2 for more information."; throw new WeakKeyException(msg); } /** - * Returns a new {@link SecretKey} with a key length suitable for use with the specified {@link SignatureAlgorithm}. + *

    Deprecation Notice

    * + *

    As of JJWT 0.12.0, symmetric (secret) key algorithm instances can generate a key of suitable + * length for that specific algorithm by calling their {@code key()} builder method directly. For example:

    + * + *
    
    +     * {@link Jwts.SIG#HS256}.key().build();
    +     * {@link Jwts.SIG#HS384}.key().build();
    +     * {@link Jwts.SIG#HS512}.key().build();
    +     * 
    + * + *

    Call those methods as needed instead of this static {@code secretKeyFor} helper method - the returned + * {@link KeyBuilder} allows callers to specify a preferred Provider or SecureRandom on the builder if + * desired, whereas this {@code secretKeyFor} method does not. Consequently this helper method will be removed + * before the 1.0 release.

    + * + *

    Previous Documentation

    + * + *

    Returns a new {@link SecretKey} with a key length suitable for use with the specified {@link SignatureAlgorithm}.

    + * *

    JWA Specification (RFC 7518), Section 3.2 * requires minimum key lengths to be used for each respective Signature Algorithm. This method returns a * secure-random generated SecretKey that adheres to the required minimum key length. The lengths are:

    * * + * * * * @@ -124,28 +130,49 @@ * * @param alg the {@code SignatureAlgorithm} to inspect to determine which key length to use. * @return a new {@link SecretKey} instance suitable for use with the specified {@link SignatureAlgorithm}. - * @throws IllegalArgumentException for any input value other than {@link SignatureAlgorithm#HS256}, - * {@link SignatureAlgorithm#HS384}, or {@link SignatureAlgorithm#HS512} + * @throws IllegalArgumentException for any input value other than {@link io.jsonwebtoken.SignatureAlgorithm#HS256}, + * {@link io.jsonwebtoken.SignatureAlgorithm#HS384}, or {@link io.jsonwebtoken.SignatureAlgorithm#HS512} + * @deprecated since 0.12.0. Use your preferred {@link MacAlgorithm} instance's + * {@link MacAlgorithm#key() key()} builder method directly. */ - public static SecretKey secretKeyFor(SignatureAlgorithm alg) throws IllegalArgumentException { + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + public static SecretKey secretKeyFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException { Assert.notNull(alg, "SignatureAlgorithm cannot be null."); - switch (alg) { - case HS256: - case HS384: - case HS512: - return Classes.invokeStatic(MAC, "generateKey", SIG_ARG_TYPES, alg); - default: - String msg = "The " + alg.name() + " algorithm does not support shared secret keys."; - throw new IllegalArgumentException(msg); + SecureDigestAlgorithm salg = Jwts.SIG.get().get(alg.name()); + if (!(salg instanceof MacAlgorithm)) { + String msg = "The " + alg.name() + " algorithm does not support shared secret keys."; + throw new IllegalArgumentException(msg); } + return ((MacAlgorithm) salg).key().build(); } /** - * Returns a new {@link KeyPair} suitable for use with the specified asymmetric algorithm. + *

    Deprecation Notice

    * + *

    As of JJWT 0.12.0, asymmetric key algorithm instances can generate KeyPairs of suitable strength + * for that specific algorithm by calling their {@code keyPair()} builder method directly. For example:

    + * + *
    +     * Jwts.SIG.{@link Jwts.SIG#RS256 RS256}.keyPair().build();
    +     * Jwts.SIG.{@link Jwts.SIG#RS384 RS384}.keyPair().build();
    +     * Jwts.SIG.{@link Jwts.SIG#RS512 RS512}.keyPair().build();
    +     * ... etc ...
    +     * Jwts.SIG.{@link Jwts.SIG#ES512 ES512}.keyPair().build();
    + * + *

    Call those methods as needed instead of this static {@code keyPairFor} helper method - the returned + * {@link KeyPairBuilder} allows callers to specify a preferred Provider or SecureRandom on the builder if + * desired, whereas this {@code keyPairFor} method does not. Consequently this helper method will be removed + * before the 1.0 release.

    + * + *

    Previous Documentation

    + * + *

    Returns a new {@link KeyPair} suitable for use with the specified asymmetric algorithm.

    + * *

    If the {@code alg} argument is an RSA algorithm, a KeyPair is generated based on the following:

    * *
    JWA HMAC-SHA Key Length Requirements
    AlgorithmKey Length
    + * * * * @@ -179,27 +206,28 @@ *

    If the {@code alg} argument is an Elliptic Curve algorithm, a KeyPair is generated based on the following:

    * *
    Generated RSA Key Sizes
    JWA AlgorithmKey Size
    + * * * * * * * * - * + * * * * * * - * + * * * * * * - * - * + * + * * * * @@ -208,24 +236,97 @@ * @param alg the {@code SignatureAlgorithm} to inspect to determine which asymmetric algorithm to use. * @return a new {@link KeyPair} suitable for use with the specified asymmetric algorithm. * @throws IllegalArgumentException if {@code alg} is not an asymmetric algorithm + * @deprecated since 0.12.0 in favor of your preferred + * {@link io.jsonwebtoken.security.SignatureAlgorithm} instance's + * {@link SignatureAlgorithm#keyPair() keyPair()} builder method directly. */ - public static KeyPair keyPairFor(SignatureAlgorithm alg) throws IllegalArgumentException { + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated + public static KeyPair keyPairFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException { Assert.notNull(alg, "SignatureAlgorithm cannot be null."); - switch (alg) { - case RS256: - case PS256: - case RS384: - case PS384: - case RS512: - case PS512: - return Classes.invokeStatic(RSA, "generateKeyPair", SIG_ARG_TYPES, alg); - case ES256: - case ES384: - case ES512: - return Classes.invokeStatic(EC, "generateKeyPair", SIG_ARG_TYPES, alg); - default: - String msg = "The " + alg.name() + " algorithm does not support Key Pairs."; - throw new IllegalArgumentException(msg); + SecureDigestAlgorithm salg = Jwts.SIG.get().get(alg.name()); + if (!(salg instanceof SignatureAlgorithm)) { + String msg = "The " + alg.name() + " algorithm does not support Key Pairs."; + throw new IllegalArgumentException(msg); } + SignatureAlgorithm asalg = ((SignatureAlgorithm) salg); + return asalg.keyPair().build(); } + + /** + * Returns a new {@link Password} instance suitable for use with password-based key derivation algorithms. + * + *

    Usage Note: Using {@code Password}s outside of key derivation contexts will likely + * fail. See the {@link Password} JavaDoc for more, and also note the Password Safety section below.

    + * + *

    Password Safety

    + * + *

    Instances returned by this method use a clone of the specified {@code password} character array + * argument - changes to the argument array will NOT be reflected in the returned key, and vice versa. If you wish + * to clear a {@code Password} instance to ensure it is no longer usable, call its {@link Password#destroy()} + * method will clear/overwrite its internal cloned char array. Also note that each subsequent call to + * {@link Password#toCharArray()} will also return a new clone of the underlying password character array per + * standard JCE key behavior.

    + * + * @param password the raw password character array to clone for use with password-based key derivation algorithms. + * @return a new {@link Password} instance that wraps a new clone of the specified {@code password} character array. + * @see Password#toCharArray() + * @since 0.12.0 + */ + public static Password password(char[] password) { + return invokeStatic("password", FOR_PASSWORD_ARG_TYPES, new Object[]{password}); + } + + /** + * Returns a {@code SecretKeyBuilder} that produces the specified key, allowing association with a + * {@link SecretKeyBuilder#provider(Provider) provider} that must be used with the key during cryptographic + * operations. For example: + * + *
    +     * SecretKey key = Keys.builder(key).provider(mandatoryProvider).build();
    + * + *

    Cryptographic algorithm implementations can inspect the resulting {@code key} instance and obtain its + * mandatory {@code Provider} if necessary.

    + * + *

    This method is primarily only useful for keys that cannot expose key material, such as PKCS11 or HSM + * (Hardware Security Module) keys, and require a specific {@code Provider} to be used during cryptographic + * operations.

    + * + * @param key the secret key to use for cryptographic operations, potentially associated with a configured + * {@link Provider} + * @return a new {@code SecretKeyBuilder} that produces the specified key, potentially associated with any + * specified provider. + * @since 0.12.0 + */ + public static SecretKeyBuilder builder(SecretKey key) { + Assert.notNull(key, "SecretKey cannot be null."); + return invokeStatic("builder", SECRET_BUILDER_ARG_TYPES, key); + } + + /** + * Returns a {@code PrivateKeyBuilder} that produces the specified key, allowing association with a + * {@link PrivateKeyBuilder#publicKey(PublicKey) publicKey} to obtain public key data if necessary, or a + * {@link SecretKeyBuilder#provider(Provider) provider} that must be used with the key during cryptographic + * operations. For example: + * + *
    +     * PrivateKey key = Keys.builder(privateKey).publicKey(publicKey).provider(mandatoryProvider).build();
    + * + *

    Cryptographic algorithm implementations can inspect the resulting {@code key} instance and obtain its + * mandatory {@code Provider} or {@code PublicKey} if necessary.

    + * + *

    This method is primarily only useful for keys that cannot expose key material, such as PKCS11 or HSM + * (Hardware Security Module) keys, and require a specific {@code Provider} or public key data to be used + * during cryptographic operations.

    + * + * @param key the private key to use for cryptographic operations, potentially associated with a configured + * {@link Provider} or {@link PublicKey}. + * @return a new {@code PrivateKeyBuilder} that produces the specified private key, potentially associated with any + * specified provider or {@code PublicKey} + * @since 0.12.0 + */ + public static PrivateKeyBuilder builder(PrivateKey key) { + Assert.notNull(key, "PrivateKey cannot be null."); + return invokeStatic("builder", PRIVATE_BUILDER_ARG_TYPES, key); + } } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/MacAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/MacAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/MacAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,65 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; + +import javax.crypto.SecretKey; + +/** + * A {@link SecureDigestAlgorithm} that uses symmetric {@link SecretKey}s to both compute and verify digests as + * message authentication codes (MACs). + * + *

    Standard Identifier

    + * + *

    {@code MacAlgorithm} extends {@link Identifiable}: when a {@code MacAlgorithm} is used to compute the MAC of a + * JWS, the value returned from {@link Identifiable#getId() macAlgorithm.getId()} will be set as the JWS + * "alg" protected header value.

    + * + *

    Key Strength

    + * + *

    MAC algorithm strength is in part attributed to how difficult it is to discover the secret key. + * As such, MAC algorithms usually require keys of a minimum length to ensure the keys are difficult to discover + * and the algorithm's security properties are maintained.

    + * + *

    The {@code MacAlgorithm} interface extends the {@link KeyLengthSupplier} interface to represent + * the length in bits (not bytes) a key must have to be used with its implementation. If you do not want to + * worry about lengths and parameters of keys required for an algorithm, it is often easier to automatically generate + * a key that adheres to the algorithms requirements, as discussed below.

    + * + *

    Key Generation

    + * + *

    {@code MacAlgorithm} extends {@link KeyBuilderSupplier} to enable {@link SecretKey} generation. + * Each {@code MacAlgorithm} algorithm instance will return a {@link KeyBuilder} that ensures any created keys will + * have a sufficient length and any algorithm parameters required by that algorithm. For example:

    + * + *
    + * SecretKey key = macAlgorithm.key().build();
    + * + *

    The resulting {@code key} is guaranteed to have the correct algorithm parameters and strength/length necessary for + * that exact {@code MacAlgorithm} instance.

    + * + *

    JWA Standard Implementations

    + * + *

    Constant definitions and utility methods for all JWA (RFC 7518) standard MAC algorithms are + * available via {@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}.

    + * + * @see io.jsonwebtoken.Jwts.SIG Jwts.SIG + * @since 0.12.0 + */ +public interface MacAlgorithm extends SecureDigestAlgorithm, + KeyBuilderSupplier, KeyLengthSupplier { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/MalformedKeyException.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/MalformedKeyException.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/MalformedKeyException.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * Exception thrown when encountering a key or key material that is incomplete or improperly configured or + * formatted and cannot be used as expected. + * + * @since 0.12.0 + */ +public class MalformedKeyException extends InvalidKeyException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public MalformedKeyException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param msg the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public MalformedKeyException(String msg, Throwable cause) { + super(msg, cause); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/MalformedKeySetException.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/MalformedKeySetException.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/MalformedKeySetException.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,44 @@ +/* + * 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.security; + +/** + * Exception thrown when encountering a {@link JwkSet} that is incomplete or improperly configured or + * formatted and cannot be used as expected. + * + * @since 0.12.0 + */ +public class MalformedKeySetException extends SecurityException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public MalformedKeySetException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public MalformedKeySetException(String message, Throwable cause) { + super(message, cause); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Message.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Message.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Message.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * A message contains a {@link #getPayload() payload} used as input to or output from a cryptographic algorithm. + * + * @param The type of payload in the message. + * @since 0.12.0 + */ +public interface Message { + + /** + * Returns the message payload used as input to or output from a cryptographic algorithm. This is almost always + * plaintext used for cryptographic signatures or encryption, or ciphertext for decryption, or a {@link Key} + * instance for wrapping or unwrapping algorithms. + * + * @return the message payload used as input to or output from a cryptographic algorithm. + */ + T getPayload(); //plaintext, ciphertext or Key +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/OctetPrivateJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/OctetPrivateJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/OctetPrivateJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,68 @@ +/* + * 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.security; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.ECPrivateKey; + +/** + * JWK representation of an Edwards Curve + * {@link PrivateKey} as defined by RFC 8037, Section 2: + * Key Type "OKP". + * + *

    Unlike the {@link EcPrivateJwk} interface, which only supports + * Weierstrass-form {@link ECPrivateKey}s, + * {@code OctetPrivateJwk} allows for multiple parameterized {@link PrivateKey} types + * because the JDK supports two different types of Edwards Curve private keys:

    + * + *

    As such, {@code OctetPrivateJwk} is parameterized to support both key types.

    + * + *

    Earlier JDK Versions

    + * + *

    Even though {@code XECPrivateKey} and {@code EdECPrivateKey} were introduced in JDK 11 and JDK 15 respectively, + * JJWT supports Octet private JWKs in earlier versions when BouncyCastle is enabled in the application classpath. When + * using earlier JDK versions, the {@code OctetPrivateJwk} instance will need be parameterized with the + * generic {@code PrivateKey} type since the latter key types would not be present. For example:

    + *
    + * OctetPrivateJwk<PrivateKey> octetPrivateJwk = getKey();
    + * + *

    OKP-specific Properties

    + * + *

    Note that the various OKP-specific properties are not available as separate dedicated getter methods, as most Java + * applications should rarely, if ever, need to access these individual key properties since they typically represent + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link PrivateKey} instance returned by {@link #toKey()} and + * query that instead.

    + * + *

    Even so, because these properties exist and are readable by nature of every JWK being a + * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method + * using an appropriate JWK parameter id, for example:

    + *
    + * jwk.get("x");
    + * jwk.get("d");
    + * // ... etc ...
    + * + * @param The type of Edwards-curve {@link PrivateKey} represented by this JWK (e.g. XECPrivateKey, EdECPrivateKey, etc). + * @param The type of Edwards-curve {@link PublicKey} represented by the JWK's corresponding + * {@link #toPublicJwk() public JWK}, for example XECPublicKey, EdECPublicKey, etc. + * @since 0.12.0 + */ +public interface OctetPrivateJwk extends PrivateJwk> { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/OctetPrivateJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/OctetPrivateJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/OctetPrivateJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,30 @@ +/* + * 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.security; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * A {@link PrivateJwkBuilder} that creates {@link OctetPrivateJwk} instances. + * + * @param The type of {@link PrivateKey} represented by the constructed {@link OctetPrivateJwk} instance. + * @param The type of {@link PublicKey} available from the constructed {@link OctetPrivateJwk}'s associated {@link PrivateJwk#toPublicJwk() public JWK} properties. + * @since 0.12.0 + */ +public interface OctetPrivateJwkBuilder extends + PrivateJwkBuilder, OctetPrivateJwk, OctetPrivateJwkBuilder> { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/OctetPublicJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/OctetPublicJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/OctetPublicJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,63 @@ +/* + * 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.security; + +import java.security.PublicKey; +import java.security.interfaces.ECPublicKey; + +/** + * JWK representation of an Edwards Curve + * {@link PublicKey} as defined by RFC 8037, Section 2: + * Key Type "OKP". + * + *

    Unlike the {@link EcPublicJwk} interface, which only supports + * Weierstrass-form {@link ECPublicKey}s, + * {@code OctetPublicJwk} allows for multiple parameterized {@link PublicKey} types + * because the JDK supports two different types of Edwards Curve public keys:

    + * + *

    As such, {@code OctetPublicJwk} is parameterized to support both key types.

    + * + *

    Earlier JDK Versions

    + * + *

    Even though {@code XECPublicKey} and {@code EdECPublicKey} were introduced in JDK 11 and JDK 15 respectively, + * JJWT supports Octet public JWKs in earlier versions when BouncyCastle is enabled in the application classpath. When + * using earlier JDK versions, the {@code OctetPublicJwk} instance will need be parameterized with the + * generic {@code PublicKey} type since the latter key types would not be present. For example:

    + *
    OctetPublicJwk<PublicKey> octetPublicJwk = getKey();
    + * + *

    OKP-specific Properties

    + * + *

    Note that the various OKP-specific properties are not available as separate dedicated getter methods, as most Java + * applications should rarely, if ever, need to access these individual key properties since they typically represent + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link PublicKey} instance returned by {@link #toKey()} and + * query that instead.

    + * + *

    Even so, because these properties exist and are readable by nature of every JWK being a + * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method + * using an appropriate JWK parameter id, for example:

    + *
    + * jwk.get("x");
    + * // ... etc ...
    + * + * @param The type of Edwards-curve {@link PublicKey} represented by this JWK (e.g. XECPublicKey, EdECPublicKey, etc). + * @since 0.12.0 + */ +public interface OctetPublicJwk extends PublicJwk { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/OctetPublicJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/OctetPublicJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/OctetPublicJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * A {@link PublicJwkBuilder} that creates {@link OctetPublicJwk} instances. + * + * @param the type of {@link PublicKey} provided by the created {@link OctetPublicJwk} (e.g. XECPublicKey, EdECPublicKey, etc). + * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce an + * {@link OctetPrivateJwk} if desired. For example, XECPrivateKey, EdECPrivateKey, etc. + * @since 0.12.0 + */ +public interface OctetPublicJwkBuilder + extends PublicJwkBuilder, OctetPrivateJwk, OctetPrivateJwkBuilder, OctetPublicJwkBuilder> { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Password.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Password.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Password.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; +import javax.security.auth.Destroyable; + +/** + * A {@code Key} suitable for use with password-based key derivation algorithms. + * + *

    Usage Warning

    + * + *

    Because raw passwords should never be used as direct inputs for cryptographic operations (such as authenticated + * hashing or encryption) - and only for derivation algorithms (like password-based encryption) - {@code Password} + * instances will throw an exception when used in these invalid contexts. Specifically, calling a + * {@code Password}'s {@link Password#getEncoded() getEncoded()} method (as would be done automatically by the + * JCA subsystem during direct cryptographic operations) will throw an + * {@link UnsupportedOperationException UnsupportedOperationException}.

    + * + * @see #toCharArray() + * @since 0.12.0 + */ +public interface Password extends SecretKey, Destroyable { + + /** + * Returns a new clone of the underlying password character array for use during derivation algorithms. Like all + * {@code SecretKey} implementations, if you wish to clear the backing password character array for + * safety/security reasons, call the {@link #destroy()} method, ensuring that both the character array is cleared + * and the {@code Password} instance can no longer be used. + * + *

    Usage

    + * + *

    Because a new clone is returned from this method each time it is invoked, it is expected that callers will + * clear the resulting clone from memory as soon as possible to reduce probability of password exposure. For + * example:

    + * + *
    
    +     * char[] clonedPassword = aPassword.toCharArray();
    +     * try {
    +     *     doSomethingWithPassword(clonedPassword);
    +     * } finally {
    +     *     // guarantee clone is cleared regardless of any Exception thrown:
    +     *     java.util.Arrays.fill(clonedPassword, '\u0000');
    +     * }
    +     * 
    + * + * @return a clone of the underlying password character array. + */ + char[] toCharArray(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PrivateJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PrivateJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PrivateJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * JWK representation of a {@link PrivateKey}. + * + *

    JWK Private Key vs Java {@code PrivateKey} differences

    + * + *

    Unlike the Java cryptography APIs, the JWK specification requires all public key and private key + * properties to be contained within every private JWK. As such, a {@code PrivateJwk} indeed represents + * private key values as its name implies, but it is probably more similar to the Java JCA concept of a + * {@link java.security.KeyPair} since it contains everything for both keys.

    + * + *

    Consequently a {@code PrivateJwk} is capable of providing two additional convenience methods:

    + *
      + *
    • {@link #toPublicJwk()} - a method to obtain a {@link PublicJwk} instance that contains only the JWK public + * key properties, and
    • + *
    • {@link #toKeyPair()} - a method to obtain both Java {@link PublicKey} and {@link PrivateKey}s in aggregate + * as a {@link KeyPair} instance if desired.
    • + *
    + * + * @param The type of {@link PrivateKey} represented by this JWK + * @param The type of {@link PublicKey} represented by the JWK's corresponding {@link #toPublicJwk() public JWK}. + * @param The type of {@link PublicJwk} reflected by the JWK's public properties. + * @since 0.12.0 + */ +public interface PrivateJwk> extends AsymmetricJwk { + + /** + * Returns the private JWK's corresponding {@link PublicJwk}, containing only the key's public properties. + * + * @return the private JWK's corresponding {@link PublicJwk}, containing only the key's public properties. + */ + M toPublicJwk(); + + /** + * Returns the key's corresponding Java {@link PrivateKey} and {@link PublicKey} in aggregate as a + * type-safe {@link KeyPair} instance. + * + * @return the key's corresponding Java {@link PrivateKey} and {@link PublicKey} in aggregate as a + * type-safe {@link KeyPair} instance. + */ + KeyPair toKeyPair(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PrivateJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PrivateJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PrivateJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * An {@link AsymmetricJwkBuilder} that creates {@link PrivateJwk} instances. + * + * @param the type of Java {@link PrivateKey} provided by the created private JWK. + * @param the type of Java {@link PublicKey} paired with the private key. + * @param the type of {@link PrivateJwk} created + * @param the type of {@link PublicJwk} paired with the created private JWK. + * @param the type of the builder, for subtype method chaining + * @see #publicKey(PublicKey) + * @since 0.12.0 + */ +public interface PrivateJwkBuilder, M extends PrivateJwk, + T extends PrivateJwkBuilder> extends AsymmetricJwkBuilder { + + /** + * Allows specifying of the {@link PublicKey} associated with the builder's existing {@link PrivateKey}, + * offering a reasonable performance enhancement when building the final private JWK. Application developers + * should prefer to use this method when possible when building private JWKs. + * + *

    As discussed in the {@link PrivateJwk} documentation, the JWK and JWA specifications require private JWKs to + * contain both private key and public key data. If a public key is not provided via this + * {@code publicKey} method, the builder implementation must go through the work to derive the + * {@code PublicKey} instance based on the {@code PrivateKey} to obtain the necessary public key information.

    + * + *

    Calling this method with the {@code PrivateKey}'s matching {@code PublicKey} instance eliminates the need + * for the builder to do that work.

    + * + * @param publicKey the {@link PublicKey} that matches the builder's existing {@link PrivateKey}. + * @return the builder for method chaining. + */ + T publicKey(L publicKey); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PrivateKeyBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PrivateKeyBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PrivateKeyBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,38 @@ +/* + * 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.security; + +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; + +/** + * A builder that allows a {@code PrivateKey} to be transparently associated with a {@link #provider(Provider)} or + * {@link #publicKey(PublicKey)} if necessary for algorithms that require them. + * + * @since 0.12.0 + */ +public interface PrivateKeyBuilder extends KeyBuilder { + + /** + * Sets the private key's corresponding {@code PublicKey} so that its public key material will be available to + * algorithms that require it. + * + * @param publicKey the private key's corresponding {@code PublicKey} + * @return the builder for method chaining. + */ + PrivateKeyBuilder publicKey(PublicKey publicKey); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PublicJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PublicJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PublicJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PublicKey; + +/** + * JWK representation of a {@link PublicKey}. + * + * @param The type of {@link PublicKey} represented by this JWK + * @since 0.12.0 + */ +public interface PublicJwk extends AsymmetricJwk { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PublicJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PublicJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/PublicJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * An {@link AsymmetricJwkBuilder} that creates {@link PublicJwk} instances. + * + * @param the type of {@link PublicKey} provided by the created public JWK. + * @param the type of {@link PrivateKey} that may be paired with the {@link PublicKey} to produce a {@link PrivateJwk} if desired. + * @param the type of {@link PublicJwk} created + * @param the type of {@link PrivateJwk} that matches the created {@link PublicJwk} + * @param

    the type of {@link PrivateJwkBuilder} that matches this builder if a {@link PrivateJwk} is desired. + * @param the type of the builder, for subtype method chaining + * @see #privateKey(PrivateKey) + * @since 0.12.0 + */ +public interface PublicJwkBuilder, M extends PrivateJwk, + P extends PrivateJwkBuilder, + T extends PublicJwkBuilder> extends AsymmetricJwkBuilder { + + /** + * Sets the {@link PrivateKey} that pairs with the builder's existing {@link PublicKey}, converting this builder + * into a {@link PrivateJwkBuilder} which will produce a corresponding {@link PrivateJwk} instance. The + * specified {@code privateKey} MUST be the exact private key paired with the builder's public key. + * + * @param privateKey the {@link PrivateKey} that pairs with the builder's existing {@link PublicKey} + * @return the builder coerced as a {@link PrivateJwkBuilder} which will produce a corresponding {@link PrivateJwk}. + */ + P privateKey(L privateKey); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Request.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Request.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/Request.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Provider; +import java.security.SecureRandom; + +/** + * A {@code Request} aggregates various parameters that may be used by a particular cryptographic algorithm. It and + * any of its subtypes implemented as a single object submitted to an algorithm effectively reflect the + * Parameter Object design pattern. This + * provides for a much cleaner request/result algorithm API instead of polluting the API with an excessive number of + * overloaded methods that would exist otherwise. + * + *

    The {@code Request} interface specifically allows for JCA {@link Provider} and {@link SecureRandom} instances + * to be used during request execution, which allows more flexibility than forcing a single {@code Provider} or + * {@code SecureRandom} for all executions. {@code Request} subtypes provide additional parameters as necessary + * depending on the type of cryptographic algorithm invoked.

    + * + * @param the type of payload in the request. + * @see #getProvider() + * @see #getSecureRandom() + * @since 0.12.0 + */ +public interface Request extends Message { + + /** + * Returns the JCA provider that should be used for cryptographic operations during the request or + * {@code null} if the JCA subsystem preferred provider should be used. + * + * @return the JCA provider that should be used for cryptographic operations during the request or + * {@code null} if the JCA subsystem preferred provider should be used. + */ + Provider getProvider(); + + /** + * Returns the {@code SecureRandom} to use when performing cryptographic operations during the request, or + * {@code null} if a default {@link SecureRandom} should be used. + * + * @return the {@code SecureRandom} to use when performing cryptographic operations during the request, or + * {@code null} if a default {@link SecureRandom} should be used. + */ + SecureRandom getSecureRandom(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/RsaPrivateJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/RsaPrivateJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/RsaPrivateJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +/** + * JWK representation of an {@link RSAPrivateKey} as defined by the JWA (RFC 7518) specification sections on + * Parameters for RSA Keys and + * Parameters for RSA Private Keys. + * + *

    Note that the various RSA-specific properties are not available as separate dedicated getter methods, as most Java + * applications should rarely, if ever, need to access these individual key properties since they typically represent + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link RSAPrivateKey} instance returned by {@link #toKey()} and + * query that instead.

    + * + *

    Even so, because these properties exist and are readable by nature of every JWK being a + * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method + * using an appropriate JWK parameter id, for example:

    + *
    + * jwk.get("n");
    + * jwk.get("e");
    + * // ... etc ...
    + * + * @since 0.12.0 + */ +public interface RsaPrivateJwk extends PrivateJwk { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/RsaPrivateJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/RsaPrivateJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/RsaPrivateJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +/** + * A {@link PrivateJwkBuilder} that creates {@link RsaPrivateJwk}s. + * + * @since 0.12.0 + */ +public interface RsaPrivateJwkBuilder extends PrivateJwkBuilder { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/RsaPublicJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/RsaPublicJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/RsaPublicJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.RSAPublicKey; + +/** + * JWK representation of an {@link RSAPublicKey} as defined by the JWA (RFC 7518) specification sections on + * Parameters for RSA Keys and + * Parameters for RSA Public Keys. + * + *

    Note that the various RSA-specific properties are not available as separate dedicated getter methods, as most Java + * applications should rarely, if ever, need to access these individual key properties since they typically represent + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link RSAPublicKey} instance returned by {@link #toKey()} and + * query that instead.

    + * + *

    Even so, because these properties exist and are readable by nature of every JWK being a + * {@link java.util.Map Map}, they are still accessible via the standard {@code Map} {@link #get(Object) get} method + * using an appropriate JWK parameter id, for example:

    + *
    + * jwk.get("n");
    + * jwk.get("e");
    + * // ... etc ...
    + * + * @since 0.12.0 + */ +public interface RsaPublicJwk extends PublicJwk { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/RsaPublicJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/RsaPublicJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/RsaPublicJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +/** + * A {@link PublicJwkBuilder} that creates {@link RsaPublicJwk}s. + * + * @since 0.12.0 + */ +public interface RsaPublicJwkBuilder extends PublicJwkBuilder { + +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecretJwk.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecretJwk.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecretJwk.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; + +/** + * JWK representation of a {@link SecretKey} as defined by the JWA (RFC 7518) specification section on + * Parameters for Symmetric Keys. + * + *

    Note that the {@code SecretKey}-specific properties are not available as separate dedicated getter methods, as + * most Java applications should rarely, if ever, need to access these individual key properties since they typically + * internal key material and/or serialization details. If you need to access these key properties, it is usually + * recommended to obtain the corresponding {@link SecretKey} instance returned by {@link #toKey()} and + * query that instead.

    + * + * @since 0.12.0 + */ +public interface SecretJwk extends Jwk { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecretJwkBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecretJwkBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecretJwkBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; + +/** + * A {@link JwkBuilder} that creates {@link SecretJwk}s. + * + * @since 0.12.0 + */ +public interface SecretJwkBuilder extends JwkBuilder { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecretKeyAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecretKeyAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecretKeyAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; + +/** + * A {@link KeyAlgorithm} that uses symmetric {@link SecretKey}s to obtain AEAD encryption and decryption keys. + * + * @since 0.12.0 + */ +public interface SecretKeyAlgorithm extends KeyAlgorithm, KeyBuilderSupplier, KeyLengthSupplier { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecretKeyBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecretKeyBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecretKeyBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import javax.crypto.SecretKey; + +/** + * A {@link KeyBuilder} that creates new secure-random {@link SecretKey}s with a length sufficient to be used by + * the security algorithm that produced this builder. + * + * @since 0.12.0 + */ +public interface SecretKeyBuilder extends KeyBuilder { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecureDigestAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecureDigestAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecureDigestAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,55 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; + +import java.io.InputStream; +import java.security.Key; + +/** + * A {@link DigestAlgorithm} that requires a {@link Key} to compute and verify the authenticity of digests using either + * digital signature or + * message + * authentication code algorithms. + * + *

    Standard Identifier

    + * + *

    {@code SecureDigestAlgorithm} extends {@link Identifiable}: when a {@code SecureDigestAlgorithm} is used to + * compute the digital signature or MAC of a JWS, the value returned from + * {@link Identifiable#getId() secureDigestAlgorithm.getId()} will be set as the JWS + * "alg" protected header value.

    + * + *

    Standard Implementations

    + * + *

    Constant definitions and utility methods for all JWA (RFC 7518) standard + * Cryptographic Algorithms for Digital Signatures and + * MACs are available via {@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}.

    + * + *

    "alg" identifier

    + * + *

    {@code SecureDigestAlgorithm} extends {@link Identifiable}: the value returned from + * {@link Identifiable#getId() getId()} will be used as the JWS "alg" protected header value.

    + * + * @param the type of {@link Key} used to create digital signatures or message authentication codes + * @param the type of {@link Key} used to verify digital signatures or message authentication codes + * @see MacAlgorithm + * @see SignatureAlgorithm + * @since 0.12.0 + */ +public interface SecureDigestAlgorithm + extends DigestAlgorithm, VerifySecureDigestRequest> { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecureRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecureRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecureRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.Key; + +/** + * A request to a cryptographic algorithm requiring a {@link Key}. + * + * @param the type of payload in the request + * @param they type of key used by the algorithm during the request + * @since 0.12.0 + */ +public interface SecureRequest extends Request, KeySupplier { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecurityBuilder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecurityBuilder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecurityBuilder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.lang.Builder; + +import java.security.Provider; +import java.security.SecureRandom; + +/** + * A Security-specific {@link Builder} that allows configuration of common JCA API parameters that might be used + * during instance creation, such as a {@link java.security.Provider} or {@link java.security.SecureRandom}. + * + * @param The type of object that will be created each time {@link #build()} is invoked. + * @param the type of SecurityBuilder returned for method chaining + * @see #provider(Provider) + * @see #random(SecureRandom) + * @since 0.12.0 + */ +public interface SecurityBuilder> extends Builder { + + /** + * Sets the JCA Security {@link Provider} to use if necessary when calling {@link #build()}. This is an optional + * property - if not specified, the default JCA Provider will be used. + * + * @param provider the JCA Security Provider instance to use if necessary when building the new instance. + * @return the builder for method chaining. + */ + B provider(Provider provider); + + /** + * Sets the {@link SecureRandom} to use if necessary when calling {@link #build()}. This is an optional property + * - if not specified and one is required, a default {@code SecureRandom} will be used. + * + * @param random the {@link SecureRandom} instance to use if necessary when building the new instance. + * @return the builder for method chaining. + */ + B random(SecureRandom random); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecurityException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecurityException.java (.../SecurityException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SecurityException.java (.../SecurityException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -18,14 +18,28 @@ import io.jsonwebtoken.JwtException; /** + * A {@code JwtException} attributed to a problem with security-related elements, such as + * cryptographic keys, algorithms, or the underlying Java JCA API. + * * @since 0.10.0 */ public class SecurityException extends JwtException { + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ public SecurityException(String message) { super(message); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public SecurityException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SignatureAlgorithm.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SignatureAlgorithm.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SignatureAlgorithm.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.Identifiable; + +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * A digital signature algorithm computes and + * verifies digests using asymmetric public/private key cryptography. + * + *

    Standard Identifier

    + * + *

    {@code SignatureAlgorithm} extends {@link Identifiable}: when a {@code SignatureAlgorithm} is used to compute + * a JWS digital signature, the value returned from {@link Identifiable#getId() signatureAlgorithm.getId()} will be + * set as the JWS "alg" protected header value.

    + * + *

    Key Pair Generation

    + * + *

    {@code SignatureAlgorithm} extends {@link KeyPairBuilderSupplier} to enable + * {@link KeyPair} generation. Each {@code SignatureAlgorithm} instance will return a + * {@link KeyPairBuilder} that ensures any created key pairs will have a sufficient length and algorithm parameters + * required by that algorithm. For example:

    + * + *
    + * KeyPair pair = signatureAlgorithm.keyPair().build();
    + * + *

    The resulting {@code pair} is guaranteed to have the correct algorithm parameters and length/strength necessary + * for that exact {@code signatureAlgorithm} instance.

    + * + *

    JWA Standard Implementations

    + * + *

    Constant definitions and utility methods for all JWA (RFC 7518) standard signature algorithms are + * available via {@link io.jsonwebtoken.Jwts.SIG Jwts.SIG}.

    + * + * @see io.jsonwebtoken.Jwts.SIG Jwts.SIG + * @since 0.12.0 + */ +public interface SignatureAlgorithm extends SecureDigestAlgorithm, KeyPairBuilderSupplier { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SignatureException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SignatureException.java (.../SignatureException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/SignatureException.java (.../SignatureException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,14 +16,28 @@ package io.jsonwebtoken.security; /** + * Exception thrown if there is problem calculating or verifying a digital signature or message authentication code. + * * @since 0.10.0 */ +@SuppressWarnings("deprecation") public class SignatureException extends io.jsonwebtoken.SignatureException { + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ public SignatureException(String message) { super(message); } + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param message the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ public SignatureException(String message, Throwable cause) { super(message, cause); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/UnsupportedKeyException.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/UnsupportedKeyException.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/UnsupportedKeyException.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +/** + * Exception thrown when encountering a key or key material that is not supported or recognized. + * + * @since 0.12.0 + */ +public class UnsupportedKeyException extends KeyException { + + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ + public UnsupportedKeyException(String message) { + super(message); + } + + /** + * Creates a new instance with the specified explanation message and underlying cause. + * + * @param msg the message explaining why the exception is thrown. + * @param cause the underlying cause that resulted in this exception being thrown. + */ + public UnsupportedKeyException(String msg, Throwable cause) { + super(msg, cause); + } +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/VerifyDigestRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/VerifyDigestRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/VerifyDigestRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,33 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.io.InputStream; + +/** + * A request to verify a previously-computed cryptographic digest (available via {@link #getDigest()}) against the + * digest to be computed for the specified {@link #getPayload() payload}. + * + *

    Secure digest algorithms that use keys to perform + * digital signature or + * message + * authentication code verification will use {@link VerifySecureDigestRequest} instead.

    + * + * @see VerifySecureDigestRequest + * @since 0.12.0 + */ +public interface VerifyDigestRequest extends Request, DigestSupplier { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/VerifySecureDigestRequest.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/VerifySecureDigestRequest.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/VerifySecureDigestRequest.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,34 @@ +/* + * Copyright © 2022 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.io.InputStream; +import java.security.Key; + +/** + * A request to a {@link SecureDigestAlgorithm} to verify a previously-computed + * digital signature or + * message + * authentication code. + * + *

    The content to verify will be available via {@link #getPayload()}, the previously-computed signature or MAC will + * be available via {@link #getDigest()}, and the verification key will be available via {@link #getKey()}.

    + * + * @param the type of {@link Key} used to verify a digital signature or message authentication code + * @since 0.12.0 + */ +public interface VerifySecureDigestRequest extends SecureRequest, VerifyDigestRequest { +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/WeakKeyException.java =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/WeakKeyException.java (.../WeakKeyException.java) (revision 0761df64c22d4bca1649836fda23e4526282498b) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/WeakKeyException.java (.../WeakKeyException.java) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -16,10 +16,18 @@ package io.jsonwebtoken.security; /** + * Exception thrown when encountering a key that is not strong enough (of sufficient length) to be used with + * a particular algorithm or in a particular security context. + * * @since 0.10.0 */ public class WeakKeyException extends InvalidKeyException { + /** + * Creates a new instance with the specified explanation message. + * + * @param message the message explaining why the exception is thrown. + */ public WeakKeyException(String message) { super(message); } Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/X509Accessor.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/X509Accessor.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/X509Accessor.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.JweHeader; +import io.jsonwebtoken.JwsHeader; + +import java.net.URI; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * Accessor methods of X.509-specific properties of a + * {@link io.jsonwebtoken.ProtectedHeader ProtectedHeader} or {@link AsymmetricJwk}, guaranteeing consistent behavior + * across similar but distinct JWT concepts with identical parameter names. + * + * @see io.jsonwebtoken.ProtectedHeader + * @see AsymmetricJwk + * @since 0.12.0 + */ +public interface X509Accessor { + + /** + * Returns the {@code x5u} (X.509 URL) that refers to a resource for the associated X.509 public key certificate + * or certificate chain, or {@code null} if not present. + * + *

    When present, the URI MUST refer to a resource for an X.509 public key certificate or certificate + * chain that conforms to RFC 5280 in PEM-encoded form, + * with each certificate delimited as specified in + * Section 6.1 of RFC 4945. + * The key in the first certificate MUST match the public key represented by other members of the + * associated ProtectedHeader or JWK. The protocol used to acquire the resource MUST provide integrity + * protection; an HTTP GET request to retrieve the certificate MUST use + * HTTP over TLS; the identity of the server + * MUST be validated, as per + * Section 6 of RFC 6125.

    + * + *
      + *
    • When present in a {@link JwsHeader}, the certificate or first certificate in the chain corresponds + * the public key complement of the private key used to digitally sign the JWS.
    • + *
    • When present in a {@link JweHeader}, the certificate or certificate chain corresponds to the + * public key to which the JWE was encrypted, and may be used to determine the private key needed to + * decrypt the JWE.
    • + *
    • When present in an {@link AsymmetricJwk}, the certificate or first certificate in the chain + * MUST contain the public key represented by the JWK.
    • + *
    + * + * @return the {@code x5u} (X.509 URL) that refers to a resource for the associated X.509 public key certificate or + * certificate chain. + * @see JWK {@code x5u} (X.509 URL) Parameter + * @see JWS {@code x5u} (X.509 URL) Header Parameter + * @see JWE {@code x5u} (X.509 URL) Header Parameter + */ + URI getX509Url(); + + /** + * Returns the associated {@code x5c} (X.509 Certificate Chain), or {@code null} if not present. The initial + * certificate MAY be followed by additional certificates, with each subsequent certificate being the + * one used to certify the previous one. + * + *
      + *
    • When present in a {@link JwsHeader}, the first certificate (at list index 0) MUST contain + * the public key complement of the private key used to digitally sign the JWS.
    • + *
    • When present in a {@link JweHeader}, the first certificate (at list index 0) MUST contain + * the public key to which the JWE was encrypted, and may be used to determine the private key needed to + * decrypt the JWE.
    • + *
    • When present in an {@link AsymmetricJwk}, the first certificate (at list index 0) + * MUST contain the public key represented by the JWK.
    • + *
    + * + * @return the associated {@code x5c} (X.509 Certificate Chain), or {@code null} if not present. + * @see JWK x5c (X.509 Certificate Chain) Parameter + * @see JWS x5c (X.509 Certificate Chain) Header Parameter + * @see JWE x5c (X.509 Certificate Chain) Header Parameter + */ + List getX509Chain(); + + /** + * Returns the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * associated X.509 Certificate, or {@code null} if not present. + * + *

    Note that certificate thumbprints are also sometimes known as certificate fingerprints.

    + * + *
      + *
    • When present in a {@link JwsHeader}, it is the SHA-1 thumbprint of the X.509 certificate complement + * of the private key used to digitally sign the JWS.
    • + *
    • When present in a {@link JweHeader}, it is the SHA-1 thumbprint of the X.509 Certificate containing + * the public key to which the JWE was encrypted, and may be used to determine the private key + * needed to decrypt the JWE.
    • + *
    • When present in an {@link AsymmetricJwk}, it is the SHA-1 thumbprint of the X.509 certificate + * containing the public key represented by the JWK.
    • + *
    + * + * @return the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * associated X.509 Certificate, or {@code null} if not present + * @see JWK x5t (X.509 Certificate SHA-1 Thumbprint) Parameter + * @see JWS x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter + * @see JWE x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter + */ + byte[] getX509Sha1Thumbprint(); + + /** + * Returns the {@code x5t#S256} (X.509 Certificate SHA-256 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * associated X.509 Certificate, or {@code null} if not present. + * + *

    Note that certificate thumbprints are also sometimes known as certificate fingerprints.

    + * + *
      + *
    • When present in a {@link JwsHeader}, it is the SHA-256 thumbprint of the X.509 certificate complement + * of the private key used to digitally sign the JWS.
    • + *
    • When present in a {@link JweHeader}, it is the SHA-256 thumbprint of the X.509 Certificate containing + * the public key to which the JWE was encrypted, and may be used to determine the private key + * needed to decrypt the JWE.
    • + *
    • When present in an {@link AsymmetricJwk}, it is the SHA-256 thumbprint of the X.509 certificate + * containing the public key represented by the JWK.
    • + *
    + * + * @return the {@code x5t#S256} (X.509 Certificate SHA-256 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * associated X.509 Certificate, or {@code null} if not present + * @see JWK x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Parameter + * @see JWS x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter + * @see JWE x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter + */ + byte[] getX509Sha256Thumbprint(); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/X509Builder.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/X509Builder.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/X509Builder.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * Additional X.509-specific builder methods for constructing an associated JWT Header or JWK, enabling method chaining. + * + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface X509Builder> extends X509Mutator { + + /** + * If the {@code enable} argument is {@code true}, compute the SHA-1 thumbprint of the first + * {@link X509Certificate} in the configured {@link #x509Chain(List) x509CertificateChain}, and set + * the resulting value as the {@link #x509Sha1Thumbprint(byte[])} parameter. + * + *

    If no chain has been configured, or {@code enable} is {@code false}, the builder will not compute nor add a + * {@code x5t} value.

    + * + * @param enable whether to compute the SHA-1 thumbprint on the first available X.509 Certificate and set + * the resulting value as the {@code x5t} value. + * @return the builder for method chaining. + */ + T x509Sha1Thumbprint(boolean enable); + + /** + * If the {@code enable} argument is {@code true}, compute the SHA-256 thumbprint of the first + * {@link X509Certificate} in the configured {@link #x509Chain(List) x509CertificateChain}, and set + * the resulting value as the {@link #x509Sha256Thumbprint(byte[])} parameter. + * + *

    If no chain has been configured, or {@code enable} is {@code false}, the builder will not compute nor add a + * {@code x5t#S256} value.

    + * + * @param enable whether to compute the SHA-256 thumbprint on the first available X.509 Certificate and set + * the resulting value as the {@code x5t#S256} value. + * @return the builder for method chaining. + */ + T x509Sha256Thumbprint(boolean enable); +} Index: 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/X509Mutator.java =================================================================== diff -u --- 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/X509Mutator.java (revision 0) +++ 3rdParty_sources/jsonwebtoken/io/jsonwebtoken/security/X509Mutator.java (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2021 jsonwebtoken.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.jsonwebtoken.security; + +import io.jsonwebtoken.JweHeader; +import io.jsonwebtoken.JwsHeader; + +import java.net.URI; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * Mutation (modifications) of X.509-specific properties of an associated JWT Header or JWK, enabling method chaining. + * + * @param the mutator subtype, for method chaining + * @since 0.12.0 + */ +public interface X509Mutator> { + + /** + * Sets the {@code x5u} (X.509 URL) that refers to a resource containing the X.509 public key certificate or + * certificate chain of the associated JWT or JWK. A {@code null} value will remove the property from the JSON map. + * + *

    The URI MUST refer to a resource for an X.509 public key certificate or certificate chain that + * conforms to RFC 5280 in PEM-encoded form, with + * each certificate delimited as specified in + * Section 6.1 of RFC 4945. + * The key in the first certificate MUST match the public key represented by other members of the + * associated JWT or JWK. The protocol used to acquire the resource MUST provide integrity protection; + * an HTTP GET request to retrieve the certificate MUST use + * HTTP over TLS; the identity of the server + * MUST be validated, as per + * Section 6 of RFC 6125.

    + * + *
      + *
    • When set for a {@link JwsHeader}, the certificate or first certificate in the chain contains + * the public key complement of the private key used to digitally sign the JWS.
    • + *
    • When set for {@link JweHeader}, the certificate or first certificate in the chain contains the + * public key to which the JWE was encrypted, and may be used to determine the private key needed to + * decrypt the JWE.
    • + *
    • When set for an {@link AsymmetricJwk}, the certificate or first certificate in the chain + * MUST contain the public key represented by the JWK.
    • + *
    + * + * @param uri the {@code x5u} (X.509 URL) that refers to a resource for the X.509 public key certificate or + * certificate chain associated with the JWT or JWK. + * @return the mutator/builder for method chaining. + * @see JWK x5u (X.509 URL) Parameter + * @see JWS x5u (X.509 URL) Header Parameter + * @see JWE x5u (X.509 URL) Header Parameter + */ + T x509Url(URI uri); + + /** + * Sets the {@code x5c} (X.509 Certificate Chain) of the associated JWT or JWK. A {@code null} value will remove the + * property from the JSON map. The initial certificate MAY be followed by additional certificates, with + * each subsequent certificate being the one used to certify the previous one. + * + *
      + *
    • When set for a {@link JwsHeader}, the first certificate (at list index 0) MUST contain + * the public key complement of the private key used to digitally sign the JWS.
    • + *
    • When set for {@link JweHeader}, the first certificate (at list index 0) MUST contain the + * public key to which the JWE was encrypted, and may be used to determine the private key needed to + * decrypt the JWE.
    • + *
    • When set for an {@link AsymmetricJwk}, the first certificate (at list index 0) MUST contain + * the public key represented by the JWK.
    • + *
    + * + * @param chain the {@code x5c} (X.509 Certificate Chain) of the associated JWT or JWK. + * @return the header/builder for method chaining. + * @see JWK x5c (X.509 Certificate Chain) Parameter + * @see JWS x5c (X.509 Certificate Chain) Header Parameter + * @see JWE x5c (X.509 Certificate Chain) Header Parameter + */ + T x509Chain(List chain); + + /** + * Sets the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * X.509 Certificate associated with the JWT or JWK. A {@code null} value will remove the + * property from the JSON map. + * + *

    Note that certificate thumbprints are also sometimes known as certificate fingerprints.

    + * + *
      + *
    • When set for a {@link JwsHeader}, it is the SHA-1 thumbprint of the X.509 certificate complement of + * the private key used to digitally sign the JWS.
    • + *
    • When set for {@link JweHeader}, it is the thumbprint of the X.509 Certificate containing the + * public key to which the JWE was encrypted, and may be used to determine the private key needed to + * decrypt the JWE.
    • + *
    • When set for an {@link AsymmetricJwk}, it is the thumbprint of the X.509 certificate containing the + * public key represented by the JWK.
    • + *
    + * + * @param thumbprint the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * X.509 Certificate associated with the JWT or JWK + * @return the header for method chaining + * @see JWK x5t (X.509 Certificate SHA-1 Thumbprint) Parameter + * @see JWS x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter + * @see JWE x5t (X.509 Certificate SHA-1 Thumbprint) Header Parameter + */ + T x509Sha1Thumbprint(byte[] thumbprint); + + /** + * Sets the {@code x5t#S256} (X.509 Certificate SHA-256 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * X.509 Certificate associated with the JWT or JWK. A {@code null} value will remove the + * property from the JSON map. + * + *

    Note that certificate thumbprints are also sometimes known as certificate fingerprints.

    + * + *
      + *
    • When set for a {@link JwsHeader}, it is the SHA-256 thumbprint of the X.509 certificate complement + * of the private key used to digitally sign the JWS.
    • + *
    • When set for {@link JweHeader}, it is the SHA-256 thumbprint of the X.509 Certificate containing the + * public key to which the JWE was encrypted, and may be used to determine the private key needed to + * decrypt the JWE.
    • + *
    • When set for a {@link AsymmetricJwk}, it is the SHA-256 thumbprint of the X.509 certificate + * containing the public key represented by the JWK.
    • + *
    + * + * @param thumbprint the {@code x5t} (X.509 Certificate SHA-1 Thumbprint) (a.k.a. digest) of the DER-encoding of the + * X.509 Certificate associated with the JWT or JWK + * @return the header for method chaining + * @see JWK x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Parameter + * @see JWS x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter + * @see JWE x5t#S256 (X.509 Certificate SHA-256 Thumbprint) Header Parameter + */ + T x509Sha256Thumbprint(byte[] thumbprint); +} Index: 3rdParty_sources/versions.txt =================================================================== diff -u -rb233be65d6df2834368af4d2e555a00795aa0f07 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- 3rdParty_sources/versions.txt (.../versions.txt) (revision b233be65d6df2834368af4d2e555a00795aa0f07) +++ 3rdParty_sources/versions.txt (.../versions.txt) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -19,6 +19,8 @@ Commons Fileupload 1.3.3 +Commons Fileupload2 2.0.0-M2 + Commons Lang 2.6 Commons IO 2.11 Index: idea_project/.idea/libraries/3rdParty.xml =================================================================== diff -u -rb233be65d6df2834368af4d2e555a00795aa0f07 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- idea_project/.idea/libraries/3rdParty.xml (.../3rdParty.xml) (revision b233be65d6df2834368af4d2e555a00795aa0f07) +++ idea_project/.idea/libraries/3rdParty.xml (.../3rdParty.xml) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -14,9 +14,6 @@ - - - @@ -57,6 +54,9 @@ + + + Index: lams_build/lib/json/jjwt-api-0.11.2.jar =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 Binary files differ Index: lams_build/lib/json/jjwt-api-0.12.5.jar =================================================================== diff -u Binary files differ Index: lams_build/lib/json/jjwt-impl-0.11.2.jar =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 Binary files differ Index: lams_build/lib/json/jjwt-impl-0.12.5.jar =================================================================== diff -u Binary files differ Index: lams_build/lib/json/jjwt-jackson-0.11.2.jar =================================================================== diff -u -r0761df64c22d4bca1649836fda23e4526282498b -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 Binary files differ Index: lams_build/lib/json/jjwt-jackson-0.12.5.jar =================================================================== diff -u Binary files differ Index: lams_build/lib/json/jjwt.module.xml =================================================================== diff -u -r881ef4e0675e2f52256fd2ce79fc6edb91eb45cb -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 --- lams_build/lib/json/jjwt.module.xml (.../jjwt.module.xml) (revision 881ef4e0675e2f52256fd2ce79fc6edb91eb45cb) +++ lams_build/lib/json/jjwt.module.xml (.../jjwt.module.xml) (revision ca3e06f3b65d2880d69fd5278773c5c71e1ca3e7) @@ -24,10 +24,10 @@ - - - - + + + + Index: lams_build/lib/json/jwks-rsa-0.20.0.jar =================================================================== diff -u -r036a686f96bc94886ff5f24989aba76e9f4d0f05 -rca3e06f3b65d2880d69fd5278773c5c71e1ca3e7 Binary files differ Index: lams_build/lib/json/jwks-rsa-0.22.1.jar =================================================================== diff -u Binary files differ
    Generated Elliptic Curve Key Parameters
    JWA AlgorithmKey SizeJWA Curve NameASN1 OID Curve Name
    EC256ES256256 bits{@code P-256}{@code secp256r1}
    EC384ES384384 bits{@code P-384}{@code secp384r1}
    EC512512 bitsES512521 bits{@code P-521}{@code secp521r1}