Refactoring and testing cont'd

This commit is contained in:
Les Hazlewood 2021-09-20 22:29:54 -07:00
parent f95b024d90
commit bfdaa754ac
65 changed files with 934 additions and 733 deletions

View File

@ -40,13 +40,13 @@ import java.util.Map;
public interface Header<T extends Header<T>> extends Map<String,Object> {
/** JWT {@code Type} (typ) value: <code>"JWT"</code> */
public static final String JWT_TYPE = "JWT";
String JWT_TYPE = "JWT";
/** JWT {@code Type} header parameter name: <code>"typ"</code> */
public static final String TYPE = "typ";
String TYPE = "typ";
/** JWT {@code Content Type} header parameter name: <code>"cty"</code> */
public static final String CONTENT_TYPE = "cty";
String CONTENT_TYPE = "cty";
/**
* JWT {@code Algorithm} header parameter name: <code>"alg"</code>.
@ -54,15 +54,15 @@ public interface Header<T extends Header<T>> extends Map<String,Object> {
* @see <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1">JWS Algorithm Header</a>
* @see <a href="https://tools.ietf.org/html/rfc7516#section-4.1.1">JWE Algorithm Header</a>
*/
public static final String ALGORITHM = "alg";
String ALGORITHM = "alg";
/** JWT {@code Compression Algorithm} header parameter name: <code>"zip"</code> */
public static final String COMPRESSION_ALGORITHM = "zip";
String COMPRESSION_ALGORITHM = "zip";
/** JJWT legacy/deprecated compression algorithm header parameter name: <code>"calg"</code>
* @deprecated use {@link #COMPRESSION_ALGORITHM} instead. */
@Deprecated
public static final String DEPRECATED_COMPRESSION_ALGORITHM = "calg";
String DEPRECATED_COMPRESSION_ALGORITHM = "calg";
/**
* Returns the <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-5.1">
@ -123,13 +123,15 @@ public interface Header<T extends Header<T>> extends Map<String,Object> {
* <li>If the JWT is a Signed JWT (a JWS), the <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1">
* <code>alg</code></a> (Algorithm) header parameter identifies the cryptographic algorithm used to secure the
* JWS. Consider using
* {@link io.jsonwebtoken.security.SignatureAlgorithms#forName(String) SignatureAlgorithms.forName} to
* convert this string value to a type-safe enum instance.</li>
* {@link io.jsonwebtoken.security.SignatureAlgorithms#findById(String) SignatureAlgorithms.findById} to
* convert this string value to a type-safe SignatureAlgorithm instance.</li>
* <li>If the JWT is an Encrypted JWT (a JWE), the
* <a href="https://tools.ietf.org/html/rfc7516#section-4.1.1"><code>alg</code></a> (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 <code>alg</code> value does not represent a
* supported algorithm, or if the recipient does not have a key that can be used with that algorithm</li>
* supported algorithm, or if the recipient does not have a key that can be used with that algorithm. Consider
* using {@link io.jsonwebtoken.security.KeyAlgorithms#findById(String) KeyAlgorithms.findById} to convert this
* string value to a type-safe KeyAlgorithm instance.</li>
* </ul>
*
* @return the {@code alg} header value or {@code null} if not present. This will always be
@ -144,14 +146,12 @@ public interface Header<T extends Header<T>> extends Map<String,Object> {
* <ul>
* <li>If the JWT is a Signed JWT (a JWS), the <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1">
* <code>alg</code></a> (Algorithm) header parameter identifies the cryptographic algorithm used to secure the
* JWS. Consider using
* {@link io.jsonwebtoken.security.SignatureAlgorithms#forName(String) SignatureAlgorithms.forName} to
* convert this string value to a type-safe enum instance.</li>
* JWS.</li>
* <li>If the JWT is an Encrypted JWT (a JWE), the
* <a href="https://tools.ietf.org/html/rfc7516#section-4.1.1"><code>alg</code></a> (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 <code>alg</code> value does not represent a
* supported algorithm, or if the recipient does not have a key that can be used with that algorithm</li>
* supported algorithm, or if the recipient does not have a key that can be used with that algorithm.</li>
* </ul>
*
* @param alg the {@code alg} header value

View File

@ -1,4 +1,4 @@
package io.jsonwebtoken.security;
package io.jsonwebtoken;
/**
* @since JJWT_RELEASE_VERSION

View File

@ -25,47 +25,47 @@ public interface JwsHeader extends Header<JwsHeader> {
/**
* JWS <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1">Algorithm Header</a> name: the string literal <b><code>alg</code></b>
*/
public static final String ALGORITHM = "alg";
String ALGORITHM = "alg";
/**
* JWS <a href="https://tools.ietf.org/html/rfc7515#section-4.1.2">JWK Set URL Header</a> name: the string literal <b><code>jku</code></b>
*/
public static final String JWK_SET_URL = "jku";
String JWK_SET_URL = "jku";
/**
* JWS <a href="https://tools.ietf.org/html/rfc7515#section-4.1.3">JSON Web Key Header</a> name: the string literal <b><code>jwk</code></b>
*/
public static final String JSON_WEB_KEY = "jwk";
String JSON_WEB_KEY = "jwk";
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.4">Key ID Header</a> name: the string literal <b><code>kid</code></b>
*/
public static final String KEY_ID = "kid";
String KEY_ID = "kid";
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.5">X.509 URL Header</a> name: the string literal <b><code>x5u</code></b>
*/
public static final String X509_URL = "x5u";
String X509_URL = "x5u";
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.6">X.509 Certificate Chain Header</a> name: the string literal <b><code>x5c</code></b>
*/
public static final String X509_CERT_CHAIN = "x5c";
String X509_CERT_CHAIN = "x5c";
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.7">X.509 Certificate SHA-1 Thumbprint Header</a> name: the string literal <b><code>x5t</code></b>
*/
public static final String X509_CERT_SHA1_THUMBPRINT = "x5t";
String X509_CERT_SHA1_THUMBPRINT = "x5t";
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.8">X.509 Certificate SHA-256 Thumbprint Header</a> name: the string literal <b><code>x5t#S256</code></b>
*/
public static final String X509_CERT_SHA256_THUMBPRINT = "x5t#S256";
String X509_CERT_SHA256_THUMBPRINT = "x5t#S256";
/**
* JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.11">Critical Header</a> name: the string literal <b><code>crit</code></b>
*/
public static final String CRITICAL = "crit";
String CRITICAL = "crit";
/**
* Returns the JWS <a href="https://tools.ietf.org/html/rfc7516#section-4.1.4">

View File

@ -22,7 +22,7 @@ package io.jsonwebtoken;
*
* @since 0.1
*/
public interface Jwt<H extends Header, B> {
public interface Jwt<H extends Header<H>, B> {
/**
* Returns the JWT {@link Header} or {@code null} if not present.

View File

@ -31,7 +31,7 @@ public interface JwtHandler<T> {
* @param jwt the parsed plaintext JWT
* @return any object to be used after inspecting the JWT, or {@code null} if no return value is necessary.
*/
T onPlaintextJwt(Jwt<Header, String> jwt);
T onPlaintextJwt(Jwt<?, String> jwt);
/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is
@ -40,7 +40,7 @@ public interface JwtHandler<T> {
* @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.
*/
T onClaimsJwt(Jwt<Header, Claims> jwt);
T onClaimsJwt(Jwt<?, Claims> jwt);
/**
* This method is invoked when a {@link io.jsonwebtoken.JwtParser JwtParser} determines that the parsed JWT is

View File

@ -31,12 +31,12 @@ package io.jsonwebtoken;
public class JwtHandlerAdapter<T> implements JwtHandler<T> {
@Override
public T onPlaintextJwt(Jwt<Header, String> jwt) {
public T onPlaintextJwt(Jwt<?, String> jwt) {
throw new UnsupportedJwtException("Unsigned plaintext JWTs are not supported.");
}
@Override
public T onClaimsJwt(Jwt<Header, Claims> jwt) {
public T onClaimsJwt(Jwt<?, Claims> jwt) {
throw new UnsupportedJwtException("Unsigned Claims JWTs are not supported.");
}

View File

@ -319,7 +319,8 @@ public interface JwtParser {
* immutable JwtParser.
* <p><b>NOTE: this method will be removed before version 1.0</b>
*/
@Deprecated
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated // TODO: remove for 1.0
JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver);
/**

View File

@ -17,11 +17,13 @@ package io.jsonwebtoken;
import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.security.KeyResolver;
import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.SignatureAlgorithm;
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
import java.security.Key;
import java.security.Provider;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
@ -54,7 +56,7 @@ public interface JwtParserBuilder {
* 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 {@code jti} value
* @return the parser builder for method chaining.
* @see MissingClaimException
* @see IncorrectClaimException
@ -66,7 +68,7 @@ public interface JwtParserBuilder {
* 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 subject value
* @return the parser builder for method chaining.
* @see MissingClaimException
* @see IncorrectClaimException
@ -78,7 +80,7 @@ public interface JwtParserBuilder {
* value does not equal 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 audience value
* @return the parser builder for method chaining.
* @see MissingClaimException
* @see IncorrectClaimException
@ -90,7 +92,7 @@ public interface JwtParserBuilder {
* 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 issuer value
* @return the parser builder for method chaining.
* @see MissingClaimException
* @see IncorrectClaimException
@ -102,7 +104,7 @@ public interface JwtParserBuilder {
* 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 issuedAt value
* @return the parser builder for method chaining.
* @see MissingClaimException
* @see IncorrectClaimException
@ -114,7 +116,7 @@ public interface JwtParserBuilder {
* 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 expiration value
* @return the parser builder for method chaining.
* @see MissingClaimException
* @see IncorrectClaimException
@ -126,7 +128,7 @@ public interface JwtParserBuilder {
* 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 not before {@code nbf} value.
* @return the parser builder for method chaining
* @see MissingClaimException
* @see IncorrectClaimException
@ -138,8 +140,8 @@ public interface JwtParserBuilder {
* 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 the claim to require
* @param value the value the claim value must equal
* @return the parser builder for method chaining.
* @see MissingClaimException
* @see IncorrectClaimException
@ -234,13 +236,13 @@ public interface JwtParserBuilder {
* <p/>
* <p>If there is any chance that the parser will encounter JWSs
* that need different signature verification keys based on the JWS being parsed, it is strongly
* recommended to configure your own {@link KeyResolver} via the
* {@link #setKeyResolver(KeyResolver) setKeyResolver} method instead of using this one.</p>
* recommended to configure your own {@link Locator Locator<?,Key>} via the
* {@link #setKeyLocator(Locator) setKeyLocator} method instead of using this one.</p>
* <p/>
* <p>Calling this method overrides any previously set signature verification key.</p>
*
* @param key the algorithm-specific signature verification key to use to verify all encountered JWS digital
* signature.
* signatures.
* @return the parser builder for method chaining.
*/
JwtParserBuilder setSigningKey(Key key);
@ -256,7 +258,7 @@ public interface JwtParserBuilder {
* <p/>
* <p>If there is any chance that the parser will encounter JWEs
* that need different decryption keys based on the JWE being parsed, it is strongly recommended to configure
* your own {@link KeyResolver} via the {@link #setKeyResolver(KeyResolver) setKeyResolver} method instead of
* your own {@link Locator Locator<?,Key>} via the {@link #setKeyLocator(Locator) setKeyLocator} method instead of
* using this one.</p>
* <p/>
* <p>Calling this method overrides any previously set decryption key.</p>
@ -266,22 +268,22 @@ public interface JwtParserBuilder {
JwtParserBuilder decryptWith(Key key);
/**
* Sets the {@link KeyResolver} used to acquire any signature verification or decryption key needed during parsing.
* Sets the {@link Locator} used to acquire any signature verification or decryption key needed during parsing.
* <ul>
* <li>If the parsed String is a JWS, the {@code KeyResolver} will be called to find the appropriate key
* <li>If the parsed String is a JWS, the {@code Locator} will be called to find the appropriate key
* necessary to verify the JWS signature.</li>
* <li>If the parsed String is a JWE, it will be called to find the appropriate decryption key.</li>
* </ul>
* <p>
* <p>Specifying a {@code KeyResolver} is necessary when the signing or decryption key is not already known before
* <p>Specifying a key {@code Locator} is necessary when the signing 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 resolver, the JwtParser will then either
* 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:</p>
* <p>
* <pre>
* Jws&lt;Claims&gt; jws = Jwts.parser().setKeyResolver(new KeyResolver() {
* Jws&lt;Claims&gt; jws = Jwts.parser().setKeyLocator(new Locator&lt;Header,Key&gt;() {
* &#64;Override
* public Key resolveKey(Header header) {
* public Key locate(Header header) {
* if (header instanceof JwsHeader) {
* return getSignatureVerificationKey((JwsHeader)header); // implement me
* } else {
@ -293,13 +295,19 @@ public interface JwtParserBuilder {
* <p>
* <p>A {@code KeyResolver} is invoked once during parsing before performing decryption or signature verification.</p>
*
* @param keyResolver the key resolver used to retrieve decryption or signature verification keys.
* @param keyLocator the locator used to retrieve decryption or signature verification keys.
* @return the parser builder for method chaining.
* @since JJWT_RELEASE_VERSION
*/
JwtParserBuilder setKeyResolver(KeyResolver keyResolver);
JwtParserBuilder setKeyLocator(Locator<? extends Header<?>, Key> keyLocator);
/**
* <h4>Deprecation Notice</h4>
* <p>This method has been deprecated as of JJWT version JJWT_RELEASE_VERSION because it only supports key location
* for JWSs (signed JWTs) instead of both signed (JWS) and encrypted (JWE) scenarios. Use the
* {@link #setKeyLocator(Locator) setKeyLocator} 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.</p>
* <h4>Previous Documentation</h4>
* Sets the {@link SigningKeyResolver} used to acquire the <code>signing key</code> 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.
* <p>
@ -323,11 +331,20 @@ public interface JwtParserBuilder {
* <p>This method should only be used if a signing key is not provided by the other {@code setSigningKey*} builder
* methods.</p>
*
* @deprecated since JJWT_RELEASE_VERSION
* @param signingKeyResolver the signing key resolver used to retrieve the signing key.
* @return the parser builder for method chaining.
*/
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated
JwtParserBuilder setSigningKeyResolver(SigningKeyResolver signingKeyResolver);
JwtParserBuilder addEncryptionAlgorithms(Collection<SymmetricAeadAlgorithm> encAlgs);
JwtParserBuilder addSignatureAlgorithms(Collection<SignatureAlgorithm<?,?>> sigAlgs);
JwtParserBuilder addKeyAlgorithms(Collection<KeyAlgorithm<?,?>> keyAlgs);
/**
* 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.

View File

@ -0,0 +1,6 @@
package io.jsonwebtoken;
public interface Locator<H extends Header<H>, R> {
R locate(H header);
}

View File

@ -44,11 +44,11 @@ import java.security.Key;
* the {@link io.jsonwebtoken.SigningKeyResolverAdapter} and overriding only the method you need to support instead of
* implementing this interface directly.</p>
*
* @see io.jsonwebtoken.SigningKeyResolverAdapter
* @since 0.4
* @deprecated since JJWT_RELEASE_VERSION. Implement {@link io.jsonwebtoken.security.KeyResolver KeyResolver} instead.
* @see io.jsonwebtoken.security.KeyResolver
* @deprecated since JJWT_RELEASE_VERSION. Implement {@link io.jsonwebtoken.Locator Locator<?, Key>} instead.
* @see io.jsonwebtoken.JwtParserBuilder#setKeyLocator(Locator)
*/
@Deprecated
public interface SigningKeyResolver {
/**

View File

@ -2,12 +2,8 @@ package io.jsonwebtoken.security;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Classes;
import io.jsonwebtoken.lang.Maps;
import io.jsonwebtoken.lang.Strings;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
/**
* @since JJWT_RELEASE_VERSION
@ -18,12 +14,30 @@ public final class EncryptionAlgorithms {
private EncryptionAlgorithms() {
}
private static final String HMAC = "io.jsonwebtoken.impl.security.HmacAesAeadAlgorithm";
private static final String GCM = "io.jsonwebtoken.impl.security.GcmAesAeadAlgorithm";
private static final Class<?>[] CTOR_ARG_TYPES = new Class[]{int.class};
private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.EncryptionAlgorithmsBridge";
private static final Class<?>[] ID_ARG_TYPES = new Class[]{String.class};
private static SymmetricAeadAlgorithm alg(String fqcn, int keyLength) {
return Classes.newInstance(fqcn, CTOR_ARG_TYPES, keyLength);
public static Collection<SymmetricAeadAlgorithm> values() {
return Classes.invokeStatic(BRIDGE_CLASSNAME, "values", null, (Object[]) null);
}
/**
* Returns the JWE Encryption Algorithm with the specified
* <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-5.1">{@code enc} algorithm identifier</a> or
* {@code null} if an algorithm for the specified {@code id} cannot be found.
*
* @param id a JWE standard {@code enc} algorithm identifier
* @return the associated Encryption Algorithm instance or {@code null} otherwise.
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-5.1">RFC 7518, Section 5.1</a>
*/
public static SymmetricAeadAlgorithm findById(String id) {
Assert.hasText(id, "id cannot be null or empty.");
return Classes.invokeStatic(BRIDGE_CLASSNAME, "findById", ID_ARG_TYPES, id);
}
private static SymmetricAeadAlgorithm forId(String id) {
Assert.hasText(id, "id cannot be null or empty.");
return Classes.invokeStatic(BRIDGE_CLASSNAME, "forId", ID_ARG_TYPES, id);
}
/**
@ -31,64 +45,40 @@ public final class EncryptionAlgorithms {
* <a href="https://tools.ietf.org/html/rfc7518#section-5.2.3">RFC 7518, Section 5.2.3</a>. This algorithm
* requires a 256 bit (32 byte) key.
*/
public static final SymmetricAeadAlgorithm A128CBC_HS256 = alg(HMAC, 128);
public static final SymmetricAeadAlgorithm A128CBC_HS256 = forId("A128CBC-HS256");
/**
* AES_192_CBC_HMAC_SHA_384 authenticated encryption algorithm, as defined by
* <a href="https://tools.ietf.org/html/rfc7518#section-5.2.4">RFC 7518, Section 5.2.4</a>. This algorithm
* requires a 384 bit (48 byte) key.
*/
public static final SymmetricAeadAlgorithm A192CBC_HS384 = alg(HMAC, 192);
public static final SymmetricAeadAlgorithm A192CBC_HS384 = forId("A192CBC-HS384");
/**
* AES_256_CBC_HMAC_SHA_512 authenticated encryption algorithm, as defined by
* <a href="https://tools.ietf.org/html/rfc7518#section-5.2.5">RFC 7518, Section 5.2.5</a>. This algorithm
* requires a 512 bit (64 byte) key.
*/
public static final SymmetricAeadAlgorithm A256CBC_HS512 = alg(HMAC, 256);
public static final SymmetricAeadAlgorithm A256CBC_HS512 = forId("A256CBC-HS512");
/**
* &quot;AES GCM using 128-bit key&quot; as defined by
* <a href="https://tools.ietf.org/html/rfc7518#section-5.3">RFC 7518, Section 5.3</a>. This algorithm requires
* a 128 bit (16 byte) key.
*/
public static final SymmetricAeadAlgorithm A128GCM = alg(GCM, 128);
public static final SymmetricAeadAlgorithm A128GCM = forId("A128GCM");
/**
* &quot;AES GCM using 192-bit key&quot; as defined by
* <a href="https://tools.ietf.org/html/rfc7518#section-5.3">RFC 7518, Section 5.3</a>. This algorithm requires
* a 192 bit (24 byte) key.
*/
public static final SymmetricAeadAlgorithm A192GCM = alg(GCM, 192);
public static final SymmetricAeadAlgorithm A192GCM = forId("A192GCM");
/**
* &quot;AES GCM using 256-bit key&quot; as defined by
* <a href="https://tools.ietf.org/html/rfc7518#section-5.3">RFC 7518, Section 5.3</a>. This algorithm requires
* a 256 bit (32 byte) key.
*/
public static final SymmetricAeadAlgorithm A256GCM = alg(GCM, 256);
private static final Map<String, SymmetricAeadAlgorithm> SYMMETRIC_VALUES_BY_NAME = Collections.unmodifiableMap(Maps
.of(A128CBC_HS256.getId(), A128CBC_HS256)
.and(A192CBC_HS384.getId(), A192CBC_HS384)
.and(A256CBC_HS512.getId(), A256CBC_HS512)
.and(A128GCM.getId(), A128GCM)
.and(A192GCM.getId(), A192GCM)
.and(A256GCM.getId(), A256GCM)
.build());
public static SymmetricAeadAlgorithm forName(String name) {
Assert.hasText(name, "name cannot be null or empty.");
SymmetricAeadAlgorithm alg = SYMMETRIC_VALUES_BY_NAME.get(name.toUpperCase());
if (alg == null) {
String msg = "'" + name + "' is not a JWE specification standard name. The standard names are: " +
Strings.collectionToCommaDelimitedString(SYMMETRIC_VALUES_BY_NAME.keySet());
throw new IllegalArgumentException(msg);
}
return alg;
}
public static Collection<SymmetricAeadAlgorithm> values() {
return SYMMETRIC_VALUES_BY_NAME.values();
}
public static final SymmetricAeadAlgorithm A256GCM = forId("A256GCM");
}

View File

@ -1,4 +1,6 @@
package io.jsonwebtoken.security;
import io.jsonwebtoken.Identifiable;
public interface HashAlgorithm extends Identifiable {
}

View File

@ -1,5 +1,7 @@
package io.jsonwebtoken.security;
import io.jsonwebtoken.Identifiable;
import java.security.Key;
import java.util.Map;
import java.util.Set;

View File

@ -1,5 +1,7 @@
package io.jsonwebtoken.security;
import io.jsonwebtoken.Identifiable;
import javax.crypto.SecretKey;
import java.security.Key;

View File

@ -7,9 +7,6 @@ import javax.crypto.SecretKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @since JJWT_RELEASE_VERSION
@ -20,62 +17,45 @@ public final class KeyAlgorithms {
private KeyAlgorithms() {
}
private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.KeyAlgorithms";
private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.KeyAlgorithmsBridge";
private static final Class<?>[] ID_ARG_TYPES = new Class[]{String.class};
private static <T> T lookup(String methodName) {
return Classes.invokeStatic(BRIDGE_CLASSNAME, methodName, null, (Object[]) null);
}
public static final KeyAlgorithm<SecretKey, SecretKey> DIRECT = lookup("direct");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128KW = lookup("a128kw");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192KW = lookup("a192kw");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256KW = lookup("a256kw");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128GCMKW = lookup("a128gcmkw");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192GCMKW = lookup("a192gcmkw");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256GCMKW = lookup("a256gcmkw");
public static final EncryptedKeyAlgorithm<RSAPublicKey, RSAPrivateKey> RSA1_5 = lookup("rsa1_5");
public static final EncryptedKeyAlgorithm<RSAPublicKey, RSAPrivateKey> RSA_OAEP = lookup("rsaOaep");
public static final EncryptedKeyAlgorithm<RSAPublicKey, RSAPrivateKey> RSA_OAEP_256 = lookup("rsaOaep256");
private static Map<String,KeyAlgorithm<?,?>> toMap(KeyAlgorithm<?,?>... algs) {
Map<String, KeyAlgorithm<?,?>> m = new LinkedHashMap<>();
for (KeyAlgorithm<?,?> alg : algs) {
m.put(alg.getId(), alg);
}
return Collections.unmodifiableMap(m);
}
private static final Map<String,KeyAlgorithm<?,?>> STANDARD_ALGORITHMS = toMap(
DIRECT, A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW, A256GCMKW, RSA1_5, RSA_OAEP, RSA_OAEP_256
);
public static Collection<? extends KeyAlgorithm<?,?>> values() {
return STANDARD_ALGORITHMS.values();
public static Collection<SymmetricAeadAlgorithm> values() {
return Classes.invokeStatic(BRIDGE_CLASSNAME, "values", null, (Object[]) null);
}
/**
* Looks up and returns the corresponding JWA standard {@code KeyAlgorithm} instance based on a
* case-<em>insensitive</em> name comparison.
* Returns the JWE KeyAlgorithm with the specified
* <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-4.1">{@code alg} key algorithm identifier</a> or
* {@code null} if an algorithm for the specified {@code id} cannot be found.
*
* @param id The case-insensitive identifier of the JWA standard {@code KeyAlgorithm} instance to return
* @return the corresponding JWA standard {@code KeyAlgorithm} enum instance based on a
* case-<em>insensitive</em> name comparison.
* @throws SignatureException if the specified value does not match any JWA standard {@code KeyAlgorithm} name.
* @param id a JWE standard {@code alg} key algorithm identifier
* @return the associated KeyAlgorithm instance or {@code null} otherwise.
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7518#section-4.1">RFC 7518, Section 4.1</a>
*/
public static KeyAlgorithm<?,?> forName(String id) {
Assert.hasText(id, "id argument cannot be null or empty.");
//try constant time lookup first. This will satisfy 99% of invocations:
KeyAlgorithm<?,?> alg = STANDARD_ALGORITHMS.get(id);
if (alg != null) {
return alg;
public static KeyAlgorithm<?, ?> findById(String id) {
Assert.hasText(id, "id cannot be null or empty.");
return Classes.invokeStatic(BRIDGE_CLASSNAME, "findById", ID_ARG_TYPES, id);
}
//fall back to case-insensitive lookup:
for (KeyAlgorithm<?,?> kalg : STANDARD_ALGORITHMS.values()) {
if (id.equalsIgnoreCase(kalg.getId())) {
return kalg;
public static KeyAlgorithm<?, ?> forId(String id) {
return forId0(id);
}
// do not change this visibility. Raw type method signature not be publicly exposed
private static <T> T forId0(String id) {
Assert.hasText(id, "id cannot be null or empty.");
return Classes.invokeStatic(BRIDGE_CLASSNAME, "forId", ID_ARG_TYPES, id);
}
// still no result - error:
throw new IllegalArgumentException("Unrecognized key algorithm id '" + id + "'");
}
public static final KeyAlgorithm<SecretKey, SecretKey> DIRECT = forId0("dir");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128KW = forId0("A128KW");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192KW = forId0("A192KW");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256KW = forId0("A256KW");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A128GCMKW = forId0("A128GCMKW");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A192GCMKW = forId0("A192GCMKW");
public static final EncryptedKeyAlgorithm<SecretKey, SecretKey> A256GCMKW = forId0("A256GCMKW");
public static final EncryptedKeyAlgorithm<RSAPublicKey, RSAPrivateKey> RSA1_5 = forId0("RSA1_5");
public static final EncryptedKeyAlgorithm<RSAPublicKey, RSAPrivateKey> RSA_OAEP = forId0("RSA-OAEP");
public static final EncryptedKeyAlgorithm<RSAPublicKey, RSAPrivateKey> RSA_OAEP_256 = forId0("RSA-OAEP-256");
}

View File

@ -115,7 +115,7 @@ public final class Keys {
@Deprecated
public static SecretKey secretKeyFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
SignatureAlgorithm<?,?> salg = SignatureAlgorithms.forName(alg.name());
SignatureAlgorithm<?,?> salg = SignatureAlgorithms.forId(alg.name());
if (!(salg instanceof SecretKeySignatureAlgorithm)) {
String msg = "The " + alg.name() + " algorithm does not support shared secret keys.";
throw new IllegalArgumentException(msg);
@ -210,7 +210,7 @@ public final class Keys {
@Deprecated
public static KeyPair keyPairFor(io.jsonwebtoken.SignatureAlgorithm alg) throws IllegalArgumentException {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
SignatureAlgorithm<?,?> salg = SignatureAlgorithms.forName(alg.name());
SignatureAlgorithm<?,?> salg = SignatureAlgorithms.forId(alg.name());
if (!(salg instanceof AsymmetricKeySignatureAlgorithm)) {
String msg = "The " + alg.name() + " algorithm does not support Key Pairs.";
throw new IllegalArgumentException(msg);

View File

@ -0,0 +1,30 @@
package io.jsonwebtoken.security;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JweHeader;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Locator;
import io.jsonwebtoken.lang.Assert;
public abstract class LocatorAdapter<H extends Header<H>, R> implements Locator<H, R> {
@Override
public final R locate(H header) {
Assert.notNull(header, "Header cannot be null.");
if (header instanceof JwsHeader) {
return locate((JwsHeader) header);
} else if (header instanceof JweHeader) {
return locate((JweHeader) header);
}
String msg = "Unrecognized header type: " + header.getClass().getName();
throw new IllegalStateException(msg);
}
protected R locate(JweHeader header) {
return null;
}
protected R locate(JwsHeader header) {
return null;
}
}

View File

@ -1,5 +1,7 @@
package io.jsonwebtoken.security;
import io.jsonwebtoken.Identifiable;
import java.security.Key;
/**

View File

@ -9,9 +9,6 @@ import java.security.PrivateKey;
import java.security.interfaces.ECKey;
import java.security.interfaces.RSAKey;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @since JJWT_RELEASE_VERSION
@ -23,89 +20,40 @@ public final class SignatureAlgorithms {
private SignatureAlgorithms() {
}
private static final String HMAC = "io.jsonwebtoken.impl.security.MacSignatureAlgorithm";
private static final Class<?>[] HMAC_ARGS = new Class[]{String.class, String.class, int.class};
private static final String BRIDGE_CLASSNAME = "io.jsonwebtoken.impl.security.SignatureAlgorithmsBridge";
private static final Class<?>[] ID_ARG_TYPES = new Class[]{String.class};
private static final String RSA = "io.jsonwebtoken.impl.security.DefaultRsaSignatureAlgorithm";
private static final Class<?>[] RSA_ARGS = new Class[]{String.class, String.class, int.class};
private static final Class<?>[] PSS_ARGS = new Class[]{String.class, String.class, int.class, int.class};
private static final String EC = "io.jsonwebtoken.impl.security.DefaultEllipticCurveSignatureAlgorithm";
private static final Class<?>[] EC_ARGS = new Class[]{String.class, String.class, String.class, int.class, int.class};
private static SecretKeySignatureAlgorithm hmacSha(int minKeyLength) {
return Classes.newInstance(HMAC, HMAC_ARGS, "HS" + minKeyLength, "HmacSHA" + minKeyLength, minKeyLength);
public static Collection<SignatureAlgorithm<?,?>> values() {
return Classes.invokeStatic(BRIDGE_CLASSNAME, "values", null, (Object[]) null);
}
private static RsaSignatureAlgorithm rsa(int digestLength, int preferredKeyLength) {
return Classes.newInstance(RSA, RSA_ARGS, "RS" + digestLength, "SHA" + digestLength + "withRSA", preferredKeyLength);
public static SignatureAlgorithm<?, ?> findById(String id) {
Assert.hasText(id, "id cannot be null or empty.");
return Classes.invokeStatic(BRIDGE_CLASSNAME, "findById", ID_ARG_TYPES, id);
}
private static RsaSignatureAlgorithm pss(int digestLength, int preferredKeyLength) {
return Classes.newInstance(RSA, PSS_ARGS, "PS" + digestLength, "RSASSA-PSS", preferredKeyLength, digestLength);
public static SignatureAlgorithm<?,?> forId(String id) {
return forId0(id);
}
private static EllipticCurveSignatureAlgorithm ec(int keySize, int signatureLength) {
int shaSize = keySize == 521 ? 512 : keySize;
return Classes.newInstance(EC, EC_ARGS, "ES" + shaSize, "SHA" + shaSize + "withECDSA", "secp" + keySize + "r1", keySize, signatureLength);
static <T> T forId0(String id) {
Assert.hasText(id, "id cannot be null or empty.");
return Classes.invokeStatic(BRIDGE_CLASSNAME, "forId", ID_ARG_TYPES, id);
}
public static final SignatureAlgorithm<Key, Key> NONE = Classes.newInstance("io.jsonwebtoken.impl.security.NoneSignatureAlgorithm");
public static final SecretKeySignatureAlgorithm HS256 = hmacSha(256);
public static final SecretKeySignatureAlgorithm HS384 = hmacSha(384);
public static final SecretKeySignatureAlgorithm HS512 = hmacSha(512);
public static final RsaSignatureAlgorithm RS256 = rsa(256, 2048);
public static final RsaSignatureAlgorithm RS384 = rsa(384, 3072);
public static final RsaSignatureAlgorithm RS512 = rsa(512, 4096);
public static final RsaSignatureAlgorithm PS256 = pss(256, 2048);
public static final RsaSignatureAlgorithm PS384 = pss(384, 3072);
public static final RsaSignatureAlgorithm PS512 = pss(512, 4096);
public static final EllipticCurveSignatureAlgorithm ES256 = ec(256, 64);
public static final EllipticCurveSignatureAlgorithm ES384 = ec(384, 96);
public static final EllipticCurveSignatureAlgorithm ES512 = ec(521, 132);
private static Map<String, SignatureAlgorithm<?,?>> toMap(SignatureAlgorithm<?,?>... algs) {
Map<String, SignatureAlgorithm<?,?>> m = new LinkedHashMap<>();
for (SignatureAlgorithm<?,?> alg : algs) {
m.put(alg.getId(), alg);
}
return Collections.unmodifiableMap(m);
}
private static final Map<String, SignatureAlgorithm<?,?>> STANDARD_ALGORITHMS = toMap(
NONE, HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512
);
public static Collection<? extends SignatureAlgorithm<?,?>> values() {
return STANDARD_ALGORITHMS.values();
}
/**
* Looks up and returns the corresponding JWA standard {@code SignatureAlgorithm} instance based on a
* case-<em>insensitive</em> name comparison.
*
* @param name The case-insensitive name of the JWA standard {@code SignatureAlgorithm} instance to return
* @return the corresponding JWA standard {@code SignatureAlgorithm} enum instance based on a
* case-<em>insensitive</em> name comparison.
* @throws SignatureException if the specified value does not match any JWA standard {@code SignatureAlgorithm}
* name.
*/
public static SignatureAlgorithm<?,?> forName(String name) {
Assert.notNull(name, "name argument cannot be null.");
//try constant time lookup first. This will satisfy 99% of invocations:
SignatureAlgorithm alg = STANDARD_ALGORITHMS.get(name);
if (alg != null) {
return alg;
}
//fall back to case-insensitive lookup:
for (SignatureAlgorithm salg : STANDARD_ALGORITHMS.values()) {
if (name.equalsIgnoreCase(salg.getId())) {
return salg;
}
}
// still no result - error:
throw new SignatureException("Unsupported signature algorithm '" + name + "'");
}
public static final SignatureAlgorithm<Key, Key> NONE = forId0("none");
public static final SecretKeySignatureAlgorithm HS256 = forId0("HS256");
public static final SecretKeySignatureAlgorithm HS384 = forId0("HS384");
public static final SecretKeySignatureAlgorithm HS512 = forId0("HS512");
public static final RsaSignatureAlgorithm RS256 = forId0("RS256");
public static final RsaSignatureAlgorithm RS384 = forId0("RS384");
public static final RsaSignatureAlgorithm RS512 = forId0("RS512");
public static final RsaSignatureAlgorithm PS256 = forId0("PS256");
public static final RsaSignatureAlgorithm PS384 = forId0("PS384");
public static final RsaSignatureAlgorithm PS512 = forId0("PS512");
public static final EllipticCurveSignatureAlgorithm ES256 = forId0("ES256");
public static final EllipticCurveSignatureAlgorithm ES384 = forId0("ES384");
public static final EllipticCurveSignatureAlgorithm ES512 = forId0("ES512");
/**
* Returns the recommended signature algorithm to be used with the specified key according to the following
@ -217,9 +165,9 @@ public final class SignatureAlgorithms {
* @throws InvalidKeyException for any key that does not match the heuristics and requirements documented above,
* since that inevitably means the Key is either insufficient or explicitly disallowed by the JWT specification.
*/
public static SignatureAlgorithm<?,?> forSigningKey(Key key) {
public static SignatureAlgorithm<?, ?> forSigningKey(Key key) {
@SuppressWarnings("deprecation")
io.jsonwebtoken.SignatureAlgorithm alg = io.jsonwebtoken.SignatureAlgorithm.forSigningKey(key);
return forName(alg.getValue());
return forId(alg.getValue());
}
}

View File

@ -1,5 +1,7 @@
package io.jsonwebtoken.security;
import io.jsonwebtoken.Identifiable;
/**
* @since JJWT_RELEASE_VERSION
*/

View File

@ -114,7 +114,7 @@ class KeysTest {
def key = createMock(SecretKey)
def salg = createMock(SecretKeySignatureAlgorithm)
expect(SignatureAlgorithms.forName(eq(name))).andReturn(salg)
expect(SignatureAlgorithms.forId(eq(name))).andReturn(salg)
expect(salg.generateKey()).andReturn(key)
replay SignatureAlgorithms, salg, key
@ -125,7 +125,7 @@ class KeysTest {
} else {
def salg = name == 'NONE' ? createMock(io.jsonwebtoken.security.SignatureAlgorithm) : createMock(AsymmetricKeySignatureAlgorithm)
expect(SignatureAlgorithms.forName(eq(name))).andReturn(salg)
expect(SignatureAlgorithms.forId(eq(name))).andReturn(salg)
replay SignatureAlgorithms, salg
try {
Keys.secretKeyFor(alg)
@ -149,7 +149,7 @@ class KeysTest {
if (name.equals('NONE') || name.startsWith('H')) {
def salg = name == 'NONE' ? createMock(io.jsonwebtoken.security.SignatureAlgorithm) : createMock(SecretKeySignatureAlgorithm)
expect(SignatureAlgorithms.forName(eq(name))).andReturn(salg)
expect(SignatureAlgorithms.forId(eq(name))).andReturn(salg)
replay SignatureAlgorithms, salg
try {
Keys.keyPairFor(alg)
@ -163,7 +163,7 @@ class KeysTest {
def pair = createMock(KeyPair)
def salg = createMock(AsymmetricKeySignatureAlgorithm)
expect(SignatureAlgorithms.forName(eq(name))).andReturn(salg)
expect(SignatureAlgorithms.forId(eq(name))).andReturn(salg)
expect(salg.generateKeyPair()).andReturn(pair)
replay SignatureAlgorithms, pair, salg

View File

@ -0,0 +1,21 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.CompressionCodecResolver;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.impl.lang.Function;
import io.jsonwebtoken.lang.Assert;
public class CompressionCodecLocator<H extends Header<H>> implements Function<H, CompressionCodec> {
private final CompressionCodecResolver resolver;
public CompressionCodecLocator(CompressionCodecResolver resolver) {
this.resolver = Assert.notNull(resolver, "CompressionCodecResolver cannot be null.");
}
@Override
public CompressionCodec apply(H header) {
return resolver.resolveCompressionCodec(header);
}
}

View File

@ -20,7 +20,6 @@ import io.jsonwebtoken.lang.Strings;
import java.util.Map;
@SuppressWarnings("unchecked")
public class DefaultHeader<T extends Header<T>> extends JwtMap implements Header<T> {
public DefaultHeader() {
@ -31,6 +30,11 @@ public class DefaultHeader<T extends Header<T>> extends JwtMap implements Header
super(map);
}
@SuppressWarnings("unchecked")
protected T tthis() {
return (T)this;
}
@Override
public String getType() {
return getString(TYPE);
@ -39,7 +43,7 @@ public class DefaultHeader<T extends Header<T>> extends JwtMap implements Header
@Override
public T setType(String typ) {
setValue(TYPE, typ);
return (T)this;
return tthis();
}
@Override
@ -50,7 +54,7 @@ public class DefaultHeader<T extends Header<T>> extends JwtMap implements Header
@Override
public T setContentType(String cty) {
setValue(CONTENT_TYPE, cty);
return (T)this;
return tthis();
}
@Override
@ -61,7 +65,7 @@ public class DefaultHeader<T extends Header<T>> extends JwtMap implements Header
@Override
public T setAlgorithm(String alg) {
setValue(ALGORITHM, alg);
return (T)this;
return tthis();
}
@SuppressWarnings("deprecation")
@ -78,7 +82,6 @@ public class DefaultHeader<T extends Header<T>> extends JwtMap implements Header
@Override
public T setCompressionAlgorithm(String compressionAlgorithm) {
setValue(COMPRESSION_ALGORITHM, compressionAlgorithm);
return (T) this;
return tthis();
}
}

View File

@ -67,8 +67,8 @@ public class DefaultJweBuilder extends DefaultJwtBuilder<JweBuilder> implements
@Override
public JweBuilder encryptWith(final SymmetricAeadAlgorithm enc) {
this.enc = Assert.notNull(enc, "EncryptionAlgorithm cannot be null.");
Assert.hasText(enc.getId(), "EncryptionAlgorithm id cannot be null or empty.");
this.enc = Assert.notNull(enc, "Encryption algorithm cannot be null.");
Assert.hasText(enc.getId(), "Encryption algorithm id cannot be null or empty.");
String encMsg = enc.getId() + " encryption failed.";
this.encFunction = wrap(encMsg, new Function<SymmetricAeadRequest, AeadResult>() {
@Override
@ -115,7 +115,7 @@ public class DefaultJweBuilder extends DefaultJwtBuilder<JweBuilder> implements
}
Assert.state(key != null, "Key is required.");
Assert.state(enc != null, "EncryptionAlgorithm is required.");
Assert.state(enc != null, "Encryption algorithm is required.");
assert alg != null : "KeyAlgorithm is required."; //always set by withKey calling withKeyFrom
if (this.serializer == null) { // try to find one based on the services available

View File

@ -1,12 +0,0 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.Jwe;
import io.jsonwebtoken.JwtException;
public class DefaultJweParser implements JweParser {
@Override
public Jwe<?> parse(String jwt) throws JwtException {
return null;
}
}

View File

@ -25,6 +25,7 @@ import io.jsonwebtoken.impl.lang.Function;
import io.jsonwebtoken.impl.lang.LegacyServices;
import io.jsonwebtoken.impl.lang.PropagatingExceptionFunction;
import io.jsonwebtoken.impl.security.DefaultSignatureRequest;
import io.jsonwebtoken.impl.security.SignatureAlgorithmsBridge;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.io.Encoders;
@ -130,9 +131,7 @@ public class DefaultJwtBuilder<T extends JwtBuilder<T>> implements JwtBuilder<T>
public T setHeaderParams(Map<String, Object> params) {
if (!Collections.isEmpty(params)) {
Header<?> header = ensureHeader();
for (Map.Entry<String, Object> entry : params.entrySet()) {
header.put(entry.getKey(), entry.getValue());
}
header.putAll(params);
}
return (T)this;
}
@ -153,32 +152,34 @@ public class DefaultJwtBuilder<T extends JwtBuilder<T>> implements JwtBuilder<T>
@Override
public T signWith(Key key) throws InvalidKeyException {
Assert.notNull(key, "Key argument cannot be null.");
SignatureAlgorithm<?,?> alg = SignatureAlgorithms.forSigningKey(key);
SignatureAlgorithm<Key,?> alg = (SignatureAlgorithm<Key,?>)SignatureAlgorithms.forSigningKey(key);
return signWith(key, alg);
}
@Override
public T signWith(Key key, final SignatureAlgorithm alg) throws InvalidKeyException {
public <K extends Key> T signWith(K key, final SignatureAlgorithm<K,?> alg) throws InvalidKeyException {
Assert.notNull(key, "Key argument cannot be null.");
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
this.key = key;
this.algorithm = alg;
this.algorithm = (SignatureAlgorithm<Key,?>)alg;
this.signFunction = new PropagatingExceptionFunction<>(SignatureException.class,
"Unable to compute " + alg.getId() + " signature.", new Function<SignatureRequest<Key>, byte[]>() {
@Override
public byte[] apply(SignatureRequest<Key> request) {
return alg.sign(request);
return algorithm.sign(request);
}
});
return (T)this;
}
@SuppressWarnings("deprecation") // TODO: remove method for 1.0
@Override
public T signWith(Key key, io.jsonwebtoken.SignatureAlgorithm alg) throws InvalidKeyException {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
return signWith(key, SignatureAlgorithms.forName(alg.getValue()));
return signWith(key, (SignatureAlgorithm<Key,?>)SignatureAlgorithmsBridge.forId(alg.getValue()));
}
@SuppressWarnings("deprecation") // TODO: remove method for 1.0
@Override
public T signWith(io.jsonwebtoken.SignatureAlgorithm alg, byte[] secretKeyBytes) throws InvalidKeyException {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
@ -188,6 +189,7 @@ public class DefaultJwtBuilder<T extends JwtBuilder<T>> implements JwtBuilder<T>
return signWith(key, alg);
}
@SuppressWarnings("deprecation") // TODO: remove method for 1.0
@Override
public T signWith(io.jsonwebtoken.SignatureAlgorithm alg, String base64EncodedSecretKey) throws InvalidKeyException {
Assert.hasText(base64EncodedSecretKey, "base64-encoded secret key cannot be null or empty.");
@ -196,6 +198,7 @@ public class DefaultJwtBuilder<T extends JwtBuilder<T>> implements JwtBuilder<T>
return signWith(alg, bytes);
}
@SuppressWarnings("deprecation") // TODO: remove method for 1.0
@Override
public T signWith(io.jsonwebtoken.SignatureAlgorithm alg, Key key) {
return signWith(key, alg);

View File

@ -22,6 +22,7 @@ import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.CompressionCodecResolver;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.IncorrectClaimException;
import io.jsonwebtoken.InvalidClaimException;
import io.jsonwebtoken.Jwe;
@ -39,13 +40,16 @@ import io.jsonwebtoken.PrematureJwtException;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
import io.jsonwebtoken.impl.lang.ConstantFunction;
import io.jsonwebtoken.impl.lang.Function;
import io.jsonwebtoken.impl.lang.LegacyServices;
import io.jsonwebtoken.impl.security.ConstantKeyLocator;
import io.jsonwebtoken.impl.security.DefaultAeadResult;
import io.jsonwebtoken.impl.security.DefaultKeyRequest;
import io.jsonwebtoken.impl.security.DefaultVerifySignatureRequest;
import io.jsonwebtoken.impl.security.DelegatingSigningKeyResolver;
import io.jsonwebtoken.impl.security.StaticKeyResolver;
import io.jsonwebtoken.impl.security.StaticSigningKeyResolver;
import io.jsonwebtoken.impl.security.EncryptionAlgorithmsBridge;
import io.jsonwebtoken.impl.security.KeyAlgorithmsBridge;
import io.jsonwebtoken.impl.security.SignatureAlgorithmsBridge;
import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.DecodingException;
@ -53,14 +57,12 @@ import io.jsonwebtoken.io.DeserializationException;
import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.lang.Arrays;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.DateFormats;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.EncryptionAlgorithms;
import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithms;
import io.jsonwebtoken.security.KeyRequest;
import io.jsonwebtoken.security.KeyResolver;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.PayloadSupplier;
import io.jsonwebtoken.security.SignatureAlgorithm;
@ -75,6 +77,7 @@ import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.Provider;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
@ -85,14 +88,62 @@ public class DefaultJwtParser implements JwtParser {
private static final JwtTokenizer jwtTokenizer = new JwtTokenizer();
// TODO: make the folling fields final for v1.0
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://datatracker.ietf.org/doc/html/rfc7515#section-4.1.1 for more information.";
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://datatracker.ietf.org/doc/html/rfc7516#section-4.1.1 for more information.";
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://datatracker.ietf.org/doc/html/rfc7516#section-4.1.2 for more information.";
private static <H extends Header<H>, R extends Identifiable> Function<H, R> backup(String id, String msg, Collection<R> extras) {
if (Collections.isEmpty(extras)) {
return ConstantFunction.forNull();
} else {
return new IdLocator<>(id, msg, new IdRegistry<>(extras), ConstantFunction.<H, R>forNull());
}
}
private static <H extends Header<H>, R extends Identifiable> Function<H, R> locFn(String id, String msg, Function<String, R> reg, Collection<R> extras) {
Function<H,R> backup = backup(id, msg, extras);
return new IdLocator<>(id, msg, reg, backup);
}
private static Function<JwsHeader, SignatureAlgorithm<?, ?>> sigFn(Collection<SignatureAlgorithm<?, ?>> extras) {
return locFn(JwsHeader.ALGORITHM, MISSING_JWS_ALG_MSG, SignatureAlgorithmsBridge.REGISTRY, extras);
}
private static Function<JweHeader, SymmetricAeadAlgorithm> encFn(Collection<SymmetricAeadAlgorithm> extras) {
return locFn(JweHeader.ENCRYPTION_ALGORITHM, MISSING_ENC_MSG, EncryptionAlgorithmsBridge.REGISTRY, extras);
}
private static Function<JweHeader, KeyAlgorithm<?, ?>> keyFn(Collection<KeyAlgorithm<?, ?>> extras) {
return locFn(JweHeader.ALGORITHM, MISSING_JWE_ALG_MSG, KeyAlgorithmsBridge.REGISTRY, extras);
}
// TODO: make the following fields final for v1.0
private Provider provider;
@SuppressWarnings("deprecation") // will remove for 1.0
private SigningKeyResolver signingKeyResolver;
private KeyResolver keyResolver;
@SuppressWarnings("rawtypes")
private Function<Header, CompressionCodec> compressionCodecLocator;
private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver();
private final Function<JwsHeader, SignatureAlgorithm<?, ?>> signatureAlgorithmLocator;
private final Function<JweHeader, SymmetricAeadAlgorithm> encryptionAlgorithmLocator;
private final Function<JweHeader, KeyAlgorithm<?, ?>> keyAlgorithmLocator;
private final Function<?, Key> keyLocator;
private Decoder<String, byte[]> base64UrlDecoder = Decoders.BASE64URL;
@ -109,30 +160,43 @@ public class DefaultJwtParser implements JwtParser {
*
* @deprecated for backward compatibility only, see other constructors.
*/
@SuppressWarnings("DeprecatedIsStillUsed") // will remove before 1.0
@Deprecated
public DefaultJwtParser() {
this.keyResolver = new StaticKeyResolver(null, null);
this.signingKeyResolver = new DelegatingSigningKeyResolver(this.keyResolver);
ConstantKeyLocator<?> constantKeyLocator = new ConstantKeyLocator<>(null, null);
this.keyLocator = constantKeyLocator;
this.signingKeyResolver = constantKeyLocator;
this.signatureAlgorithmLocator = sigFn(Collections.<SignatureAlgorithm<?, ?>>emptyList());
this.keyAlgorithmLocator = keyFn(Collections.<KeyAlgorithm<?, ?>>emptyList());
this.encryptionAlgorithmLocator = encFn(Collections.<SymmetricAeadAlgorithm>emptyList());
this.compressionCodecLocator = new CompressionCodecLocator<>(new DefaultCompressionCodecResolver());
}
@SuppressWarnings("deprecation") //SigningKeyResolver will be removed for 1.0
DefaultJwtParser(Provider provider,
SigningKeyResolver signingKeyResolver,
KeyResolver keyResolver,
Function<?,Key> keyLocator,
Clock clock,
long allowedClockSkewMillis,
Claims expectedClaims,
Decoder<String, byte[]> base64UrlDecoder,
Deserializer<Map<String, ?>> deserializer,
CompressionCodecResolver compressionCodecResolver) {
CompressionCodecResolver compressionCodecResolver,
Collection<SignatureAlgorithm<?, ?>> extraSigAlgs,
Collection<KeyAlgorithm<?, ?>> extraKeyAlgs,
Collection<SymmetricAeadAlgorithm> extraEncAlgs) {
this.provider = provider;
this.signingKeyResolver = Assert.notNull(signingKeyResolver, "SigningKeyResolver cannot be null.");
this.keyResolver = Assert.notNull(keyResolver, "KeyResolver cannot be null.");
this.keyLocator = Assert.notNull(keyLocator, "Key Locator cannot be null.");
this.clock = clock;
this.allowedClockSkewMillis = allowedClockSkewMillis;
this.expectedClaims = expectedClaims;
this.base64UrlDecoder = base64UrlDecoder;
this.deserializer = deserializer;
this.compressionCodecResolver = compressionCodecResolver;
this.signatureAlgorithmLocator = sigFn(extraSigAlgs);
this.keyAlgorithmLocator = keyFn(extraKeyAlgs);
this.encryptionAlgorithmLocator = encFn(extraEncAlgs);
this.compressionCodecLocator = new CompressionCodecLocator<>(compressionCodecResolver);
}
@Override
@ -229,10 +293,11 @@ public class DefaultJwtParser implements JwtParser {
@Override
public JwtParser setSigningKey(final Key key) {
Assert.notNull(key, "signing key cannot be null.");
setSigningKeyResolver(new StaticSigningKeyResolver(key));
setSigningKeyResolver(new ConstantKeyLocator<>(key, null));
return this;
}
@SuppressWarnings("deprecation") // required until 1.0
@Override
public JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver) {
Assert.notNull(signingKeyResolver, "SigningKeyResolver cannot be null.");
@ -243,7 +308,7 @@ public class DefaultJwtParser implements JwtParser {
@Override
public JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver) {
Assert.notNull(compressionCodecResolver, "compressionCodecResolver cannot be null.");
this.compressionCodecResolver = compressionCodecResolver;
this.compressionCodecLocator = new CompressionCodecLocator<>(compressionCodecResolver);
return this;
}
@ -271,13 +336,14 @@ public class DefaultJwtParser implements JwtParser {
}
@Override
public Jwt<?,?> parse(String compact) throws ExpiredJwtException, MalformedJwtException, SignatureException {
public Jwt<?, ?> parse(String compact) 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
//noinspection deprecation
this.deserializer = LegacyServices.loadFirst(Deserializer.class);
}
@ -293,9 +359,8 @@ public class DefaultJwtParser implements JwtParser {
// =============== Header =================
final byte[] headerBytes = base64UrlDecode(base64UrlHeader, "protected header");
String origValue = new String(headerBytes, Strings.UTF_8);
Map<String, ?> m = readValue(origValue, "protected header" );
@SuppressWarnings("rawtypes")
Header header = tokenized instanceof TokenizedJwe ? new DefaultJweHeader(m) : new DefaultJwsHeader(m);
Map<String, ?> m = readValue(origValue, "protected header");
Header<?> header = tokenized.createHeader(m);
// https://tools.ietf.org/html/rfc7515#section-10.7 , second-to-last bullet point, note the use of 'always':
//
@ -303,14 +368,17 @@ public class DefaultJwtParser implements JwtParser {
// Protected Header. (This is always the case when using the JWS
// Compact Serialization and is the approach taken by CMS [RFC6211].)
//
final String alg = header.getAlgorithm();
final String alg = Strings.clean(header.getAlgorithm());
if (!Strings.hasText(alg)) {
String msg = "Compact JWT strings MUST always have an Algorithm ('alg') header value per https://tools.ietf.org/html/rfc7515#section-4.1.1 and https://tools.ietf.org/html/rfc7516#section-4.1.1. Also see https://tools.ietf.org/html/rfc7515#section-10.7 for more information.";
String msg = "Compact JWT strings MUST always have an 'alg' (Algorithm) header value per " +
"https://tools.ietf.org/html/rfc7515#section-4.1.1 and " +
"https://tools.ietf.org/html/rfc7516#section-4.1.1. Also see " +
"https://tools.ietf.org/html/rfc7515#section-10.7 for more information.";
throw new MalformedJwtException(msg);
}
// =============== Body =================
CompressionCodec compressionCodec = compressionCodecResolver.resolveCompressionCodec(header);
CompressionCodec compressionCodec = compressionCodecLocator.apply(header);
byte[] bytes = base64UrlDecode(tokenized.getBody(), "payload"); // Only JWS body can be empty per https://github.com/jwtk/jjwt/pull/540
if (tokenized instanceof TokenizedJwe && Arrays.length(bytes) == 0) {
String msg = "Compact JWE strings MUST always contain a payload (ciphertext).";
@ -324,8 +392,8 @@ public class DefaultJwtParser implements JwtParser {
byte[] tag = null;
if (tokenized instanceof TokenizedJwe) { //need to decrypt the ciphertext
TokenizedJwe tokenizedJwe = (TokenizedJwe)tokenized;
JweHeader jweHeader = (JweHeader)header;
TokenizedJwe tokenizedJwe = (TokenizedJwe) tokenized;
JweHeader jweHeader = (JweHeader) header;
byte[] cekBytes = new byte[0]; //ignored unless using an encrypted key algorithm
String base64Url = tokenizedJwe.getEncryptedKey();
@ -346,6 +414,9 @@ public class DefaultJwtParser implements JwtParser {
throw new MalformedJwtException(msg);
}
// This is intentional - 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://datatracker.ietf.org/doc/html/rfc7516#section-5.1, Step 14.
final byte[] aad = base64UrlHeader.getBytes(StandardCharsets.US_ASCII);
base64Url = tokenizedJwe.getDigest();
@ -359,19 +430,28 @@ public class DefaultJwtParser implements JwtParser {
String enc = jweHeader.getEncryptionAlgorithm();
if (!Strings.hasText(enc)) {
String msg = "JWE header does not contain the required 'enc' (Encryption Algorithm) header value";
throw new MalformedJwtException(msg);
throw new MalformedJwtException(MISSING_ENC_MSG);
}
final SymmetricAeadAlgorithm encAlg = EncryptionAlgorithms.forName(enc); //TODO: ensure lookup goes through a resolver
final KeyAlgorithm<?,Key> keyAlg = (KeyAlgorithm<?,Key>)KeyAlgorithms.forName(alg); //TODO: ensure lookup goes through a resolver
final Key key = this.keyResolver.resolveKey(jweHeader);
if (key == null) {
String msg = "Unable to locate decryption key for encryption algorithm '" + encAlg.getId() +
"' using key management algorithm '" + keyAlg.getId() + "'.";
final SymmetricAeadAlgorithm encAlg = this.encryptionAlgorithmLocator.apply(jweHeader);
if (encAlg == null) {
String msg = "Unrecognized JWE encryption algorithm identifier: " + enc;
throw new UnsupportedJwtException(msg);
}
KeyRequest<byte[],Key> request = new DefaultKeyRequest<>(this.provider, null, cekBytes, key, jweHeader);
@SuppressWarnings("rawtypes") final KeyAlgorithm keyAlg = this.keyAlgorithmLocator.apply(jweHeader);
if (keyAlg == null) {
String msg = "Unrecognized JWE key management algorithm: " + alg;
throw new UnsupportedJwtException(msg);
}
final Key key = ((Function<JweHeader,Key>)this.keyLocator).apply(jweHeader);
if (key == null) {
String msg = "No key available for the '" + keyAlg.getId() + "' key management algorithm. Unable to " +
"perform '" + encAlg + "' decryption.";
throw new UnsupportedJwtException(msg);
}
KeyRequest<byte[], ?> request = new DefaultKeyRequest<>(this.provider, null, cekBytes, key, jweHeader);
final SecretKey cek = keyAlg.getDecryptionKey(request);
SymmetricAeadDecryptionRequest decryptRequest =
@ -388,13 +468,14 @@ public class DefaultJwtParser implements JwtParser {
claims = new DefaultClaims(claimsMap);
}
Jwt<?,?> jwt;
Jwt<?, ?> jwt;
Object body = claims != null ? claims : payload;
if (tokenized instanceof TokenizedJwe) {
if (header instanceof JweHeader) {
jwt = new DefaultJwe<>((JweHeader)header, body, iv, tag);
} else { // JWS
if (!Strings.hasText(tokenized.getDigest()) && SignatureAlgorithms.NONE.getId().equalsIgnoreCase(alg)) {
jwt = new DefaultJwt<>(header, body);
//noinspection rawtypes
jwt = new DefaultJwt(header, body);
} else {
jwt = new DefaultJws<>((JwsHeader)header, body, tokenized.getDigest());
}
@ -403,12 +484,15 @@ public class DefaultJwtParser implements JwtParser {
// =============== Signature =================
if (jwt instanceof Jws) { // it's a JWS, validate the signature
Jws<?> jws = (Jws<?>)jwt;
Jws<?> jws = (Jws<?>) jwt;
final JwsHeader jwsHeader = jws.getHeader();
//TODO: ensure lookup goes through resolver (to support custom algorithms):
SignatureAlgorithm algorithm = SignatureAlgorithms.forName(alg);
SignatureAlgorithm<?,Key> algorithm = (SignatureAlgorithm<?,Key>)signatureAlgorithmLocator.apply(jwsHeader);
if (algorithm == null) {
String msg = "Unrecognized JWS algorithm identifier: " + alg;
throw new UnsupportedJwtException(msg);
}
String digest = tokenized.getDigest();
@ -575,7 +659,7 @@ public class DefaultJwtParser implements JwtParser {
Assert.notNull(handler, "JwtHandler argument cannot be null.");
Assert.hasText(compact, "JWT String argument cannot be null or empty.");
Jwt<?,?> jwt = parse(compact);
Jwt<?, ?> jwt = parse(compact);
if (jwt instanceof Jws) {
Jws<?> jws = (Jws<?>) jwt;
@ -586,39 +670,39 @@ public class DefaultJwtParser implements JwtParser {
return handler.onPlaintextJws((Jws<String>) jws);
}
} else if (jwt instanceof Jwe) {
Jwe<?> jwe = (Jwe<?>)jwt;
Jwe<?> jwe = (Jwe<?>) jwt;
Object body = jwe.getBody();
if (body instanceof Claims) {
return handler.onClaimsJwe((Jwe<Claims>)jwe);
return handler.onClaimsJwe((Jwe<Claims>) jwe);
} else {
return handler.onPlaintextJwe((Jwe<String>) jwe);
}
} else {
Object body = jwt.getBody();
if (body instanceof Claims) {
return handler.onClaimsJwt((Jwt<Header, Claims>) jwt);
return handler.onClaimsJwt((Jwt<?, Claims>) jwt);
} else {
return handler.onPlaintextJwt((Jwt<Header, String>) jwt);
return handler.onPlaintextJwt((Jwt<?, String>) jwt);
}
}
}
@Override
public Jwt<Header, String> parsePlaintextJwt(String plaintextJwt) {
return parse(plaintextJwt, new JwtHandlerAdapter<Jwt<Header, String>>() {
public Jwt<?, String> parsePlaintextJwt(String plaintextJwt) {
return parse(plaintextJwt, new JwtHandlerAdapter<Jwt<?, String>>() {
@Override
public Jwt<Header, String> onPlaintextJwt(Jwt<Header, String> jwt) {
public Jwt<?, String> onPlaintextJwt(Jwt<?, String> jwt) {
return jwt;
}
});
}
@Override
public Jwt<Header, Claims> parseClaimsJwt(String claimsJwt) {
public Jwt<?, Claims> parseClaimsJwt(String claimsJwt) {
try {
return parse(claimsJwt, new JwtHandlerAdapter<Jwt<Header, Claims>>() {
return parse(claimsJwt, new JwtHandlerAdapter<Jwt<?, Claims>>() {
@Override
public Jwt<Header, Claims> onClaimsJwt(Jwt<Header, Claims> jwt) {
public Jwt<?, Claims> onClaimsJwt(Jwt<?, Claims> jwt) {
return jwt;
}
});
@ -680,7 +764,6 @@ public class DefaultJwtParser implements JwtParser {
}
}
@SuppressWarnings("unchecked")
protected Map<String, ?> readValue(String val, final String name) {
try {
byte[] bytes = val.getBytes(Strings.UTF_8);

View File

@ -18,24 +18,33 @@ package io.jsonwebtoken.impl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.CompressionCodecResolver;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JweHeader;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.JwtParserBuilder;
import io.jsonwebtoken.Locator;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
import io.jsonwebtoken.impl.lang.ConstantFunction;
import io.jsonwebtoken.impl.lang.Function;
import io.jsonwebtoken.impl.lang.LocatorFunction;
import io.jsonwebtoken.impl.lang.Services;
import io.jsonwebtoken.impl.security.DelegatingSigningKeyResolver;
import io.jsonwebtoken.impl.security.StaticKeyResolver;
import io.jsonwebtoken.impl.security.StaticSigningKeyResolver;
import io.jsonwebtoken.impl.security.ConstantKeyLocator;
import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.KeyResolver;
import io.jsonwebtoken.security.KeyAlgorithm;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureAlgorithm;
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
import java.security.Key;
import java.security.Provider;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Map;
/**
@ -57,13 +66,18 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
private Provider provider;
private Key signatureVerificationKey;
private Key decryptionKey;
private KeyResolver keyResolver;
@SuppressWarnings({"rawtypes"})
private Function<Header, Key> keyLocator = ConstantFunction.forNull();
@SuppressWarnings("deprecation") //TODO: remove for 1.0
private SigningKeyResolver signingKeyResolver;
private SigningKeyResolver signingKeyResolver = new ConstantKeyLocator<>(null, null);
private CompressionCodecResolver compressionCodecResolver;
private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver();
private final Collection<SymmetricAeadAlgorithm> extraEncryptionAlgorithms = new LinkedHashSet<>();
private final Collection<KeyAlgorithm<?, ?>> extraKeyAlgorithms = new LinkedHashSet<>();
private final Collection<SignatureAlgorithm<?, ?>> extraSignatureAlgorithms = new LinkedHashSet<>();
private Decoder<String, byte[]> base64UrlDecoder = Decoders.BASE64URL;
@ -172,16 +186,52 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
return setSigningKey(bytes);
}
@SuppressWarnings("rawtypes")
@Override
public JwtParserBuilder setSigningKey(Key key) {
public JwtParserBuilder setSigningKey(final Key key) {
Assert.notNull(key, "signing key cannot be null.");
this.signatureVerificationKey = key;
return setSigningKeyResolver(new StaticSigningKeyResolver(key));
final Function<Header, Key> existing = this.keyLocator;
this.keyLocator = new Function<Header, Key>() {
@Override
public Key apply(Header header) {
return header instanceof JwsHeader ? key : existing.apply(header);
}
};
return setSigningKeyResolver(new ConstantKeyLocator<>(key, null));
}
@SuppressWarnings("rawtypes")
@Override
public JwtParserBuilder decryptWith(final Key key) {
Assert.notNull(key, "decryption key cannot be null.");
final Function<Header, Key> existing = this.keyLocator;
this.keyLocator = new Function<Header, Key>() {
@Override
public Key apply(Header header) {
return header instanceof JweHeader ? key : existing.apply(header);
}
};
return this;
}
@Override
public JwtParserBuilder decryptWith(Key key) {
this.decryptionKey = Assert.notNull(key, "decryption key cannot be null.");
public JwtParserBuilder addEncryptionAlgorithms(Collection<SymmetricAeadAlgorithm> encAlgs) {
Assert.notEmpty(encAlgs, "Additional EncryptionAlgorithm collection cannot be null or empty.");
this.extraEncryptionAlgorithms.addAll(encAlgs);
return this;
}
@Override
public JwtParserBuilder addSignatureAlgorithms(Collection<SignatureAlgorithm<?, ?>> sigAlgs) {
Assert.notEmpty(sigAlgs, "Additional SignatureAlgorithm collection cannot be null or empty.");
this.extraSignatureAlgorithms.addAll(sigAlgs);
return this;
}
@Override
public JwtParserBuilder addKeyAlgorithms(Collection<KeyAlgorithm<?, ?>> keyAlgs) {
Assert.notEmpty(keyAlgs, "Additional KeyAlgorithm collection cannot be null or empty.");
this.extraKeyAlgorithms.addAll(keyAlgs);
return this;
}
@ -193,10 +243,15 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
return this;
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static Function<Header, Key> coerce(Function f) {
return (Function<Header, Key>) f;
}
@Override
public JwtParserBuilder setKeyResolver(KeyResolver keyResolver) {
Assert.notNull(keyResolver, "KeyResolver cannot be null.");
this.keyResolver = keyResolver;
public JwtParserBuilder setKeyLocator(Locator<? extends Header<?>, Key> keyLocator) {
Assert.notNull(keyLocator, "Key locator cannot be null.");
this.keyLocator = coerce(new LocatorFunction<>(keyLocator));
return this;
}
@ -218,28 +273,25 @@ public class DefaultJwtParserBuilder implements JwtParserBuilder {
this.deserializer = Services.loadFirst(Deserializer.class);
}
// if the compressionCodecResolver is not set default it.
if (this.compressionCodecResolver == null) {
this.compressionCodecResolver = new DefaultCompressionCodecResolver();
}
if (this.keyResolver == null) {
this.keyResolver = new StaticKeyResolver(this.signatureVerificationKey, this.decryptionKey);
}
if (this.signingKeyResolver == null) {
this.signingKeyResolver = new DelegatingSigningKeyResolver(this.keyResolver);
}
// Invariants. If these are ever violated, it's an error in this class implementation
// (we default to non-null instances, and the setters should never allow null):
assert this.keyLocator != null : "Key locator should never be null.";
assert this.signingKeyResolver != null : "SigningKeyResolver should never be null.";
assert this.compressionCodecResolver != null : "CompressionCodecResolver should never be null.";
return new ImmutableJwtParser(new DefaultJwtParser(
provider,
signingKeyResolver,
keyResolver,
keyLocator,
clock,
allowedClockSkewMillis,
expectedClaims,
base64UrlDecoder,
deserializer,
compressionCodecResolver));
compressionCodecResolver,
extraSignatureAlgorithms,
extraKeyAlgorithms,
extraEncryptionAlgorithms
));
}
}

View File

@ -1,5 +1,9 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.Header;
import java.util.Map;
class DefaultTokenizedJwe extends DefaultTokenizedJwt implements TokenizedJwe {
private final String encryptedKey;
@ -20,4 +24,9 @@ class DefaultTokenizedJwe extends DefaultTokenizedJwt implements TokenizedJwe {
public String getIv() {
return this.iv;
}
@Override
public Header<?> createHeader(Map<String, ?> m) {
return new DefaultJweHeader(m);
}
}

View File

@ -1,5 +1,11 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.lang.Strings;
import java.util.Map;
class DefaultTokenizedJwt implements TokenizedJwt {
private final String protectedHeader;
@ -26,4 +32,12 @@ class DefaultTokenizedJwt implements TokenizedJwt {
public String getDigest() {
return this.digest;
}
@Override
public Header<?> createHeader(Map<String, ?> m) {
if (Strings.hasText(getDigest())) {
return new DefaultJwsHeader(m);
}
return new DefaultHeader<>(m);
}
}

View File

@ -0,0 +1,62 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JweHeader;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.lang.Function;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
public class IdLocator<H extends Header<H>, R> implements Function<H, R> {
private final String headerName;
private final String requiredMsg;
private final boolean headerValueRequired;
private final Function<String, R> primary;
private final Function<H, R> backup;
public IdLocator(String headerName, String requiredMsg, Function<String, R> primary, Function<H, R> backup) {
this.headerName = Assert.hasText(headerName, "Header name cannot be null or empty.");
this.requiredMsg = requiredMsg;
this.headerValueRequired = Strings.hasText(requiredMsg);
this.primary = Assert.notNull(primary, "Primary lookup function cannot be null.");
this.backup = Assert.notNull(backup, "Backup locator cannot be null.");
}
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 apply(H header) {
Assert.notNull(header, "Header argument cannot be null.");
Object val = header.get(this.headerName);
String id = val != null ? String.valueOf(val) : null;
if (this.headerValueRequired && !Strings.hasText(id)) {
throw new MalformedJwtException(requiredMsg);
}
R instance = primary.apply(id);
if (instance == null) {
instance = backup.apply(header);
}
if (this.headerValueRequired && instance == null) {
String msg = "Unrecognized " + type(header) + " '" + this.headerName + "' header value: " + id;
throw new UnsupportedJwtException(msg);
}
return instance;
}
}

View File

@ -0,0 +1,47 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.impl.lang.Registry;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
public class IdRegistry<T extends Identifiable> implements Registry<String, T> {
private final Map<String, T> INSTANCES;
public IdRegistry(Collection<T> instances) {
Assert.notEmpty(instances, "Collection of Identifiable instances may not be null or empty.");
Map<String, T> m = new LinkedHashMap<>(instances.size());
for (T instance : instances) {
String id = Assert.hasText(Strings.clean(instance.getId()), "All Identifiable instances within the collection cannot be null or empty.");
m.put(id, instance);
}
this.INSTANCES = java.util.Collections.unmodifiableMap(m);
}
@Override
public T apply(String id) {
Assert.hasText(id, "id argument cannot be null or empty.");
//try constant time lookup first. This will satisfy 99% of invocations:
T instance = INSTANCES.get(id);
if (instance != null) {
return instance;
}
//fall back to case-insensitive lookup:
for (T i : INSTANCES.values()) {
if (id.equalsIgnoreCase(i.getId())) {
return i;
}
}
return null; //no match
}
@Override
public Collection<T> values() {
return this.INSTANCES.values();
}
}

View File

@ -156,12 +156,12 @@ class ImmutableJwtParser implements JwtParser {
}
@Override
public Jwt<Header, String> parsePlaintextJwt(String plaintextJwt) throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException {
public <H extends Header<H>> Jwt<H, String> parsePlaintextJwt(String plaintextJwt) throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException {
return this.jwtParser.parsePlaintextJwt(plaintextJwt);
}
@Override
public Jwt<Header, Claims> parseClaimsJwt(String claimsJwt) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException {
public <H extends Header<H>> Jwt<H, Claims> parseClaimsJwt(String claimsJwt) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException {
return this.jwtParser.parseClaimsJwt(claimsJwt);
}

View File

@ -1,10 +0,0 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.Jwe;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtException;
public interface JweParser {
Jwe<?> parse(String jwt) throws JwtException;
}

View File

@ -178,7 +178,6 @@ public class JwtMap implements Map<String, Object> {
return map.remove(o);
}
@SuppressWarnings("NullableProblems")
@Override
public void putAll(Map<? extends String, ?> m) {
if (m == null) {

View File

@ -1,9 +1,14 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.Header;
import java.util.Map;
public interface TokenizedJwt {
/**
* Protected header.
*
* @return protected header.
*/
String getProtected();
@ -17,4 +22,6 @@ public interface TokenizedJwt {
* Signature for JWS, AAD Tag for JWE.
*/
String getDigest();
Header<?> createHeader(Map<String, ?> m);
}

View File

@ -0,0 +1,29 @@
package io.jsonwebtoken.impl.lang;
/**
* Function that always returns the same value
*
* @param <T> Input type
* @param <R> Return value type
*/
public final class ConstantFunction<T, R> implements Function<T, R> {
private static final Function<?, ?> NULL = new ConstantFunction<>(null);
private final R value;
public ConstantFunction(R value) {
this.value = value;
}
@SuppressWarnings("unchecked")
public static <T, R> Function<T, R> forNull() {
return (Function<T, R>) NULL;
}
@Override
public R apply(T t) {
return this.value;
}
}

View File

@ -0,0 +1,19 @@
package io.jsonwebtoken.impl.lang;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Locator;
import io.jsonwebtoken.lang.Assert;
public class LocatorFunction<H extends Header<H>, R> implements Function<H, R> {
private final Locator<H, R> locator;
public LocatorFunction(Locator<H, R> locator) {
this.locator = Assert.notNull(locator, "Locator instance cannot be null.");
}
@Override
public R apply(H h) {
return this.locator.locate(h);
}
}

View File

@ -0,0 +1,8 @@
package io.jsonwebtoken.impl.lang;
import java.util.Collection;
public interface Registry<I, T> extends Function<I, T> {
Collection<T> values();
}

View File

@ -0,0 +1,48 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JweHeader;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.impl.lang.Function;
import io.jsonwebtoken.security.LocatorAdapter;
import java.security.Key;
@SuppressWarnings("deprecation")
public class ConstantKeyLocator<H extends Header<H>> extends LocatorAdapter<H, Key> implements SigningKeyResolver, Function<H, Key> {
private final Key jwsKey;
private final Key jweKey;
public ConstantKeyLocator(Key jwsKey, Key jweKey) {
this.jwsKey = jwsKey;
this.jweKey = jweKey;
}
@Override
public Key resolveSigningKey(JwsHeader header, Claims claims) {
return locate(header);
}
@Override
public Key resolveSigningKey(JwsHeader header, String plaintext) {
return locate(header);
}
@Override
protected Key locate(JwsHeader header) {
return this.jwsKey;
}
@Override
protected Key locate(JweHeader header) {
return this.jweKey;
}
@Override
public Key apply(H header) {
return locate(header);
}
}

View File

@ -1,6 +1,6 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.Identifiable;
import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.SecurityRequest;

View File

@ -17,7 +17,6 @@ import java.security.Signature;
import java.security.interfaces.ECKey;
import java.security.spec.ECGenParameterSpec;
@SuppressWarnings("unused") //used via reflection in the io.jsonwebtoken.security.SignatureAlgorithms class
public class DefaultEllipticCurveSignatureAlgorithm<SK extends ECKey & PrivateKey, VK extends ECKey & PublicKey> extends AbstractSignatureAlgorithm<SK, VK> implements EllipticCurveSignatureAlgorithm<SK, VK> {
private static final String EC_PUBLIC_KEY_REQD_MSG =
@ -31,6 +30,14 @@ public class DefaultEllipticCurveSignatureAlgorithm<SK extends ECKey & PrivateKe
private final int signatureLength;
private static int shaSize(int keyBitLength) {
return keyBitLength == 521 ? 512 : keyBitLength;
}
public DefaultEllipticCurveSignatureAlgorithm(int keyBitLength, int signatureLength) {
this("ES" + shaSize(keyBitLength), "SHA" + shaSize(keyBitLength) + "withECDSA", "secp" + keyBitLength + "r1", keyBitLength, signatureLength);
}
public DefaultEllipticCurveSignatureAlgorithm(String name, String jcaName, String curveName, int minKeyLength, int signatureLength) {
super(name, jcaName);
Assert.hasText(curveName, "Curve name cannot be null or empty.");

View File

@ -1,40 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.JweHeader;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.EncryptionAlgorithmLocator;
import io.jsonwebtoken.security.EncryptionAlgorithms;
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
/**
* @since JJWT_RELEASE_VERSION
*/
public class DefaultEncryptionAlgorithmLocator implements EncryptionAlgorithmLocator {
@Override
public SymmetricAeadAlgorithm getEncryptionAlgorithm(JweHeader jweHeader) {
String enc = Strings.clean(jweHeader.getEncryptionAlgorithm());
//TODO: this check needs to be in the parser, to be enforced regardless of the locator implementation
if (enc == null) {
String msg = "JWE header does not contain an 'enc' header parameter. This header parameter is mandatory " +
"per the JWE Specification, Section 4.1.2. See " +
"https://tools.ietf.org/html/rfc7516#section-4.1.2 for more information.";
throw new MalformedJwtException(msg);
}
try {
return EncryptionAlgorithms.forName(enc); //TODO: change to findByName and let the parser throw on null return. See below:
} catch (IllegalArgumentException e) {
//TODO: move this check to the parser - needs to be enforced if the locator returns null or throws a non-JWT exception
//couldn't find one:
String msg = "JWE 'enc' header parameter value of '" + enc + "' does not match a JWE standard algorithm " +
"identifier. If '" + enc + "' represents a custom algorithm, the JwtParser must be configured with " +
"a custom EncryptionAlgorithmLocator instance that knows how to return a compatible " +
"EncryptionAlgorithm instance. Otherwise, this JWE is invalid and may not be used safely.";
throw new UnsupportedJwtException(msg, e);
}
}
}

View File

@ -1,5 +1,6 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.impl.JwtMap;
import io.jsonwebtoken.impl.io.CodecConverter;
import io.jsonwebtoken.impl.lang.BiFunction;
@ -11,7 +12,6 @@ import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Objects;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.Identifiable;
import io.jsonwebtoken.security.MalformedKeyException;
import java.net.URI;

View File

@ -17,7 +17,6 @@ import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;
@SuppressWarnings("unused") //used via reflection in the io.jsonwebtoken.security.SignatureAlgorithms class
public class DefaultRsaSignatureAlgorithm<SK extends RSAKey & PrivateKey, VK extends RSAKey & PublicKey> extends AbstractSignatureAlgorithm<SK, VK> implements RsaSignatureAlgorithm<SK, VK> {
static {
@ -46,6 +45,14 @@ public class DefaultRsaSignatureAlgorithm<SK extends RSAKey & PrivateKey, VK ext
this.algorithmParameterSpec = algParam;
}
public DefaultRsaSignatureAlgorithm(int digestBitLength, int preferredKeyBitLength) {
this("RS" + digestBitLength, "SHA" + digestBitLength + "withRSA", preferredKeyBitLength);
}
public DefaultRsaSignatureAlgorithm(int digestBitLength, int preferredKeyBitLength, int pssSaltBitLength) {
this("PS" + digestBitLength, "RSASSA-PSS", preferredKeyBitLength, pssSaltBitLength);
}
public DefaultRsaSignatureAlgorithm(String name, String jcaName, int preferredKeyLengthBits) {
this(name, jcaName, preferredKeyLengthBits, null);
}

View File

@ -1,28 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.KeyResolver;
import java.security.Key;
public class DelegatingSigningKeyResolver implements SigningKeyResolver {
private final KeyResolver keyResolver;
public DelegatingSigningKeyResolver(KeyResolver keyResolver) {
this.keyResolver = Assert.notNull(keyResolver, "KeyResolver cannot be null.");
}
@Override
public Key resolveSigningKey(JwsHeader header, Claims claims) {
return this.keyResolver.resolveKey(header);
}
@Override
public Key resolveSigningKey(JwsHeader header, String plaintext) {
return this.keyResolver.resolveKey(header);
}
}

View File

@ -1,8 +1,8 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.security.CryptoException;
import io.jsonwebtoken.security.CryptoRequest;
import io.jsonwebtoken.security.Identifiable;
import io.jsonwebtoken.security.KeyException;
import io.jsonwebtoken.security.PayloadSupplier;

View File

@ -0,0 +1,48 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.IdRegistry;
import io.jsonwebtoken.impl.lang.Registry;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.SymmetricAeadAlgorithm;
import java.util.Collection;
@SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.EncryptionAlgorithms implementation
public class EncryptionAlgorithmsBridge {
// prevent instantiation
private EncryptionAlgorithmsBridge() {
}
//For parser implementation - do not expose outside the impl module:
public static final Registry<String, SymmetricAeadAlgorithm> REGISTRY;
static {
REGISTRY = new IdRegistry<>(Collections.of(
(SymmetricAeadAlgorithm) new HmacAesAeadAlgorithm(128),
new HmacAesAeadAlgorithm(192),
new HmacAesAeadAlgorithm(256),
new GcmAesAeadAlgorithm(128),
new GcmAesAeadAlgorithm(192),
new GcmAesAeadAlgorithm(256)
));
}
public static Collection<SymmetricAeadAlgorithm> values() {
return REGISTRY.values();
}
public static SymmetricAeadAlgorithm findById(String id) {
return REGISTRY.apply(id);
}
public static SymmetricAeadAlgorithm forId(String id) {
SymmetricAeadAlgorithm alg = findById(id);
if (alg == null) {
String msg = "Unrecognized JWA EncryptionAlgorithm identifier: " + id;
throw new UnsupportedJwtException(msg);
}
return alg;
}
}

View File

@ -1,6 +1,6 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.Identifiable;
import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.security.Jwk;
import java.security.Key;

View File

@ -1,6 +1,6 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.Identifiable;
import io.jsonwebtoken.Identifiable;
import java.net.URI;
import java.security.Key;

View File

@ -1,8 +1,7 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.Identifiable;
import io.jsonwebtoken.impl.lang.Converter;
import io.jsonwebtoken.security.Identifiable;
import io.jsonwebtoken.security.Jwk;
import java.security.Key;
import java.util.Map;

View File

@ -1,71 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.security.EncryptedKeyAlgorithm;
import io.jsonwebtoken.security.KeyAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.MGF1ParameterSpec;
//bridge class for the API so it doesn't need to know implementation/constructor argument details
public final class KeyAlgorithms {
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);
// prevent instantiation
private KeyAlgorithms() {
}
public static KeyAlgorithm<SecretKey, SecretKey> direct() {
return new DirectKeyAlgorithm();
}
public static EncryptedKeyAlgorithm<SecretKey, SecretKey> a128kw() {
return new AesWrapKeyAlgorithm(128);
}
public static EncryptedKeyAlgorithm<SecretKey, SecretKey> a192kw() {
return new AesWrapKeyAlgorithm(192);
}
public static EncryptedKeyAlgorithm<SecretKey, SecretKey> a256kw() {
return new AesWrapKeyAlgorithm(256);
}
public static EncryptedKeyAlgorithm<SecretKey, SecretKey> a128gcmkw() {
return new AesGcmKeyAlgorithm(128);
}
public static EncryptedKeyAlgorithm<SecretKey, SecretKey> a192gcmkw() {
return new AesGcmKeyAlgorithm(192);
}
public static EncryptedKeyAlgorithm<SecretKey, SecretKey> a256gcmkw() {
return new AesGcmKeyAlgorithm(256);
}
public static <EK extends RSAKey & PublicKey, DK extends RSAKey & PrivateKey> EncryptedKeyAlgorithm<EK, DK> rsa1_5() {
return new DefaultRsaKeyAlgorithm<>(RSA1_5_ID, RSA1_5_TRANSFORMATION);
}
public static <EK extends RSAKey & PublicKey, DK extends RSAKey & PrivateKey> EncryptedKeyAlgorithm<EK, DK> rsaOaep() {
return new DefaultRsaKeyAlgorithm<>(RSA_OAEP_ID, RSA_OAEP_TRANSFORMATION);
}
public static <EK extends RSAKey & PublicKey, DK extends RSAKey & PrivateKey> EncryptedKeyAlgorithm<EK, DK> rsaOaep256() {
return new DefaultRsaKeyAlgorithm<>(RSA_OAEP_256_ID, RSA_OAEP_256_TRANSFORMATION, RSA_OAEP_256_SPEC);
}
}

View File

@ -0,0 +1,65 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.IdRegistry;
import io.jsonwebtoken.impl.lang.Registry;
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;
import java.util.Collection;
@SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.KeyAlgorithms implementation
public final class KeyAlgorithmsBridge {
// prevent instantiation
private KeyAlgorithmsBridge() {
}
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);
//For parser implementation - do not expose outside the impl module
public static final Registry<String, KeyAlgorithm<?, ?>> REGISTRY;
static {
REGISTRY = new IdRegistry<>(Collections.<KeyAlgorithm<?, ?>>of(
new DirectKeyAlgorithm(),
new AesWrapKeyAlgorithm(128),
new AesWrapKeyAlgorithm(192),
new AesWrapKeyAlgorithm(256),
new AesGcmKeyAlgorithm(128),
new AesGcmKeyAlgorithm(192),
new AesGcmKeyAlgorithm(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)
));
}
public static Collection<KeyAlgorithm<?, ?>> values() {
return REGISTRY.values();
}
public static KeyAlgorithm<?, ?> findById(String id) {
return REGISTRY.apply(id);
}
public static KeyAlgorithm<?, ?> forId(String id) {
KeyAlgorithm<?, ?> instance = findById(id);
if (instance == null) {
String msg = "Unrecognized JWA KeyAlgorithm identifier: " + id;
throw new UnsupportedJwtException(msg);
}
return instance;
}
}

View File

@ -5,8 +5,8 @@ import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.SignatureRequest;
import io.jsonwebtoken.security.SecretKeySignatureAlgorithm;
import io.jsonwebtoken.security.SignatureRequest;
import io.jsonwebtoken.security.WeakKeyException;
import javax.crypto.Mac;
@ -16,7 +16,6 @@ import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
@SuppressWarnings("unused") //used via reflection in the io.jsonwebtoken.security.SignatureAlgorithms class
public class MacSignatureAlgorithm extends AbstractSignatureAlgorithm<SecretKey, SecretKey> implements SecretKeySignatureAlgorithm {
private final int minKeyLength; //in bits
@ -38,6 +37,10 @@ public class MacSignatureAlgorithm extends AbstractSignatureAlgorithm<SecretKey,
VALID_HS256_JCA_NAMES.addAll(VALID_HS384_JCA_NAMES);
}
public MacSignatureAlgorithm(int digestBitLength) {
this("HS" + digestBitLength, "HmacSHA" + digestBitLength, digestBitLength);
}
public MacSignatureAlgorithm(String id, String jcaName, int minKeyLength) {
super(id, jcaName);
Assert.isTrue(minKeyLength > 0, "minKeyLength must be greater than zero.");

View File

@ -0,0 +1,56 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.impl.IdRegistry;
import io.jsonwebtoken.impl.lang.Registry;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.SignatureAlgorithm;
import java.util.Collection;
@SuppressWarnings({"unused"}) // reflection bridge class for the io.jsonwebtoken.security.SignatureAlgorithms implementation
public class SignatureAlgorithmsBridge {
//prevent instantiation
private SignatureAlgorithmsBridge() {
}
//For parser implementation - do not expose outside the impl module
public static final Registry<String, SignatureAlgorithm<?, ?>> REGISTRY;
static {
//noinspection RedundantTypeArguments
REGISTRY = new IdRegistry<>(Collections.<SignatureAlgorithm<?, ?>>of(
new NoneSignatureAlgorithm(),
new MacSignatureAlgorithm(256),
new MacSignatureAlgorithm(384),
new MacSignatureAlgorithm(512),
new DefaultRsaSignatureAlgorithm<>(256, 2048),
new DefaultRsaSignatureAlgorithm<>(384, 3072),
new DefaultRsaSignatureAlgorithm<>(512, 4096),
new DefaultRsaSignatureAlgorithm<>(256, 2048, 256),
new DefaultRsaSignatureAlgorithm<>(384, 3072, 384),
new DefaultRsaSignatureAlgorithm<>(512, 4096, 512),
new DefaultEllipticCurveSignatureAlgorithm<>(256, 64),
new DefaultEllipticCurveSignatureAlgorithm<>(384, 96),
new DefaultEllipticCurveSignatureAlgorithm<>(521, 132)
));
}
public static Collection<SignatureAlgorithm<?, ?>> values() {
return REGISTRY.values();
}
public static SignatureAlgorithm<?, ?> findById(String id) {
return REGISTRY.apply(id);
}
public static SignatureAlgorithm<?, ?> forId(String id) {
SignatureAlgorithm<?, ?> instance = findById(id);
if (instance == null) {
String msg = "Unrecognized JWA SignatureAlgorithm identifier: " + id;
throw new UnsupportedJwtException(msg);
}
return instance;
}
}

View File

@ -1,40 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.KeyResolver;
import java.security.Key;
public class StaticKeyResolver implements KeyResolver {
private final Key signatureVerificationKey;
private final Key decryptionKey;
public StaticKeyResolver(Key signatureVerificationKey, Key decryptionKey) {
this.signatureVerificationKey = signatureVerificationKey;
this.decryptionKey = decryptionKey;
}
@Override
public Key resolveKey(Header<?> header) {
if (header instanceof JwsHeader) {
if (this.signatureVerificationKey == null) {
String msg = "Signed JWTs are not supported: the JwtParser has not been configured with a " +
"signature verification key or a KeyResolver. Consider configuring the JwtParserBuilder with " +
"one of these to ensure it can use the necessary key to verify JWS signatures.";
throw new UnsupportedJwtException(msg);
}
return this.signatureVerificationKey;
} else { // JweHeader
if (this.decryptionKey == null) {
String msg = "Encrypted JWTs are not supported: the JwtParser has not been configured with a " +
"decryption key or a KeyResolver. Consider configuring the JwtParserBuilder with " +
"one of these to ensure it can use the necessary key to decrypt JWEs.";
throw new UnsupportedJwtException(msg);
}
return this.decryptionKey;
}
}
}

View File

@ -1,28 +0,0 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.lang.Assert;
import java.security.Key;
//TODO: delete when removing SigningKeyResolver
public class StaticSigningKeyResolver implements SigningKeyResolver {
private final Key key;
public StaticSigningKeyResolver(Key key) {
this.key = Assert.notNull(key, "Key cannot be null.");
}
@Override
public Key resolveSigningKey(JwsHeader header, Claims claims) {
return this.key;
}
@Override
public Key resolveSigningKey(JwsHeader header, String plaintext) {
return this.key;
}
}

View File

@ -10,17 +10,17 @@ import javax.crypto.spec.SecretKeySpec
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertSame
class StaticKeyResolverTest {
class ConstantKeyLocatorTest {
@Test
void testSignatureVerificationKey() {
def key = new SecretKeySpec(new byte[1], 'AES') //dummy key for testing
assertSame key, new StaticKeyResolver(key, null).resolveKey(new DefaultJwsHeader())
assertSame key, new ConstantKeyLocator(key, null).resolveKey(new DefaultJwsHeader())
}
@Test
void testSignatureVerificationKeyMissing() {
def resolver = new StaticKeyResolver(null, null)
def resolver = new ConstantKeyLocator(null, null)
try {
resolver.resolveKey(new DefaultJwsHeader())
} catch (UnsupportedJwtException uje) {
@ -34,12 +34,12 @@ class StaticKeyResolverTest {
@Test
void testDecryptionKey() {
def key = new SecretKeySpec(new byte[1], 'AES') //dummy key for testing
assertSame key, new StaticKeyResolver(null, key).resolveKey(new DefaultJweHeader())
assertSame key, new ConstantKeyLocator(null, key).resolveKey(new DefaultJweHeader())
}
@Test
void testDecryptionKeyMissing() {
def resolver = new StaticKeyResolver(null, null)
def resolver = new ConstantKeyLocator(null, null)
try {
resolver.resolveKey(new DefaultJweHeader())
} catch (UnsupportedJwtException uje) {

View File

@ -1,98 +0,0 @@
package io.jsonwebtoken.impl.security
import io.jsonwebtoken.JweHeader
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.MalformedJwtException
import io.jsonwebtoken.UnsupportedJwtException
import io.jsonwebtoken.security.EncryptionAlgorithms
import org.junit.Before
import org.junit.Test
import static org.junit.Assert.*
/**
* @since JJWT_RELEASE_VERSION
*/
class DefaultEncryptionAlgorithmLocatorTest {
private DefaultEncryptionAlgorithmLocator locator
@Before
void setUp() {
locator = new DefaultEncryptionAlgorithmLocator()
}
private static JweHeader header(String enc) {
return Jwts.jweHeader().setEncryptionAlgorithm(enc)
}
@Test
void testA128CBCHS256() {
assertSame EncryptionAlgorithms.A128CBC_HS256, locator.getEncryptionAlgorithm(header('A128CBC-HS256'))
}
@Test
void testA192CBCHS384() {
assertSame EncryptionAlgorithms.A192CBC_HS384, locator.getEncryptionAlgorithm(header('A192CBC-HS384'))
}
@Test
void testA256CBCHS512() {
assertSame EncryptionAlgorithms.A256CBC_HS512, locator.getEncryptionAlgorithm(header('A256CBC-HS512'))
}
@Test
void testA128GCM() {
assertSame EncryptionAlgorithms.A128GCM, locator.getEncryptionAlgorithm(header('A128GCM'))
}
@Test
void testA192GCM() {
assertSame EncryptionAlgorithms.A192GCM, locator.getEncryptionAlgorithm(header('A192GCM'))
}
@Test
void testA256GCM() {
assertSame EncryptionAlgorithms.A256GCM, locator.getEncryptionAlgorithm(header('A256GCM'))
}
@Test
void testMissingEncAlg() {
try {
locator.getEncryptionAlgorithm(Jwts.jweHeader())
fail()
} catch (MalformedJwtException expected) {
}
}
@Test
void testNullEncAlg() {
try {
locator.getEncryptionAlgorithm(header(null))
fail()
} catch (MalformedJwtException expected) {
}
}
@Test
void testEmptyEncAlg() {
try {
locator.getEncryptionAlgorithm(header(' '))
fail()
} catch (MalformedJwtException expected) {
}
}
@Test
void testUnknownEncAlg() {
try {
locator.getEncryptionAlgorithm(header('foo'))
fail()
} catch (UnsupportedJwtException e) {
assertEquals "JWE 'enc' header parameter value of 'foo' does not match a JWE standard algorithm " +
"identifier. If 'foo' represents a custom algorithm, the JwtParser must be configured " +
"with a custom EncryptionAlgorithmLocator instance that knows how to return a compatible " +
"EncryptionAlgorithm instance. Otherwise, this JWE is invalid and may not be used safely.", e.message
}
}
}

View File

@ -1,6 +1,6 @@
package io.jsonwebtoken.impl.security
import io.jsonwebtoken.io.Decoders
import io.jsonwebtoken.security.EncryptionAlgorithms
import org.junit.Test
@ -70,9 +70,7 @@ class RFC7518AppendixB1Test {
void test() {
def alg = EncryptionAlgorithms.A128CBC_HS256
def request = new DefaultSymmetricAeadRequest(null, null, P, KEY, A, IV)
def result = alg.encrypt(request);
byte[] ciphertext = result.getPayload()
@ -84,33 +82,9 @@ class RFC7518AppendixB1Test {
assertArrayEquals IV, iv //shouldn't have been altered
// now test decryption:
def dreq = new DefaultAeadResult(null, null, ciphertext, KEY, A, tag, iv)
byte[] decryptionResult = alg.decrypt(dreq).getPayload()
assertArrayEquals(P, decryptionResult)
String encryptedCek = 'UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm' +
'1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7Pc' +
'HALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIF' +
'NPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8' +
'rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv' +
'-B3oWh2TbqmScqXMR4gp_A' as String
ciphertext = Decoders.BASE64URL.decode('KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY')
byte[] aad = Decoders.BASE64URL.decode('eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0')
tag = Decoders.BASE64URL.decode('9hH0vgRfYgPnAHOd8stkvw')
iv = Decoders.BASE64URL.decode('AxY8DCtDaGlsbGljb3RoZQ')
SecretKey key = new SecretKeySpec([4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106,
206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156,
44, 207] as byte[], "AES")
dreq = new DefaultAeadResult(null, null, ciphertext, key, aad, tag, iv)
decryptionResult = alg.decrypt(dreq).getPayload()
println decryptionResult
}
}

View File

@ -70,9 +70,7 @@ class RFC7518AppendixB2Test {
void test() {
def alg = EncryptionAlgorithms.A192CBC_HS384
SymmetricAeadRequest req = new DefaultSymmetricAeadRequest(null, null, P, KEY, A, IV)
AeadResult result = alg.encrypt(req)
byte[] resultCiphertext = result.getPayload()
@ -86,7 +84,6 @@ class RFC7518AppendixB2Test {
// now test decryption:
def dreq = new DefaultAeadResult(null, null, resultCiphertext, KEY, A, resultTag, resultIv)
byte[] decryptionResult = alg.decrypt(dreq).getPayload()
assertArrayEquals(P, decryptionResult)
}
}

View File

@ -70,9 +70,7 @@ class RFC7518AppendixB3Test {
void test() {
def alg = EncryptionAlgorithms.A256CBC_HS512
SymmetricAeadRequest req = new DefaultSymmetricAeadRequest(null, null, P, KEY, A, IV)
AeadResult result = alg.encrypt(req)
byte[] resultCiphertext = result.getPayload()

View File

@ -1,8 +1,7 @@
package io.jsonwebtoken.security
import io.jsonwebtoken.impl.security.DefaultSymmetricAeadRequest
import io.jsonwebtoken.impl.security.DefaultAeadResult
import io.jsonwebtoken.impl.security.EncryptionAlgorithm
import io.jsonwebtoken.impl.security.DefaultSymmetricAeadRequest
import io.jsonwebtoken.impl.security.GcmAesAeadAlgorithm
import org.junit.Test

View File

@ -4,9 +4,7 @@ import org.junit.Test
import java.security.Key
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertSame
import static org.junit.Assert.assertTrue
import static org.junit.Assert.*
class KeyAlgorithmsTest {
@ -33,22 +31,12 @@ class KeyAlgorithmsTest {
}
@Test
void testForNameExactId() {
assertSame KeyAlgorithms.A128KW, KeyAlgorithms.forName('A128KW')
void testFindByExactId() {
assertSame KeyAlgorithms.A128KW, KeyAlgorithms.findById('A128KW')
}
@Test
void testForNameCaseInsensitive() {
assertSame KeyAlgorithms.A128GCMKW, KeyAlgorithms.forName('a128GcMkW')
}
@Test
void testForNameUnrecognizedId() {
try {
KeyAlgorithms.forName('foo')
} catch (IllegalArgumentException iae) {
String msg = "Unrecognized key algorithm id 'foo'"
assertEquals msg, iae.getMessage()
}
void testFindByIdCaseInsensitive() {
assertSame KeyAlgorithms.A128GCMKW, KeyAlgorithms.findById('a128GcMkW')
}
}

View File

@ -1,8 +1,9 @@
package io.jsonwebtoken.security
import static org.junit.Assert.*
import org.junit.Test
import static org.junit.Assert.assertSame
class SignatureAlgorithmsTest {
@Test
@ -13,7 +14,7 @@ class SignatureAlgorithmsTest {
@Test
void testForNameCaseInsensitive() {
for(SignatureAlgorithm alg : SignatureAlgorithms.STANDARD_ALGORITHMS.values()) {
assertSame alg, SignatureAlgorithms.forName(alg.getId().toLowerCase())
assertSame alg, SignatureAlgorithms.forId(alg.getId().toLowerCase())
}
}
}