mirror of https://github.com/jwtk/jjwt.git
Merge pull request #367 from jwtk/334-key-length-assertions
Key strength assertions during signing and verification
This commit is contained in:
commit
711d0d5556
|
@ -17,6 +17,7 @@ package io.jsonwebtoken;
|
||||||
|
|
||||||
import io.jsonwebtoken.io.Decoder;
|
import io.jsonwebtoken.io.Decoder;
|
||||||
import io.jsonwebtoken.io.Deserializer;
|
import io.jsonwebtoken.io.Deserializer;
|
||||||
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
|
@ -16,6 +16,16 @@
|
||||||
package io.jsonwebtoken;
|
package io.jsonwebtoken;
|
||||||
|
|
||||||
import io.jsonwebtoken.lang.RuntimeEnvironment;
|
import io.jsonwebtoken.lang.RuntimeEnvironment;
|
||||||
|
import io.jsonwebtoken.security.InvalidKeyException;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
import io.jsonwebtoken.security.WeakKeyException;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.security.Key;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.interfaces.ECKey;
|
||||||
|
import java.security.interfaces.RSAKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
@ -25,85 +35,104 @@ import io.jsonwebtoken.lang.RuntimeEnvironment;
|
||||||
*/
|
*/
|
||||||
public enum SignatureAlgorithm {
|
public enum SignatureAlgorithm {
|
||||||
|
|
||||||
/** JWA name for {@code No digital signature or MAC performed} */
|
/**
|
||||||
NONE("none", "No digital signature or MAC performed", "None", null, false),
|
* JWA name for {@code No digital signature or MAC performed}
|
||||||
|
*/
|
||||||
|
NONE("none", "No digital signature or MAC performed", "None", null, false, 0, 0),
|
||||||
|
|
||||||
/** JWA algorithm name for {@code HMAC using SHA-256} */
|
/**
|
||||||
HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true),
|
* JWA algorithm name for {@code HMAC using SHA-256}
|
||||||
|
*/
|
||||||
|
HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true, 256, 256),
|
||||||
|
|
||||||
/** JWA algorithm name for {@code HMAC using SHA-384} */
|
/**
|
||||||
HS384("HS384", "HMAC using SHA-384", "HMAC", "HmacSHA384", true),
|
* JWA algorithm name for {@code HMAC using SHA-384}
|
||||||
|
*/
|
||||||
|
HS384("HS384", "HMAC using SHA-384", "HMAC", "HmacSHA384", true, 384, 384),
|
||||||
|
|
||||||
/** JWA algorithm name for {@code HMAC using SHA-512} */
|
/**
|
||||||
HS512("HS512", "HMAC using SHA-512", "HMAC", "HmacSHA512", true),
|
* JWA algorithm name for {@code HMAC using SHA-512}
|
||||||
|
*/
|
||||||
|
HS512("HS512", "HMAC using SHA-512", "HMAC", "HmacSHA512", true, 512, 512),
|
||||||
|
|
||||||
/** JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-256} */
|
/**
|
||||||
RS256("RS256", "RSASSA-PKCS-v1_5 using SHA-256", "RSA", "SHA256withRSA", true),
|
* JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-256}
|
||||||
|
*/
|
||||||
|
RS256("RS256", "RSASSA-PKCS-v1_5 using SHA-256", "RSA", "SHA256withRSA", true, 256, 2048),
|
||||||
|
|
||||||
/** JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-384} */
|
/**
|
||||||
RS384("RS384", "RSASSA-PKCS-v1_5 using SHA-384", "RSA", "SHA384withRSA", true),
|
* JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-384}
|
||||||
|
*/
|
||||||
|
RS384("RS384", "RSASSA-PKCS-v1_5 using SHA-384", "RSA", "SHA384withRSA", true, 384, 2048),
|
||||||
|
|
||||||
/** JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-512} */
|
/**
|
||||||
RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "RSA", "SHA512withRSA", true),
|
* JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-512}
|
||||||
|
*/
|
||||||
|
RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "RSA", "SHA512withRSA", true, 512, 2048),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWA algorithm name for {@code ECDSA using P-256 and SHA-256}. <b>This is not a JDK standard algorithm and
|
* JWA algorithm name for {@code ECDSA using P-256 and SHA-256}. <b>This is not a JDK standard algorithm and
|
||||||
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
|
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
|
||||||
* automatically if found in the runtime classpath.
|
* automatically if found in the runtime classpath.
|
||||||
*/
|
*/
|
||||||
ES256("ES256", "ECDSA using P-256 and SHA-256", "Elliptic Curve", "SHA256withECDSA", false),
|
ES256("ES256", "ECDSA using P-256 and SHA-256", "ECDSA", "SHA256withECDSA", false, 256, 256),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWA algorithm name for {@code ECDSA using P-384 and SHA-384}. <b>This is not a JDK standard algorithm and
|
* JWA algorithm name for {@code ECDSA using P-384 and SHA-384}. <b>This is not a JDK standard algorithm and
|
||||||
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
|
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
|
||||||
* automatically if found in the runtime classpath.
|
* automatically if found in the runtime classpath.
|
||||||
*/
|
*/
|
||||||
ES384("ES384", "ECDSA using P-384 and SHA-384", "Elliptic Curve", "SHA384withECDSA", false),
|
ES384("ES384", "ECDSA using P-384 and SHA-384", "ECDSA", "SHA384withECDSA", false, 384, 384),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWA algorithm name for {@code ECDSA using P-512 and SHA-512}. <b>This is not a JDK standard algorithm and
|
* JWA algorithm name for {@code ECDSA using P-521 and SHA-512}. <b>This is not a JDK standard algorithm and
|
||||||
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
|
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
|
||||||
* automatically if found in the runtime classpath.
|
* automatically if found in the runtime classpath.
|
||||||
*/
|
*/
|
||||||
ES512("ES512", "ECDSA using P-512 and SHA-512", "Elliptic Curve", "SHA512withECDSA", false),
|
ES512("ES512", "ECDSA using P-521 and SHA-512", "ECDSA", "SHA512withECDSA", false, 512, 521),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWA algorithm name for {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256}. <b>This is not a JDK standard
|
* JWA algorithm name for {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256}. <b>This is not a JDK standard
|
||||||
* algorithm and requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle
|
* algorithm and requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle
|
||||||
* will be used automatically if found in the runtime classpath.
|
* will be used automatically if found in the runtime classpath.
|
||||||
*/
|
*/
|
||||||
PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "RSA", "SHA256withRSAandMGF1", false),
|
PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "RSA", "SHA256withRSAandMGF1", false, 256, 2048),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWA algorithm name for {@code RSASSA-PSS using SHA-384 and MGF1 with SHA-384}. <b>This is not a JDK standard
|
* JWA algorithm name for {@code RSASSA-PSS using SHA-384 and MGF1 with SHA-384}. <b>This is not a JDK standard
|
||||||
* algorithm and requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle
|
* algorithm and requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle
|
||||||
* will be used automatically if found in the runtime classpath.
|
* will be used automatically if found in the runtime classpath.
|
||||||
*/
|
*/
|
||||||
PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "RSA", "SHA384withRSAandMGF1", false),
|
PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "RSA", "SHA384withRSAandMGF1", false, 384, 2048),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWA algorithm name for {@code RSASSA-PSS using SHA-512 and MGF1 with SHA-512}. <b>This is not a JDK standard
|
* JWA algorithm name for {@code RSASSA-PSS using SHA-512 and MGF1 with SHA-512}. <b>This is not a JDK standard
|
||||||
* algorithm and requires that a JCA provider like BouncyCastle be in the classpath.</b> BouncyCastle will be used
|
* algorithm and requires that a JCA provider like BouncyCastle be in the classpath.</b> BouncyCastle will be used
|
||||||
* automatically if found in the runtime classpath.
|
* automatically if found in the runtime classpath.
|
||||||
*/
|
*/
|
||||||
PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "SHA512withRSAandMGF1", false);
|
PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "SHA512withRSAandMGF1", false, 512, 2048);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
RuntimeEnvironment.enableBouncyCastleIfPossible();
|
RuntimeEnvironment.enableBouncyCastleIfPossible();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
private final String description;
|
private final String description;
|
||||||
private final String familyName;
|
private final String familyName;
|
||||||
private final String jcaName;
|
private final String jcaName;
|
||||||
private final boolean jdkStandard;
|
private final boolean jdkStandard;
|
||||||
|
private final int digestLength;
|
||||||
|
private final int minKeyLength;
|
||||||
|
|
||||||
SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard) {
|
SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard,
|
||||||
|
int digestLength, int minKeyLength) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.familyName = familyName;
|
this.familyName = familyName;
|
||||||
this.jcaName = jcaName;
|
this.jcaName = jcaName;
|
||||||
this.jdkStandard = jdkStandard;
|
this.jdkStandard = jdkStandard;
|
||||||
|
this.digestLength = digestLength;
|
||||||
|
this.minKeyLength = minKeyLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -130,67 +159,66 @@ public enum SignatureAlgorithm {
|
||||||
* following table:
|
* following table:
|
||||||
*
|
*
|
||||||
* <table>
|
* <table>
|
||||||
* <caption>Crypto Family</caption>
|
* <caption>Crypto Family</caption>
|
||||||
* <thead>
|
* <thead>
|
||||||
* <tr>
|
* <tr>
|
||||||
* <th>SignatureAlgorithm</th>
|
* <th>SignatureAlgorithm</th>
|
||||||
* <th>Family Name</th>
|
* <th>Family Name</th>
|
||||||
* </tr>
|
* </tr>
|
||||||
* </thead>
|
* </thead>
|
||||||
* <tbody>
|
* <tbody>
|
||||||
* <tr>
|
* <tr>
|
||||||
* <td>HS256</td>
|
* <td>HS256</td>
|
||||||
* <td>HMAC</td>
|
* <td>HMAC</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* <tr>
|
* <tr>
|
||||||
* <td>HS384</td>
|
* <td>HS384</td>
|
||||||
* <td>HMAC</td>
|
* <td>HMAC</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* <tr>
|
* <tr>
|
||||||
* <td>HS512</td>
|
* <td>HS512</td>
|
||||||
* <td>HMAC</td>
|
* <td>HMAC</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* <tr>
|
* <tr>
|
||||||
* <td>RS256</td>
|
* <td>RS256</td>
|
||||||
* <td>RSA</td>
|
* <td>RSA</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* <tr>
|
* <tr>
|
||||||
* <td>RS384</td>
|
* <td>RS384</td>
|
||||||
* <td>RSA</td>
|
* <td>RSA</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* <tr>
|
* <tr>
|
||||||
* <td>RS512</td>
|
* <td>RS512</td>
|
||||||
* <td>RSA</td>
|
* <td>RSA</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* <tr>
|
* <tr>
|
||||||
* <td>PS256</td>
|
* <td>PS256</td>
|
||||||
* <td>RSA</td>
|
* <td>RSA</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* <tr>
|
* <tr>
|
||||||
* <td>PS384</td>
|
* <td>PS384</td>
|
||||||
* <td>RSA</td>
|
* <td>RSA</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* <tr>
|
* <tr>
|
||||||
* <td>PS512</td>
|
* <td>PS512</td>
|
||||||
* <td>RSA</td>
|
* <td>RSA</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* <tr>
|
* <tr>
|
||||||
* <td>ES256</td>
|
* <td>ES256</td>
|
||||||
* <td>Elliptic Curve</td>
|
* <td>ECDSA</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* <tr>
|
* <tr>
|
||||||
* <td>ES384</td>
|
* <td>ES384</td>
|
||||||
* <td>Elliptic Curve</td>
|
* <td>ECDSA</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* <tr>
|
* <tr>
|
||||||
* <td>ES512</td>
|
* <td>ES512</td>
|
||||||
* <td>Elliptic Curve</td>
|
* <td>ECDSA</td>
|
||||||
* </tr>
|
* </tr>
|
||||||
* </tbody>
|
* </tbody>
|
||||||
* </table>
|
* </table>
|
||||||
*
|
*
|
||||||
* @return Returns the cryptographic family name of the signature algorithm.
|
* @return Returns the cryptographic family name of the signature algorithm.
|
||||||
*
|
|
||||||
* @since 0.5
|
* @since 0.5
|
||||||
*/
|
*/
|
||||||
public String getFamilyName() {
|
public String getFamilyName() {
|
||||||
|
@ -225,7 +253,7 @@ public enum SignatureAlgorithm {
|
||||||
* @return {@code true} if the enum instance represents an HMAC signature algorithm, {@code false} otherwise.
|
* @return {@code true} if the enum instance represents an HMAC signature algorithm, {@code false} otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isHmac() {
|
public boolean isHmac() {
|
||||||
return name().startsWith("HS");
|
return familyName.equals("HMAC");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -236,18 +264,152 @@ public enum SignatureAlgorithm {
|
||||||
* {@code false} otherwise.
|
* {@code false} otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isRsa() {
|
public boolean isRsa() {
|
||||||
return getDescription().startsWith("RSASSA");
|
return familyName.equals("RSA");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns {@code true} if the enum instance represents an Elliptic Curve signature algorithm, {@code false}
|
* Returns {@code true} if the enum instance represents an Elliptic Curve ECDSA signature algorithm, {@code false}
|
||||||
* otherwise.
|
* otherwise.
|
||||||
*
|
*
|
||||||
* @return {@code true} if the enum instance represents an Elliptic Curve signature algorithm, {@code false}
|
* @return {@code true} if the enum instance represents an Elliptic Curve ECDSA signature algorithm, {@code false}
|
||||||
* otherwise.
|
* otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isEllipticCurve() {
|
public boolean isEllipticCurve() {
|
||||||
return name().startsWith("ES");
|
return familyName.equals("ECDSA");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* {@link InvalidKeyException} if the key is not allowed or not secure enough for this algorithm.
|
||||||
|
*
|
||||||
|
* @param key the key to check for validity.
|
||||||
|
* @throws InvalidKeyException if the key is not allowed or not secure enough for this algorithm.
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
public void assertValidSigningKey(Key key) throws InvalidKeyException {
|
||||||
|
assertValid(key, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns quietly if the specified key is allowed to verify signatures using this algorithm
|
||||||
|
* according to the <a href="https://tools.ietf.org/html/rfc7518">JWT JWA Specification (RFC 7518)</a> or throws an
|
||||||
|
* {@link InvalidKeyException} if the key is not allowed or not secure enough for this algorithm.
|
||||||
|
*
|
||||||
|
* @param key the key to check for validity.
|
||||||
|
* @throws InvalidKeyException if the key is not allowed or not secure enough for this algorithm.
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
public void assertValidVerificationKey(Key key) throws InvalidKeyException {
|
||||||
|
assertValid(key, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.10.0 to support assertValid(Key, boolean)
|
||||||
|
*/
|
||||||
|
private static String keyType(boolean signing) {
|
||||||
|
return signing ? "signing" : "verification";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
private void assertValid(Key key, boolean signing) throws InvalidKeyException {
|
||||||
|
|
||||||
|
if (this == NONE) {
|
||||||
|
|
||||||
|
String msg = "The 'NONE' signature algorithm does not support cryptographic keys.";
|
||||||
|
throw new InvalidKeyException(msg);
|
||||||
|
|
||||||
|
} else if (isHmac()) {
|
||||||
|
|
||||||
|
if (!(key instanceof SecretKey)) {
|
||||||
|
String msg = this.familyName + " " + keyType(signing) + " keys must be SecretKey instances.";
|
||||||
|
throw new InvalidKeyException(msg);
|
||||||
|
}
|
||||||
|
SecretKey secretKey = (SecretKey) key;
|
||||||
|
|
||||||
|
byte[] encoded = secretKey.getEncoded();
|
||||||
|
if (encoded == null) {
|
||||||
|
throw new InvalidKeyException("The " + keyType(signing) + " key's encoded bytes cannot be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String alg = secretKey.getAlgorithm();
|
||||||
|
if (alg == null) {
|
||||||
|
throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm cannot be null.");
|
||||||
|
}
|
||||||
|
if (!HS256.jcaName.equals(alg) && !HS384.jcaName.equals(alg) && !HS512.jcaName.equals(alg)) {
|
||||||
|
throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm '" + alg +
|
||||||
|
"' does not equal a valid HmacSHA* algorithm name and cannot be used with " + name() + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = encoded.length * 8; //size in bits
|
||||||
|
if (size < this.minKeyLength) {
|
||||||
|
String msg = "The " + keyType(signing) + " key's size is " + size + " bits which " +
|
||||||
|
"is not secure enough for the " + name() + " algorithm. The JWT " +
|
||||||
|
"JWA Specification (RFC 7518, Section 3.2) states that keys used with " + name() + " MUST have a " +
|
||||||
|
"size >= " + minKeyLength + " bits (the key size must be greater than or equal to the hash " +
|
||||||
|
"output size). Consider using the " + Keys.class.getName() + " class's " +
|
||||||
|
"'secretKeyFor(SignatureAlgorithm." + name() + ")' method to create a key guaranteed to be " +
|
||||||
|
"secure enough for " + name() + ". See " +
|
||||||
|
"https://tools.ietf.org/html/rfc7518#section-3.2 for more information.";
|
||||||
|
throw new WeakKeyException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { //EC or RSA
|
||||||
|
|
||||||
|
if (signing) {
|
||||||
|
if (!(key instanceof PrivateKey)) {
|
||||||
|
String msg = familyName + " signing keys must be PrivateKey instances.";
|
||||||
|
throw new InvalidKeyException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEllipticCurve()) {
|
||||||
|
|
||||||
|
if (!(key instanceof ECKey)) {
|
||||||
|
String msg = familyName + " " + keyType(signing) + " keys must be ECKey instances.";
|
||||||
|
throw new InvalidKeyException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ECKey ecKey = (ECKey) key;
|
||||||
|
int size = ecKey.getParams().getOrder().bitLength();
|
||||||
|
if (size < this.minKeyLength) {
|
||||||
|
String msg = "The " + keyType(signing) + " key's size (ECParameterSpec order) is " + size +
|
||||||
|
" bits which is not secure enough for the " + name() + " algorithm. The JWT " +
|
||||||
|
"JWA Specification (RFC 7518, Section 3.4) states that keys used with " +
|
||||||
|
name() + " MUST have a size >= " + this.minKeyLength +
|
||||||
|
" bits. Consider using the " + Keys.class.getName() + " class's " +
|
||||||
|
"'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " +
|
||||||
|
"to be secure enough for " + name() + ". See " +
|
||||||
|
"https://tools.ietf.org/html/rfc7518#section-3.4 for more information.";
|
||||||
|
throw new WeakKeyException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { //RSA
|
||||||
|
|
||||||
|
if (!(key instanceof RSAKey)) {
|
||||||
|
String msg = familyName + " " + keyType(signing) + " keys must be RSAKey instances.";
|
||||||
|
throw new InvalidKeyException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
RSAKey rsaKey = (RSAKey)key;
|
||||||
|
int size = rsaKey.getModulus().bitLength();
|
||||||
|
if (size < this.minKeyLength) {
|
||||||
|
|
||||||
|
String section = name().startsWith("P") ? "3.5" : "3.3";
|
||||||
|
|
||||||
|
String msg = "The " + keyType(signing) + " key's size is " + size + " bits which is not secure " +
|
||||||
|
"enough for the " + name() + " algorithm. The JWT JWA Specification (RFC 7518, Section " +
|
||||||
|
section + ") states that keys used with " + name() + " MUST have a size >= " +
|
||||||
|
this.minKeyLength + " bits. Consider using the " + Keys.class.getName() + " class's " +
|
||||||
|
"'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " +
|
||||||
|
"to be secure enough for " + name() + ". See " +
|
||||||
|
"https://tools.ietf.org/html/rfc7518#section-" + section + " for more information.";
|
||||||
|
throw new WeakKeyException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,12 +15,16 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken;
|
package io.jsonwebtoken;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.security.SecurityException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception indicating that either calculating a signature or verifying an existing signature of a JWT failed.
|
* Exception indicating that either calculating a signature or verifying an existing signature of a JWT failed.
|
||||||
*
|
*
|
||||||
* @since 0.1
|
* @since 0.1
|
||||||
|
* @deprecated in favor of {@link io.jsonwebtoken.security.SecurityException}; this class will be removed before 1.0
|
||||||
*/
|
*/
|
||||||
public class SignatureException extends JwtException {
|
@Deprecated
|
||||||
|
public class SignatureException extends SecurityException {
|
||||||
|
|
||||||
public SignatureException(String message) {
|
public SignatureException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package io.jsonwebtoken.security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
public class InvalidKeyException extends KeyException {
|
||||||
|
|
||||||
|
public InvalidKeyException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package io.jsonwebtoken.security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
public class KeyException extends SecurityException {
|
||||||
|
|
||||||
|
public KeyException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package io.jsonwebtoken.crypto;
|
package io.jsonwebtoken.security;
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
@ -24,6 +24,22 @@ public final class Keys {
|
||||||
private Keys() {
|
private Keys() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public static final int bitLength(Key key) throws IllegalArgumentException {
|
||||||
|
Assert.notNull(key, "Key cannot be null.");
|
||||||
|
if (key instanceof SecretKey) {
|
||||||
|
byte[] encoded = key.getEncoded();
|
||||||
|
return Arrays.length(encoded) * 8;
|
||||||
|
} else if (key instanceof RSAKey) {
|
||||||
|
return ((RSAKey)key).getModulus().bitLength();
|
||||||
|
} else if (key instanceof ECKey) {
|
||||||
|
return ((ECKey)key).getParams().getOrder().bitLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Unsupported key type: " + key.getClass().getName());
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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}.
|
||||||
*
|
*
|
|
@ -0,0 +1,17 @@
|
||||||
|
package io.jsonwebtoken.security;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.JwtException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
public class SecurityException extends JwtException {
|
||||||
|
|
||||||
|
public SecurityException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SecurityException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package io.jsonwebtoken.security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
public class SignatureException extends io.jsonwebtoken.SignatureException {
|
||||||
|
|
||||||
|
public SignatureException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignatureException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package io.jsonwebtoken.security;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
public class WeakKeyException extends InvalidKeyException {
|
||||||
|
|
||||||
|
public WeakKeyException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,17 +15,33 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken
|
package io.jsonwebtoken
|
||||||
|
|
||||||
|
import io.jsonwebtoken.security.InvalidKeyException
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
|
import io.jsonwebtoken.security.SignatureException
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
import java.security.Key
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import java.security.interfaces.ECPrivateKey
|
||||||
|
import java.security.interfaces.ECPublicKey
|
||||||
|
import java.security.interfaces.RSAPrivateKey
|
||||||
|
import java.security.interfaces.RSAPublicKey
|
||||||
|
import java.security.spec.ECParameterSpec
|
||||||
|
|
||||||
|
import static org.easymock.EasyMock.*
|
||||||
import static org.junit.Assert.*
|
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',
|
||||||
'ES256', 'ES384', 'ES512', 'PS256', 'PS384', 'PS512', 'NONE']
|
'ES256', 'ES384', 'ES512', 'PS256', 'PS384', 'PS512', 'NONE']
|
||||||
|
|
||||||
for( String name : algNames ) {
|
for (String name : algNames) {
|
||||||
testName(name)
|
testName(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +60,7 @@ class SignatureAlgorithmTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testIsHmac() {
|
void testIsHmac() {
|
||||||
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
if (alg.name().startsWith("HS")) {
|
if (alg.name().startsWith("HS")) {
|
||||||
assertTrue alg.isHmac()
|
assertTrue alg.isHmac()
|
||||||
} else {
|
} else {
|
||||||
|
@ -55,7 +71,7 @@ class SignatureAlgorithmTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHmacFamilyName() {
|
void testHmacFamilyName() {
|
||||||
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
if (alg.name().startsWith("HS")) {
|
if (alg.name().startsWith("HS")) {
|
||||||
assertEquals alg.getFamilyName(), "HMAC"
|
assertEquals alg.getFamilyName(), "HMAC"
|
||||||
}
|
}
|
||||||
|
@ -64,7 +80,7 @@ class SignatureAlgorithmTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testIsRsa() {
|
void testIsRsa() {
|
||||||
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
if (alg.getDescription().startsWith("RSASSA")) {
|
if (alg.getDescription().startsWith("RSASSA")) {
|
||||||
assertTrue alg.isRsa()
|
assertTrue alg.isRsa()
|
||||||
} else {
|
} else {
|
||||||
|
@ -75,7 +91,7 @@ class SignatureAlgorithmTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRsaFamilyName() {
|
void testRsaFamilyName() {
|
||||||
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
if (alg.name().startsWith("RS") || alg.name().startsWith("PS")) {
|
if (alg.name().startsWith("RS") || alg.name().startsWith("PS")) {
|
||||||
assertEquals alg.getFamilyName(), "RSA"
|
assertEquals alg.getFamilyName(), "RSA"
|
||||||
}
|
}
|
||||||
|
@ -84,7 +100,7 @@ class SignatureAlgorithmTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testIsEllipticCurve() {
|
void testIsEllipticCurve() {
|
||||||
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
if (alg.name().startsWith("ES")) {
|
if (alg.name().startsWith("ES")) {
|
||||||
assertTrue alg.isEllipticCurve()
|
assertTrue alg.isEllipticCurve()
|
||||||
} else {
|
} else {
|
||||||
|
@ -95,16 +111,16 @@ class SignatureAlgorithmTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testEllipticCurveFamilyName() {
|
void testEllipticCurveFamilyName() {
|
||||||
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
if (alg.name().startsWith("ES")) {
|
if (alg.name().startsWith("ES")) {
|
||||||
assertEquals alg.getFamilyName(), "Elliptic Curve"
|
assertEquals alg.getFamilyName(), "ECDSA"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testIsJdkStandard() {
|
void testIsJdkStandard() {
|
||||||
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
if (alg.name().startsWith("ES") || alg.name().startsWith("PS") || alg == SignatureAlgorithm.NONE) {
|
if (alg.name().startsWith("ES") || alg.name().startsWith("PS") || alg == SignatureAlgorithm.NONE) {
|
||||||
assertFalse alg.isJdkStandard()
|
assertFalse alg.isJdkStandard()
|
||||||
} else {
|
} else {
|
||||||
|
@ -112,4 +128,618 @@ class SignatureAlgorithmTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidSigningKeyWithNoneAlgorithm() {
|
||||||
|
Key key = createMock(Key)
|
||||||
|
try {
|
||||||
|
SignatureAlgorithm.NONE.assertValidSigningKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The 'NONE' signature algorithm does not support cryptographic keys." as String, expected.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidHmacSigningKeyHappyPath() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) {
|
||||||
|
|
||||||
|
SecretKey key = createMock(SecretKey)
|
||||||
|
int numBits = alg.minKeyLength
|
||||||
|
int numBytes = numBits / 8 as int
|
||||||
|
expect(key.getEncoded()).andReturn(new byte[numBytes])
|
||||||
|
expect(key.getAlgorithm()).andReturn(alg.jcaName)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidHmacSigningKeyNotSecretKey() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) {
|
||||||
|
|
||||||
|
Key key = createMock(Key)
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals 'HMAC signing keys must be SecretKey instances.', expected.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidHmacSigningKeyNullBytes() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) {
|
||||||
|
|
||||||
|
SecretKey key = createMock(SecretKey)
|
||||||
|
expect(key.getEncoded()).andReturn(null)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The signing key's encoded bytes cannot be null.", expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidHmacSigningKeyMissingAlgorithm() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) {
|
||||||
|
|
||||||
|
SecretKey key = createMock(SecretKey)
|
||||||
|
expect(key.getEncoded()).andReturn(new byte[alg.minKeyLength / 8 as int])
|
||||||
|
expect(key.getAlgorithm()).andReturn(null)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The signing key's algorithm cannot be null.", expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidHmacSigningKeyUnsupportedAlgorithm() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) {
|
||||||
|
|
||||||
|
SecretKey key = createMock(SecretKey)
|
||||||
|
expect(key.getEncoded()).andReturn(new byte[alg.minKeyLength / 8 as int])
|
||||||
|
expect(key.getAlgorithm()).andReturn('AES')
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The signing key's algorithm 'AES' does not equal a valid HmacSHA* algorithm " +
|
||||||
|
"name and cannot be used with ${alg.name()}." as String, expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidHmacSigningKeyInsufficientKeyLength() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) {
|
||||||
|
|
||||||
|
SecretKey key = createMock(SecretKey)
|
||||||
|
int numBits = alg.minKeyLength - 8 //8 bits shorter than expected
|
||||||
|
int numBytes = numBits / 8 as int
|
||||||
|
expect(key.getEncoded()).andReturn(new byte[numBytes])
|
||||||
|
expect(key.getAlgorithm()).andReturn(alg.jcaName)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The signing key's size is $numBits bits which is not secure enough for the " +
|
||||||
|
"${alg.name()} algorithm. The JWT JWA Specification " +
|
||||||
|
"(RFC 7518, Section 3.2) states that keys used with ${alg.name()} MUST have a size >= " +
|
||||||
|
"${alg.minKeyLength} bits (the key size must be greater than or equal to the hash output " +
|
||||||
|
"size). Consider using the ${Keys.class.getName()} class's 'secretKeyFor(" +
|
||||||
|
"SignatureAlgorithm.${alg.name()})' method to create a key guaranteed to be secure enough " +
|
||||||
|
"for ${alg.name()}. See https://tools.ietf.org/html/rfc7518#section-3.2 for " +
|
||||||
|
"more information." as String, expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidECSigningKeyHappyPath() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) {
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
replay key, spec
|
||||||
|
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
|
||||||
|
verify key, spec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidECSigningNotPrivateKey() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) {
|
||||||
|
|
||||||
|
ECPublicKey key = createMock(ECPublicKey)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals 'ECDSA signing keys must be PrivateKey instances.', expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidECSigningKeyNotECKey() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) {
|
||||||
|
|
||||||
|
PrivateKey key = createMock(PrivateKey)
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals 'ECDSA signing keys must be ECKey instances.', expected.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidECSigningKeyInsufficientKeyLength() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) {
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
replay key, spec
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The signing key's size (ECParameterSpec order) is ${order.bitLength()} bits " +
|
||||||
|
"which is not secure enough for the ${alg.name()} algorithm. The JWT JWA Specification " +
|
||||||
|
"(RFC 7518, Section 3.4) states that keys used with ${alg.name()} MUST have a size >= " +
|
||||||
|
"${alg.minKeyLength} bits. Consider using the ${Keys.class.getName()} class's " +
|
||||||
|
"'keyPairFor(SignatureAlgorithm.${alg.name()})' method to create a key pair guaranteed " +
|
||||||
|
"to be secure enough for ${alg.name()}. See " +
|
||||||
|
"https://tools.ietf.org/html/rfc7518#section-3.4 for more information." as String, expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key, spec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidRSASigningKeyHappyPath() {
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidRSASigningNotPrivateKey() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) {
|
||||||
|
|
||||||
|
RSAPublicKey key = createMock(RSAPublicKey)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals 'RSA signing keys must be PrivateKey instances.', expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidRSASigningKeyNotRSAKey() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) {
|
||||||
|
|
||||||
|
PrivateKey key = createMock(PrivateKey)
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals 'RSA signing keys must be RSAKey instances.', expected.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidRSASigningKeyInsufficientKeyLength() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) {
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidSigningKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The signing key's size is ${modulus.bitLength()} bits which is not secure " +
|
||||||
|
"enough for the ${alg.name()} algorithm. The JWT JWA Specification " +
|
||||||
|
"(RFC 7518, Section ${section}) states that keys used with ${alg.name()} MUST have a size >= " +
|
||||||
|
"${alg.minKeyLength} bits. Consider using the ${Keys.class.getName()} class's " +
|
||||||
|
"'keyPairFor(SignatureAlgorithm.${alg.name()})' method to create a key pair guaranteed " +
|
||||||
|
"to be secure enough for ${alg.name()}. See " +
|
||||||
|
"https://tools.ietf.org/html/rfc7518#section-${section} for more information." as String, expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidVerificationKeyWithNoneAlgorithm() {
|
||||||
|
Key key = createMock(Key)
|
||||||
|
try {
|
||||||
|
SignatureAlgorithm.NONE.assertValidVerificationKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The 'NONE' signature algorithm does not support cryptographic keys." as String, expected.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidHmacVerificationKeyHappyPath() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) {
|
||||||
|
|
||||||
|
SecretKey key = createMock(SecretKey)
|
||||||
|
int numBits = alg.minKeyLength
|
||||||
|
int numBytes = numBits / 8 as int
|
||||||
|
expect(key.getEncoded()).andReturn(new byte[numBytes])
|
||||||
|
expect(key.getAlgorithm()).andReturn(alg.jcaName)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
alg.assertValidVerificationKey(key)
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidHmacVerificationKeyNotSecretKey() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) {
|
||||||
|
|
||||||
|
Key key = createMock(Key)
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidVerificationKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals 'HMAC verification keys must be SecretKey instances.', expected.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidHmacVerificationKeyNullBytes() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) {
|
||||||
|
|
||||||
|
SecretKey key = createMock(SecretKey)
|
||||||
|
expect(key.getEncoded()).andReturn(null)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidVerificationKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The verification key's encoded bytes cannot be null.", expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidHmacVerificationKeyMissingAlgorithm() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) {
|
||||||
|
|
||||||
|
SecretKey key = createMock(SecretKey)
|
||||||
|
expect(key.getEncoded()).andReturn(new byte[alg.minKeyLength / 8 as int])
|
||||||
|
expect(key.getAlgorithm()).andReturn(null)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidVerificationKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The verification key's algorithm cannot be null.", expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidHmacVerificationKeyUnsupportedAlgorithm() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) {
|
||||||
|
|
||||||
|
SecretKey key = createMock(SecretKey)
|
||||||
|
expect(key.getEncoded()).andReturn(new byte[alg.minKeyLength / 8 as int])
|
||||||
|
expect(key.getAlgorithm()).andReturn('AES')
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidVerificationKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The verification key's algorithm 'AES' does not equal a valid HmacSHA* algorithm " +
|
||||||
|
"name and cannot be used with ${alg.name()}." as String, expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidHmacVerificationKeyInsufficientKeyLength() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isHmac() }) {
|
||||||
|
|
||||||
|
SecretKey key = createMock(SecretKey)
|
||||||
|
int numBits = alg.minKeyLength - 8 // 8 bits (1 byte) less than required
|
||||||
|
int numBytes = numBits / 8 as int
|
||||||
|
expect(key.getEncoded()).andReturn(new byte[numBytes])
|
||||||
|
expect(key.getAlgorithm()).andReturn(alg.jcaName)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidVerificationKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The verification key's size is $numBits bits which is not secure enough for the " +
|
||||||
|
"${alg.name()} algorithm. The JWT JWA Specification " +
|
||||||
|
"(RFC 7518, Section 3.2) states that keys used with ${alg.name()} MUST have a size >= " +
|
||||||
|
"${alg.minKeyLength} bits (the key size must be greater than or equal to the hash output " +
|
||||||
|
"size). Consider using the ${Keys.class.getName()} class's 'secretKeyFor(" +
|
||||||
|
"SignatureAlgorithm.${alg.name()})' method to create a key guaranteed to be secure enough " +
|
||||||
|
"for ${alg.name()}. See https://tools.ietf.org/html/rfc7518#section-3.2 for " +
|
||||||
|
"more information." as String, expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidECVerificationKeyHappyPath() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) {
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
replay key, spec
|
||||||
|
|
||||||
|
alg.assertValidVerificationKey(key)
|
||||||
|
|
||||||
|
verify key, spec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidECVerificationKeyNotECKey() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) {
|
||||||
|
|
||||||
|
PrivateKey key = createMock(PrivateKey)
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidVerificationKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals 'ECDSA verification keys must be ECKey instances.', expected.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidECVerificationKeyInsufficientKeyLength() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isEllipticCurve() }) {
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
replay key, spec
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidVerificationKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The verification key's size (ECParameterSpec order) is ${order.bitLength()} bits " +
|
||||||
|
"which is not secure enough for the ${alg.name()} algorithm. The JWT JWA Specification " +
|
||||||
|
"(RFC 7518, Section 3.4) states that keys used with ${alg.name()} MUST have a size >= " +
|
||||||
|
"${alg.minKeyLength} bits. Consider using the ${Keys.class.getName()} class's " +
|
||||||
|
"'keyPairFor(SignatureAlgorithm.${alg.name()})' method to create a key pair guaranteed " +
|
||||||
|
"to be secure enough for ${alg.name()}. See " +
|
||||||
|
"https://tools.ietf.org/html/rfc7518#section-3.4 for more information." as String, expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key, spec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidRSAVerificationKeyHappyPath() {
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
alg.assertValidVerificationKey(key)
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidRSAVerificationKeyNotRSAKey() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) {
|
||||||
|
|
||||||
|
PrivateKey key = createMock(PrivateKey)
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidVerificationKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals 'RSA verification keys must be RSAKey instances.', expected.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAssertValidRSAVerificationKeyInsufficientKeyLength() {
|
||||||
|
|
||||||
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values().findAll { it.isRsa() }) {
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
replay key
|
||||||
|
|
||||||
|
try {
|
||||||
|
alg.assertValidVerificationKey(key)
|
||||||
|
fail()
|
||||||
|
} catch (InvalidKeyException expected) {
|
||||||
|
assertEquals "The verification key's size is ${modulus.bitLength()} bits which is not secure enough " +
|
||||||
|
"for the ${alg.name()} algorithm. The JWT JWA Specification " +
|
||||||
|
"(RFC 7518, Section ${section}) states that keys used with ${alg.name()} MUST have a size >= " +
|
||||||
|
"${alg.minKeyLength} bits. Consider using the ${Keys.class.getName()} class's " +
|
||||||
|
"'keyPairFor(SignatureAlgorithm.${alg.name()})' method to create a key pair guaranteed " +
|
||||||
|
"to be secure enough for ${alg.name()}. See " +
|
||||||
|
"https://tools.ietf.org/html/rfc7518#section-${section} for more information." as String, expected.message
|
||||||
|
}
|
||||||
|
|
||||||
|
verify key
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package io.jsonwebtoken.crypto
|
package io.jsonwebtoken.security
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.lang.Classes
|
import io.jsonwebtoken.lang.Classes
|
||||||
|
@ -13,14 +13,8 @@ import java.security.KeyPair
|
||||||
import static org.easymock.EasyMock.eq
|
import static org.easymock.EasyMock.eq
|
||||||
import static org.easymock.EasyMock.expect
|
import static org.easymock.EasyMock.expect
|
||||||
import static org.easymock.EasyMock.same
|
import static org.easymock.EasyMock.same
|
||||||
import static org.junit.Assert.assertEquals
|
import static org.junit.Assert.*
|
||||||
import static org.junit.Assert.assertSame
|
import static org.powermock.api.easymock.PowerMock.*
|
||||||
import static org.junit.Assert.fail
|
|
||||||
import static org.powermock.api.easymock.PowerMock.mockStatic
|
|
||||||
import static org.powermock.api.easymock.PowerMock.createMock
|
|
||||||
import static org.powermock.api.easymock.PowerMock.replay
|
|
||||||
import static org.powermock.api.easymock.PowerMock.reset
|
|
||||||
import static org.powermock.api.easymock.PowerMock.verify
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This test class is for cursory API-level testing only (what is available to the API module at build time).
|
* This test class is for cursory API-level testing only (what is available to the API module at build time).
|
|
@ -0,0 +1,22 @@
|
||||||
|
package io.jsonwebtoken.security
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals
|
||||||
|
|
||||||
|
class SignatureExceptionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStringConstructor() {
|
||||||
|
def exception = new SignatureException("my message")
|
||||||
|
assertEquals "my message", exception.getMessage()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCauseConstructor() {
|
||||||
|
def ioException = new IOException("root error")
|
||||||
|
def exception = new SignatureException("wrapping", ioException)
|
||||||
|
assertEquals "wrapping", exception.getMessage()
|
||||||
|
assertEquals ioException, exception.getCause()
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,19 +22,20 @@ import io.jsonwebtoken.JwsHeader;
|
||||||
import io.jsonwebtoken.JwtBuilder;
|
import io.jsonwebtoken.JwtBuilder;
|
||||||
import io.jsonwebtoken.JwtParser;
|
import io.jsonwebtoken.JwtParser;
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.io.Encoder;
|
|
||||||
import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
|
import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
|
||||||
import io.jsonwebtoken.impl.crypto.JwtSigner;
|
import io.jsonwebtoken.impl.crypto.JwtSigner;
|
||||||
|
import io.jsonwebtoken.impl.io.InstanceLocator;
|
||||||
import io.jsonwebtoken.io.Decoders;
|
import io.jsonwebtoken.io.Decoders;
|
||||||
|
import io.jsonwebtoken.io.Encoder;
|
||||||
import io.jsonwebtoken.io.Encoders;
|
import io.jsonwebtoken.io.Encoders;
|
||||||
import io.jsonwebtoken.io.SerializationException;
|
import io.jsonwebtoken.io.SerializationException;
|
||||||
import io.jsonwebtoken.io.Serializer;
|
import io.jsonwebtoken.io.Serializer;
|
||||||
import io.jsonwebtoken.impl.io.InstanceLocator;
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
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 javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -108,13 +109,12 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey) {
|
public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKeyBytes) {
|
||||||
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
|
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
|
||||||
Assert.notEmpty(secretKey, "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.");
|
||||||
this.algorithm = alg;
|
SecretKey key = new SecretKeySpec(secretKeyBytes, alg.getJcaName());
|
||||||
this.key = new SecretKeySpec(secretKey, alg.getJcaName());
|
return signWith(alg, key);
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -129,6 +129,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
||||||
public JwtBuilder signWith(SignatureAlgorithm alg, Key key) {
|
public JwtBuilder signWith(SignatureAlgorithm alg, Key key) {
|
||||||
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
|
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
|
||||||
Assert.notNull(key, "Key argument 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.algorithm = alg;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -34,22 +34,24 @@ import io.jsonwebtoken.MalformedJwtException;
|
||||||
import io.jsonwebtoken.MissingClaimException;
|
import io.jsonwebtoken.MissingClaimException;
|
||||||
import io.jsonwebtoken.PrematureJwtException;
|
import io.jsonwebtoken.PrematureJwtException;
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.SignatureException;
|
|
||||||
import io.jsonwebtoken.SigningKeyResolver;
|
import io.jsonwebtoken.SigningKeyResolver;
|
||||||
import io.jsonwebtoken.UnsupportedJwtException;
|
import io.jsonwebtoken.UnsupportedJwtException;
|
||||||
import io.jsonwebtoken.io.Decoder;
|
|
||||||
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
|
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
|
||||||
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
|
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
|
||||||
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
|
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
|
||||||
|
import io.jsonwebtoken.impl.io.InstanceLocator;
|
||||||
|
import io.jsonwebtoken.io.Decoder;
|
||||||
import io.jsonwebtoken.io.Decoders;
|
import io.jsonwebtoken.io.Decoders;
|
||||||
import io.jsonwebtoken.io.DeserializationException;
|
import io.jsonwebtoken.io.DeserializationException;
|
||||||
import io.jsonwebtoken.io.Deserializer;
|
import io.jsonwebtoken.io.Deserializer;
|
||||||
import io.jsonwebtoken.impl.io.InstanceLocator;
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
import io.jsonwebtoken.lang.Classes;
|
import io.jsonwebtoken.lang.Classes;
|
||||||
import io.jsonwebtoken.lang.DateFormats;
|
import io.jsonwebtoken.lang.DateFormats;
|
||||||
import io.jsonwebtoken.lang.Objects;
|
import io.jsonwebtoken.lang.Objects;
|
||||||
import io.jsonwebtoken.lang.Strings;
|
import io.jsonwebtoken.lang.Strings;
|
||||||
|
import io.jsonwebtoken.security.InvalidKeyException;
|
||||||
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
import io.jsonwebtoken.security.WeakKeyException;
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
@ -359,8 +361,11 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
|
|
||||||
JwtSignatureValidator validator;
|
JwtSignatureValidator validator;
|
||||||
try {
|
try {
|
||||||
|
algorithm.assertValidVerificationKey(key); //since 0.10.0: https://github.com/jwtk/jjwt/issues/334
|
||||||
validator = createSignatureValidator(algorithm, key);
|
validator = createSignatureValidator(algorithm, key);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (WeakKeyException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (InvalidKeyException | IllegalArgumentException e) {
|
||||||
String algName = algorithm.getValue();
|
String algName = algorithm.getValue();
|
||||||
String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " +
|
String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " +
|
||||||
"algorithm, but the specified signing key of type " + key.getClass().getName() +
|
"algorithm, but the specified signing key of type " + key.getClass().getName() +
|
||||||
|
|
|
@ -15,6 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.impl.crypto;
|
package io.jsonwebtoken.impl.crypto;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.JwtException;
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
|
@ -22,10 +26,6 @@ import java.security.SecureRandom;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import io.jsonwebtoken.JwtException;
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ElliptiCurve crypto provider.
|
* ElliptiCurve crypto provider.
|
||||||
*
|
*
|
||||||
|
@ -172,8 +172,7 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
|
||||||
*
|
*
|
||||||
* @throws JwtException If the ASN.1/DER signature format is invalid.
|
* @throws JwtException If the ASN.1/DER signature format is invalid.
|
||||||
*/
|
*/
|
||||||
public static byte[] transcodeSignatureToConcat(final byte[] derSignature, int outputLength)
|
public static byte[] transcodeSignatureToConcat(final byte[] derSignature, int outputLength) throws JwtException {
|
||||||
throws JwtException {
|
|
||||||
|
|
||||||
if (derSignature.length < 8 || derSignature[0] != 48) {
|
if (derSignature.length < 8 || derSignature[0] != 48) {
|
||||||
throw new JwtException("Invalid ECDSA signature format");
|
throw new JwtException("Invalid ECDSA signature format");
|
||||||
|
@ -191,16 +190,16 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
|
||||||
byte rLength = derSignature[offset + 1];
|
byte rLength = derSignature[offset + 1];
|
||||||
|
|
||||||
int i = rLength;
|
int i = rLength;
|
||||||
while ((i > 0)
|
while ((i > 0) && (derSignature[(offset + 2 + rLength) - i] == 0)) {
|
||||||
&& (derSignature[(offset + 2 + rLength) - i] == 0))
|
|
||||||
i--;
|
i--;
|
||||||
|
}
|
||||||
|
|
||||||
byte sLength = derSignature[offset + 2 + rLength + 1];
|
byte sLength = derSignature[offset + 2 + rLength + 1];
|
||||||
|
|
||||||
int j = sLength;
|
int j = sLength;
|
||||||
while ((j > 0)
|
while ((j > 0) && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0)) {
|
||||||
&& (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0))
|
|
||||||
j--;
|
j--;
|
||||||
|
}
|
||||||
|
|
||||||
int rawLen = Math.max(i, j);
|
int rawLen = Math.max(i, j);
|
||||||
rawLen = Math.max(rawLen, outputLength / 2);
|
rawLen = Math.max(rawLen, outputLength / 2);
|
||||||
|
@ -234,16 +233,15 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
|
||||||
*
|
*
|
||||||
* @throws JwtException If the ECDSA JWS signature format is invalid.
|
* @throws JwtException If the ECDSA JWS signature format is invalid.
|
||||||
*/
|
*/
|
||||||
public static byte[] transcodeSignatureToDER(byte[] jwsSignature)
|
public static byte[] transcodeSignatureToDER(byte[] jwsSignature) throws JwtException {
|
||||||
throws JwtException {
|
|
||||||
|
|
||||||
int rawLen = jwsSignature.length / 2;
|
int rawLen = jwsSignature.length / 2;
|
||||||
|
|
||||||
int i = rawLen;
|
int i = rawLen;
|
||||||
|
|
||||||
while((i > 0)
|
while((i > 0) && (jwsSignature[rawLen - i] == 0)) {
|
||||||
&& (jwsSignature[rawLen - i] == 0))
|
|
||||||
i--;
|
i--;
|
||||||
|
}
|
||||||
|
|
||||||
int j = i;
|
int j = i;
|
||||||
|
|
||||||
|
@ -253,9 +251,9 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
|
||||||
|
|
||||||
int k = rawLen;
|
int k = rawLen;
|
||||||
|
|
||||||
while ((k > 0)
|
while ((k > 0) && (jwsSignature[2 * rawLen - k] == 0)) {
|
||||||
&& (jwsSignature[2 * rawLen - k] == 0))
|
|
||||||
k--;
|
k--;
|
||||||
|
}
|
||||||
|
|
||||||
int l = k;
|
int l = k;
|
||||||
|
|
||||||
|
|
|
@ -15,16 +15,16 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.impl.crypto;
|
package io.jsonwebtoken.impl.crypto;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.security.interfaces.ECPublicKey;
|
import java.security.interfaces.ECPublicKey;
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
|
||||||
import io.jsonwebtoken.SignatureException;
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
|
||||||
|
|
||||||
public class EllipticCurveSignatureValidator extends EllipticCurveProvider implements SignatureValidator {
|
public class EllipticCurveSignatureValidator extends EllipticCurveProvider implements SignatureValidator {
|
||||||
|
|
||||||
private static final String EC_PUBLIC_KEY_REQD_MSG =
|
private static final String EC_PUBLIC_KEY_REQD_MSG =
|
||||||
|
|
|
@ -15,16 +15,16 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.impl.crypto;
|
package io.jsonwebtoken.impl.crypto;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.JwtException;
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.security.interfaces.ECKey;
|
import java.security.interfaces.ECKey;
|
||||||
|
|
||||||
import io.jsonwebtoken.JwtException;
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
|
||||||
import io.jsonwebtoken.SignatureException;
|
|
||||||
|
|
||||||
public class EllipticCurveSigner extends EllipticCurveProvider implements Signer {
|
public class EllipticCurveSigner extends EllipticCurveProvider implements Signer {
|
||||||
|
|
||||||
public EllipticCurveSigner(SignatureAlgorithm alg, Key key) {
|
public EllipticCurveSigner(SignatureAlgorithm alg, Key key) {
|
||||||
|
|
|
@ -18,9 +18,10 @@ package io.jsonwebtoken.impl.crypto;
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
public abstract class MacProvider extends SignatureProvider {
|
public abstract class MacProvider extends SignatureProvider {
|
||||||
|
@ -31,11 +32,11 @@ public abstract class MacProvider extends SignatureProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new secure-random 512 bit secret key suitable for creating and verifying HMAC signatures. This is a
|
* Generates a new secure-random 512 bit secret key suitable for creating and verifying HMAC-SHA signatures. This
|
||||||
* convenience method that immediately delegates to {@link #generateKey(SignatureAlgorithm)} using {@link
|
* is a convenience method that immediately delegates to {@link #generateKey(SignatureAlgorithm)} using {@link
|
||||||
* SignatureAlgorithm#HS512} as the method argument.
|
* SignatureAlgorithm#HS512} as the method argument.
|
||||||
*
|
*
|
||||||
* @return a new secure-random 512 bit secret key suitable for creating and verifying HMAC signatures.
|
* @return a new secure-random 512 bit secret key suitable for creating and verifying HMAC-SHA signatures.
|
||||||
* @see #generateKey(SignatureAlgorithm)
|
* @see #generateKey(SignatureAlgorithm)
|
||||||
* @see #generateKey(SignatureAlgorithm, SecureRandom)
|
* @see #generateKey(SignatureAlgorithm, SecureRandom)
|
||||||
* @since 0.5
|
* @since 0.5
|
||||||
|
@ -78,26 +79,22 @@ public abstract class MacProvider extends SignatureProvider {
|
||||||
* @see #generateKey()
|
* @see #generateKey()
|
||||||
* @see #generateKey(SignatureAlgorithm)
|
* @see #generateKey(SignatureAlgorithm)
|
||||||
* @since 0.5
|
* @since 0.5
|
||||||
|
* @deprecated since 0.10.0 - use {@link #generateKey(SignatureAlgorithm)} instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public static SecretKey generateKey(SignatureAlgorithm alg, SecureRandom random) {
|
public static SecretKey generateKey(SignatureAlgorithm alg, SecureRandom random) {
|
||||||
|
|
||||||
Assert.isTrue(alg.isHmac(), "SignatureAlgorithm argument must represent an HMAC algorithm.");
|
Assert.isTrue(alg.isHmac(), "SignatureAlgorithm argument must represent an HMAC algorithm.");
|
||||||
|
|
||||||
byte[] bytes;
|
KeyGenerator gen;
|
||||||
|
|
||||||
switch (alg) {
|
try {
|
||||||
case HS256:
|
gen = KeyGenerator.getInstance(alg.getJcaName());
|
||||||
bytes = new byte[32];
|
} catch (NoSuchAlgorithmException e) {
|
||||||
break;
|
throw new IllegalStateException("The " + alg.getJcaName() + " algorithm is not available. " +
|
||||||
case HS384:
|
"This should never happen on JDK 7 or later - please report this to the JJWT developers.", e);
|
||||||
bytes = new byte[48];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
bytes = new byte[64];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
random.nextBytes(bytes);
|
return gen.generateKey();
|
||||||
|
|
||||||
return new SecretKeySpec(bytes, alg.getJcaName());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
package io.jsonwebtoken.impl.crypto;
|
package io.jsonwebtoken.impl.crypto;
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.SignatureException;
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
package io.jsonwebtoken.impl.crypto;
|
package io.jsonwebtoken.impl.crypto;
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.SignatureException;
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
@ -127,9 +127,9 @@ public abstract class RsaProvider extends SignatureProvider {
|
||||||
* @see #generateKeyPair(String, int, SecureRandom)
|
* @see #generateKeyPair(String, int, SecureRandom)
|
||||||
* @since 0.10.0
|
* @since 0.10.0
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused") //used by io.jsonwebtoken.crypto.Keys
|
@SuppressWarnings("unused") //used by io.jsonwebtoken.security.Keys
|
||||||
public static KeyPair generateKeyPair(SignatureAlgorithm alg) {
|
public static KeyPair generateKeyPair(SignatureAlgorithm alg) {
|
||||||
Assert.isTrue("RSA".equalsIgnoreCase(alg.getFamilyName()), "Only RSA algorithms are supported by this method.");
|
Assert.isTrue(alg.isRsa(), "Only RSA algorithms are supported by this method.");
|
||||||
int keySizeInBits = 4096;
|
int keySizeInBits = 4096;
|
||||||
switch (alg) {
|
switch (alg) {
|
||||||
case RS256:
|
case RS256:
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
package io.jsonwebtoken.impl.crypto;
|
package io.jsonwebtoken.impl.crypto;
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.SignatureException;
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
package io.jsonwebtoken.impl.crypto;
|
package io.jsonwebtoken.impl.crypto;
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.SignatureException;
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
|
|
@ -15,16 +15,16 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.impl.crypto;
|
package io.jsonwebtoken.impl.crypto;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.lang.RuntimeEnvironment;
|
||||||
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
|
||||||
import io.jsonwebtoken.SignatureException;
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
|
||||||
import io.jsonwebtoken.lang.RuntimeEnvironment;
|
|
||||||
|
|
||||||
abstract class SignatureProvider {
|
abstract class SignatureProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.impl.crypto;
|
package io.jsonwebtoken.impl.crypto;
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureException;
|
import io.jsonwebtoken.security.SignatureException;
|
||||||
|
|
||||||
public interface Signer {
|
public interface Signer {
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import io.jsonwebtoken.impl.DefaultClock
|
||||||
import io.jsonwebtoken.impl.FixedClock
|
import io.jsonwebtoken.impl.FixedClock
|
||||||
import io.jsonwebtoken.io.Encoders
|
import io.jsonwebtoken.io.Encoders
|
||||||
import io.jsonwebtoken.lang.Strings
|
import io.jsonwebtoken.lang.Strings
|
||||||
|
import io.jsonwebtoken.security.SignatureException
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
|
@ -19,12 +19,11 @@ import io.jsonwebtoken.impl.DefaultHeader
|
||||||
import io.jsonwebtoken.impl.DefaultJwsHeader
|
import io.jsonwebtoken.impl.DefaultJwsHeader
|
||||||
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver
|
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver
|
||||||
import io.jsonwebtoken.impl.compression.GzipCompressionCodec
|
import io.jsonwebtoken.impl.compression.GzipCompressionCodec
|
||||||
import io.jsonwebtoken.impl.crypto.EllipticCurveProvider
|
|
||||||
import io.jsonwebtoken.impl.crypto.MacProvider
|
|
||||||
import io.jsonwebtoken.impl.crypto.RsaProvider
|
|
||||||
import io.jsonwebtoken.io.Encoders
|
|
||||||
import io.jsonwebtoken.impl.io.RuntimeClasspathSerializerLocator
|
import io.jsonwebtoken.impl.io.RuntimeClasspathSerializerLocator
|
||||||
|
import io.jsonwebtoken.io.Encoders
|
||||||
import io.jsonwebtoken.lang.Strings
|
import io.jsonwebtoken.lang.Strings
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
|
import io.jsonwebtoken.security.WeakKeyException
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
|
@ -348,11 +347,12 @@ class JwtsTest {
|
||||||
@Test
|
@Test
|
||||||
void testUncompressedJwt() {
|
void testUncompressedJwt() {
|
||||||
|
|
||||||
byte[] key = MacProvider.generateKey().getEncoded()
|
SignatureAlgorithm alg = SignatureAlgorithm.HS256
|
||||||
|
byte[] key = Keys.secretKeyFor(alg).encoded
|
||||||
|
|
||||||
String id = UUID.randomUUID().toString()
|
String id = UUID.randomUUID().toString()
|
||||||
|
|
||||||
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
|
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(alg, key)
|
||||||
.claim("state", "hello this is an amazing jwt").compact()
|
.claim("state", "hello this is an amazing jwt").compact()
|
||||||
|
|
||||||
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
|
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
|
||||||
|
@ -369,11 +369,12 @@ class JwtsTest {
|
||||||
@Test
|
@Test
|
||||||
void testCompressedJwtWithDeflate() {
|
void testCompressedJwtWithDeflate() {
|
||||||
|
|
||||||
byte[] key = MacProvider.generateKey().getEncoded()
|
SignatureAlgorithm alg = SignatureAlgorithm.HS256
|
||||||
|
byte[] key = Keys.secretKeyFor(alg).encoded
|
||||||
|
|
||||||
String id = UUID.randomUUID().toString()
|
String id = UUID.randomUUID().toString()
|
||||||
|
|
||||||
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
|
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(alg, key)
|
||||||
.claim("state", "hello this is an amazing jwt").compressWith(CompressionCodecs.DEFLATE).compact()
|
.claim("state", "hello this is an amazing jwt").compressWith(CompressionCodecs.DEFLATE).compact()
|
||||||
|
|
||||||
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
|
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
|
||||||
|
@ -390,11 +391,12 @@ class JwtsTest {
|
||||||
@Test
|
@Test
|
||||||
void testCompressedJwtWithGZIP() {
|
void testCompressedJwtWithGZIP() {
|
||||||
|
|
||||||
byte[] key = MacProvider.generateKey().getEncoded()
|
SignatureAlgorithm alg = SignatureAlgorithm.HS256
|
||||||
|
byte[] key = Keys.secretKeyFor(alg).encoded
|
||||||
|
|
||||||
String id = UUID.randomUUID().toString()
|
String id = UUID.randomUUID().toString()
|
||||||
|
|
||||||
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
|
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(alg, key)
|
||||||
.claim("state", "hello this is an amazing jwt").compressWith(CompressionCodecs.GZIP).compact()
|
.claim("state", "hello this is an amazing jwt").compressWith(CompressionCodecs.GZIP).compact()
|
||||||
|
|
||||||
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
|
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
|
||||||
|
@ -410,11 +412,13 @@ class JwtsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCompressedWithCustomResolver() {
|
void testCompressedWithCustomResolver() {
|
||||||
byte[] key = MacProvider.generateKey().getEncoded()
|
|
||||||
|
SignatureAlgorithm alg = SignatureAlgorithm.HS256
|
||||||
|
byte[] key = Keys.secretKeyFor(alg).encoded
|
||||||
|
|
||||||
String id = UUID.randomUUID().toString()
|
String id = UUID.randomUUID().toString()
|
||||||
|
|
||||||
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
|
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(alg, key)
|
||||||
.claim("state", "hello this is an amazing jwt").compressWith(new GzipCompressionCodec() {
|
.claim("state", "hello this is an amazing jwt").compressWith(new GzipCompressionCodec() {
|
||||||
@Override
|
@Override
|
||||||
String getAlgorithmName() {
|
String getAlgorithmName() {
|
||||||
|
@ -446,11 +450,13 @@ class JwtsTest {
|
||||||
|
|
||||||
@Test(expected = CompressionException.class)
|
@Test(expected = CompressionException.class)
|
||||||
void testCompressedJwtWithUnrecognizedHeader() {
|
void testCompressedJwtWithUnrecognizedHeader() {
|
||||||
byte[] key = MacProvider.generateKey().getEncoded()
|
|
||||||
|
SignatureAlgorithm alg = SignatureAlgorithm.HS256
|
||||||
|
byte[] key = Keys.secretKeyFor(alg).encoded
|
||||||
|
|
||||||
String id = UUID.randomUUID().toString()
|
String id = UUID.randomUUID().toString()
|
||||||
|
|
||||||
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
|
String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(alg, key)
|
||||||
.claim("state", "hello this is an amazing jwt").compressWith(new GzipCompressionCodec() {
|
.claim("state", "hello this is an amazing jwt").compressWith(new GzipCompressionCodec() {
|
||||||
@Override
|
@Override
|
||||||
String getAlgorithmName() {
|
String getAlgorithmName() {
|
||||||
|
@ -464,11 +470,12 @@ class JwtsTest {
|
||||||
@Test
|
@Test
|
||||||
void testCompressStringPayloadWithDeflate() {
|
void testCompressStringPayloadWithDeflate() {
|
||||||
|
|
||||||
byte[] key = MacProvider.generateKey().getEncoded()
|
SignatureAlgorithm alg = SignatureAlgorithm.HS256
|
||||||
|
byte[] key = Keys.secretKeyFor(alg).encoded
|
||||||
|
|
||||||
String payload = "this is my test for a payload"
|
String payload = "this is my test for a payload"
|
||||||
|
|
||||||
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, key)
|
String compact = Jwts.builder().setPayload(payload).signWith(alg, key)
|
||||||
.compressWith(CompressionCodecs.DEFLATE).compact()
|
.compressWith(CompressionCodecs.DEFLATE).compact()
|
||||||
|
|
||||||
def jws = Jwts.parser().setSigningKey(key).parsePlaintextJws(compact)
|
def jws = Jwts.parser().setSigningKey(key).parsePlaintextJws(compact)
|
||||||
|
@ -482,62 +489,62 @@ class JwtsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHS256() {
|
void testHS256() {
|
||||||
testHmac(SignatureAlgorithm.HS256);
|
testHmac(SignatureAlgorithm.HS256)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHS384() {
|
void testHS384() {
|
||||||
testHmac(SignatureAlgorithm.HS384);
|
testHmac(SignatureAlgorithm.HS384)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHS512() {
|
void testHS512() {
|
||||||
testHmac(SignatureAlgorithm.HS512);
|
testHmac(SignatureAlgorithm.HS512)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRS256() {
|
void testRS256() {
|
||||||
testRsa(SignatureAlgorithm.RS256);
|
testRsa(SignatureAlgorithm.RS256)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRS384() {
|
void testRS384() {
|
||||||
testRsa(SignatureAlgorithm.RS384);
|
testRsa(SignatureAlgorithm.RS384)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRS512() {
|
void testRS512() {
|
||||||
testRsa(SignatureAlgorithm.RS512);
|
testRsa(SignatureAlgorithm.RS512)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPS256() {
|
void testPS256() {
|
||||||
testRsa(SignatureAlgorithm.PS256);
|
testRsa(SignatureAlgorithm.PS256)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPS384() {
|
void testPS384() {
|
||||||
testRsa(SignatureAlgorithm.PS384);
|
testRsa(SignatureAlgorithm.PS384)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPS512() {
|
void testPS512() {
|
||||||
testRsa(SignatureAlgorithm.PS512, 2048, false);
|
testRsa(SignatureAlgorithm.PS512)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRSA256WithPrivateKeyValidation() {
|
void testRSA256WithPrivateKeyValidation() {
|
||||||
testRsa(SignatureAlgorithm.RS256, 1024, true);
|
testRsa(SignatureAlgorithm.RS256, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRSA384WithPrivateKeyValidation() {
|
void testRSA384WithPrivateKeyValidation() {
|
||||||
testRsa(SignatureAlgorithm.RS384, 1024, true);
|
testRsa(SignatureAlgorithm.RS384, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRSA512WithPrivateKeyValidation() {
|
void testRSA512WithPrivateKeyValidation() {
|
||||||
testRsa(SignatureAlgorithm.RS512, 1024, true);
|
testRsa(SignatureAlgorithm.RS512, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -565,17 +572,34 @@ class JwtsTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testParseClaimsJwsWithWeakHmacKey() {
|
||||||
|
|
||||||
|
SignatureAlgorithm alg = SignatureAlgorithm.HS384
|
||||||
|
def key = Keys.secretKeyFor(alg)
|
||||||
|
def weakKey = Keys.secretKeyFor(SignatureAlgorithm.HS256)
|
||||||
|
|
||||||
|
String jws = Jwts.builder().setSubject("Foo").signWith(alg, key).compact()
|
||||||
|
|
||||||
|
try {
|
||||||
|
Jwts.parser().setSigningKey(weakKey).parseClaimsJws(jws)
|
||||||
|
fail('parseClaimsJws must fail for weak keys')
|
||||||
|
} catch (WeakKeyException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Asserts correct/expected behavior discussed in https://github.com/jwtk/jjwt/issues/20
|
//Asserts correct/expected behavior discussed in https://github.com/jwtk/jjwt/issues/20
|
||||||
@Test
|
@Test
|
||||||
void testParseClaimsJwsWithUnsignedJwt() {
|
void testParseClaimsJwsWithUnsignedJwt() {
|
||||||
|
|
||||||
//create random signing key for testing:
|
//create random signing key for testing:
|
||||||
byte[] key = MacProvider.generateKey().getEncoded()
|
SignatureAlgorithm alg = SignatureAlgorithm.HS256
|
||||||
|
byte[] key = Keys.secretKeyFor(alg).encoded
|
||||||
|
|
||||||
String notSigned = Jwts.builder().setSubject("Foo").compact();
|
String notSigned = Jwts.builder().setSubject("Foo").compact()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Jwts.parser().setSigningKey(key).parseClaimsJws(notSigned);
|
Jwts.parser().setSigningKey(key).parseClaimsJws(notSigned)
|
||||||
fail('parseClaimsJws must fail for unsigned JWTs')
|
fail('parseClaimsJws must fail for unsigned JWTs')
|
||||||
} catch (UnsupportedJwtException expected) {
|
} catch (UnsupportedJwtException expected) {
|
||||||
assertEquals expected.message, 'Unsigned Claims JWTs are not supported.'
|
assertEquals expected.message, 'Unsigned Claims JWTs are not supported.'
|
||||||
|
@ -587,27 +611,28 @@ class JwtsTest {
|
||||||
void testForgedTokenWithSwappedHeaderUsingNoneAlgorithm() {
|
void testForgedTokenWithSwappedHeaderUsingNoneAlgorithm() {
|
||||||
|
|
||||||
//create random signing key for testing:
|
//create random signing key for testing:
|
||||||
byte[] key = MacProvider.generateKey().getEncoded()
|
SignatureAlgorithm alg = SignatureAlgorithm.HS256
|
||||||
|
byte[] key = Keys.secretKeyFor(alg).encoded
|
||||||
|
|
||||||
//this is a 'real', valid JWT:
|
//this is a 'real', valid JWT:
|
||||||
String compact = Jwts.builder().setSubject("Joe").signWith(SignatureAlgorithm.HS256, key).compact();
|
String compact = Jwts.builder().setSubject("Joe").signWith(alg, key).compact()
|
||||||
|
|
||||||
//Now strip off the signature so we can add it back in later on a forged token:
|
//Now strip off the signature so we can add it back in later on a forged token:
|
||||||
int i = compact.lastIndexOf('.');
|
int i = compact.lastIndexOf('.')
|
||||||
String signature = compact.substring(i + 1);
|
String signature = compact.substring(i + 1)
|
||||||
|
|
||||||
//now let's create a fake header and payload with whatever we want (without signing):
|
//now let's create a fake header and payload with whatever we want (without signing):
|
||||||
String forged = Jwts.builder().setSubject("Not Joe").compact();
|
String forged = Jwts.builder().setSubject("Not Joe").compact()
|
||||||
|
|
||||||
//assert that our forged header has a 'NONE' algorithm:
|
//assert that our forged header has a 'NONE' algorithm:
|
||||||
assertEquals Jwts.parser().parseClaimsJwt(forged).getHeader().get('alg'), 'none'
|
assertEquals Jwts.parser().parseClaimsJwt(forged).getHeader().get('alg'), 'none'
|
||||||
|
|
||||||
//now let's forge it by appending the signature the server expects:
|
//now let's forge it by appending the signature the server expects:
|
||||||
forged += signature;
|
forged += signature
|
||||||
|
|
||||||
//now assert that, when the server tries to parse the forged token, parsing fails:
|
//now assert that, when the server tries to parse the forged token, parsing fails:
|
||||||
try {
|
try {
|
||||||
Jwts.parser().setSigningKey(key).parse(forged);
|
Jwts.parser().setSigningKey(key).parse(forged)
|
||||||
fail("Parsing must fail for a forged token.")
|
fail("Parsing must fail for a forged token.")
|
||||||
} catch (MalformedJwtException expected) {
|
} catch (MalformedJwtException expected) {
|
||||||
assertEquals expected.message, 'JWT string has a digest/signature, but the header does not reference a valid signature algorithm.'
|
assertEquals expected.message, 'JWT string has a digest/signature, but the header does not reference a valid signature algorithm.'
|
||||||
|
@ -619,9 +644,9 @@ class JwtsTest {
|
||||||
void testParseForgedRsaPublicKeyAsHmacTokenVerifiedWithTheRsaPrivateKey() {
|
void testParseForgedRsaPublicKeyAsHmacTokenVerifiedWithTheRsaPrivateKey() {
|
||||||
|
|
||||||
//Create a legitimate RSA public and private key pair:
|
//Create a legitimate RSA public and private key pair:
|
||||||
KeyPair kp = RsaProvider.generateKeyPair(1024)
|
KeyPair kp = Keys.keyPairFor(SignatureAlgorithm.RS256)
|
||||||
PublicKey publicKey = kp.getPublic();
|
PublicKey publicKey = kp.getPublic()
|
||||||
PrivateKey privateKey = kp.getPrivate();
|
PrivateKey privateKey = kp.getPrivate()
|
||||||
|
|
||||||
String header = base64Url(toJson(['alg': 'HS256']))
|
String header = base64Url(toJson(['alg': 'HS256']))
|
||||||
String body = base64Url(toJson('foo'))
|
String body = base64Url(toJson('foo'))
|
||||||
|
@ -651,7 +676,7 @@ class JwtsTest {
|
||||||
void testParseForgedRsaPublicKeyAsHmacTokenVerifiedWithTheRsaPublicKey() {
|
void testParseForgedRsaPublicKeyAsHmacTokenVerifiedWithTheRsaPublicKey() {
|
||||||
|
|
||||||
//Create a legitimate RSA public and private key pair:
|
//Create a legitimate RSA public and private key pair:
|
||||||
KeyPair kp = RsaProvider.generateKeyPair(1024)
|
KeyPair kp = Keys.keyPairFor(SignatureAlgorithm.RS256)
|
||||||
PublicKey publicKey = kp.getPublic();
|
PublicKey publicKey = kp.getPublic();
|
||||||
//PrivateKey privateKey = kp.getPrivate();
|
//PrivateKey privateKey = kp.getPrivate();
|
||||||
|
|
||||||
|
@ -683,7 +708,7 @@ class JwtsTest {
|
||||||
void testParseForgedEllipticCurvePublicKeyAsHmacToken() {
|
void testParseForgedEllipticCurvePublicKeyAsHmacToken() {
|
||||||
|
|
||||||
//Create a legitimate RSA public and private key pair:
|
//Create a legitimate RSA public and private key pair:
|
||||||
KeyPair kp = EllipticCurveProvider.generateKeyPair()
|
KeyPair kp = Keys.keyPairFor(SignatureAlgorithm.ES256)
|
||||||
PublicKey publicKey = kp.getPublic();
|
PublicKey publicKey = kp.getPublic();
|
||||||
//PrivateKey privateKey = kp.getPrivate();
|
//PrivateKey privateKey = kp.getPrivate();
|
||||||
|
|
||||||
|
@ -703,29 +728,29 @@ class JwtsTest {
|
||||||
|
|
||||||
// Assert that the parser does not recognized the forged token:
|
// Assert that the parser does not recognized the forged token:
|
||||||
try {
|
try {
|
||||||
Jwts.parser().setSigningKey(publicKey).parse(forged);
|
Jwts.parser().setSigningKey(publicKey).parse(forged)
|
||||||
fail("Forged token must not be successfully parsed.")
|
fail("Forged token must not be successfully parsed.")
|
||||||
} catch (UnsupportedJwtException expected) {
|
} catch (UnsupportedJwtException expected) {
|
||||||
assertTrue expected.getMessage().startsWith('The parsed JWT indicates it was signed with the')
|
assertTrue expected.getMessage().startsWith('The parsed JWT indicates it was signed with the')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void testRsa(SignatureAlgorithm alg, int keySize = 1024, boolean verifyWithPrivateKey = false) {
|
static void testRsa(SignatureAlgorithm alg, boolean verifyWithPrivateKey = false) {
|
||||||
|
|
||||||
KeyPair kp = RsaProvider.generateKeyPair(keySize)
|
KeyPair kp = Keys.keyPairFor(alg)
|
||||||
PublicKey publicKey = kp.getPublic();
|
PublicKey publicKey = kp.getPublic()
|
||||||
PrivateKey privateKey = kp.getPrivate();
|
PrivateKey privateKey = kp.getPrivate()
|
||||||
|
|
||||||
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(alg, privateKey).compact()
|
||||||
|
|
||||||
def key = publicKey;
|
def key = publicKey
|
||||||
if (verifyWithPrivateKey) {
|
if (verifyWithPrivateKey) {
|
||||||
key = privateKey;
|
key = privateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
def token = Jwts.parser().setSigningKey(key).parse(jwt);
|
def token = Jwts.parser().setSigningKey(key).parse(jwt)
|
||||||
|
|
||||||
assert [alg: alg.name()] == token.header
|
assert [alg: alg.name()] == token.header
|
||||||
//noinspection GrEqualsBetweenInconvertibleTypes
|
//noinspection GrEqualsBetweenInconvertibleTypes
|
||||||
|
@ -733,12 +758,13 @@ class JwtsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void testHmac(SignatureAlgorithm alg) {
|
static void testHmac(SignatureAlgorithm alg) {
|
||||||
|
|
||||||
//create random signing key for testing:
|
//create random signing key for testing:
|
||||||
byte[] key = MacProvider.generateKey().encoded
|
byte[] key = Keys.secretKeyFor(alg).encoded
|
||||||
|
|
||||||
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, key).compact();
|
String jwt = Jwts.builder().setClaims(claims).signWith(alg, key).compact()
|
||||||
|
|
||||||
def token = Jwts.parser().setSigningKey(key).parse(jwt)
|
def token = Jwts.parser().setSigningKey(key).parse(jwt)
|
||||||
|
|
||||||
|
@ -749,20 +775,20 @@ class JwtsTest {
|
||||||
|
|
||||||
static void testEC(SignatureAlgorithm alg, boolean verifyWithPrivateKey = false) {
|
static void testEC(SignatureAlgorithm alg, boolean verifyWithPrivateKey = false) {
|
||||||
|
|
||||||
KeyPair pair = EllipticCurveProvider.generateKeyPair(alg)
|
KeyPair pair = Keys.keyPairFor(alg)
|
||||||
PublicKey publicKey = pair.getPublic()
|
PublicKey publicKey = pair.getPublic()
|
||||||
PrivateKey privateKey = pair.getPrivate()
|
PrivateKey privateKey = pair.getPrivate()
|
||||||
|
|
||||||
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(alg, privateKey).compact()
|
||||||
|
|
||||||
def key = publicKey;
|
def key = publicKey
|
||||||
if (verifyWithPrivateKey) {
|
if (verifyWithPrivateKey) {
|
||||||
key = privateKey;
|
key = privateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
def token = Jwts.parser().setSigningKey(key).parse(jwt);
|
def token = Jwts.parser().setSigningKey(key).parse(jwt)
|
||||||
|
|
||||||
assert token.header == [alg: alg.name()]
|
assert token.header == [alg: alg.name()]
|
||||||
//noinspection GrEqualsBetweenInconvertibleTypes
|
//noinspection GrEqualsBetweenInconvertibleTypes
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken
|
package io.jsonwebtoken
|
||||||
|
|
||||||
import io.jsonwebtoken.impl.crypto.RsaProvider
|
import io.jsonwebtoken.security.Keys
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals
|
import static org.junit.Assert.assertEquals
|
||||||
|
@ -26,9 +26,11 @@ class RsaSigningKeyResolverAdapterTest {
|
||||||
@Test
|
@Test
|
||||||
void testResolveClaimsSigningKeyWithRsaKey() {
|
void testResolveClaimsSigningKeyWithRsaKey() {
|
||||||
|
|
||||||
def pair = RsaProvider.generateKeyPair(1024) //real apps should use 4096 or better. We're only reducing the size here so the tests are fast
|
def alg = SignatureAlgorithm.RS256
|
||||||
|
|
||||||
def compact = Jwts.builder().claim('foo', 'bar').signWith(SignatureAlgorithm.RS256, pair.private).compact()
|
def pair = Keys.keyPairFor(alg)
|
||||||
|
|
||||||
|
def compact = Jwts.builder().claim('foo', 'bar').signWith(alg, pair.private).compact()
|
||||||
|
|
||||||
Jws<Claims> jws = Jwts.parser().setSigningKey(pair.public).parseClaimsJws(compact)
|
Jws<Claims> jws = Jwts.parser().setSigningKey(pair.public).parseClaimsJws(compact)
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,11 @@ package io.jsonwebtoken.impl
|
||||||
import io.jsonwebtoken.JwsHeader
|
import io.jsonwebtoken.JwsHeader
|
||||||
import io.jsonwebtoken.Jwts
|
import io.jsonwebtoken.Jwts
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.impl.crypto.MacProvider
|
import io.jsonwebtoken.security.Keys
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import static org.junit.Assert.*
|
|
||||||
|
import static org.junit.Assert.assertEquals
|
||||||
|
import static org.junit.Assert.assertSame
|
||||||
|
|
||||||
class DefaultJwsTest {
|
class DefaultJwsTest {
|
||||||
|
|
||||||
|
@ -38,8 +40,9 @@ class DefaultJwsTest {
|
||||||
@Test
|
@Test
|
||||||
void testToString() {
|
void testToString() {
|
||||||
//create random signing key for testing:
|
//create random signing key for testing:
|
||||||
byte[] key = MacProvider.generateKey().encoded
|
SignatureAlgorithm alg = SignatureAlgorithm.HS256
|
||||||
String compact = Jwts.builder().claim('foo', 'bar').signWith(SignatureAlgorithm.HS256, key).compact();
|
byte[] key = Keys.secretKeyFor(alg).encoded
|
||||||
|
String compact = Jwts.builder().claim('foo', 'bar').signWith(alg, key).compact();
|
||||||
int i = compact.lastIndexOf('.')
|
int i = compact.lastIndexOf('.')
|
||||||
String signature = compact.substring(i + 1)
|
String signature = compact.substring(i + 1)
|
||||||
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
|
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
|
||||||
|
|
|
@ -16,14 +16,14 @@
|
||||||
package io.jsonwebtoken.impl
|
package io.jsonwebtoken.impl
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import io.jsonwebtoken.CompressionCodecs
|
||||||
import io.jsonwebtoken.Jwts
|
import io.jsonwebtoken.Jwts
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.io.Encoder
|
import io.jsonwebtoken.io.Encoder
|
||||||
import io.jsonwebtoken.io.EncodingException
|
import io.jsonwebtoken.io.EncodingException
|
||||||
import io.jsonwebtoken.CompressionCodecs
|
|
||||||
import io.jsonwebtoken.impl.crypto.MacProvider
|
|
||||||
import io.jsonwebtoken.io.SerializationException
|
import io.jsonwebtoken.io.SerializationException
|
||||||
import io.jsonwebtoken.io.Serializer
|
import io.jsonwebtoken.io.Serializer
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import static org.junit.Assert.*
|
import static org.junit.Assert.*
|
||||||
|
@ -174,8 +174,9 @@ class DefaultJwtBuilderTest {
|
||||||
def b = new DefaultJwtBuilder()
|
def b = new DefaultJwtBuilder()
|
||||||
b.setHeader(Jwts.jwsHeader().setKeyId('a'))
|
b.setHeader(Jwts.jwsHeader().setKeyId('a'))
|
||||||
b.setPayload('foo')
|
b.setPayload('foo')
|
||||||
def key = MacProvider.generateKey()
|
def alg = SignatureAlgorithm.HS256
|
||||||
b.signWith(SignatureAlgorithm.HS256, key)
|
def key = Keys.secretKeyFor(alg)
|
||||||
|
b.signWith(alg, key)
|
||||||
b.compact()
|
b.compact()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,14 +339,14 @@ class DefaultJwtBuilderTest {
|
||||||
def serializer = new Serializer() {
|
def serializer = new Serializer() {
|
||||||
@Override
|
@Override
|
||||||
byte[] serialize(Object o) throws SerializationException {
|
byte[] serialize(Object o) throws SerializationException {
|
||||||
return objectMapper.writeValueAsBytes(o);
|
return objectMapper.writeValueAsBytes(o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def b = new DefaultJwtBuilder().serializeToJsonWith(serializer)
|
def b = new DefaultJwtBuilder().serializeToJsonWith(serializer)
|
||||||
assertSame serializer, b.serializer
|
assertSame serializer, b.serializer
|
||||||
|
|
||||||
def key = MacProvider.generateKey(SignatureAlgorithm.HS256)
|
def key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
|
||||||
|
|
||||||
String jws = b.signWith(SignatureAlgorithm.HS256, key)
|
String jws = b.signWith(SignatureAlgorithm.HS256, key)
|
||||||
.claim('foo', 'bar')
|
.claim('foo', 'bar')
|
||||||
|
|
|
@ -4,13 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import io.jsonwebtoken.Jwts
|
import io.jsonwebtoken.Jwts
|
||||||
import io.jsonwebtoken.MalformedJwtException
|
import io.jsonwebtoken.MalformedJwtException
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.io.Decoder
|
import io.jsonwebtoken.io.*
|
||||||
import io.jsonwebtoken.io.DecodingException
|
|
||||||
import io.jsonwebtoken.impl.crypto.MacProvider
|
|
||||||
import io.jsonwebtoken.io.DeserializationException
|
|
||||||
import io.jsonwebtoken.io.Deserializer
|
|
||||||
import io.jsonwebtoken.io.Encoders
|
|
||||||
import io.jsonwebtoken.lang.Strings
|
import io.jsonwebtoken.lang.Strings
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
|
@ -60,7 +56,7 @@ class DefaultJwtParserTest {
|
||||||
def p = new DefaultJwtParser().deserializeJsonWith(deserializer)
|
def p = new DefaultJwtParser().deserializeJsonWith(deserializer)
|
||||||
assertSame deserializer, p.deserializer
|
assertSame deserializer, p.deserializer
|
||||||
|
|
||||||
def key = MacProvider.generateKey(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(SignatureAlgorithm.HS256, key).compact()
|
||||||
|
|
||||||
|
@ -74,7 +70,7 @@ class DefaultJwtParserTest {
|
||||||
String body = Encoders.BASE64URL.encode('{"hello":"world"}'.getBytes(Strings.UTF_8))
|
String body = Encoders.BASE64URL.encode('{"hello":"world"}'.getBytes(Strings.UTF_8))
|
||||||
String compact = header + '.' + body + '.'
|
String compact = header + '.' + body + '.'
|
||||||
|
|
||||||
SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256)
|
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
|
||||||
Mac mac = Mac.getInstance('HmacSHA256')
|
Mac mac = Mac.getInstance('HmacSHA256')
|
||||||
mac.init(key)
|
mac.init(key)
|
||||||
byte[] signatureBytes = mac.doFinal(compact.getBytes(Strings.UTF_8))
|
byte[] signatureBytes = mac.doFinal(compact.getBytes(Strings.UTF_8))
|
||||||
|
@ -92,7 +88,7 @@ class DefaultJwtParserTest {
|
||||||
String body = Encoders.BASE64URL.encode('{"hello":"world"}'.getBytes(Strings.UTF_8))
|
String body = Encoders.BASE64URL.encode('{"hello":"world"}'.getBytes(Strings.UTF_8))
|
||||||
String compact = header + '.' + body + '.'
|
String compact = header + '.' + body + '.'
|
||||||
|
|
||||||
SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256)
|
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
|
||||||
Mac mac = Mac.getInstance('HmacSHA256')
|
Mac mac = Mac.getInstance('HmacSHA256')
|
||||||
mac.init(key)
|
mac.init(key)
|
||||||
byte[] signatureBytes = mac.doFinal(compact.getBytes(Strings.UTF_8))
|
byte[] signatureBytes = mac.doFinal(compact.getBytes(Strings.UTF_8))
|
||||||
|
@ -110,7 +106,7 @@ class DefaultJwtParserTest {
|
||||||
String body = Encoders.BASE64URL.encode('{"hello":"world"}'.getBytes(Strings.UTF_8))
|
String body = Encoders.BASE64URL.encode('{"hello":"world"}'.getBytes(Strings.UTF_8))
|
||||||
String compact = header + '.' + body + '.'
|
String compact = header + '.' + body + '.'
|
||||||
|
|
||||||
SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256)
|
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
|
||||||
Mac mac = Mac.getInstance('HmacSHA256')
|
Mac mac = Mac.getInstance('HmacSHA256')
|
||||||
mac.init(key)
|
mac.init(key)
|
||||||
byte[] signatureBytes = mac.doFinal(compact.getBytes(Strings.UTF_8))
|
byte[] signatureBytes = mac.doFinal(compact.getBytes(Strings.UTF_8))
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.io.Decoders
|
import io.jsonwebtoken.io.Decoders
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull
|
import static org.junit.Assert.assertNotNull
|
||||||
|
@ -15,7 +16,7 @@ class DefaultJwtSignatureValidatorTest {
|
||||||
void testDeprecatedTwoArgCtor() {
|
void testDeprecatedTwoArgCtor() {
|
||||||
|
|
||||||
def alg = SignatureAlgorithm.HS256
|
def alg = SignatureAlgorithm.HS256
|
||||||
def key = MacProvider.generateKey(alg)
|
def key = Keys.secretKeyFor(alg)
|
||||||
def validator = new DefaultJwtSignatureValidator(alg, key)
|
def validator = new DefaultJwtSignatureValidator(alg, key)
|
||||||
|
|
||||||
assertNotNull validator.signatureValidator
|
assertNotNull validator.signatureValidator
|
||||||
|
@ -28,7 +29,7 @@ class DefaultJwtSignatureValidatorTest {
|
||||||
void testDeprecatedThreeArgCtor() {
|
void testDeprecatedThreeArgCtor() {
|
||||||
|
|
||||||
def alg = SignatureAlgorithm.HS256
|
def alg = SignatureAlgorithm.HS256
|
||||||
def key = MacProvider.generateKey(alg)
|
def key = Keys.secretKeyFor(alg)
|
||||||
def validator = new DefaultJwtSignatureValidator(DefaultSignatureValidatorFactory.INSTANCE, alg, key)
|
def validator = new DefaultJwtSignatureValidator(DefaultSignatureValidatorFactory.INSTANCE, alg, key)
|
||||||
|
|
||||||
assertNotNull validator.signatureValidator
|
assertNotNull validator.signatureValidator
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.io.Encoders
|
import io.jsonwebtoken.io.Encoders
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull
|
import static org.junit.Assert.assertNotNull
|
||||||
|
@ -16,7 +17,7 @@ class DefaultJwtSignerTest {
|
||||||
void testDeprecatedTwoArgCtor() {
|
void testDeprecatedTwoArgCtor() {
|
||||||
|
|
||||||
def alg = SignatureAlgorithm.HS256
|
def alg = SignatureAlgorithm.HS256
|
||||||
def key = MacProvider.generateKey(alg)
|
def key = Keys.secretKeyFor(alg)
|
||||||
def signer = new DefaultJwtSigner(alg, key)
|
def signer = new DefaultJwtSigner(alg, key)
|
||||||
|
|
||||||
assertNotNull signer.signer
|
assertNotNull signer.signer
|
||||||
|
@ -30,7 +31,7 @@ class DefaultJwtSignerTest {
|
||||||
void testDeprecatedThreeArgCtor() {
|
void testDeprecatedThreeArgCtor() {
|
||||||
|
|
||||||
def alg = SignatureAlgorithm.HS256
|
def alg = SignatureAlgorithm.HS256
|
||||||
def key = MacProvider.generateKey(alg)
|
def key = Keys.secretKeyFor(alg)
|
||||||
def signer = new DefaultJwtSigner(DefaultSignerFactory.INSTANCE, alg, key)
|
def signer = new DefaultJwtSigner(DefaultSignerFactory.INSTANCE, alg, key)
|
||||||
|
|
||||||
assertNotNull signer.signer
|
assertNotNull signer.signer
|
||||||
|
|
|
@ -16,15 +16,19 @@
|
||||||
package io.jsonwebtoken.impl.crypto
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import static org.junit.Assert.*
|
|
||||||
|
import static org.junit.Assert.assertEquals
|
||||||
|
import static org.junit.Assert.fail
|
||||||
|
|
||||||
class DefaultSignatureValidatorFactoryTest {
|
class DefaultSignatureValidatorFactoryTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testNoneAlgorithm() {
|
void testNoneAlgorithm() {
|
||||||
try {
|
try {
|
||||||
new DefaultSignatureValidatorFactory().createSignatureValidator(SignatureAlgorithm.NONE, MacProvider.generateKey())
|
new DefaultSignatureValidatorFactory().createSignatureValidator(
|
||||||
|
SignatureAlgorithm.NONE, Keys.secretKeyFor(SignatureAlgorithm.HS256))
|
||||||
fail()
|
fail()
|
||||||
} catch (IllegalArgumentException iae) {
|
} catch (IllegalArgumentException iae) {
|
||||||
assertEquals iae.message, "The 'NONE' algorithm cannot be used for signing."
|
assertEquals iae.message, "The 'NONE' algorithm cannot be used for signing."
|
||||||
|
|
|
@ -16,8 +16,11 @@
|
||||||
package io.jsonwebtoken.impl.crypto
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import io.jsonwebtoken.security.Keys
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import static org.junit.Assert.*
|
|
||||||
|
import static org.junit.Assert.assertEquals
|
||||||
|
import static org.junit.Assert.fail
|
||||||
|
|
||||||
class DefaultSignerFactoryTest {
|
class DefaultSignerFactoryTest {
|
||||||
|
|
||||||
|
@ -27,7 +30,7 @@ class DefaultSignerFactoryTest {
|
||||||
def factory = new DefaultSignerFactory();
|
def factory = new DefaultSignerFactory();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
factory.createSigner(SignatureAlgorithm.NONE, MacProvider.generateKey());
|
factory.createSigner(SignatureAlgorithm.NONE, Keys.secretKeyFor(SignatureAlgorithm.HS256))
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException iae) {
|
} catch (IllegalArgumentException iae) {
|
||||||
assertEquals iae.message, "The 'NONE' algorithm cannot be used for signing."
|
assertEquals iae.message, "The 'NONE' algorithm cannot be used for signing."
|
||||||
|
|
|
@ -17,8 +17,8 @@ package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
import io.jsonwebtoken.JwtException
|
import io.jsonwebtoken.JwtException
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.SignatureException
|
|
||||||
import io.jsonwebtoken.io.Decoders
|
import io.jsonwebtoken.io.Decoders
|
||||||
|
import io.jsonwebtoken.security.SignatureException
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
@ -143,6 +143,14 @@ class EllipticCurveSignatureValidatorTest {
|
||||||
EllipticCurveProvider.transcodeSignatureToDER(signature)
|
EllipticCurveProvider.transcodeSignatureToDER(signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPaddedSignatureToDER() {
|
||||||
|
def signature = new byte[32]
|
||||||
|
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
|
||||||
|
signature[0] = 0 as byte
|
||||||
|
EllipticCurveProvider.transcodeSignatureToDER(signature) //no exception
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void edgeCaseSignatureToConcatLengthTest() {
|
void edgeCaseSignatureToConcatLengthTest() {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -17,7 +17,8 @@ package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
import io.jsonwebtoken.JwtException
|
import io.jsonwebtoken.JwtException
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.SignatureException
|
import io.jsonwebtoken.security.Keys
|
||||||
|
import io.jsonwebtoken.security.SignatureException
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import java.security.InvalidKeyException
|
import java.security.InvalidKeyException
|
||||||
|
@ -31,37 +32,40 @@ class EllipticCurveSignerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConstructorWithoutECAlg() {
|
void testConstructorWithoutECAlg() {
|
||||||
|
SignatureAlgorithm alg = SignatureAlgorithm.HS256
|
||||||
try {
|
try {
|
||||||
new EllipticCurveSigner(SignatureAlgorithm.HS256, MacProvider.generateKey());
|
new EllipticCurveSigner(alg, Keys.secretKeyFor(alg))
|
||||||
fail('EllipticCurveSigner should reject non ECPrivateKeys');
|
fail('EllipticCurveSigner should reject non ECPrivateKeys')
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertEquals expected.message, 'SignatureAlgorithm must be an Elliptic Curve algorithm.';
|
assertEquals expected.message, 'SignatureAlgorithm must be an Elliptic Curve algorithm.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConstructorWithoutECPrivateKey() {
|
void testConstructorWithoutECPrivateKey() {
|
||||||
def key = MacProvider.generateKey()
|
def key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
|
||||||
try {
|
try {
|
||||||
new EllipticCurveSigner(SignatureAlgorithm.ES256, key);
|
new EllipticCurveSigner(SignatureAlgorithm.ES256, key)
|
||||||
fail('EllipticCurveSigner should reject non ECPrivateKey instances.')
|
fail('EllipticCurveSigner should reject non ECPrivateKey instances.')
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
assertEquals expected.message, "Elliptic Curve signatures must be computed using an EC PrivateKey. The specified key of " +
|
assertEquals expected.message, "Elliptic Curve signatures must be computed using an EC PrivateKey. The specified key of " +
|
||||||
"type " + key.getClass().getName() + " is not an EC PrivateKey.";
|
"type " + key.getClass().getName() + " is not an EC PrivateKey."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDoSignWithInvalidKeyException() {
|
void testDoSignWithInvalidKeyException() {
|
||||||
|
|
||||||
KeyPair kp = EllipticCurveProvider.generateKeyPair()
|
SignatureAlgorithm alg = SignatureAlgorithm.ES256
|
||||||
PublicKey publicKey = kp.getPublic();
|
|
||||||
PrivateKey privateKey = kp.getPrivate();
|
KeyPair kp = Keys.keyPairFor(alg)
|
||||||
|
PublicKey publicKey = kp.getPublic()
|
||||||
|
PrivateKey privateKey = kp.getPrivate()
|
||||||
|
|
||||||
String msg = 'foo'
|
String msg = 'foo'
|
||||||
final InvalidKeyException ex = new InvalidKeyException(msg)
|
final InvalidKeyException ex = new InvalidKeyException(msg)
|
||||||
|
|
||||||
def signer = new EllipticCurveSigner(SignatureAlgorithm.ES256, privateKey) {
|
def signer = new EllipticCurveSigner(alg, privateKey) {
|
||||||
@Override
|
@Override
|
||||||
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException {
|
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException {
|
||||||
throw ex
|
throw ex
|
||||||
|
|
|
@ -17,31 +17,39 @@ package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import static org.junit.Assert.*
|
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals
|
||||||
|
|
||||||
class MacProviderTest {
|
class MacProviderTest {
|
||||||
|
|
||||||
|
private void testHmac(SignatureAlgorithm alg) {
|
||||||
|
testHmac(alg, MacProvider.generateKey(alg))
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testHmac(SignatureAlgorithm alg, SecretKey key) {
|
||||||
|
assertEquals alg.jcaName, key.algorithm
|
||||||
|
assertEquals alg.digestLength / 8 as int, key.encoded.length
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDefault() {
|
void testDefault() {
|
||||||
byte[] bytes = MacProvider.generateKey().encoded
|
testHmac(SignatureAlgorithm.HS512, MacProvider.generateKey())
|
||||||
assertEquals 64, bytes.length
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHS256() {
|
void testHS256() {
|
||||||
byte[] bytes = MacProvider.generateKey(SignatureAlgorithm.HS256).encoded
|
testHmac(SignatureAlgorithm.HS256)
|
||||||
assertEquals 32, bytes.length
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHS384() {
|
void testHS384() {
|
||||||
byte[] bytes = MacProvider.generateKey(SignatureAlgorithm.HS384).encoded
|
testHmac(SignatureAlgorithm.HS384)
|
||||||
assertEquals 48, bytes.length
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHS512() {
|
void testHS512() {
|
||||||
byte[] bytes = MacProvider.generateKey(SignatureAlgorithm.HS512).encoded
|
testHmac(SignatureAlgorithm.HS512)
|
||||||
assertEquals 64, bytes.length
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,18 +16,47 @@
|
||||||
package io.jsonwebtoken.impl.crypto
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.SignatureException
|
import io.jsonwebtoken.security.SignatureException
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import static org.junit.Assert.*
|
|
||||||
|
|
||||||
import javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
import java.security.InvalidKeyException
|
import java.security.InvalidKeyException
|
||||||
|
import java.security.Key
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
|
|
||||||
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
class MacSignerTest {
|
class MacSignerTest {
|
||||||
|
|
||||||
private static final Random rng = new Random(); //doesn't need to be secure - we're just testing
|
private static final Random rng = new Random(); //doesn't need to be secure - we're just testing
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCtorArgNotASecretKey() {
|
||||||
|
|
||||||
|
def key = new Key() {
|
||||||
|
@Override
|
||||||
|
String getAlgorithm() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String getFormat() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
byte[] getEncoded() {
|
||||||
|
return new byte[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new MacSigner(SignatureAlgorithm.HS256, key)
|
||||||
|
fail()
|
||||||
|
} catch (IllegalArgumentException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testNoSuchAlgorithmException() {
|
void testNoSuchAlgorithmException() {
|
||||||
byte[] key = new byte[32];
|
byte[] key = new byte[32];
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.powermock.core.classloader.annotations.PrepareForTest
|
||||||
|
import org.powermock.modules.junit4.PowerMockRunner
|
||||||
|
|
||||||
|
import javax.crypto.KeyGenerator
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
|
|
||||||
|
import static org.easymock.EasyMock.eq
|
||||||
|
import static org.easymock.EasyMock.expect
|
||||||
|
import static org.junit.Assert.*
|
||||||
|
import static org.powermock.api.easymock.PowerMock.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This needs to be a separate class beyond MacProviderTest because it mocks the KeyGenerator class which messes up
|
||||||
|
* the other implementation tests in MacProviderTest.
|
||||||
|
*/
|
||||||
|
@RunWith(PowerMockRunner.class)
|
||||||
|
@PrepareForTest([KeyGenerator])
|
||||||
|
class PowermockMacProviderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNoSuchAlgorithm() {
|
||||||
|
|
||||||
|
mockStatic(KeyGenerator)
|
||||||
|
|
||||||
|
def alg = SignatureAlgorithm.HS256
|
||||||
|
def ex = new NoSuchAlgorithmException('foo')
|
||||||
|
|
||||||
|
expect(KeyGenerator.getInstance(eq(alg.jcaName))).andThrow(ex)
|
||||||
|
|
||||||
|
replay KeyGenerator
|
||||||
|
|
||||||
|
try {
|
||||||
|
MacProvider.generateKey(alg)
|
||||||
|
fail()
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
assertEquals 'The HmacSHA256 algorithm is not available. This should never happen on JDK 7 or later - ' +
|
||||||
|
'please report this to the JJWT developers.', e.message
|
||||||
|
assertSame ex, e.getCause()
|
||||||
|
}
|
||||||
|
|
||||||
|
verify KeyGenerator
|
||||||
|
|
||||||
|
reset KeyGenerator
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,8 @@
|
||||||
package io.jsonwebtoken.impl.crypto
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.SignatureException
|
import io.jsonwebtoken.security.SignatureException
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException
|
import java.security.InvalidAlgorithmParameterException
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
|
@ -25,7 +26,6 @@ import java.security.interfaces.RSAPrivateKey
|
||||||
import java.security.interfaces.RSAPublicKey
|
import java.security.interfaces.RSAPublicKey
|
||||||
import java.security.spec.PSSParameterSpec
|
import java.security.spec.PSSParameterSpec
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
import static org.junit.Assert.*
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
class RsaProviderTest {
|
class RsaProviderTest {
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
package io.jsonwebtoken.impl.crypto
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.SignatureException
|
import io.jsonwebtoken.security.Keys
|
||||||
|
import io.jsonwebtoken.security.SignatureException
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import java.security.*
|
import java.security.*
|
||||||
|
@ -30,7 +31,7 @@ class RsaSignatureValidatorTest {
|
||||||
@Test
|
@Test
|
||||||
void testConstructorWithNonRsaKey() {
|
void testConstructorWithNonRsaKey() {
|
||||||
try {
|
try {
|
||||||
new RsaSignatureValidator(SignatureAlgorithm.RS256, MacProvider.generateKey());
|
new RsaSignatureValidator(SignatureAlgorithm.RS256, Keys.secretKeyFor(SignatureAlgorithm.HS256));
|
||||||
fail()
|
fail()
|
||||||
} catch (IllegalArgumentException iae) {
|
} catch (IllegalArgumentException iae) {
|
||||||
assertEquals "RSA Signature validation requires either a RSAPublicKey or RSAPrivateKey instance.", iae.message
|
assertEquals "RSA Signature validation requires either a RSAPublicKey or RSAPrivateKey instance.", iae.message
|
||||||
|
@ -40,17 +41,16 @@ class RsaSignatureValidatorTest {
|
||||||
@Test
|
@Test
|
||||||
void testDoVerifyWithInvalidKeyException() {
|
void testDoVerifyWithInvalidKeyException() {
|
||||||
|
|
||||||
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
|
SignatureAlgorithm alg = SignatureAlgorithm.RS256
|
||||||
keyGenerator.initialize(1024);
|
|
||||||
|
|
||||||
KeyPair kp = keyGenerator.genKeyPair();
|
KeyPair kp = Keys.keyPairFor(alg)
|
||||||
PublicKey publicKey = kp.getPublic();
|
PublicKey publicKey = kp.getPublic()
|
||||||
PrivateKey privateKey = kp.getPrivate();
|
PrivateKey privateKey = kp.getPrivate()
|
||||||
|
|
||||||
String msg = 'foo'
|
String msg = 'foo'
|
||||||
final InvalidKeyException ex = new InvalidKeyException(msg)
|
final InvalidKeyException ex = new InvalidKeyException(msg)
|
||||||
|
|
||||||
RsaSignatureValidator v = new RsaSignatureValidator(SignatureAlgorithm.RS256, publicKey) {
|
RsaSignatureValidator v = new RsaSignatureValidator(alg, publicKey) {
|
||||||
@Override
|
@Override
|
||||||
protected boolean doVerify(Signature sig, PublicKey pk, byte[] data, byte[] signature) throws InvalidKeyException, java.security.SignatureException {
|
protected boolean doVerify(Signature sig, PublicKey pk, byte[] data, byte[] signature) throws InvalidKeyException, java.security.SignatureException {
|
||||||
throw ex;
|
throw ex;
|
||||||
|
|
|
@ -16,17 +16,12 @@
|
||||||
package io.jsonwebtoken.impl.crypto
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.SignatureException
|
import io.jsonwebtoken.security.SignatureException
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
import java.security.InvalidKeyException
|
import java.security.*
|
||||||
import java.security.KeyPair
|
|
||||||
import java.security.KeyPairGenerator
|
|
||||||
import java.security.MessageDigest
|
|
||||||
import java.security.PrivateKey
|
|
||||||
import java.security.PublicKey
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
import static org.junit.Assert.*
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
class RsaSignerTest {
|
class RsaSignerTest {
|
||||||
|
|
|
@ -16,12 +16,12 @@
|
||||||
package io.jsonwebtoken.impl.crypto
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.SignatureException
|
import io.jsonwebtoken.security.SignatureException
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.security.Signature
|
import java.security.Signature
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
import static org.junit.Assert.*
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
class SignatureProviderTest {
|
class SignatureProviderTest {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package io.jsonwebtoken.crypto
|
package io.jsonwebtoken.security
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -10,6 +10,7 @@ import java.security.PublicKey
|
||||||
import java.security.interfaces.ECPrivateKey
|
import java.security.interfaces.ECPrivateKey
|
||||||
import java.security.interfaces.ECPublicKey
|
import java.security.interfaces.ECPublicKey
|
||||||
import java.security.interfaces.RSAPrivateKey
|
import java.security.interfaces.RSAPrivateKey
|
||||||
|
import java.security.interfaces.RSAPublicKey
|
||||||
|
|
||||||
import static org.junit.Assert.*
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
|
@ -27,11 +28,9 @@ class KeysImplTest {
|
||||||
|
|
||||||
String name = alg.name()
|
String name = alg.name()
|
||||||
|
|
||||||
int bitLength = name.equalsIgnoreCase("NONE") ? 0 : name.substring(2).toInteger()
|
if (alg.isHmac()) {
|
||||||
|
|
||||||
if (name.startsWith('H')) {
|
|
||||||
SecretKey key = Keys.secretKeyFor(alg)
|
SecretKey key = Keys.secretKeyFor(alg)
|
||||||
assertEquals bitLength, key.getEncoded().length * 8 //convert byte count to bit count
|
assertEquals alg.minKeyLength, key.getEncoded().length * 8 //convert byte count to bit count
|
||||||
assertEquals alg.jcaName, key.algorithm
|
assertEquals alg.jcaName, key.algorithm
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
@ -51,40 +50,40 @@ class KeysImplTest {
|
||||||
for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
|
|
||||||
String name = alg.name()
|
String name = alg.name()
|
||||||
int bitLength = name.equalsIgnoreCase("NONE") ? 0 : name.substring(2).toInteger()
|
|
||||||
|
|
||||||
if (name.startsWith('R') || name.startsWith('P')) {
|
if (alg.isRsa()) {
|
||||||
|
|
||||||
KeyPair pair = Keys.keyPairFor(alg)
|
KeyPair pair = Keys.keyPairFor(alg)
|
||||||
assertNotNull pair
|
assertNotNull pair
|
||||||
|
|
||||||
PublicKey pub = pair.getPublic()
|
PublicKey pub = pair.getPublic()
|
||||||
|
assert pub instanceof RSAPublicKey
|
||||||
|
assertEquals alg.familyName, pub.algorithm
|
||||||
|
assertEquals alg.digestLength * 8, pub.modulus.bitLength()
|
||||||
|
|
||||||
PrivateKey priv = pair.getPrivate()
|
PrivateKey priv = pair.getPrivate()
|
||||||
assert priv instanceof RSAPrivateKey
|
assert priv instanceof RSAPrivateKey
|
||||||
assertEquals alg.familyName, pub.algorithm
|
|
||||||
assertEquals alg.familyName, priv.algorithm
|
assertEquals alg.familyName, priv.algorithm
|
||||||
assertEquals bitLength * 8, priv.modulus.bitLength()
|
assertEquals alg.digestLength * 8, priv.modulus.bitLength()
|
||||||
|
|
||||||
} else if (name.startsWith('E')) {
|
} else if (alg.isEllipticCurve()) {
|
||||||
|
|
||||||
KeyPair pair = Keys.keyPairFor(alg);
|
KeyPair pair = Keys.keyPairFor(alg);
|
||||||
assertNotNull pair
|
assertNotNull pair
|
||||||
|
|
||||||
if (alg == SignatureAlgorithm.ES512) {
|
String asn1oid = "secp${alg.minKeyLength}r1"
|
||||||
bitLength = 521
|
|
||||||
}
|
|
||||||
|
|
||||||
String asn1oid = "secp${bitLength}r1"
|
|
||||||
|
|
||||||
PublicKey pub = pair.getPublic()
|
PublicKey pub = pair.getPublic()
|
||||||
assert pub instanceof ECPublicKey
|
assert pub instanceof ECPublicKey
|
||||||
assertEquals "ECDSA", pub.algorithm
|
assertEquals "ECDSA", pub.algorithm
|
||||||
assertEquals asn1oid, pub.params.name
|
assertEquals asn1oid, pub.params.name
|
||||||
assertEquals bitLength, pub.params.order.bitLength()
|
assertEquals alg.minKeyLength, pub.params.order.bitLength()
|
||||||
|
|
||||||
PrivateKey priv = pair.getPrivate()
|
PrivateKey priv = pair.getPrivate()
|
||||||
assert priv instanceof ECPrivateKey
|
assert priv instanceof ECPrivateKey
|
||||||
assertEquals "ECDSA", priv.algorithm
|
assertEquals "ECDSA", priv.algorithm
|
||||||
assertEquals asn1oid, priv.params.name
|
assertEquals asn1oid, priv.params.name
|
||||||
|
assertEquals alg.minKeyLength, priv.params.order.bitLength()
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
Loading…
Reference in New Issue