Merge pull request #368 from jwtk/366-builder-signing-key

Added JwtBuilder#signWith(Key) with tests and refactoring.
This commit is contained in:
Les Hazlewood 2018-07-28 00:13:23 -04:00 committed by GitHub
commit f26831cf16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 643 additions and 106 deletions

View File

@ -15,9 +15,13 @@
*/ */
package io.jsonwebtoken; package io.jsonwebtoken;
import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoder; import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.io.Serializer;
import io.jsonwebtoken.security.Keys;
import java.security.InvalidKeyException;
import java.security.Key; import java.security.Key;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
@ -139,7 +143,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @return the builder instance for method chaining. * @return the builder instance for method chaining.
* @since 0.2 * @since 0.2
*/ */
@Override //only for better/targeted JavaDoc @Override
//only for better/targeted JavaDoc
JwtBuilder setIssuer(String iss); JwtBuilder setIssuer(String iss);
/** /**
@ -165,7 +170,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @return the builder instance for method chaining. * @return the builder instance for method chaining.
* @since 0.2 * @since 0.2
*/ */
@Override //only for better/targeted JavaDoc @Override
//only for better/targeted JavaDoc
JwtBuilder setSubject(String sub); JwtBuilder setSubject(String sub);
/** /**
@ -191,7 +197,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @return the builder instance for method chaining. * @return the builder instance for method chaining.
* @since 0.2 * @since 0.2
*/ */
@Override //only for better/targeted JavaDoc @Override
//only for better/targeted JavaDoc
JwtBuilder setAudience(String aud); JwtBuilder setAudience(String aud);
/** /**
@ -219,7 +226,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @return the builder instance for method chaining. * @return the builder instance for method chaining.
* @since 0.2 * @since 0.2
*/ */
@Override //only for better/targeted JavaDoc @Override
//only for better/targeted JavaDoc
JwtBuilder setExpiration(Date exp); JwtBuilder setExpiration(Date exp);
/** /**
@ -247,7 +255,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @return the builder instance for method chaining. * @return the builder instance for method chaining.
* @since 0.2 * @since 0.2
*/ */
@Override //only for better/targeted JavaDoc @Override
//only for better/targeted JavaDoc
JwtBuilder setNotBefore(Date nbf); JwtBuilder setNotBefore(Date nbf);
/** /**
@ -275,7 +284,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @return the builder instance for method chaining. * @return the builder instance for method chaining.
* @since 0.2 * @since 0.2
*/ */
@Override //only for better/targeted JavaDoc @Override
//only for better/targeted JavaDoc
JwtBuilder setIssuedAt(Date iat); JwtBuilder setIssuedAt(Date iat);
/** /**
@ -305,7 +315,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @return the builder instance for method chaining. * @return the builder instance for method chaining.
* @since 0.2 * @since 0.2
*/ */
@Override //only for better/targeted JavaDoc @Override
//only for better/targeted JavaDoc
JwtBuilder setId(String jti); JwtBuilder setId(String jti);
/** /**
@ -326,21 +337,53 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* </pre> * </pre>
* <p>if desired.</p> * <p>if desired.</p>
* *
* @param name the JWT Claims property name * @param name the JWT Claims property name
* @param value the value to set for the specified Claims property name * @param value the value to set for the specified Claims property name
* @return the builder instance for method chaining. * @return the builder instance for method chaining.
* @since 0.2 * @since 0.2
*/ */
JwtBuilder claim(String name, Object value); JwtBuilder claim(String name, Object value);
/**
* Signs the constructed JWT with the specified key using the key's
* {@link SignatureAlgorithm#forSigningKey(Key) recommended signature algorithm}, producing a JWS. If the
* recommended signature algorithm isn't sufficient for your needs, consider using
* {@link #signWith(Key, SignatureAlgorithm)} instead.
*
* <p>If you are looking to invoke this method with a byte array that you are confident may be used for HMAC-SHA
* algorithms, consider using {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to
* convert the byte array into a valid {@code Key}.</p>
*
* @param key the key to use for signing
* @return the builder instance for method chaining.
* @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as
* described by {@link SignatureAlgorithm#forSigningKey(Key)}.
* @see #signWith(Key, SignatureAlgorithm)
* @since 0.10.0
*/
JwtBuilder signWith(Key key) throws InvalidKeyException;
/** /**
* Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS.
* *
* <h4>Deprecation Notice: Deprecated as of 0.10.0</h4>
*
* <p>Use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to
* obtain the {@code Key} and then invoke {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)}.</p>
*
* <p>This method will be removed in the 1.0 release.</p>
*
* @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS.
* @param secretKey the algorithm-specific signing key to use to digitally sign the JWT. * @param secretKey the algorithm-specific signing key to use to digitally sign the JWT.
* @return the builder for method chaining. * @return the builder for method chaining.
* @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as
* described by {@link SignatureAlgorithm#forSigningKey(Key)}.
* @deprecated as of 0.10.0: use {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(bytes)} to
* obtain the {@code Key} and then invoke {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)}.
* This method will be removed in the 1.0 release.
*/ */
JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey); @Deprecated
JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey) throws InvalidKeyException;
/** /**
* Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS.
@ -348,7 +391,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* <p>This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting * <p>This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting
* byte array is used to invoke {@link #signWith(SignatureAlgorithm, byte[])}.</p> * byte array is used to invoke {@link #signWith(SignatureAlgorithm, byte[])}.</p>
* *
* <h4>Deprecation Notice: Deprecated as of 0.10.0, will be removed in 1.0.0</h4> * <h4>Deprecation Notice: Deprecated as of 0.10.0, will be removed in the 1.0 release.</h4>
* *
* <p>This method has been deprecated because the {@code key} argument for this method can be confusing: keys for * <p>This method has been deprecated because the {@code key} argument for this method can be confusing: keys for
* cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were * cryptographic operations are always binary (byte arrays), and many people were confused as to how bytes were
@ -368,26 +411,63 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* StackOverflow answer</a> explaining why raw (non-base64-encoded) strings are almost always incorrect for * StackOverflow answer</a> explaining why raw (non-base64-encoded) strings are almost always incorrect for
* signature operations.</p> * signature operations.</p>
* *
* <p>Finally, please use the {@link #signWith(SignatureAlgorithm, Key)} method, as this method and the * <p>To perform the correct logic with base64EncodedSecretKey strings with JJWT >= 0.10.0, you may do this:
* {@code byte[]} variant will be removed before the 1.0.0 release.</p> * <pre><code>
* byte[] keyBytes = {@link Decoders Decoders}.{@link Decoders#BASE64 BASE64}.{@link Decoder#decode(Object) decode(base64EncodedSecretKey)};
* Key key = {@link Keys Keys}.{@link Keys#hmacShaKeyFor(byte[]) hmacShaKeyFor(keyBytes)};
* jwtBuilder.signWith(key); //or {@link #signWith(Key, SignatureAlgorithm)}
* </code></pre>
* </p>
*
* <p>This method will be removed in the 1.0 release.</p>
* *
* @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS.
* @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signing key to use to digitally sign the * @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signing key to use to digitally sign the
* JWT. * JWT.
* @return the builder for method chaining. * @return the builder for method chaining.
* @deprecated as of 0.10.0 - use {@link #signWith(SignatureAlgorithm, Key)} instead. * @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as
* described by {@link SignatureAlgorithm#forSigningKey(Key)}.
* @deprecated as of 0.10.0: use {@link #signWith(Key)} or {@link #signWith(Key, SignatureAlgorithm)} instead. This
* method will be removed in the 1.0 release.
*/ */
@Deprecated @Deprecated
JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey); JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) throws InvalidKeyException;
/** /**
* Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS. * Signs the constructed JWT using the specified algorithm with the specified key, producing a JWS.
* *
* <p>It is typically recommended to call the {@link #signWith(Key)} instead for simplicity.
* However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if
* you want explicit control over the signature algorithm used with the specified key.</p>
*
* @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS. * @param alg the JWS algorithm to use to digitally sign the JWT, thereby producing a JWS.
* @param key the algorithm-specific signing key to use to digitally sign the JWT. * @param key the algorithm-specific signing key to use to digitally sign the JWT.
* @return the builder for method chaining. * @return the builder for method chaining.
* @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for
* the specified algorithm.
* @see #signWith(Key)
* @deprecated since 0.10.0: use {@link #signWith(Key, SignatureAlgorithm)} instead. This method will be removed
* in the 1.0 release.
*/ */
JwtBuilder signWith(SignatureAlgorithm alg, Key key); @Deprecated
JwtBuilder signWith(SignatureAlgorithm alg, Key key) throws InvalidKeyException;
/**
* Signs the constructed JWT with the specified key using the specified algorithm, producing a JWS.
*
* <p>It is typically recommended to call the {@link #signWith(Key)} instead for simplicity.
* However, this method can be useful if the recommended algorithm heuristics do not meet your needs or if
* you want explicit control over the signature algorithm used with the specified key.</p>
*
* @param key the signing key to use to digitally sign the JWT.
* @param alg the JWS algorithm to use with the key to digitally sign the JWT, thereby producing a JWS.
* @return the builder for method chaining.
* @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification for
* the specified algorithm.
* @see #signWith(Key)
* @since 0.10.0
*/
JwtBuilder signWith(Key key, SignatureAlgorithm alg) throws InvalidKeyException;
/** /**
* Compresses the JWT body using the specified {@link CompressionCodec}. * Compresses the JWT body using the specified {@link CompressionCodec}.
@ -407,10 +487,9 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* <p>Compression when creating JWE tokens however should be universally accepted for any * <p>Compression when creating JWE tokens however should be universally accepted for any
* library that supports JWE.</p> * library that supports JWE.</p>
* *
* @see io.jsonwebtoken.CompressionCodecs
*
* @param codec implementation of the {@link CompressionCodec} to be used. * @param codec implementation of the {@link CompressionCodec} to be used.
* @return the builder for method chaining. * @return the builder for method chaining.
* @see io.jsonwebtoken.CompressionCodecs
* @since 0.6.0 * @since 0.6.0
*/ */
JwtBuilder compressWith(CompressionCodec codec); JwtBuilder compressWith(CompressionCodec codec);
@ -439,7 +518,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
* @return the builder for method chaining. * @return the builder for method chaining.
* @since 0.10.0 * @since 0.10.0
*/ */
JwtBuilder serializeToJsonWith(Serializer<Map<String,?>> serializer); JwtBuilder serializeToJsonWith(Serializer<Map<String, ?>> serializer);
/** /**
* Actually builds the JWT and serializes it to a compact, URL-safe string according to the * Actually builds the JWT and serializes it to a compact, URL-safe string according to the

View File

@ -26,6 +26,9 @@ import java.security.Key;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.interfaces.ECKey; import java.security.interfaces.ECKey;
import java.security.interfaces.RSAKey; import java.security.interfaces.RSAKey;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** /**
* Type-safe representation of standard JWT signature algorithm names as defined in the * Type-safe representation of standard JWT signature algorithm names as defined in the
@ -116,6 +119,13 @@ public enum SignatureAlgorithm {
RuntimeEnvironment.enableBouncyCastleIfPossible(); RuntimeEnvironment.enableBouncyCastleIfPossible();
} }
//purposefully ordered higher to lower:
private static final List<SignatureAlgorithm> PREFERRED_HMAC_ALGS = Collections.unmodifiableList(Arrays.asList(
SignatureAlgorithm.HS512, SignatureAlgorithm.HS384, SignatureAlgorithm.HS256));
//purposefully ordered higher to lower:
private static final List<SignatureAlgorithm> PREFERRED_EC_ALGS = Collections.unmodifiableList(Arrays.asList(
SignatureAlgorithm.ES512, SignatureAlgorithm.ES384, SignatureAlgorithm.ES256));
private final String value; private final String value;
private final String description; private final String description;
private final String familyName; private final String familyName;
@ -278,6 +288,18 @@ public enum SignatureAlgorithm {
return familyName.equals("ECDSA"); return familyName.equals("ECDSA");
} }
/**
* Returns the minimum key length in bits (not bytes) that may be used with this algorithm according to the
* <a href="https://tools.ietf.org/html/rfc7518">JWT JWA Specification (RFC 7518)</a>.
*
* @return the minimum key length in bits (not bytes) that may be used with this algorithm according to the
* <a href="https://tools.ietf.org/html/rfc7518">JWT JWA Specification (RFC 7518)</a>.
* @since 0.10.0
*/
public int getMinKeyLength() {
return this.minKeyLength;
}
/** /**
* Returns quietly if the specified key is allowed to create signatures using this algorithm * Returns quietly if the specified key is allowed to create signatures using this algorithm
* according to the <a href="https://tools.ietf.org/html/rfc7518">JWT JWA Specification (RFC 7518)</a> or throws an * according to the <a href="https://tools.ietf.org/html/rfc7518">JWT JWA Specification (RFC 7518)</a> or throws an
@ -393,7 +415,7 @@ public enum SignatureAlgorithm {
throw new InvalidKeyException(msg); throw new InvalidKeyException(msg);
} }
RSAKey rsaKey = (RSAKey)key; RSAKey rsaKey = (RSAKey) key;
int size = rsaKey.getModulus().bitLength(); int size = rsaKey.getModulus().bitLength();
if (size < this.minKeyLength) { if (size < this.minKeyLength) {
@ -412,6 +434,194 @@ public enum SignatureAlgorithm {
} }
} }
/**
* Returns the recommended signature algorithm to be used with the specified key according to the following
* heuristics:
*
* <table>
* <caption>Key Signature Algorithm</caption>
* <thead>
* <tr>
* <th>If the Key is a:</th>
* <th>And:</th>
* <th>With a key size of:</th>
* <th>The returned SignatureAlgorithm will be:</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>{@link SecretKey}</td>
* <td><code>{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA256")</code><sup>1</sup></td>
* <td>256 &lt;= size &lt;= 383 <sup>2</sup></td>
* <td>{@link SignatureAlgorithm#HS256 HS256}</td>
* </tr>
* <tr>
* <td>{@link SecretKey}</td>
* <td><code>{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA384")</code><sup>1</sup></td>
* <td>384 &lt;= size &lt;= 511</td>
* <td>{@link SignatureAlgorithm#HS384 HS384}</td>
* </tr>
* <tr>
* <td>{@link SecretKey}</td>
* <td><code>{@link Key#getAlgorithm() getAlgorithm()}.equals("HmacSHA512")</code><sup>1</sup></td>
* <td>512 &lt;= size</td>
* <td>{@link SignatureAlgorithm#HS512 HS512}</td>
* </tr>
* <tr>
* <td>{@link ECKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>256 &lt;= size &lt;= 383 <sup>3</sup></td>
* <td>{@link SignatureAlgorithm#ES256 ES256}</td>
* </tr>
* <tr>
* <td>{@link ECKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>384 &lt;= size &lt;= 511</td>
* <td>{@link SignatureAlgorithm#ES384 ES384}</td>
* </tr>
* <tr>
* <td>{@link ECKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>4096 &lt;= size</td>
* <td>{@link SignatureAlgorithm#ES512 ES512}</td>
* </tr>
* <tr>
* <td>{@link RSAKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>2048 &lt;= size &lt;= 3071 <sup>4,5</sup></td>
* <td>{@link SignatureAlgorithm#RS256 RS256}</td>
* </tr>
* <tr>
* <td>{@link RSAKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>3072 &lt;= size &lt;= 4095 <sup>5</sup></td>
* <td>{@link SignatureAlgorithm#RS384 RS384}</td>
* </tr>
* <tr>
* <td>{@link RSAKey}</td>
* <td><code>instanceof {@link PrivateKey}</code></td>
* <td>4096 &lt;= size <sup>5</sup></td>
* <td>{@link SignatureAlgorithm#RS512 RS512}</td>
* </tr>
* </tbody>
* </table>
* <p>Notes:</p>
* <ol>
* <li>{@code SecretKey} instances must have an {@link Key#getAlgorithm() algorithm} name equal
* to {@code HmacSHA256}, {@code HmacSHA384} or {@code HmacSHA512}. If not, the key bytes might not be
* suitable for HMAC signatures will be rejected with a {@link InvalidKeyException}. </li>
* <li>The JWT <a href="https://tools.ietf.org/html/rfc7518#section-3.2">JWA Specification (RFC 7518,
* Section 3.2)</a> mandates that HMAC-SHA-* signing keys <em>MUST</em> be 256 bits or greater.
* {@code SecretKey}s with key lengths less than 256 bits will be rejected with an
* {@link WeakKeyException}.</li>
* <li>The JWT <a href="https://tools.ietf.org/html/rfc7518#section-3.4">JWA Specification (RFC 7518,
* Section 3.4)</a> mandates that ECDSA signing key lengths <em>MUST</em> be 256 bits or greater.
* {@code ECKey}s with key lengths less than 256 bits will be rejected with a
* {@link WeakKeyException}.</li>
* <li>The JWT <a href="https://tools.ietf.org/html/rfc7518#section-3.3">JWA Specification (RFC 7518,
* Section 3.3)</a> mandates that RSA signing key lengths <em>MUST</em> be 2048 bits or greater.
* {@code RSAKey}s with key lengths less than 2048 bits will be rejected with a
* {@link WeakKeyException}.</li>
* <li>Technically any RSA key of length >= 2048 bits may be used with the {@link #RS256}, {@link #RS384}, and
* {@link #RS512} algorithms, so we assume an RSA signature algorithm based on the key length to
* parallel similar decisions in the JWT specification for HMAC and ECDSA signature algorithms.
* This is not required - just a convenience.</li>
* </ol>
* <p>This implementation does not return the {@link #PS256}, {@link #PS256}, {@link #PS256} RSA variant for any
* specified {@link RSAKey} because:
* <ul>
* <li>The JWT <a href="https://tools.ietf.org/html/rfc7518#section-3.1">JWA Specification (RFC 7518,
* Section 3.1)</a> indicates that {@link #RS256}, {@link #RS384}, and {@link #RS512} are
* recommended algorithms while the {@code PS}* variants are simply marked as optional.</li>
* <li>The {@link #RS256}, {@link #RS384}, and {@link #RS512} algorithms are available in the JDK by default
* while the {@code PS}* variants require an additional JCA Provider (like BouncyCastle).</li>
* </ul>
* </p>
*
* <p>Finally, this method will throw an {@link InvalidKeyException} for any key that does not match the
* heuristics and requirements documented above, since that inevitably means the Key is either insufficient or
* explicitly disallowed by the JWT specification.</p>
*
* @param key the key to inspect
* @return the recommended signature algorithm to be used with the specified key
* @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.
* @since 0.10.0
*/
public static SignatureAlgorithm forSigningKey(Key key) throws InvalidKeyException {
if (key == null) {
throw new InvalidKeyException("Key argument cannot be null.");
}
if (!(key instanceof SecretKey ||
(key instanceof PrivateKey && (key instanceof ECKey || key instanceof RSAKey)))) {
String msg = "JWT standard signing algorithms require either 1) a SecretKey for HMAC-SHA algorithms or " +
"2) a private RSAKey for RSA algorithms or 3) a private ECKey for Elliptic Curve algorithms. " +
"The specified key is of type " + key.getClass().getName();
throw new InvalidKeyException(msg);
}
if (key instanceof SecretKey) {
SecretKey secretKey = (SecretKey)key;
String secretKeyAlg = secretKey.getAlgorithm();
for(SignatureAlgorithm alg : PREFERRED_HMAC_ALGS) {
if (alg.jcaName.equals(secretKeyAlg)) {
alg.assertValidSigningKey(key);
return alg;
}
}
String msg = "The specified SecretKey algorithm did not equal one of the three required JCA " +
"algorithm names of HmacSHA256, HmacSHA384, or HmacSHA512.";
throw new InvalidKeyException(msg);
}
if (key instanceof RSAKey) {
RSAKey rsaKey = (RSAKey) key;
int bitLength = rsaKey.getModulus().bitLength();
if (bitLength >= 4096) {
RS512.assertValidSigningKey(key);
return RS512;
} else if (bitLength >= 3072) {
RS384.assertValidSigningKey(key);
return RS384;
} else if (bitLength >= RS256.minKeyLength) {
RS256.assertValidSigningKey(key);
return RS256;
}
String msg = "The specified RSA signing key is not strong enough to be used with JWT RSA signature " +
"algorithms. The JWT specification requires RSA keys to be >= 2048 bits long. The specified RSA " +
"key is " + bitLength + " bits. See https://tools.ietf.org/html/rfc7518#section-3.3 for more " +
"information.";
throw new WeakKeyException(msg);
}
// if we've made it this far in the method, the key is an ECKey due to the instanceof assertions at the
// top of the method
ECKey ecKey = (ECKey) key;
int bitLength = ecKey.getParams().getOrder().bitLength();
for (SignatureAlgorithm alg : PREFERRED_EC_ALGS) {
if (bitLength >= alg.minKeyLength) {
alg.assertValidSigningKey(key);
return alg;
}
}
String msg = "The specified Elliptic Curve signing key is not strong enough to be used with JWT ECDSA " +
"signature algorithms. The JWT specification requires ECDSA keys to be >= 256 bits long. " +
"The specified ECDSA key is " + bitLength + " bits. See " +
"https://tools.ietf.org/html/rfc7518#section-3.4 for more information.";
throw new WeakKeyException(msg);
}
/** /**
* Looks up and returns the corresponding {@code SignatureAlgorithm} enum instance based on a * Looks up and returns the corresponding {@code SignatureAlgorithm} enum instance based on a
* case-<em>insensitive</em> name comparison. * case-<em>insensitive</em> name comparison.

View File

@ -5,7 +5,11 @@ import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Classes; import io.jsonwebtoken.lang.Classes;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.KeyPair; import java.security.KeyPair;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** /**
* Utility class for securely generating {@link SecretKey}s and {@link KeyPair}s. * Utility class for securely generating {@link SecretKey}s and {@link KeyPair}s.
@ -20,6 +24,10 @@ public final class Keys {
private static final Class[] SIG_ARG_TYPES = new Class[]{SignatureAlgorithm.class}; private static final Class[] SIG_ARG_TYPES = new Class[]{SignatureAlgorithm.class};
//purposefully ordered higher to lower:
private static final List<SignatureAlgorithm> PREFERRED_HMAC_ALGS = Collections.unmodifiableList(Arrays.asList(
SignatureAlgorithm.HS512, SignatureAlgorithm.HS384, SignatureAlgorithm.HS256));
//prevent instantiation //prevent instantiation
private Keys() { private Keys() {
} }
@ -40,6 +48,39 @@ public final class Keys {
} }
*/ */
/**
* Creates a new SecretKey instance for use with HMAC-SHA algorithms based on the specified key byte array.
*
* @param bytes the key byte array
* @return a new SecretKey instance for use with HMAC-SHA algorithms based on the specified key byte array.
* @throws WeakKeyException if the key byte array length is less than 256 bits (32 bytes) as mandated by the
* <a href="https://tools.ietf.org/html/rfc7518#section-3.2">JWT JWA Specification
* (RFC 7518, Section 3.2)</a>
*/
public static SecretKey hmacShaKeyFor(byte[] bytes) throws WeakKeyException {
if (bytes == null) {
throw new InvalidKeyException("SecretKey byte array cannot be null.");
}
int bitLength = bytes.length * 8;
for (SignatureAlgorithm alg : PREFERRED_HMAC_ALGS) {
if (bitLength >= alg.getMinKeyLength()) {
return new SecretKeySpec(bytes, alg.getJcaName());
}
}
String msg = "The specified key byte array is " + bitLength + " bits which " +
"is not secure enough for any JWT HMAC-SHA algorithm. The JWT " +
"JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a " +
"size >= 256 bits (the key size must be greater than or equal to the hash " +
"output size). Consider using the " + Keys.class.getName() + "#secretKeyFor(SignatureAlgorithm) method " +
"to create a key guaranteed to be secure enough for your preferred HMAC-SHA algorithm. See " +
"https://tools.ietf.org/html/rfc7518#section-3.2 for more information.";
throw new WeakKeyException(msg);
}
/** /**
* Returns a new {@link SecretKey} with a key length suitable for use with the specified {@link SignatureAlgorithm}. * Returns a new {@link SecretKey} with a key length suitable for use with the specified {@link SignatureAlgorithm}.
* *

View File

@ -18,9 +18,11 @@ package io.jsonwebtoken
import io.jsonwebtoken.security.InvalidKeyException import io.jsonwebtoken.security.InvalidKeyException
import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.Keys
import io.jsonwebtoken.security.SignatureException import io.jsonwebtoken.security.SignatureException
import io.jsonwebtoken.security.WeakKeyException
import org.junit.Test import org.junit.Test
import javax.crypto.SecretKey import javax.crypto.SecretKey
import javax.crypto.spec.SecretKeySpec
import java.security.Key import java.security.Key
import java.security.PrivateKey import java.security.PrivateKey
import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPrivateKey
@ -34,8 +36,6 @@ import static org.junit.Assert.*
class SignatureAlgorithmTest { class SignatureAlgorithmTest {
private static final Random random = new Random() //does not need to be secure for testing
@Test @Test
void testNames() { void testNames() {
def algNames = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', def algNames = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512',
@ -129,6 +129,171 @@ class SignatureAlgorithmTest {
} }
} }
@Test
void testGetMinKeyLength() {
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
if (alg == SignatureAlgorithm.NONE) {
assertEquals 0, alg.getMinKeyLength()
} else {
if (alg.isRsa()) {
assertEquals 2048, alg.getMinKeyLength()
} else {
int num = alg.name().substring(2, 5).toInteger()
if (alg == SignatureAlgorithm.ES512) {
num = 521
}
assertEquals num, alg.getMinKeyLength()
}
}
}
}
@Test
void testForSigningKeyNullArgument() {
try {
SignatureAlgorithm.forSigningKey(null)
} catch (InvalidKeyException expected) {
assertEquals 'Key argument cannot be null.', expected.message
}
}
@Test
void testForSigningKeyInvalidType() {
def key = new Key() {
@Override
String getAlgorithm() {
return null
}
@Override
String getFormat() {
return null
}
@Override
byte[] getEncoded() {
return new byte[0]
}
}
try {
SignatureAlgorithm.forSigningKey(key)
fail()
} catch (InvalidKeyException expected) {
assertTrue expected.getMessage().startsWith("JWT standard signing algorithms require either 1) a " +
"SecretKey for HMAC-SHA algorithms or 2) a private RSAKey for RSA algorithms or 3) a private " +
"ECKey for Elliptic Curve algorithms. The specified key is of type ")
}
}
@Test
void testForSigningKeySecretKeyInvalidAlgName() {
try {
SignatureAlgorithm.forSigningKey(new SecretKeySpec(new byte[1], 'AES'))
fail()
} catch (InvalidKeyException e) {
assertEquals "The specified SecretKey algorithm did not equal one of the three required JCA " +
"algorithm names of HmacSHA256, HmacSHA384, or HmacSHA512.", e.message
}
}
@Test
void testForSigningKeySecretKeyWeakKey() {
try {
SignatureAlgorithm.forSigningKey(new SecretKeySpec(new byte[1], 'HmacSHA256'))
fail()
} catch (WeakKeyException expected) {
}
}
@Test
void testForSigningKeySecretKeyHappyPath() {
for(SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) {
int numBytes = alg.minKeyLength / 8 as int
assertEquals alg, SignatureAlgorithm.forSigningKey(Keys.hmacShaKeyFor(new byte[numBytes]))
}
}
@Test
void testForSigningKeyRSAWeakKey() {
RSAPrivateKey key = createMock(RSAPrivateKey)
BigInteger modulus = createMock(BigInteger)
expect(key.getModulus()).andStubReturn(modulus)
expect(modulus.bitLength()).andStubReturn(1024)
replay key, modulus
try {
SignatureAlgorithm.forSigningKey(key)
fail()
} catch (WeakKeyException expected) {
}
verify key, modulus
}
@Test
void testForSigningKeyRSAHappyPath() {
for(SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.name().startsWith("RS") }) {
int heuristicKeyLength = (alg == SignatureAlgorithm.RS512 ? 4096 : (alg == SignatureAlgorithm.RS384 ? 3072 : 2048))
RSAPrivateKey key = createMock(RSAPrivateKey)
BigInteger modulus = createMock(BigInteger)
expect(key.getModulus()).andStubReturn(modulus)
expect(modulus.bitLength()).andStubReturn(heuristicKeyLength)
replay key, modulus
assertEquals alg, SignatureAlgorithm.forSigningKey(key)
verify key, modulus
}
}
@Test
void testForSigningKeyECWeakKey() {
ECPrivateKey key = createMock(ECPrivateKey)
ECParameterSpec spec = createMock(ECParameterSpec)
BigInteger order = createMock(BigInteger)
expect(key.getParams()).andStubReturn(spec)
expect(spec.getOrder()).andStubReturn(order)
expect(order.bitLength()).andReturn(128)
replay key, spec, order
try {
SignatureAlgorithm.forSigningKey(key)
fail()
} catch (WeakKeyException expected) {
}
verify key, spec, order
}
@Test
void testForSigningKeyECHappyPath() {
for(SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) {
ECPrivateKey key = createMock(ECPrivateKey)
ECParameterSpec spec = createMock(ECParameterSpec)
BigInteger order = createMock(BigInteger)
expect(key.getParams()).andStubReturn(spec)
expect(spec.getOrder()).andStubReturn(order)
expect(order.bitLength()).andStubReturn(alg.minKeyLength)
replay key, spec, order
assertEquals alg, SignatureAlgorithm.forSigningKey(key)
verify key, spec, order
}
}
@Test @Test
void testAssertValidSigningKeyWithNoneAlgorithm() { void testAssertValidSigningKeyWithNoneAlgorithm() {
Key key = createMock(Key) Key key = createMock(Key)
@ -279,19 +444,16 @@ class SignatureAlgorithmTest {
ECPrivateKey key = createMock(ECPrivateKey) ECPrivateKey key = createMock(ECPrivateKey)
ECParameterSpec spec = createMock(ECParameterSpec) ECParameterSpec spec = createMock(ECParameterSpec)
int numBits = alg.minKeyLength BigInteger order = createMock(BigInteger)
int numBytes = numBits / 8 as int expect(key.getParams()).andStubReturn(spec)
byte[] orderBytes = new byte[numBytes + 1] expect(spec.getOrder()).andStubReturn(order)
random.nextBytes(orderBytes) expect(order.bitLength()).andStubReturn(alg.minKeyLength)
BigInteger order = new BigInteger(orderBytes)
expect(key.getParams()).andReturn(spec)
expect(spec.getOrder()).andReturn(order)
replay key, spec replay key, spec, order
alg.assertValidSigningKey(key) alg.assertValidSigningKey(key)
verify key, spec verify key, spec, order
} }
} }
@ -338,15 +500,12 @@ class SignatureAlgorithmTest {
ECPrivateKey key = createMock(ECPrivateKey) ECPrivateKey key = createMock(ECPrivateKey)
ECParameterSpec spec = createMock(ECParameterSpec) ECParameterSpec spec = createMock(ECParameterSpec)
int numBits = alg.minKeyLength - 8 // 8 bits less than expected BigInteger order = createMock(BigInteger)
int numBytes = numBits / 8 as int expect(key.getParams()).andStubReturn(spec)
byte[] orderBytes = new byte[numBytes] expect(spec.getOrder()).andStubReturn(order)
random.nextBytes(orderBytes) expect(order.bitLength()).andStubReturn(alg.minKeyLength - 8) //1 byte less than expected
BigInteger order = new BigInteger(orderBytes)
expect(key.getParams()).andReturn(spec)
expect(spec.getOrder()).andReturn(order)
replay key, spec replay key, spec, order
try { try {
alg.assertValidSigningKey(key) alg.assertValidSigningKey(key)
@ -361,7 +520,7 @@ class SignatureAlgorithmTest {
"https://tools.ietf.org/html/rfc7518#section-3.4 for more information." as String, expected.message "https://tools.ietf.org/html/rfc7518#section-3.4 for more information." as String, expected.message
} }
verify key, spec verify key, spec, order
} }
} }
@ -371,18 +530,15 @@ class SignatureAlgorithmTest {
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) {
RSAPrivateKey key = createMock(RSAPrivateKey) RSAPrivateKey key = createMock(RSAPrivateKey)
int numBits = alg.minKeyLength BigInteger modulus = createMock(BigInteger)
int numBytes = numBits / 8 as int expect(key.getModulus()).andStubReturn(modulus)
byte[] modulusBytes = new byte[numBytes + 1] expect(modulus.bitLength()).andStubReturn(alg.minKeyLength)
random.nextBytes(modulusBytes)
BigInteger modulus = new BigInteger(modulusBytes)
expect(key.getModulus()).andReturn(modulus)
replay key replay key, modulus
alg.assertValidSigningKey(key) alg.assertValidSigningKey(key)
verify key verify key, modulus
} }
} }
@ -430,14 +586,11 @@ class SignatureAlgorithmTest {
String section = alg.name().startsWith("P") ? "3.5" : "3.3" String section = alg.name().startsWith("P") ? "3.5" : "3.3"
RSAPrivateKey key = createMock(RSAPrivateKey) RSAPrivateKey key = createMock(RSAPrivateKey)
int numBits = alg.minKeyLength - 8 BigInteger modulus = createMock(BigInteger)
int numBytes = numBits / 8 as int expect(key.getModulus()).andStubReturn(modulus)
byte[] modulusBytes = new byte[numBytes] expect(modulus.bitLength()).andStubReturn(alg.minKeyLength - 8) // 1 less byte
random.nextBytes(modulusBytes)
BigInteger modulus = new BigInteger(modulusBytes)
expect(key.getModulus()).andReturn(modulus)
replay key replay key, modulus
try { try {
alg.assertValidSigningKey(key) alg.assertValidSigningKey(key)
@ -452,7 +605,7 @@ class SignatureAlgorithmTest {
"https://tools.ietf.org/html/rfc7518#section-${section} for more information." as String, expected.message "https://tools.ietf.org/html/rfc7518#section-${section} for more information." as String, expected.message
} }
verify key verify key, modulus
} }
} }
@ -606,19 +759,16 @@ class SignatureAlgorithmTest {
ECPrivateKey key = createMock(ECPrivateKey) ECPrivateKey key = createMock(ECPrivateKey)
ECParameterSpec spec = createMock(ECParameterSpec) ECParameterSpec spec = createMock(ECParameterSpec)
int numBits = alg.minKeyLength BigInteger order = createMock(BigInteger)
int numBytes = numBits / 8 as int expect(key.getParams()).andStubReturn(spec)
byte[] orderBytes = new byte[numBytes + 1] expect(spec.getOrder()).andStubReturn(order)
random.nextBytes(orderBytes) expect(order.bitLength()).andStubReturn(alg.minKeyLength)
BigInteger order = new BigInteger(orderBytes)
expect(key.getParams()).andReturn(spec)
expect(spec.getOrder()).andReturn(order)
replay key, spec replay key, spec, order
alg.assertValidVerificationKey(key) alg.assertValidVerificationKey(key)
verify key, spec verify key, spec, order
} }
} }
@ -645,15 +795,12 @@ class SignatureAlgorithmTest {
ECPrivateKey key = createMock(ECPrivateKey) ECPrivateKey key = createMock(ECPrivateKey)
ECParameterSpec spec = createMock(ECParameterSpec) ECParameterSpec spec = createMock(ECParameterSpec)
int numBits = alg.minKeyLength - 8 // 8 bits = 1 byte BigInteger order = createMock(BigInteger)
int numBytes = numBits / 8 as int expect(key.getParams()).andStubReturn(spec)
byte[] orderBytes = new byte[numBytes] expect(spec.getOrder()).andStubReturn(order)
random.nextBytes(orderBytes) expect(order.bitLength()).andStubReturn(alg.minKeyLength - 8) // 1 less byte
BigInteger order = new BigInteger(orderBytes)
expect(key.getParams()).andReturn(spec)
expect(spec.getOrder()).andReturn(order)
replay key, spec replay key, spec, order
try { try {
alg.assertValidVerificationKey(key) alg.assertValidVerificationKey(key)
@ -668,7 +815,7 @@ class SignatureAlgorithmTest {
"https://tools.ietf.org/html/rfc7518#section-3.4 for more information." as String, expected.message "https://tools.ietf.org/html/rfc7518#section-3.4 for more information." as String, expected.message
} }
verify key, spec verify key, spec, order
} }
} }
@ -678,18 +825,15 @@ class SignatureAlgorithmTest {
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) { for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) {
RSAPrivateKey key = createMock(RSAPrivateKey) RSAPrivateKey key = createMock(RSAPrivateKey)
int numBits = alg.minKeyLength BigInteger modulus = createMock(BigInteger)
int numBytes = numBits / 8 as int expect(key.getModulus()).andStubReturn(modulus)
byte[] modulusBytes = new byte[numBytes + 1] expect(modulus.bitLength()).andStubReturn(alg.minKeyLength)
random.nextBytes(modulusBytes)
BigInteger modulus = new BigInteger(modulusBytes)
expect(key.getModulus()).andReturn(modulus)
replay key replay key, modulus
alg.assertValidVerificationKey(key) alg.assertValidVerificationKey(key)
verify key verify key, modulus
} }
} }
@ -717,14 +861,11 @@ class SignatureAlgorithmTest {
String section = alg.name().startsWith("P") ? "3.5" : "3.3" String section = alg.name().startsWith("P") ? "3.5" : "3.3"
RSAPrivateKey key = createMock(RSAPrivateKey) RSAPrivateKey key = createMock(RSAPrivateKey)
int numBits = alg.minKeyLength - 8 // 8 bits = 1 byte BigInteger modulus = createMock(BigInteger)
int numBytes = numBits / 8 as int expect(key.getModulus()).andStubReturn(modulus)
byte[] modulusBytes = new byte[numBytes] expect(modulus.bitLength()).andStubReturn(alg.minKeyLength - 8) //one less byte
random.nextBytes(modulusBytes)
BigInteger modulus = new BigInteger(modulusBytes)
expect(key.getModulus()).andReturn(modulus)
replay key replay key, modulus
try { try {
alg.assertValidVerificationKey(key) alg.assertValidVerificationKey(key)
@ -739,7 +880,7 @@ class SignatureAlgorithmTest {
"https://tools.ietf.org/html/rfc7518#section-${section} for more information." as String, expected.message "https://tools.ietf.org/html/rfc7518#section-${section} for more information." as String, expected.message
} }
verify key verify key, modulus
} }
} }
} }

View File

@ -30,6 +30,32 @@ class KeysTest {
new Keys() new Keys()
} }
@Test
void testHmacShaKeyForWithNullArgument() {
try {
Keys.hmacShaKeyFor(null)
} catch (InvalidKeyException expected) {
assertEquals 'SecretKey byte array cannot be null.', expected.message
}
}
@Test
void testHmacShaKeyForWithWeakKey() {
int numBytes = 31
int numBits = numBytes * 8
try {
Keys.hmacShaKeyFor(new byte[numBytes])
} catch (WeakKeyException expected) {
assertEquals "The specified key byte array is " + numBits + " bits which " +
"is not secure enough for any JWT HMAC-SHA algorithm. The JWT " +
"JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a " +
"size >= 256 bits (the key size must be greater than or equal to the hash " +
"output size). Consider using the " + Keys.class.getName() + "#secretKeyFor(SignatureAlgorithm) method " +
"to create a key guaranteed to be secure enough for your preferred HMAC-SHA algorithm. See " +
"https://tools.ietf.org/html/rfc7518#section-3.2 for more information." as String, expected.message
}
}
@Test @Test
void testSecretKeyFor() { void testSecretKeyFor() {

View File

@ -34,6 +34,7 @@ import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Classes; import io.jsonwebtoken.lang.Classes;
import io.jsonwebtoken.lang.Collections; import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings; import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.InvalidKeyException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
@ -109,16 +110,33 @@ public class DefaultJwtBuilder implements JwtBuilder {
} }
@Override @Override
public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKeyBytes) { public JwtBuilder signWith(Key key) throws InvalidKeyException {
Assert.notNull(key, "Key argument cannot be null.");
SignatureAlgorithm alg = SignatureAlgorithm.forSigningKey(key);
return signWith(key, alg);
}
@Override
public JwtBuilder signWith(Key key, SignatureAlgorithm alg) throws InvalidKeyException {
Assert.notNull(key, "Key argument cannot be null.");
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
alg.assertValidSigningKey(key); //since 0.10.0 for https://github.com/jwtk/jjwt/issues/334
this.algorithm = alg;
this.key = key;
return this;
}
@Override
public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKeyBytes) throws InvalidKeyException {
Assert.notNull(alg, "SignatureAlgorithm cannot be null."); Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
Assert.notEmpty(secretKeyBytes, "secret key byte array cannot be null or empty."); Assert.notEmpty(secretKeyBytes, "secret key byte array cannot be null or empty.");
Assert.isTrue(alg.isHmac(), "Key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead."); Assert.isTrue(alg.isHmac(), "Key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.");
SecretKey key = new SecretKeySpec(secretKeyBytes, alg.getJcaName()); SecretKey key = new SecretKeySpec(secretKeyBytes, alg.getJcaName());
return signWith(alg, key); return signWith(key, alg);
} }
@Override @Override
public JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) { public JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) throws InvalidKeyException {
Assert.hasText(base64EncodedSecretKey, "base64-encoded secret key cannot be null or empty."); Assert.hasText(base64EncodedSecretKey, "base64-encoded secret key cannot be null or empty.");
Assert.isTrue(alg.isHmac(), "Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead."); Assert.isTrue(alg.isHmac(), "Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.");
byte[] bytes = Decoders.BASE64.decode(base64EncodedSecretKey); byte[] bytes = Decoders.BASE64.decode(base64EncodedSecretKey);
@ -127,12 +145,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
@Override @Override
public JwtBuilder signWith(SignatureAlgorithm alg, Key key) { public JwtBuilder signWith(SignatureAlgorithm alg, Key key) {
Assert.notNull(alg, "SignatureAlgorithm cannot be null."); return signWith(key, alg);
Assert.notNull(key, "Key argument cannot be null.");
alg.assertValidSigningKey(key); //since 0.10.0 for https://github.com/jwtk/jjwt/issues/334
this.algorithm = alg;
this.key = key;
return this;
} }
@Override @Override

View File

@ -641,7 +641,7 @@ class JwtParserTest {
SecretKeySpec key = new SecretKeySpec(randomKey(), "HmacSHA256") SecretKeySpec key = new SecretKeySpec(randomKey(), "HmacSHA256")
String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact() String compact = Jwts.builder().setSubject(subject).signWith(key, SignatureAlgorithm.HS256).compact()
def signingKeyResolver = new SigningKeyResolverAdapter() { def signingKeyResolver = new SigningKeyResolverAdapter() {
@Override @Override

View File

@ -579,7 +579,7 @@ class JwtsTest {
def key = Keys.secretKeyFor(alg) def key = Keys.secretKeyFor(alg)
def weakKey = Keys.secretKeyFor(SignatureAlgorithm.HS256) def weakKey = Keys.secretKeyFor(SignatureAlgorithm.HS256)
String jws = Jwts.builder().setSubject("Foo").signWith(alg, key).compact() String jws = Jwts.builder().setSubject("Foo").signWith(key, alg).compact()
try { try {
Jwts.parser().setSigningKey(weakKey).parseClaimsJws(jws) Jwts.parser().setSigningKey(weakKey).parseClaimsJws(jws)
@ -743,7 +743,7 @@ class JwtsTest {
def claims = [iss: 'joe', exp: later(), 'http://example.com/is_root': true] def claims = [iss: 'joe', exp: later(), 'http://example.com/is_root': true]
String jwt = Jwts.builder().setClaims(claims).signWith(alg, privateKey).compact() String jwt = Jwts.builder().setClaims(claims).signWith(privateKey, alg).compact()
def key = publicKey def key = publicKey
if (verifyWithPrivateKey) { if (verifyWithPrivateKey) {
@ -781,7 +781,7 @@ class JwtsTest {
def claims = [iss: 'joe', exp: later(), 'http://example.com/is_root': true] def claims = [iss: 'joe', exp: later(), 'http://example.com/is_root': true]
String jwt = Jwts.builder().setClaims(claims).signWith(alg, privateKey).compact() String jwt = Jwts.builder().setClaims(claims).signWith(privateKey, alg).compact()
def key = publicKey def key = publicKey
if (verifyWithPrivateKey) { if (verifyWithPrivateKey) {

View File

@ -30,7 +30,7 @@ class RsaSigningKeyResolverAdapterTest {
def pair = Keys.keyPairFor(alg) def pair = Keys.keyPairFor(alg)
def compact = Jwts.builder().claim('foo', 'bar').signWith(alg, pair.private).compact() def compact = Jwts.builder().claim('foo', 'bar').signWith(pair.private, alg).compact()
Jws<Claims> jws = Jwts.parser().setSigningKey(pair.public).parseClaimsJws(compact) Jws<Claims> jws = Jwts.parser().setSigningKey(pair.public).parseClaimsJws(compact)

View File

@ -26,6 +26,10 @@ import io.jsonwebtoken.io.Serializer
import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.Keys
import org.junit.Test import org.junit.Test
import javax.crypto.KeyGenerator
import javax.crypto.SecretKeyFactory
import java.security.KeyFactory
import static org.junit.Assert.* import static org.junit.Assert.*
class DefaultJwtBuilderTest { class DefaultJwtBuilderTest {
@ -176,8 +180,12 @@ class DefaultJwtBuilderTest {
b.setPayload('foo') b.setPayload('foo')
def alg = SignatureAlgorithm.HS256 def alg = SignatureAlgorithm.HS256
def key = Keys.secretKeyFor(alg) def key = Keys.secretKeyFor(alg)
b.signWith(key, alg)
String s1 = b.compact()
//ensure deprecated signWith(alg, key) produces the same result:
b.signWith(alg, key) b.signWith(alg, key)
b.compact() String s2 = b.compact()
assertEquals s1, s2
} }
@Test @Test
@ -220,6 +228,25 @@ class DefaultJwtBuilderTest {
} }
} }
@Test
void testSignWithKeyOnly() {
def b = new DefaultJwtBuilder()
b.setHeader(Jwts.jwsHeader().setKeyId('a'))
b.setPayload('foo')
def key = KeyGenerator.getInstance('HmacSHA256').generateKey()
b.signWith(key)
String s1 = b.compact()
//ensure matches same result with specified algorithm:
b.signWith(key, SignatureAlgorithm.HS256)
String s2 = b.compact()
assertEquals s1, s2
}
@Test @Test
void testSignWithBytesWithoutHmac() { void testSignWithBytesWithoutHmac() {
def bytes = new byte[16]; def bytes = new byte[16];
@ -348,7 +375,7 @@ class DefaultJwtBuilderTest {
def key = Keys.secretKeyFor(SignatureAlgorithm.HS256) def key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
String jws = b.signWith(SignatureAlgorithm.HS256, key) String jws = b.signWith(key, SignatureAlgorithm.HS256)
.claim('foo', 'bar') .claim('foo', 'bar')
.compact() .compact()

View File

@ -58,7 +58,7 @@ class DefaultJwtParserTest {
def key = Keys.secretKeyFor(SignatureAlgorithm.HS256) def key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
String jws = Jwts.builder().claim('foo', 'bar').signWith(SignatureAlgorithm.HS256, key).compact() String jws = Jwts.builder().claim('foo', 'bar').signWith(key, SignatureAlgorithm.HS256).compact()
assertEquals 'bar', p.setSigningKey(key).parseClaimsJws(jws).getBody().get('foo') assertEquals 'bar', p.setSigningKey(key).parseClaimsJws(jws).getBody().get('foo')
} }