From 9189253668e7410202bcd4af9ef5855bf2e84dab Mon Sep 17 00:00:00 2001
From: Les Hazlewood <121180+lhazlewood@users.noreply.github.com>
Date: Fri, 27 Jul 2018 15:03:53 -0400
Subject: [PATCH] 334: key strength assertions and signature validation.
Resolves #334
---
.../main/java/io/jsonwebtoken/JwtParser.java | 1 +
.../io/jsonwebtoken/SignatureAlgorithm.java | 340 ++++++---
.../io/jsonwebtoken/SignatureException.java | 6 +-
.../security/InvalidKeyException.java | 11 +
.../jsonwebtoken/security/KeyException.java | 11 +
.../{crypto => security}/Keys.java | 18 +-
.../security/SecurityException.java | 17 +
.../security/SignatureException.java | 15 +
.../security/WeakKeyException.java | 11 +
.../SignatureAlgorithmTest.groovy | 648 +++++++++++++++++-
.../{crypto => security}/KeysTest.groovy | 12 +-
.../security/SignatureExceptionTest.groovy | 22 +
.../jsonwebtoken/impl/DefaultJwtBuilder.java | 15 +-
.../jsonwebtoken/impl/DefaultJwtParser.java | 13 +-
.../impl/crypto/EllipticCurveProvider.java | 30 +-
.../EllipticCurveSignatureValidator.java | 8 +-
.../impl/crypto/EllipticCurveSigner.java | 8 +-
.../jsonwebtoken/impl/crypto/MacProvider.java | 31 +-
.../jsonwebtoken/impl/crypto/MacSigner.java | 2 +-
.../jsonwebtoken/impl/crypto/RsaProvider.java | 6 +-
.../impl/crypto/RsaSignatureValidator.java | 2 +-
.../jsonwebtoken/impl/crypto/RsaSigner.java | 2 +-
.../impl/crypto/SignatureProvider.java | 10 +-
.../io/jsonwebtoken/impl/crypto/Signer.java | 2 +-
.../io/jsonwebtoken/JwtParserTest.groovy | 1 +
.../groovy/io/jsonwebtoken/JwtsTest.groovy | 144 ++--
.../RsaSigningKeyResolverAdapterTest.groovy | 8 +-
.../jsonwebtoken/impl/DefaultJwsTest.groovy | 11 +-
.../impl/DefaultJwtBuilderTest.groovy | 13 +-
.../impl/DefaultJwtParserTest.groovy | 16 +-
.../DefaultJwtSignatureValidatorTest.groovy | 5 +-
.../impl/crypto/DefaultJwtSignerTest.groovy | 5 +-
...efaultSignatureValidatorFactoryTest.groovy | 8 +-
.../crypto/DefaultSignerFactoryTest.groovy | 7 +-
...EllipticCurveSignatureValidatorTest.groovy | 10 +-
.../crypto/EllipticCurveSignerTest.groovy | 26 +-
.../impl/crypto/MacProviderTest.groovy | 26 +-
.../impl/crypto/MacSignerTest.groovy | 33 +-
.../crypto/PowermockMacProviderTest.groovy | 50 ++
.../impl/crypto/RsaProviderTest.groovy | 4 +-
.../crypto/RsaSignatureValidatorTest.groovy | 16 +-
.../impl/crypto/RsaSignerTest.groovy | 11 +-
.../impl/crypto/SignatureProviderTest.groovy | 4 +-
.../{crypto => security}/KeysImplTest.groovy | 31 +-
44 files changed, 1348 insertions(+), 322 deletions(-)
create mode 100644 api/src/main/java/io/jsonwebtoken/security/InvalidKeyException.java
create mode 100644 api/src/main/java/io/jsonwebtoken/security/KeyException.java
rename api/src/main/java/io/jsonwebtoken/{crypto => security}/Keys.java (89%)
create mode 100644 api/src/main/java/io/jsonwebtoken/security/SecurityException.java
create mode 100644 api/src/main/java/io/jsonwebtoken/security/SignatureException.java
create mode 100644 api/src/main/java/io/jsonwebtoken/security/WeakKeyException.java
rename api/src/test/groovy/io/jsonwebtoken/{crypto => security}/KeysTest.groovy (85%)
create mode 100644 api/src/test/groovy/io/jsonwebtoken/security/SignatureExceptionTest.groovy
create mode 100644 impl/src/test/groovy/io/jsonwebtoken/impl/crypto/PowermockMacProviderTest.groovy
rename impl/src/test/groovy/io/jsonwebtoken/{crypto => security}/KeysImplTest.groovy (75%)
diff --git a/api/src/main/java/io/jsonwebtoken/JwtParser.java b/api/src/main/java/io/jsonwebtoken/JwtParser.java
index 9b76e2ea..82d48494 100644
--- a/api/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/api/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -17,6 +17,7 @@ package io.jsonwebtoken;
import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Deserializer;
+import io.jsonwebtoken.security.SignatureException;
import java.security.Key;
import java.util.Date;
diff --git a/api/src/main/java/io/jsonwebtoken/SignatureAlgorithm.java b/api/src/main/java/io/jsonwebtoken/SignatureAlgorithm.java
index a44c0b15..d216a522 100644
--- a/api/src/main/java/io/jsonwebtoken/SignatureAlgorithm.java
+++ b/api/src/main/java/io/jsonwebtoken/SignatureAlgorithm.java
@@ -16,6 +16,16 @@
package io.jsonwebtoken;
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
@@ -25,85 +35,104 @@ import io.jsonwebtoken.lang.RuntimeEnvironment;
*/
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}. This is not a JDK standard algorithm and
* requires that a JCA provider like BouncyCastle be in the runtime classpath. BouncyCastle will be used
* 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}. This is not a JDK standard algorithm and
* requires that a JCA provider like BouncyCastle be in the runtime classpath. BouncyCastle will be used
* 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}. This is not a JDK standard algorithm and
+ * JWA algorithm name for {@code ECDSA using P-521 and SHA-512}. This is not a JDK standard algorithm and
* requires that a JCA provider like BouncyCastle be in the runtime classpath. BouncyCastle will be used
* 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}. This is not a JDK standard
* algorithm and requires that a JCA provider like BouncyCastle be in the runtime classpath. BouncyCastle
* 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}. This is not a JDK standard
* algorithm and requires that a JCA provider like BouncyCastle be in the runtime classpath. BouncyCastle
* 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}. This is not a JDK standard
* algorithm and requires that a JCA provider like BouncyCastle be in the classpath. BouncyCastle will be used
* 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 {
RuntimeEnvironment.enableBouncyCastleIfPossible();
}
- private final String value;
- private final String description;
- private final String familyName;
- private final String jcaName;
+ private final String value;
+ private final String description;
+ private final String familyName;
+ private final String jcaName;
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.description = description;
this.familyName = familyName;
this.jcaName = jcaName;
this.jdkStandard = jdkStandard;
+ this.digestLength = digestLength;
+ this.minKeyLength = minKeyLength;
}
/**
@@ -130,67 +159,66 @@ public enum SignatureAlgorithm {
* following table:
*
*
- * Crypto Family
- *
- *
- * SignatureAlgorithm |
- * Family Name |
- *
- *
- *
- *
- * HS256 |
- * HMAC |
- *
- *
- * HS384 |
- * HMAC |
- *
- *
- * HS512 |
- * HMAC |
- *
- *
- * RS256 |
- * RSA |
- *
- *
- * RS384 |
- * RSA |
- *
- *
- * RS512 |
- * RSA |
- *
- *
- * PS256 |
- * RSA |
- *
- *
- * PS384 |
- * RSA |
- *
- *
- * PS512 |
- * RSA |
- *
- *
- * ES256 |
- * Elliptic Curve |
- *
- *
- * ES384 |
- * Elliptic Curve |
- *
- *
- * ES512 |
- * Elliptic Curve |
- *
- *
+ * Crypto Family
+ *
+ *
+ * SignatureAlgorithm |
+ * Family Name |
+ *
+ *
+ *
+ *
+ * HS256 |
+ * HMAC |
+ *
+ *
+ * HS384 |
+ * HMAC |
+ *
+ *
+ * HS512 |
+ * HMAC |
+ *
+ *
+ * RS256 |
+ * RSA |
+ *
+ *
+ * RS384 |
+ * RSA |
+ *
+ *
+ * RS512 |
+ * RSA |
+ *
+ *
+ * PS256 |
+ * RSA |
+ *
+ *
+ * PS384 |
+ * RSA |
+ *
+ *
+ * PS512 |
+ * RSA |
+ *
+ *
+ * ES256 |
+ * ECDSA |
+ *
+ *
+ * ES384 |
+ * ECDSA |
+ *
+ *
+ * ES512 |
+ * ECDSA |
+ *
+ *
*
*
* @return Returns the cryptographic family name of the signature algorithm.
- *
* @since 0.5
*/
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.
*/
public boolean isHmac() {
- return name().startsWith("HS");
+ return familyName.equals("HMAC");
}
/**
@@ -236,18 +264,152 @@ public enum SignatureAlgorithm {
* {@code false} otherwise.
*/
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.
*
- * @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.
*/
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 JWT JWA Specification (RFC 7518) 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 JWT JWA Specification (RFC 7518) 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);
+ }
+ }
+ }
}
/**
diff --git a/api/src/main/java/io/jsonwebtoken/SignatureException.java b/api/src/main/java/io/jsonwebtoken/SignatureException.java
index 312403f9..e98b4c72 100644
--- a/api/src/main/java/io/jsonwebtoken/SignatureException.java
+++ b/api/src/main/java/io/jsonwebtoken/SignatureException.java
@@ -15,12 +15,16 @@
*/
package io.jsonwebtoken;
+import io.jsonwebtoken.security.SecurityException;
+
/**
* Exception indicating that either calculating a signature or verifying an existing signature of a JWT failed.
*
* @since 0.1
+ * @deprecated in favor of {@link io.jsonwebtoken.security.SecurityException}; this class will be removed before 1.0
*/
-public class SignatureException extends JwtException {
+@Deprecated
+public class SignatureException extends SecurityException {
public SignatureException(String message) {
super(message);
diff --git a/api/src/main/java/io/jsonwebtoken/security/InvalidKeyException.java b/api/src/main/java/io/jsonwebtoken/security/InvalidKeyException.java
new file mode 100644
index 00000000..3c21361b
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/InvalidKeyException.java
@@ -0,0 +1,11 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since 0.10.0
+ */
+public class InvalidKeyException extends KeyException {
+
+ public InvalidKeyException(String message) {
+ super(message);
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/KeyException.java b/api/src/main/java/io/jsonwebtoken/security/KeyException.java
new file mode 100644
index 00000000..d435a762
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/KeyException.java
@@ -0,0 +1,11 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since 0.10.0
+ */
+public class KeyException extends SecurityException {
+
+ public KeyException(String message) {
+ super(message);
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/crypto/Keys.java b/api/src/main/java/io/jsonwebtoken/security/Keys.java
similarity index 89%
rename from api/src/main/java/io/jsonwebtoken/crypto/Keys.java
rename to api/src/main/java/io/jsonwebtoken/security/Keys.java
index c3c79ec3..92cf43b5 100644
--- a/api/src/main/java/io/jsonwebtoken/crypto/Keys.java
+++ b/api/src/main/java/io/jsonwebtoken/security/Keys.java
@@ -1,4 +1,4 @@
-package io.jsonwebtoken.crypto;
+package io.jsonwebtoken.security;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
@@ -24,6 +24,22 @@ public final class 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}.
*
diff --git a/api/src/main/java/io/jsonwebtoken/security/SecurityException.java b/api/src/main/java/io/jsonwebtoken/security/SecurityException.java
new file mode 100644
index 00000000..57e80b16
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/SecurityException.java
@@ -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);
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/SignatureException.java b/api/src/main/java/io/jsonwebtoken/security/SignatureException.java
new file mode 100644
index 00000000..2ab77cfd
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/SignatureException.java
@@ -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);
+ }
+}
diff --git a/api/src/main/java/io/jsonwebtoken/security/WeakKeyException.java b/api/src/main/java/io/jsonwebtoken/security/WeakKeyException.java
new file mode 100644
index 00000000..ad32cde2
--- /dev/null
+++ b/api/src/main/java/io/jsonwebtoken/security/WeakKeyException.java
@@ -0,0 +1,11 @@
+package io.jsonwebtoken.security;
+
+/**
+ * @since 0.10.0
+ */
+public class WeakKeyException extends InvalidKeyException {
+
+ public WeakKeyException(String message) {
+ super(message);
+ }
+}
diff --git a/api/src/test/groovy/io/jsonwebtoken/SignatureAlgorithmTest.groovy b/api/src/test/groovy/io/jsonwebtoken/SignatureAlgorithmTest.groovy
index 2a5757b8..e900f8d1 100644
--- a/api/src/test/groovy/io/jsonwebtoken/SignatureAlgorithmTest.groovy
+++ b/api/src/test/groovy/io/jsonwebtoken/SignatureAlgorithmTest.groovy
@@ -15,17 +15,33 @@
*/
package io.jsonwebtoken
+import io.jsonwebtoken.security.InvalidKeyException
+import io.jsonwebtoken.security.Keys
+import io.jsonwebtoken.security.SignatureException
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.*
class SignatureAlgorithmTest {
+ private static final Random random = new Random() //does not need to be secure for testing
+
@Test
void testNames() {
def algNames = ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512',
'ES256', 'ES384', 'ES512', 'PS256', 'PS384', 'PS512', 'NONE']
- for( String name : algNames ) {
+ for (String name : algNames) {
testName(name)
}
}
@@ -44,7 +60,7 @@ class SignatureAlgorithmTest {
@Test
void testIsHmac() {
- for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
+ for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
if (alg.name().startsWith("HS")) {
assertTrue alg.isHmac()
} else {
@@ -55,7 +71,7 @@ class SignatureAlgorithmTest {
@Test
void testHmacFamilyName() {
- for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
+ for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
if (alg.name().startsWith("HS")) {
assertEquals alg.getFamilyName(), "HMAC"
}
@@ -64,7 +80,7 @@ class SignatureAlgorithmTest {
@Test
void testIsRsa() {
- for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
+ for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
if (alg.getDescription().startsWith("RSASSA")) {
assertTrue alg.isRsa()
} else {
@@ -75,7 +91,7 @@ class SignatureAlgorithmTest {
@Test
void testRsaFamilyName() {
- for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
+ for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
if (alg.name().startsWith("RS") || alg.name().startsWith("PS")) {
assertEquals alg.getFamilyName(), "RSA"
}
@@ -84,7 +100,7 @@ class SignatureAlgorithmTest {
@Test
void testIsEllipticCurve() {
- for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
+ for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
if (alg.name().startsWith("ES")) {
assertTrue alg.isEllipticCurve()
} else {
@@ -95,16 +111,16 @@ class SignatureAlgorithmTest {
@Test
void testEllipticCurveFamilyName() {
- for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
+ for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
if (alg.name().startsWith("ES")) {
- assertEquals alg.getFamilyName(), "Elliptic Curve"
+ assertEquals alg.getFamilyName(), "ECDSA"
}
}
}
@Test
void testIsJdkStandard() {
- for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
+ for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
if (alg.name().startsWith("ES") || alg.name().startsWith("PS") || alg == SignatureAlgorithm.NONE) {
assertFalse alg.isJdkStandard()
} 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
+ }
+ }
}
diff --git a/api/src/test/groovy/io/jsonwebtoken/crypto/KeysTest.groovy b/api/src/test/groovy/io/jsonwebtoken/security/KeysTest.groovy
similarity index 85%
rename from api/src/test/groovy/io/jsonwebtoken/crypto/KeysTest.groovy
rename to api/src/test/groovy/io/jsonwebtoken/security/KeysTest.groovy
index ba624e97..0e46cde5 100644
--- a/api/src/test/groovy/io/jsonwebtoken/crypto/KeysTest.groovy
+++ b/api/src/test/groovy/io/jsonwebtoken/security/KeysTest.groovy
@@ -1,4 +1,4 @@
-package io.jsonwebtoken.crypto
+package io.jsonwebtoken.security
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.lang.Classes
@@ -13,14 +13,8 @@ import java.security.KeyPair
import static org.easymock.EasyMock.eq
import static org.easymock.EasyMock.expect
import static org.easymock.EasyMock.same
-import static org.junit.Assert.assertEquals
-import static org.junit.Assert.assertSame
-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
+import static org.junit.Assert.*
+import static org.powermock.api.easymock.PowerMock.*
/**
* This test class is for cursory API-level testing only (what is available to the API module at build time).
diff --git a/api/src/test/groovy/io/jsonwebtoken/security/SignatureExceptionTest.groovy b/api/src/test/groovy/io/jsonwebtoken/security/SignatureExceptionTest.groovy
new file mode 100644
index 00000000..8110ef4b
--- /dev/null
+++ b/api/src/test/groovy/io/jsonwebtoken/security/SignatureExceptionTest.groovy
@@ -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()
+ }
+}
\ No newline at end of file
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
index c40b90e6..54e256c8 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
@@ -22,19 +22,20 @@ import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.SignatureAlgorithm;
-import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
import io.jsonwebtoken.impl.crypto.JwtSigner;
+import io.jsonwebtoken.impl.io.InstanceLocator;
import io.jsonwebtoken.io.Decoders;
+import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.io.SerializationException;
import io.jsonwebtoken.io.Serializer;
-import io.jsonwebtoken.impl.io.InstanceLocator;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Classes;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings;
+import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;
@@ -108,13 +109,12 @@ public class DefaultJwtBuilder implements JwtBuilder {
}
@Override
- public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey) {
+ public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKeyBytes) {
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.");
- this.algorithm = alg;
- this.key = new SecretKeySpec(secretKey, alg.getJcaName());
- return this;
+ SecretKey key = new SecretKeySpec(secretKeyBytes, alg.getJcaName());
+ return signWith(alg, key);
}
@Override
@@ -129,6 +129,7 @@ public class DefaultJwtBuilder implements JwtBuilder {
public JwtBuilder signWith(SignatureAlgorithm alg, Key key) {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
Assert.notNull(key, "Key argument cannot be null.");
+ alg.assertValidSigningKey(key); //since 0.10.0 for https://github.com/jwtk/jjwt/issues/334
this.algorithm = alg;
this.key = key;
return this;
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index 13b2c035..7812a94a 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -34,22 +34,24 @@ import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.MissingClaimException;
import io.jsonwebtoken.PrematureJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
-import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.UnsupportedJwtException;
-import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
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.DeserializationException;
import io.jsonwebtoken.io.Deserializer;
-import io.jsonwebtoken.impl.io.InstanceLocator;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Classes;
import io.jsonwebtoken.lang.DateFormats;
import io.jsonwebtoken.lang.Objects;
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 java.security.Key;
@@ -359,8 +361,11 @@ public class DefaultJwtParser implements JwtParser {
JwtSignatureValidator validator;
try {
+ algorithm.assertValidVerificationKey(key); //since 0.10.0: https://github.com/jwtk/jjwt/issues/334
validator = createSignatureValidator(algorithm, key);
- } catch (IllegalArgumentException e) {
+ } catch (WeakKeyException e) {
+ throw e;
+ } catch (InvalidKeyException | IllegalArgumentException e) {
String algName = algorithm.getValue();
String msg = "The parsed JWT indicates it was signed with the " + algName + " signature " +
"algorithm, but the specified signing key of type " + key.getClass().getName() +
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveProvider.java b/impl/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveProvider.java
index 23107df1..2c7b01cd 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveProvider.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveProvider.java
@@ -15,6 +15,10 @@
*/
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.KeyPair;
import java.security.KeyPairGenerator;
@@ -22,10 +26,6 @@ import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
-import io.jsonwebtoken.JwtException;
-import io.jsonwebtoken.SignatureAlgorithm;
-import io.jsonwebtoken.lang.Assert;
-
/**
* ElliptiCurve crypto provider.
*
@@ -172,8 +172,7 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
*
* @throws JwtException If the ASN.1/DER signature format is invalid.
*/
- public static byte[] transcodeSignatureToConcat(final byte[] derSignature, int outputLength)
- throws JwtException {
+ public static byte[] transcodeSignatureToConcat(final byte[] derSignature, int outputLength) throws JwtException {
if (derSignature.length < 8 || derSignature[0] != 48) {
throw new JwtException("Invalid ECDSA signature format");
@@ -191,16 +190,16 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
byte rLength = derSignature[offset + 1];
int i = rLength;
- while ((i > 0)
- && (derSignature[(offset + 2 + rLength) - i] == 0))
+ while ((i > 0) && (derSignature[(offset + 2 + rLength) - i] == 0)) {
i--;
+ }
byte sLength = derSignature[offset + 2 + rLength + 1];
int j = sLength;
- while ((j > 0)
- && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0))
+ while ((j > 0) && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0)) {
j--;
+ }
int rawLen = Math.max(i, j);
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.
*/
- public static byte[] transcodeSignatureToDER(byte[] jwsSignature)
- throws JwtException {
+ public static byte[] transcodeSignatureToDER(byte[] jwsSignature) throws JwtException {
int rawLen = jwsSignature.length / 2;
int i = rawLen;
- while((i > 0)
- && (jwsSignature[rawLen - i] == 0))
+ while((i > 0) && (jwsSignature[rawLen - i] == 0)) {
i--;
+ }
int j = i;
@@ -253,9 +251,9 @@ public abstract class EllipticCurveProvider extends SignatureProvider {
int k = rawLen;
- while ((k > 0)
- && (jwsSignature[2 * rawLen - k] == 0))
+ while ((k > 0) && (jwsSignature[2 * rawLen - k] == 0)) {
k--;
+ }
int l = k;
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidator.java b/impl/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidator.java
index 09ab14db..3fc1fd9a 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidator.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidator.java
@@ -15,16 +15,16 @@
*/
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.Key;
import java.security.PublicKey;
import java.security.Signature;
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 {
private static final String EC_PUBLIC_KEY_REQD_MSG =
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSigner.java b/impl/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSigner.java
index 2e1f3c7b..9aeb1e8b 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSigner.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/crypto/EllipticCurveSigner.java
@@ -15,16 +15,16 @@
*/
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.Key;
import java.security.PrivateKey;
import java.security.Signature;
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 EllipticCurveSigner(SignatureAlgorithm alg, Key key) {
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/crypto/MacProvider.java b/impl/src/main/java/io/jsonwebtoken/impl/crypto/MacProvider.java
index 9afa71cf..b0117411 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/crypto/MacProvider.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/crypto/MacProvider.java
@@ -18,9 +18,10 @@ package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
+import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
+import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
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
- * convenience method that immediately delegates to {@link #generateKey(SignatureAlgorithm)} using {@link
+ * Generates a new secure-random 512 bit secret key suitable for creating and verifying HMAC-SHA signatures. This
+ * is a convenience method that immediately delegates to {@link #generateKey(SignatureAlgorithm)} using {@link
* 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, SecureRandom)
* @since 0.5
@@ -78,26 +79,22 @@ public abstract class MacProvider extends SignatureProvider {
* @see #generateKey()
* @see #generateKey(SignatureAlgorithm)
* @since 0.5
+ * @deprecated since 0.10.0 - use {@link #generateKey(SignatureAlgorithm)} instead.
*/
+ @Deprecated
public static SecretKey generateKey(SignatureAlgorithm alg, SecureRandom random) {
Assert.isTrue(alg.isHmac(), "SignatureAlgorithm argument must represent an HMAC algorithm.");
- byte[] bytes;
+ KeyGenerator gen;
- switch (alg) {
- case HS256:
- bytes = new byte[32];
- break;
- case HS384:
- bytes = new byte[48];
- break;
- default:
- bytes = new byte[64];
+ try {
+ gen = KeyGenerator.getInstance(alg.getJcaName());
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("The " + alg.getJcaName() + " algorithm is not available. " +
+ "This should never happen on JDK 7 or later - please report this to the JJWT developers.", e);
}
- random.nextBytes(bytes);
-
- return new SecretKeySpec(bytes, alg.getJcaName());
+ return gen.generateKey();
}
}
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/crypto/MacSigner.java b/impl/src/main/java/io/jsonwebtoken/impl/crypto/MacSigner.java
index 46ad8c43..07379f53 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/crypto/MacSigner.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/crypto/MacSigner.java
@@ -16,8 +16,8 @@
package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
-import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.lang.Assert;
+import io.jsonwebtoken.security.SignatureException;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/crypto/RsaProvider.java b/impl/src/main/java/io/jsonwebtoken/impl/crypto/RsaProvider.java
index cd5346dc..2d6991f4 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/crypto/RsaProvider.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/crypto/RsaProvider.java
@@ -16,8 +16,8 @@
package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
-import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.lang.Assert;
+import io.jsonwebtoken.security.SignatureException;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
@@ -127,9 +127,9 @@ public abstract class RsaProvider extends SignatureProvider {
* @see #generateKeyPair(String, int, SecureRandom)
* @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) {
- 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;
switch (alg) {
case RS256:
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/crypto/RsaSignatureValidator.java b/impl/src/main/java/io/jsonwebtoken/impl/crypto/RsaSignatureValidator.java
index a306d748..90dc8b47 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/crypto/RsaSignatureValidator.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/crypto/RsaSignatureValidator.java
@@ -16,8 +16,8 @@
package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
-import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.lang.Assert;
+import io.jsonwebtoken.security.SignatureException;
import java.security.InvalidKeyException;
import java.security.Key;
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/crypto/RsaSigner.java b/impl/src/main/java/io/jsonwebtoken/impl/crypto/RsaSigner.java
index 087ac33f..6bc87931 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/crypto/RsaSigner.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/crypto/RsaSigner.java
@@ -16,7 +16,7 @@
package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
-import io.jsonwebtoken.SignatureException;
+import io.jsonwebtoken.security.SignatureException;
import java.security.InvalidKeyException;
import java.security.Key;
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/crypto/SignatureProvider.java b/impl/src/main/java/io/jsonwebtoken/impl/crypto/SignatureProvider.java
index e10c7a77..7419a478 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/crypto/SignatureProvider.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/crypto/SignatureProvider.java
@@ -15,16 +15,16 @@
*/
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.NoSuchAlgorithmException;
import java.security.SecureRandom;
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 {
/**
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/crypto/Signer.java b/impl/src/main/java/io/jsonwebtoken/impl/crypto/Signer.java
index 096cc135..18600f24 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/crypto/Signer.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/crypto/Signer.java
@@ -15,7 +15,7 @@
*/
package io.jsonwebtoken.impl.crypto;
-import io.jsonwebtoken.SignatureException;
+import io.jsonwebtoken.security.SignatureException;
public interface Signer {
diff --git a/impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index 7660179f..a4e5bfc0 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -19,6 +19,7 @@ import io.jsonwebtoken.impl.DefaultClock
import io.jsonwebtoken.impl.FixedClock
import io.jsonwebtoken.io.Encoders
import io.jsonwebtoken.lang.Strings
+import io.jsonwebtoken.security.SignatureException
import org.junit.Test
import javax.crypto.spec.SecretKeySpec
diff --git a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
index a671f584..3d4db5b0 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
@@ -19,12 +19,11 @@ import io.jsonwebtoken.impl.DefaultHeader
import io.jsonwebtoken.impl.DefaultJwsHeader
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver
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.io.Encoders
import io.jsonwebtoken.lang.Strings
+import io.jsonwebtoken.security.Keys
+import io.jsonwebtoken.security.WeakKeyException
import org.junit.Test
import javax.crypto.Mac
@@ -348,11 +347,12 @@ class JwtsTest {
@Test
void testUncompressedJwt() {
- byte[] key = MacProvider.generateKey().getEncoded()
+ SignatureAlgorithm alg = SignatureAlgorithm.HS256
+ byte[] key = Keys.secretKeyFor(alg).encoded
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()
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
@@ -369,11 +369,12 @@ class JwtsTest {
@Test
void testCompressedJwtWithDeflate() {
- byte[] key = MacProvider.generateKey().getEncoded()
+ SignatureAlgorithm alg = SignatureAlgorithm.HS256
+ byte[] key = Keys.secretKeyFor(alg).encoded
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()
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
@@ -390,11 +391,12 @@ class JwtsTest {
@Test
void testCompressedJwtWithGZIP() {
- byte[] key = MacProvider.generateKey().getEncoded()
+ SignatureAlgorithm alg = SignatureAlgorithm.HS256
+ byte[] key = Keys.secretKeyFor(alg).encoded
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()
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
@@ -410,11 +412,13 @@ class JwtsTest {
@Test
void testCompressedWithCustomResolver() {
- byte[] key = MacProvider.generateKey().getEncoded()
+
+ SignatureAlgorithm alg = SignatureAlgorithm.HS256
+ byte[] key = Keys.secretKeyFor(alg).encoded
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() {
@Override
String getAlgorithmName() {
@@ -446,11 +450,13 @@ class JwtsTest {
@Test(expected = CompressionException.class)
void testCompressedJwtWithUnrecognizedHeader() {
- byte[] key = MacProvider.generateKey().getEncoded()
+
+ SignatureAlgorithm alg = SignatureAlgorithm.HS256
+ byte[] key = Keys.secretKeyFor(alg).encoded
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() {
@Override
String getAlgorithmName() {
@@ -464,11 +470,12 @@ class JwtsTest {
@Test
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 compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, key)
+ String compact = Jwts.builder().setPayload(payload).signWith(alg, key)
.compressWith(CompressionCodecs.DEFLATE).compact()
def jws = Jwts.parser().setSigningKey(key).parsePlaintextJws(compact)
@@ -482,62 +489,62 @@ class JwtsTest {
@Test
void testHS256() {
- testHmac(SignatureAlgorithm.HS256);
+ testHmac(SignatureAlgorithm.HS256)
}
@Test
void testHS384() {
- testHmac(SignatureAlgorithm.HS384);
+ testHmac(SignatureAlgorithm.HS384)
}
@Test
void testHS512() {
- testHmac(SignatureAlgorithm.HS512);
+ testHmac(SignatureAlgorithm.HS512)
}
@Test
void testRS256() {
- testRsa(SignatureAlgorithm.RS256);
+ testRsa(SignatureAlgorithm.RS256)
}
@Test
void testRS384() {
- testRsa(SignatureAlgorithm.RS384);
+ testRsa(SignatureAlgorithm.RS384)
}
@Test
void testRS512() {
- testRsa(SignatureAlgorithm.RS512);
+ testRsa(SignatureAlgorithm.RS512)
}
@Test
void testPS256() {
- testRsa(SignatureAlgorithm.PS256);
+ testRsa(SignatureAlgorithm.PS256)
}
@Test
void testPS384() {
- testRsa(SignatureAlgorithm.PS384);
+ testRsa(SignatureAlgorithm.PS384)
}
@Test
void testPS512() {
- testRsa(SignatureAlgorithm.PS512, 2048, false);
+ testRsa(SignatureAlgorithm.PS512)
}
@Test
void testRSA256WithPrivateKeyValidation() {
- testRsa(SignatureAlgorithm.RS256, 1024, true);
+ testRsa(SignatureAlgorithm.RS256, true)
}
@Test
void testRSA384WithPrivateKeyValidation() {
- testRsa(SignatureAlgorithm.RS384, 1024, true);
+ testRsa(SignatureAlgorithm.RS384, true)
}
@Test
void testRSA512WithPrivateKeyValidation() {
- testRsa(SignatureAlgorithm.RS512, 1024, true);
+ testRsa(SignatureAlgorithm.RS512, true)
}
@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
@Test
void testParseClaimsJwsWithUnsignedJwt() {
//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 {
- Jwts.parser().setSigningKey(key).parseClaimsJws(notSigned);
+ Jwts.parser().setSigningKey(key).parseClaimsJws(notSigned)
fail('parseClaimsJws must fail for unsigned JWTs')
} catch (UnsupportedJwtException expected) {
assertEquals expected.message, 'Unsigned Claims JWTs are not supported.'
@@ -587,27 +611,28 @@ class JwtsTest {
void testForgedTokenWithSwappedHeaderUsingNoneAlgorithm() {
//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:
- 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:
- int i = compact.lastIndexOf('.');
- String signature = compact.substring(i + 1);
+ int i = compact.lastIndexOf('.')
+ String signature = compact.substring(i + 1)
//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:
assertEquals Jwts.parser().parseClaimsJwt(forged).getHeader().get('alg'), 'none'
//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:
try {
- Jwts.parser().setSigningKey(key).parse(forged);
+ Jwts.parser().setSigningKey(key).parse(forged)
fail("Parsing must fail for a forged token.")
} catch (MalformedJwtException expected) {
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() {
//Create a legitimate RSA public and private key pair:
- KeyPair kp = RsaProvider.generateKeyPair(1024)
- PublicKey publicKey = kp.getPublic();
- PrivateKey privateKey = kp.getPrivate();
+ KeyPair kp = Keys.keyPairFor(SignatureAlgorithm.RS256)
+ PublicKey publicKey = kp.getPublic()
+ PrivateKey privateKey = kp.getPrivate()
String header = base64Url(toJson(['alg': 'HS256']))
String body = base64Url(toJson('foo'))
@@ -651,7 +676,7 @@ class JwtsTest {
void testParseForgedRsaPublicKeyAsHmacTokenVerifiedWithTheRsaPublicKey() {
//Create a legitimate RSA public and private key pair:
- KeyPair kp = RsaProvider.generateKeyPair(1024)
+ KeyPair kp = Keys.keyPairFor(SignatureAlgorithm.RS256)
PublicKey publicKey = kp.getPublic();
//PrivateKey privateKey = kp.getPrivate();
@@ -683,7 +708,7 @@ class JwtsTest {
void testParseForgedEllipticCurvePublicKeyAsHmacToken() {
//Create a legitimate RSA public and private key pair:
- KeyPair kp = EllipticCurveProvider.generateKeyPair()
+ KeyPair kp = Keys.keyPairFor(SignatureAlgorithm.ES256)
PublicKey publicKey = kp.getPublic();
//PrivateKey privateKey = kp.getPrivate();
@@ -703,29 +728,29 @@ class JwtsTest {
// Assert that the parser does not recognized the forged token:
try {
- Jwts.parser().setSigningKey(publicKey).parse(forged);
+ Jwts.parser().setSigningKey(publicKey).parse(forged)
fail("Forged token must not be successfully parsed.")
} catch (UnsupportedJwtException expected) {
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)
- PublicKey publicKey = kp.getPublic();
- PrivateKey privateKey = kp.getPrivate();
+ KeyPair kp = Keys.keyPairFor(alg)
+ PublicKey publicKey = kp.getPublic()
+ PrivateKey privateKey = kp.getPrivate()
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) {
- 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
//noinspection GrEqualsBetweenInconvertibleTypes
@@ -733,12 +758,13 @@ class JwtsTest {
}
static void testHmac(SignatureAlgorithm alg) {
+
//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]
- 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)
@@ -749,20 +775,20 @@ class JwtsTest {
static void testEC(SignatureAlgorithm alg, boolean verifyWithPrivateKey = false) {
- KeyPair pair = EllipticCurveProvider.generateKeyPair(alg)
+ KeyPair pair = Keys.keyPairFor(alg)
PublicKey publicKey = pair.getPublic()
PrivateKey privateKey = pair.getPrivate()
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) {
- 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()]
//noinspection GrEqualsBetweenInconvertibleTypes
diff --git a/impl/src/test/groovy/io/jsonwebtoken/RsaSigningKeyResolverAdapterTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/RsaSigningKeyResolverAdapterTest.groovy
index 79fcf548..03f3e9bf 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/RsaSigningKeyResolverAdapterTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/RsaSigningKeyResolverAdapterTest.groovy
@@ -15,7 +15,7 @@
*/
package io.jsonwebtoken
-import io.jsonwebtoken.impl.crypto.RsaProvider
+import io.jsonwebtoken.security.Keys
import org.junit.Test
import static org.junit.Assert.assertEquals
@@ -26,9 +26,11 @@ class RsaSigningKeyResolverAdapterTest {
@Test
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 jws = Jwts.parser().setSigningKey(pair.public).parseClaimsJws(compact)
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy
index 33f0d97a..c2ff205c 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy
@@ -18,9 +18,11 @@ package io.jsonwebtoken.impl
import io.jsonwebtoken.JwsHeader
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
-import io.jsonwebtoken.impl.crypto.MacProvider
+import io.jsonwebtoken.security.Keys
import org.junit.Test
-import static org.junit.Assert.*
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertSame
class DefaultJwsTest {
@@ -38,8 +40,9 @@ class DefaultJwsTest {
@Test
void testToString() {
//create random signing key for testing:
- byte[] key = MacProvider.generateKey().encoded
- String compact = Jwts.builder().claim('foo', 'bar').signWith(SignatureAlgorithm.HS256, key).compact();
+ SignatureAlgorithm alg = SignatureAlgorithm.HS256
+ byte[] key = Keys.secretKeyFor(alg).encoded
+ String compact = Jwts.builder().claim('foo', 'bar').signWith(alg, key).compact();
int i = compact.lastIndexOf('.')
String signature = compact.substring(i + 1)
def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
index dfeea5cd..9f8d1491 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
@@ -16,14 +16,14 @@
package io.jsonwebtoken.impl
import com.fasterxml.jackson.databind.ObjectMapper
+import io.jsonwebtoken.CompressionCodecs
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.io.Encoder
import io.jsonwebtoken.io.EncodingException
-import io.jsonwebtoken.CompressionCodecs
-import io.jsonwebtoken.impl.crypto.MacProvider
import io.jsonwebtoken.io.SerializationException
import io.jsonwebtoken.io.Serializer
+import io.jsonwebtoken.security.Keys
import org.junit.Test
import static org.junit.Assert.*
@@ -174,8 +174,9 @@ class DefaultJwtBuilderTest {
def b = new DefaultJwtBuilder()
b.setHeader(Jwts.jwsHeader().setKeyId('a'))
b.setPayload('foo')
- def key = MacProvider.generateKey()
- b.signWith(SignatureAlgorithm.HS256, key)
+ def alg = SignatureAlgorithm.HS256
+ def key = Keys.secretKeyFor(alg)
+ b.signWith(alg, key)
b.compact()
}
@@ -338,14 +339,14 @@ class DefaultJwtBuilderTest {
def serializer = new Serializer() {
@Override
byte[] serialize(Object o) throws SerializationException {
- return objectMapper.writeValueAsBytes(o);
+ return objectMapper.writeValueAsBytes(o)
}
}
def b = new DefaultJwtBuilder().serializeToJsonWith(serializer)
assertSame serializer, b.serializer
- def key = MacProvider.generateKey(SignatureAlgorithm.HS256)
+ def key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
String jws = b.signWith(SignatureAlgorithm.HS256, key)
.claim('foo', 'bar')
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserTest.groovy
index 50e15848..e0390236 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtParserTest.groovy
@@ -4,13 +4,9 @@ import com.fasterxml.jackson.databind.ObjectMapper
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.MalformedJwtException
import io.jsonwebtoken.SignatureAlgorithm
-import io.jsonwebtoken.io.Decoder
-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.io.*
import io.jsonwebtoken.lang.Strings
+import io.jsonwebtoken.security.Keys
import org.junit.Test
import javax.crypto.Mac
@@ -60,7 +56,7 @@ class DefaultJwtParserTest {
def p = new DefaultJwtParser().deserializeJsonWith(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()
@@ -74,7 +70,7 @@ class DefaultJwtParserTest {
String body = Encoders.BASE64URL.encode('{"hello":"world"}'.getBytes(Strings.UTF_8))
String compact = header + '.' + body + '.'
- SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256)
+ SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
Mac mac = Mac.getInstance('HmacSHA256')
mac.init(key)
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 compact = header + '.' + body + '.'
- SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256)
+ SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
Mac mac = Mac.getInstance('HmacSHA256')
mac.init(key)
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 compact = header + '.' + body + '.'
- SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256)
+ SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
Mac mac = Mac.getInstance('HmacSHA256')
mac.init(key)
byte[] signatureBytes = mac.doFinal(compact.getBytes(Strings.UTF_8))
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignatureValidatorTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignatureValidatorTest.groovy
index 0d65f7fb..b510ae80 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignatureValidatorTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignatureValidatorTest.groovy
@@ -2,6 +2,7 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.io.Decoders
+import io.jsonwebtoken.security.Keys
import org.junit.Test
import static org.junit.Assert.assertNotNull
@@ -15,7 +16,7 @@ class DefaultJwtSignatureValidatorTest {
void testDeprecatedTwoArgCtor() {
def alg = SignatureAlgorithm.HS256
- def key = MacProvider.generateKey(alg)
+ def key = Keys.secretKeyFor(alg)
def validator = new DefaultJwtSignatureValidator(alg, key)
assertNotNull validator.signatureValidator
@@ -28,7 +29,7 @@ class DefaultJwtSignatureValidatorTest {
void testDeprecatedThreeArgCtor() {
def alg = SignatureAlgorithm.HS256
- def key = MacProvider.generateKey(alg)
+ def key = Keys.secretKeyFor(alg)
def validator = new DefaultJwtSignatureValidator(DefaultSignatureValidatorFactory.INSTANCE, alg, key)
assertNotNull validator.signatureValidator
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignerTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignerTest.groovy
index 2ff81f25..5888899a 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignerTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultJwtSignerTest.groovy
@@ -2,6 +2,7 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.io.Encoders
+import io.jsonwebtoken.security.Keys
import org.junit.Test
import static org.junit.Assert.assertNotNull
@@ -16,7 +17,7 @@ class DefaultJwtSignerTest {
void testDeprecatedTwoArgCtor() {
def alg = SignatureAlgorithm.HS256
- def key = MacProvider.generateKey(alg)
+ def key = Keys.secretKeyFor(alg)
def signer = new DefaultJwtSigner(alg, key)
assertNotNull signer.signer
@@ -30,7 +31,7 @@ class DefaultJwtSignerTest {
void testDeprecatedThreeArgCtor() {
def alg = SignatureAlgorithm.HS256
- def key = MacProvider.generateKey(alg)
+ def key = Keys.secretKeyFor(alg)
def signer = new DefaultJwtSigner(DefaultSignerFactory.INSTANCE, alg, key)
assertNotNull signer.signer
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultSignatureValidatorFactoryTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultSignatureValidatorFactoryTest.groovy
index 04ec851d..a09c198a 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultSignatureValidatorFactoryTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultSignatureValidatorFactoryTest.groovy
@@ -16,15 +16,19 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
+import io.jsonwebtoken.security.Keys
import org.junit.Test
-import static org.junit.Assert.*
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.fail
class DefaultSignatureValidatorFactoryTest {
@Test
void testNoneAlgorithm() {
try {
- new DefaultSignatureValidatorFactory().createSignatureValidator(SignatureAlgorithm.NONE, MacProvider.generateKey())
+ new DefaultSignatureValidatorFactory().createSignatureValidator(
+ SignatureAlgorithm.NONE, Keys.secretKeyFor(SignatureAlgorithm.HS256))
fail()
} catch (IllegalArgumentException iae) {
assertEquals iae.message, "The 'NONE' algorithm cannot be used for signing."
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultSignerFactoryTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultSignerFactoryTest.groovy
index d3139640..5d33fd74 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultSignerFactoryTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/DefaultSignerFactoryTest.groovy
@@ -16,8 +16,11 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
+import io.jsonwebtoken.security.Keys
import org.junit.Test
-import static org.junit.Assert.*
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.fail
class DefaultSignerFactoryTest {
@@ -27,7 +30,7 @@ class DefaultSignerFactoryTest {
def factory = new DefaultSignerFactory();
try {
- factory.createSigner(SignatureAlgorithm.NONE, MacProvider.generateKey());
+ factory.createSigner(SignatureAlgorithm.NONE, Keys.secretKeyFor(SignatureAlgorithm.HS256))
fail();
} catch (IllegalArgumentException iae) {
assertEquals iae.message, "The 'NONE' algorithm cannot be used for signing."
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidatorTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidatorTest.groovy
index f175f21c..cdb37951 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidatorTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignatureValidatorTest.groovy
@@ -17,8 +17,8 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.JwtException
import io.jsonwebtoken.SignatureAlgorithm
-import io.jsonwebtoken.SignatureException
import io.jsonwebtoken.io.Decoders
+import io.jsonwebtoken.security.SignatureException
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.junit.Test
@@ -143,6 +143,14 @@ class EllipticCurveSignatureValidatorTest {
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
void edgeCaseSignatureToConcatLengthTest() {
try {
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignerTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignerTest.groovy
index d4e4b395..97baac2d 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignerTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/EllipticCurveSignerTest.groovy
@@ -17,7 +17,8 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.JwtException
import io.jsonwebtoken.SignatureAlgorithm
-import io.jsonwebtoken.SignatureException
+import io.jsonwebtoken.security.Keys
+import io.jsonwebtoken.security.SignatureException
import org.junit.Test
import java.security.InvalidKeyException
@@ -31,37 +32,40 @@ class EllipticCurveSignerTest {
@Test
void testConstructorWithoutECAlg() {
+ SignatureAlgorithm alg = SignatureAlgorithm.HS256
try {
- new EllipticCurveSigner(SignatureAlgorithm.HS256, MacProvider.generateKey());
- fail('EllipticCurveSigner should reject non ECPrivateKeys');
+ new EllipticCurveSigner(alg, Keys.secretKeyFor(alg))
+ fail('EllipticCurveSigner should reject non ECPrivateKeys')
} catch (IllegalArgumentException expected) {
- assertEquals expected.message, 'SignatureAlgorithm must be an Elliptic Curve algorithm.';
+ assertEquals expected.message, 'SignatureAlgorithm must be an Elliptic Curve algorithm.'
}
}
@Test
void testConstructorWithoutECPrivateKey() {
- def key = MacProvider.generateKey()
+ def key = Keys.secretKeyFor(SignatureAlgorithm.HS256)
try {
- new EllipticCurveSigner(SignatureAlgorithm.ES256, key);
+ new EllipticCurveSigner(SignatureAlgorithm.ES256, key)
fail('EllipticCurveSigner should reject non ECPrivateKey instances.')
} catch (IllegalArgumentException expected) {
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
void testDoSignWithInvalidKeyException() {
- KeyPair kp = EllipticCurveProvider.generateKeyPair()
- PublicKey publicKey = kp.getPublic();
- PrivateKey privateKey = kp.getPrivate();
+ SignatureAlgorithm alg = SignatureAlgorithm.ES256
+
+ KeyPair kp = Keys.keyPairFor(alg)
+ PublicKey publicKey = kp.getPublic()
+ PrivateKey privateKey = kp.getPrivate()
String msg = 'foo'
final InvalidKeyException ex = new InvalidKeyException(msg)
- def signer = new EllipticCurveSigner(SignatureAlgorithm.ES256, privateKey) {
+ def signer = new EllipticCurveSigner(alg, privateKey) {
@Override
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException {
throw ex
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/MacProviderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/MacProviderTest.groovy
index ea3a87e5..086028cd 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/MacProviderTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/MacProviderTest.groovy
@@ -17,31 +17,39 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import org.junit.Test
-import static org.junit.Assert.*
+
+import javax.crypto.SecretKey
+
+import static org.junit.Assert.assertEquals
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
void testDefault() {
- byte[] bytes = MacProvider.generateKey().encoded
- assertEquals 64, bytes.length
+ testHmac(SignatureAlgorithm.HS512, MacProvider.generateKey())
}
@Test
void testHS256() {
- byte[] bytes = MacProvider.generateKey(SignatureAlgorithm.HS256).encoded
- assertEquals 32, bytes.length
+ testHmac(SignatureAlgorithm.HS256)
}
@Test
void testHS384() {
- byte[] bytes = MacProvider.generateKey(SignatureAlgorithm.HS384).encoded
- assertEquals 48, bytes.length
+ testHmac(SignatureAlgorithm.HS384)
}
@Test
void testHS512() {
- byte[] bytes = MacProvider.generateKey(SignatureAlgorithm.HS512).encoded
- assertEquals 64, bytes.length
+ testHmac(SignatureAlgorithm.HS512)
}
}
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/MacSignerTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/MacSignerTest.groovy
index f796beae..ab6ebca7 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/MacSignerTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/MacSignerTest.groovy
@@ -16,18 +16,47 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
-import io.jsonwebtoken.SignatureException
+import io.jsonwebtoken.security.SignatureException
import org.junit.Test
-import static org.junit.Assert.*
import javax.crypto.Mac
import java.security.InvalidKeyException
+import java.security.Key
import java.security.NoSuchAlgorithmException
+import static org.junit.Assert.*
+
class MacSignerTest {
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
void testNoSuchAlgorithmException() {
byte[] key = new byte[32];
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/PowermockMacProviderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/PowermockMacProviderTest.groovy
new file mode 100644
index 00000000..824ad0e6
--- /dev/null
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/PowermockMacProviderTest.groovy
@@ -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
+ }
+}
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaProviderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaProviderTest.groovy
index 7887fc74..5d27a295 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaProviderTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaProviderTest.groovy
@@ -16,7 +16,8 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
-import io.jsonwebtoken.SignatureException
+import io.jsonwebtoken.security.SignatureException
+import org.junit.Test
import java.security.InvalidAlgorithmParameterException
import java.security.KeyPair
@@ -25,7 +26,6 @@ import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
import java.security.spec.PSSParameterSpec
-import org.junit.Test
import static org.junit.Assert.*
class RsaProviderTest {
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignatureValidatorTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignatureValidatorTest.groovy
index 5cc3fef9..99bf2ea4 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignatureValidatorTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignatureValidatorTest.groovy
@@ -16,7 +16,8 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
-import io.jsonwebtoken.SignatureException
+import io.jsonwebtoken.security.Keys
+import io.jsonwebtoken.security.SignatureException
import org.junit.Test
import java.security.*
@@ -30,7 +31,7 @@ class RsaSignatureValidatorTest {
@Test
void testConstructorWithNonRsaKey() {
try {
- new RsaSignatureValidator(SignatureAlgorithm.RS256, MacProvider.generateKey());
+ new RsaSignatureValidator(SignatureAlgorithm.RS256, Keys.secretKeyFor(SignatureAlgorithm.HS256));
fail()
} catch (IllegalArgumentException iae) {
assertEquals "RSA Signature validation requires either a RSAPublicKey or RSAPrivateKey instance.", iae.message
@@ -40,17 +41,16 @@ class RsaSignatureValidatorTest {
@Test
void testDoVerifyWithInvalidKeyException() {
- KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
- keyGenerator.initialize(1024);
+ SignatureAlgorithm alg = SignatureAlgorithm.RS256
- KeyPair kp = keyGenerator.genKeyPair();
- PublicKey publicKey = kp.getPublic();
- PrivateKey privateKey = kp.getPrivate();
+ KeyPair kp = Keys.keyPairFor(alg)
+ PublicKey publicKey = kp.getPublic()
+ PrivateKey privateKey = kp.getPrivate()
String msg = 'foo'
final InvalidKeyException ex = new InvalidKeyException(msg)
- RsaSignatureValidator v = new RsaSignatureValidator(SignatureAlgorithm.RS256, publicKey) {
+ RsaSignatureValidator v = new RsaSignatureValidator(alg, publicKey) {
@Override
protected boolean doVerify(Signature sig, PublicKey pk, byte[] data, byte[] signature) throws InvalidKeyException, java.security.SignatureException {
throw ex;
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignerTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignerTest.groovy
index 4b804442..d0470636 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignerTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignerTest.groovy
@@ -16,17 +16,12 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
-import io.jsonwebtoken.SignatureException
+import io.jsonwebtoken.security.SignatureException
+import org.junit.Test
import javax.crypto.spec.SecretKeySpec
-import java.security.InvalidKeyException
-import java.security.KeyPair
-import java.security.KeyPairGenerator
-import java.security.MessageDigest
-import java.security.PrivateKey
-import java.security.PublicKey
+import java.security.*
-import org.junit.Test
import static org.junit.Assert.*
class RsaSignerTest {
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/SignatureProviderTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/SignatureProviderTest.groovy
index e595231c..91158c0d 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/SignatureProviderTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/crypto/SignatureProviderTest.groovy
@@ -16,12 +16,12 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
-import io.jsonwebtoken.SignatureException
+import io.jsonwebtoken.security.SignatureException
+import org.junit.Test
import java.security.NoSuchAlgorithmException
import java.security.Signature
-import org.junit.Test
import static org.junit.Assert.*
class SignatureProviderTest {
diff --git a/impl/src/test/groovy/io/jsonwebtoken/crypto/KeysImplTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/security/KeysImplTest.groovy
similarity index 75%
rename from impl/src/test/groovy/io/jsonwebtoken/crypto/KeysImplTest.groovy
rename to impl/src/test/groovy/io/jsonwebtoken/security/KeysImplTest.groovy
index 151d4c05..1ff2420c 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/crypto/KeysImplTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/security/KeysImplTest.groovy
@@ -1,4 +1,4 @@
-package io.jsonwebtoken.crypto
+package io.jsonwebtoken.security
import io.jsonwebtoken.SignatureAlgorithm
import org.junit.Test
@@ -10,6 +10,7 @@ import java.security.PublicKey
import java.security.interfaces.ECPrivateKey
import java.security.interfaces.ECPublicKey
import java.security.interfaces.RSAPrivateKey
+import java.security.interfaces.RSAPublicKey
import static org.junit.Assert.*
@@ -27,11 +28,9 @@ class KeysImplTest {
String name = alg.name()
- int bitLength = name.equalsIgnoreCase("NONE") ? 0 : name.substring(2).toInteger()
-
- if (name.startsWith('H')) {
+ if (alg.isHmac()) {
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
} else {
try {
@@ -51,40 +50,40 @@ class KeysImplTest {
for (SignatureAlgorithm alg : SignatureAlgorithm.values()) {
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)
assertNotNull pair
+
PublicKey pub = pair.getPublic()
+ assert pub instanceof RSAPublicKey
+ assertEquals alg.familyName, pub.algorithm
+ assertEquals alg.digestLength * 8, pub.modulus.bitLength()
+
PrivateKey priv = pair.getPrivate()
assert priv instanceof RSAPrivateKey
- assertEquals alg.familyName, pub.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);
assertNotNull pair
- if (alg == SignatureAlgorithm.ES512) {
- bitLength = 521
- }
-
- String asn1oid = "secp${bitLength}r1"
+ String asn1oid = "secp${alg.minKeyLength}r1"
PublicKey pub = pair.getPublic()
assert pub instanceof ECPublicKey
assertEquals "ECDSA", pub.algorithm
assertEquals asn1oid, pub.params.name
- assertEquals bitLength, pub.params.order.bitLength()
+ assertEquals alg.minKeyLength, pub.params.order.bitLength()
PrivateKey priv = pair.getPrivate()
assert priv instanceof ECPrivateKey
assertEquals "ECDSA", priv.algorithm
assertEquals asn1oid, priv.params.name
+ assertEquals alg.minKeyLength, priv.params.order.bitLength()
} else {
try {