mirror of https://github.com/jwtk/jjwt.git
Merge pull request #368 from jwtk/366-builder-signing-key
Added JwtBuilder#signWith(Key) with tests and refactoring.
This commit is contained in:
commit
f26831cf16
|
@ -15,9 +15,13 @@
|
|||
*/
|
||||
package io.jsonwebtoken;
|
||||
|
||||
import io.jsonwebtoken.io.Decoder;
|
||||
import io.jsonwebtoken.io.Decoders;
|
||||
import io.jsonwebtoken.io.Encoder;
|
||||
import io.jsonwebtoken.io.Serializer;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
@ -139,7 +143,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
|||
* @return the builder instance for method chaining.
|
||||
* @since 0.2
|
||||
*/
|
||||
@Override //only for better/targeted JavaDoc
|
||||
@Override
|
||||
//only for better/targeted JavaDoc
|
||||
JwtBuilder setIssuer(String iss);
|
||||
|
||||
/**
|
||||
|
@ -165,7 +170,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
|||
* @return the builder instance for method chaining.
|
||||
* @since 0.2
|
||||
*/
|
||||
@Override //only for better/targeted JavaDoc
|
||||
@Override
|
||||
//only for better/targeted JavaDoc
|
||||
JwtBuilder setSubject(String sub);
|
||||
|
||||
/**
|
||||
|
@ -191,7 +197,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
|||
* @return the builder instance for method chaining.
|
||||
* @since 0.2
|
||||
*/
|
||||
@Override //only for better/targeted JavaDoc
|
||||
@Override
|
||||
//only for better/targeted JavaDoc
|
||||
JwtBuilder setAudience(String aud);
|
||||
|
||||
/**
|
||||
|
@ -219,7 +226,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
|||
* @return the builder instance for method chaining.
|
||||
* @since 0.2
|
||||
*/
|
||||
@Override //only for better/targeted JavaDoc
|
||||
@Override
|
||||
//only for better/targeted JavaDoc
|
||||
JwtBuilder setExpiration(Date exp);
|
||||
|
||||
/**
|
||||
|
@ -247,7 +255,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
|||
* @return the builder instance for method chaining.
|
||||
* @since 0.2
|
||||
*/
|
||||
@Override //only for better/targeted JavaDoc
|
||||
@Override
|
||||
//only for better/targeted JavaDoc
|
||||
JwtBuilder setNotBefore(Date nbf);
|
||||
|
||||
/**
|
||||
|
@ -275,7 +284,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
|||
* @return the builder instance for method chaining.
|
||||
* @since 0.2
|
||||
*/
|
||||
@Override //only for better/targeted JavaDoc
|
||||
@Override
|
||||
//only for better/targeted JavaDoc
|
||||
JwtBuilder setIssuedAt(Date iat);
|
||||
|
||||
/**
|
||||
|
@ -305,7 +315,8 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
|||
* @return the builder instance for method chaining.
|
||||
* @since 0.2
|
||||
*/
|
||||
@Override //only for better/targeted JavaDoc
|
||||
@Override
|
||||
//only for better/targeted JavaDoc
|
||||
JwtBuilder setId(String jti);
|
||||
|
||||
/**
|
||||
|
@ -326,21 +337,53 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
|||
* </pre>
|
||||
* <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
|
||||
* @return the builder instance for method chaining.
|
||||
* @since 0.2
|
||||
*/
|
||||
JwtBuilder claim(String name, Object value);
|
||||
|
||||
/**
|
||||
* Signs the constructed JWT with the specified key using the key's
|
||||
* {@link SignatureAlgorithm#forSigningKey(Key) recommended signature algorithm}, producing a JWS. If the
|
||||
* recommended signature algorithm isn't sufficient for your needs, consider using
|
||||
* {@link #signWith(Key, SignatureAlgorithm)} instead.
|
||||
*
|
||||
* <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.
|
||||
*
|
||||
* <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 secretKey the algorithm-specific signing key to use to digitally sign the JWT.
|
||||
* @return the builder for method chaining.
|
||||
* @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification as
|
||||
* described by {@link SignatureAlgorithm#forSigningKey(Key)}.
|
||||
* @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.
|
||||
|
@ -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
|
||||
* 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
|
||||
* 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
|
||||
* signature operations.</p>
|
||||
*
|
||||
* <p>Finally, please use the {@link #signWith(SignatureAlgorithm, Key)} method, as this method and the
|
||||
* {@code byte[]} variant will be removed before the 1.0.0 release.</p>
|
||||
* <p>To perform the correct logic with base64EncodedSecretKey strings with JJWT >= 0.10.0, you may do this:
|
||||
* <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 base64EncodedSecretKey the BASE64-encoded algorithm-specific signing key to use to digitally sign the
|
||||
* JWT.
|
||||
* @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
|
||||
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.
|
||||
*
|
||||
* <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 key the algorithm-specific signing key to use to digitally sign the JWT.
|
||||
* @return the builder for method chaining.
|
||||
* @throws InvalidKeyException if the Key is insufficient or explicitly disallowed by the JWT specification 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}.
|
||||
|
@ -407,10 +487,9 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
|||
* <p>Compression when creating JWE tokens however should be universally accepted for any
|
||||
* library that supports JWE.</p>
|
||||
*
|
||||
* @see io.jsonwebtoken.CompressionCodecs
|
||||
*
|
||||
* @param codec implementation of the {@link CompressionCodec} to be used.
|
||||
* @return the builder for method chaining.
|
||||
* @see io.jsonwebtoken.CompressionCodecs
|
||||
* @since 0.6.0
|
||||
*/
|
||||
JwtBuilder compressWith(CompressionCodec codec);
|
||||
|
@ -439,7 +518,7 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
|||
* @return the builder for method chaining.
|
||||
* @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
|
||||
|
|
|
@ -26,6 +26,9 @@ import java.security.Key;
|
|||
import java.security.PrivateKey;
|
||||
import java.security.interfaces.ECKey;
|
||||
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
|
||||
|
@ -116,6 +119,13 @@ public enum SignatureAlgorithm {
|
|||
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 description;
|
||||
private final String familyName;
|
||||
|
@ -278,6 +288,18 @@ public enum SignatureAlgorithm {
|
|||
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
|
||||
* 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);
|
||||
}
|
||||
|
||||
RSAKey rsaKey = (RSAKey)key;
|
||||
RSAKey rsaKey = (RSAKey) key;
|
||||
int size = rsaKey.getModulus().bitLength();
|
||||
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 <= size <= 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 <= size <= 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 <= size</td>
|
||||
* <td>{@link SignatureAlgorithm#HS512 HS512}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link ECKey}</td>
|
||||
* <td><code>instanceof {@link PrivateKey}</code></td>
|
||||
* <td>256 <= size <= 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 <= size <= 511</td>
|
||||
* <td>{@link SignatureAlgorithm#ES384 ES384}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link ECKey}</td>
|
||||
* <td><code>instanceof {@link PrivateKey}</code></td>
|
||||
* <td>4096 <= size</td>
|
||||
* <td>{@link SignatureAlgorithm#ES512 ES512}</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>{@link RSAKey}</td>
|
||||
* <td><code>instanceof {@link PrivateKey}</code></td>
|
||||
* <td>2048 <= size <= 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 <= size <= 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 <= 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
|
||||
* case-<em>insensitive</em> name comparison.
|
||||
|
|
|
@ -5,7 +5,11 @@ import io.jsonwebtoken.lang.Assert;
|
|||
import io.jsonwebtoken.lang.Classes;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 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};
|
||||
|
||||
//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
|
||||
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}.
|
||||
*
|
||||
|
|
|
@ -18,9 +18,11 @@ package io.jsonwebtoken
|
|||
import io.jsonwebtoken.security.InvalidKeyException
|
||||
import io.jsonwebtoken.security.Keys
|
||||
import io.jsonwebtoken.security.SignatureException
|
||||
import io.jsonwebtoken.security.WeakKeyException
|
||||
import org.junit.Test
|
||||
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import java.security.Key
|
||||
import java.security.PrivateKey
|
||||
import java.security.interfaces.ECPrivateKey
|
||||
|
@ -34,8 +36,6 @@ import static org.junit.Assert.*
|
|||
|
||||
class SignatureAlgorithmTest {
|
||||
|
||||
private static final Random random = new Random() //does not need to be secure for testing
|
||||
|
||||
@Test
|
||||
void testNames() {
|
||||
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
|
||||
void testAssertValidSigningKeyWithNoneAlgorithm() {
|
||||
Key key = createMock(Key)
|
||||
|
@ -279,19 +444,16 @@ class SignatureAlgorithmTest {
|
|||
|
||||
ECPrivateKey key = createMock(ECPrivateKey)
|
||||
ECParameterSpec spec = createMock(ECParameterSpec)
|
||||
int numBits = alg.minKeyLength
|
||||
int numBytes = numBits / 8 as int
|
||||
byte[] orderBytes = new byte[numBytes + 1]
|
||||
random.nextBytes(orderBytes)
|
||||
BigInteger order = new BigInteger(orderBytes)
|
||||
expect(key.getParams()).andReturn(spec)
|
||||
expect(spec.getOrder()).andReturn(order)
|
||||
BigInteger order = createMock(BigInteger)
|
||||
expect(key.getParams()).andStubReturn(spec)
|
||||
expect(spec.getOrder()).andStubReturn(order)
|
||||
expect(order.bitLength()).andStubReturn(alg.minKeyLength)
|
||||
|
||||
replay key, spec
|
||||
replay key, spec, order
|
||||
|
||||
alg.assertValidSigningKey(key)
|
||||
|
||||
verify key, spec
|
||||
verify key, spec, order
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -338,15 +500,12 @@ class SignatureAlgorithmTest {
|
|||
|
||||
ECPrivateKey key = createMock(ECPrivateKey)
|
||||
ECParameterSpec spec = createMock(ECParameterSpec)
|
||||
int numBits = alg.minKeyLength - 8 // 8 bits less than expected
|
||||
int numBytes = numBits / 8 as int
|
||||
byte[] orderBytes = new byte[numBytes]
|
||||
random.nextBytes(orderBytes)
|
||||
BigInteger order = new BigInteger(orderBytes)
|
||||
expect(key.getParams()).andReturn(spec)
|
||||
expect(spec.getOrder()).andReturn(order)
|
||||
BigInteger order = createMock(BigInteger)
|
||||
expect(key.getParams()).andStubReturn(spec)
|
||||
expect(spec.getOrder()).andStubReturn(order)
|
||||
expect(order.bitLength()).andStubReturn(alg.minKeyLength - 8) //1 byte less than expected
|
||||
|
||||
replay key, spec
|
||||
replay key, spec, order
|
||||
|
||||
try {
|
||||
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
|
||||
}
|
||||
|
||||
verify key, spec
|
||||
verify key, spec, order
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -371,18 +530,15 @@ class SignatureAlgorithmTest {
|
|||
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) {
|
||||
|
||||
RSAPrivateKey key = createMock(RSAPrivateKey)
|
||||
int numBits = alg.minKeyLength
|
||||
int numBytes = numBits / 8 as int
|
||||
byte[] modulusBytes = new byte[numBytes + 1]
|
||||
random.nextBytes(modulusBytes)
|
||||
BigInteger modulus = new BigInteger(modulusBytes)
|
||||
expect(key.getModulus()).andReturn(modulus)
|
||||
BigInteger modulus = createMock(BigInteger)
|
||||
expect(key.getModulus()).andStubReturn(modulus)
|
||||
expect(modulus.bitLength()).andStubReturn(alg.minKeyLength)
|
||||
|
||||
replay key
|
||||
replay key, modulus
|
||||
|
||||
alg.assertValidSigningKey(key)
|
||||
|
||||
verify key
|
||||
verify key, modulus
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -430,14 +586,11 @@ class SignatureAlgorithmTest {
|
|||
String section = alg.name().startsWith("P") ? "3.5" : "3.3"
|
||||
|
||||
RSAPrivateKey key = createMock(RSAPrivateKey)
|
||||
int numBits = alg.minKeyLength - 8
|
||||
int numBytes = numBits / 8 as int
|
||||
byte[] modulusBytes = new byte[numBytes]
|
||||
random.nextBytes(modulusBytes)
|
||||
BigInteger modulus = new BigInteger(modulusBytes)
|
||||
expect(key.getModulus()).andReturn(modulus)
|
||||
BigInteger modulus = createMock(BigInteger)
|
||||
expect(key.getModulus()).andStubReturn(modulus)
|
||||
expect(modulus.bitLength()).andStubReturn(alg.minKeyLength - 8) // 1 less byte
|
||||
|
||||
replay key
|
||||
replay key, modulus
|
||||
|
||||
try {
|
||||
alg.assertValidSigningKey(key)
|
||||
|
@ -452,7 +605,7 @@ class SignatureAlgorithmTest {
|
|||
"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)
|
||||
ECParameterSpec spec = createMock(ECParameterSpec)
|
||||
int numBits = alg.minKeyLength
|
||||
int numBytes = numBits / 8 as int
|
||||
byte[] orderBytes = new byte[numBytes + 1]
|
||||
random.nextBytes(orderBytes)
|
||||
BigInteger order = new BigInteger(orderBytes)
|
||||
expect(key.getParams()).andReturn(spec)
|
||||
expect(spec.getOrder()).andReturn(order)
|
||||
BigInteger order = createMock(BigInteger)
|
||||
expect(key.getParams()).andStubReturn(spec)
|
||||
expect(spec.getOrder()).andStubReturn(order)
|
||||
expect(order.bitLength()).andStubReturn(alg.minKeyLength)
|
||||
|
||||
replay key, spec
|
||||
replay key, spec, order
|
||||
|
||||
alg.assertValidVerificationKey(key)
|
||||
|
||||
verify key, spec
|
||||
verify key, spec, order
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -645,15 +795,12 @@ class SignatureAlgorithmTest {
|
|||
|
||||
ECPrivateKey key = createMock(ECPrivateKey)
|
||||
ECParameterSpec spec = createMock(ECParameterSpec)
|
||||
int numBits = alg.minKeyLength - 8 // 8 bits = 1 byte
|
||||
int numBytes = numBits / 8 as int
|
||||
byte[] orderBytes = new byte[numBytes]
|
||||
random.nextBytes(orderBytes)
|
||||
BigInteger order = new BigInteger(orderBytes)
|
||||
expect(key.getParams()).andReturn(spec)
|
||||
expect(spec.getOrder()).andReturn(order)
|
||||
BigInteger order = createMock(BigInteger)
|
||||
expect(key.getParams()).andStubReturn(spec)
|
||||
expect(spec.getOrder()).andStubReturn(order)
|
||||
expect(order.bitLength()).andStubReturn(alg.minKeyLength - 8) // 1 less byte
|
||||
|
||||
replay key, spec
|
||||
replay key, spec, order
|
||||
|
||||
try {
|
||||
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
|
||||
}
|
||||
|
||||
verify key, spec
|
||||
verify key, spec, order
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -678,18 +825,15 @@ class SignatureAlgorithmTest {
|
|||
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) {
|
||||
|
||||
RSAPrivateKey key = createMock(RSAPrivateKey)
|
||||
int numBits = alg.minKeyLength
|
||||
int numBytes = numBits / 8 as int
|
||||
byte[] modulusBytes = new byte[numBytes + 1]
|
||||
random.nextBytes(modulusBytes)
|
||||
BigInteger modulus = new BigInteger(modulusBytes)
|
||||
expect(key.getModulus()).andReturn(modulus)
|
||||
BigInteger modulus = createMock(BigInteger)
|
||||
expect(key.getModulus()).andStubReturn(modulus)
|
||||
expect(modulus.bitLength()).andStubReturn(alg.minKeyLength)
|
||||
|
||||
replay key
|
||||
replay key, modulus
|
||||
|
||||
alg.assertValidVerificationKey(key)
|
||||
|
||||
verify key
|
||||
verify key, modulus
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -717,14 +861,11 @@ class SignatureAlgorithmTest {
|
|||
String section = alg.name().startsWith("P") ? "3.5" : "3.3"
|
||||
|
||||
RSAPrivateKey key = createMock(RSAPrivateKey)
|
||||
int numBits = alg.minKeyLength - 8 // 8 bits = 1 byte
|
||||
int numBytes = numBits / 8 as int
|
||||
byte[] modulusBytes = new byte[numBytes]
|
||||
random.nextBytes(modulusBytes)
|
||||
BigInteger modulus = new BigInteger(modulusBytes)
|
||||
expect(key.getModulus()).andReturn(modulus)
|
||||
BigInteger modulus = createMock(BigInteger)
|
||||
expect(key.getModulus()).andStubReturn(modulus)
|
||||
expect(modulus.bitLength()).andStubReturn(alg.minKeyLength - 8) //one less byte
|
||||
|
||||
replay key
|
||||
replay key, modulus
|
||||
|
||||
try {
|
||||
alg.assertValidVerificationKey(key)
|
||||
|
@ -739,7 +880,7 @@ class SignatureAlgorithmTest {
|
|||
"https://tools.ietf.org/html/rfc7518#section-${section} for more information." as String, expected.message
|
||||
}
|
||||
|
||||
verify key
|
||||
verify key, modulus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,32 @@ class KeysTest {
|
|||
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
|
||||
void testSecretKeyFor() {
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import io.jsonwebtoken.lang.Assert;
|
|||
import io.jsonwebtoken.lang.Classes;
|
||||
import io.jsonwebtoken.lang.Collections;
|
||||
import io.jsonwebtoken.lang.Strings;
|
||||
import io.jsonwebtoken.security.InvalidKeyException;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
@ -109,16 +110,33 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
}
|
||||
|
||||
@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.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.");
|
||||
SecretKey key = new SecretKeySpec(secretKeyBytes, alg.getJcaName());
|
||||
return signWith(alg, key);
|
||||
return signWith(key, alg);
|
||||
}
|
||||
|
||||
@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.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);
|
||||
|
@ -127,12 +145,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
|
||||
@Override
|
||||
public JwtBuilder signWith(SignatureAlgorithm alg, Key key) {
|
||||
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
|
||||
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;
|
||||
return signWith(key, alg);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -641,7 +641,7 @@ class JwtParserTest {
|
|||
|
||||
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() {
|
||||
@Override
|
||||
|
|
|
@ -579,7 +579,7 @@ class JwtsTest {
|
|||
def key = Keys.secretKeyFor(alg)
|
||||
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 {
|
||||
Jwts.parser().setSigningKey(weakKey).parseClaimsJws(jws)
|
||||
|
@ -743,7 +743,7 @@ class JwtsTest {
|
|||
|
||||
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
|
||||
if (verifyWithPrivateKey) {
|
||||
|
@ -781,7 +781,7 @@ class JwtsTest {
|
|||
|
||||
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
|
||||
if (verifyWithPrivateKey) {
|
||||
|
|
|
@ -30,7 +30,7 @@ class RsaSigningKeyResolverAdapterTest {
|
|||
|
||||
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)
|
||||
|
||||
|
|
|
@ -26,6 +26,10 @@ import io.jsonwebtoken.io.Serializer
|
|||
import io.jsonwebtoken.security.Keys
|
||||
import org.junit.Test
|
||||
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import java.security.KeyFactory
|
||||
|
||||
import static org.junit.Assert.*
|
||||
|
||||
class DefaultJwtBuilderTest {
|
||||
|
@ -176,8 +180,12 @@ class DefaultJwtBuilderTest {
|
|||
b.setPayload('foo')
|
||||
def alg = SignatureAlgorithm.HS256
|
||||
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.compact()
|
||||
String s2 = b.compact()
|
||||
assertEquals s1, s2
|
||||
}
|
||||
|
||||
@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
|
||||
void testSignWithBytesWithoutHmac() {
|
||||
def bytes = new byte[16];
|
||||
|
@ -348,7 +375,7 @@ class DefaultJwtBuilderTest {
|
|||
|
||||
def key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
|
||||
|
||||
String jws = b.signWith(SignatureAlgorithm.HS256, key)
|
||||
String jws = b.signWith(key, SignatureAlgorithm.HS256)
|
||||
.claim('foo', 'bar')
|
||||
.compact()
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ class DefaultJwtParserTest {
|
|||
|
||||
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')
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue