334: key strength assertions and signature validation. Resolves #334

This commit is contained in:
Les Hazlewood 2018-07-27 15:03:53 -04:00
parent 45f83d0cb1
commit 9189253668
44 changed files with 1348 additions and 322 deletions

View File

@ -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;

View File

@ -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}. <b>This is not a JDK standard algorithm and
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
* automatically if found in the runtime classpath.
*/
ES256("ES256", "ECDSA using P-256 and SHA-256", "Elliptic Curve", "SHA256withECDSA", false),
ES256("ES256", "ECDSA using P-256 and SHA-256", "ECDSA", "SHA256withECDSA", false, 256, 256),
/**
* JWA algorithm name for {@code ECDSA using P-384 and SHA-384}. <b>This is not a JDK standard algorithm and
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> 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}. <b>This is not a JDK standard algorithm and
* JWA algorithm name for {@code ECDSA using P-521 and SHA-512}. <b>This is not a JDK standard algorithm and
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
* automatically if found in the runtime classpath.
*/
ES512("ES512", "ECDSA using P-512 and SHA-512", "Elliptic Curve", "SHA512withECDSA", false),
ES512("ES512", "ECDSA using P-521 and SHA-512", "ECDSA", "SHA512withECDSA", false, 512, 521),
/**
* JWA algorithm name for {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256}. <b>This is not a JDK standard
* algorithm and requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle
* will be used automatically if found in the runtime classpath.
*/
PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "RSA", "SHA256withRSAandMGF1", false),
PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "RSA", "SHA256withRSAandMGF1", false, 256, 2048),
/**
* JWA algorithm name for {@code RSASSA-PSS using SHA-384 and MGF1 with SHA-384}. <b>This is not a JDK standard
* algorithm and requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle
* will be used automatically if found in the runtime classpath.
*/
PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "RSA", "SHA384withRSAandMGF1", false),
PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "RSA", "SHA384withRSAandMGF1", false, 384, 2048),
/**
* JWA algorithm name for {@code RSASSA-PSS using SHA-512 and MGF1 with SHA-512}. <b>This is not a JDK standard
* algorithm and requires that a JCA provider like BouncyCastle be in the classpath.</b> BouncyCastle will be used
* automatically if found in the runtime classpath.
*/
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:
*
* <table>
* <caption>Crypto Family</caption>
* <thead>
* <tr>
* <th>SignatureAlgorithm</th>
* <th>Family Name</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>HS256</td>
* <td>HMAC</td>
* </tr>
* <tr>
* <td>HS384</td>
* <td>HMAC</td>
* </tr>
* <tr>
* <td>HS512</td>
* <td>HMAC</td>
* </tr>
* <tr>
* <td>RS256</td>
* <td>RSA</td>
* </tr>
* <tr>
* <td>RS384</td>
* <td>RSA</td>
* </tr>
* <tr>
* <td>RS512</td>
* <td>RSA</td>
* </tr>
* <tr>
* <td>PS256</td>
* <td>RSA</td>
* </tr>
* <tr>
* <td>PS384</td>
* <td>RSA</td>
* </tr>
* <tr>
* <td>PS512</td>
* <td>RSA</td>
* </tr>
* <tr>
* <td>ES256</td>
* <td>Elliptic Curve</td>
* </tr>
* <tr>
* <td>ES384</td>
* <td>Elliptic Curve</td>
* </tr>
* <tr>
* <td>ES512</td>
* <td>Elliptic Curve</td>
* </tr>
* </tbody>
* <caption>Crypto Family</caption>
* <thead>
* <tr>
* <th>SignatureAlgorithm</th>
* <th>Family Name</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>HS256</td>
* <td>HMAC</td>
* </tr>
* <tr>
* <td>HS384</td>
* <td>HMAC</td>
* </tr>
* <tr>
* <td>HS512</td>
* <td>HMAC</td>
* </tr>
* <tr>
* <td>RS256</td>
* <td>RSA</td>
* </tr>
* <tr>
* <td>RS384</td>
* <td>RSA</td>
* </tr>
* <tr>
* <td>RS512</td>
* <td>RSA</td>
* </tr>
* <tr>
* <td>PS256</td>
* <td>RSA</td>
* </tr>
* <tr>
* <td>PS384</td>
* <td>RSA</td>
* </tr>
* <tr>
* <td>PS512</td>
* <td>RSA</td>
* </tr>
* <tr>
* <td>ES256</td>
* <td>ECDSA</td>
* </tr>
* <tr>
* <td>ES384</td>
* <td>ECDSA</td>
* </tr>
* <tr>
* <td>ES512</td>
* <td>ECDSA</td>
* </tr>
* </tbody>
* </table>
*
* @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 <a href="https://tools.ietf.org/html/rfc7518">JWT JWA Specification (RFC 7518)</a> or throws an
* {@link InvalidKeyException} if the key is not allowed or not secure enough for this algorithm.
*
* @param key the key to check for validity.
* @throws InvalidKeyException if the key is not allowed or not secure enough for this algorithm.
* @since 0.10.0
*/
public void assertValidSigningKey(Key key) throws InvalidKeyException {
assertValid(key, true);
}
/**
* Returns quietly if the specified key is allowed to verify signatures using this algorithm
* according to the <a href="https://tools.ietf.org/html/rfc7518">JWT JWA Specification (RFC 7518)</a> or throws an
* {@link InvalidKeyException} if the key is not allowed or not secure enough for this algorithm.
*
* @param key the key to check for validity.
* @throws InvalidKeyException if the key is not allowed or not secure enough for this algorithm.
* @since 0.10.0
*/
public void assertValidVerificationKey(Key key) throws InvalidKeyException {
assertValid(key, false);
}
/**
* @since 0.10.0 to support assertValid(Key, boolean)
*/
private static String keyType(boolean signing) {
return signing ? "signing" : "verification";
}
/**
* @since 0.10.0
*/
private void assertValid(Key key, boolean signing) throws InvalidKeyException {
if (this == NONE) {
String msg = "The 'NONE' signature algorithm does not support cryptographic keys.";
throw new InvalidKeyException(msg);
} else if (isHmac()) {
if (!(key instanceof SecretKey)) {
String msg = this.familyName + " " + keyType(signing) + " keys must be SecretKey instances.";
throw new InvalidKeyException(msg);
}
SecretKey secretKey = (SecretKey) key;
byte[] encoded = secretKey.getEncoded();
if (encoded == null) {
throw new InvalidKeyException("The " + keyType(signing) + " key's encoded bytes cannot be null.");
}
String alg = secretKey.getAlgorithm();
if (alg == null) {
throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm cannot be null.");
}
if (!HS256.jcaName.equals(alg) && !HS384.jcaName.equals(alg) && !HS512.jcaName.equals(alg)) {
throw new InvalidKeyException("The " + keyType(signing) + " key's algorithm '" + alg +
"' does not equal a valid HmacSHA* algorithm name and cannot be used with " + name() + ".");
}
int size = encoded.length * 8; //size in bits
if (size < this.minKeyLength) {
String msg = "The " + keyType(signing) + " key's size is " + size + " bits which " +
"is not secure enough for the " + name() + " algorithm. The JWT " +
"JWA Specification (RFC 7518, Section 3.2) states that keys used with " + name() + " MUST have a " +
"size >= " + minKeyLength + " bits (the key size must be greater than or equal to the hash " +
"output size). Consider using the " + Keys.class.getName() + " class's " +
"'secretKeyFor(SignatureAlgorithm." + name() + ")' method to create a key guaranteed to be " +
"secure enough for " + name() + ". See " +
"https://tools.ietf.org/html/rfc7518#section-3.2 for more information.";
throw new WeakKeyException(msg);
}
} else { //EC or RSA
if (signing) {
if (!(key instanceof PrivateKey)) {
String msg = familyName + " signing keys must be PrivateKey instances.";
throw new InvalidKeyException(msg);
}
}
if (isEllipticCurve()) {
if (!(key instanceof ECKey)) {
String msg = familyName + " " + keyType(signing) + " keys must be ECKey instances.";
throw new InvalidKeyException(msg);
}
ECKey ecKey = (ECKey) key;
int size = ecKey.getParams().getOrder().bitLength();
if (size < this.minKeyLength) {
String msg = "The " + keyType(signing) + " key's size (ECParameterSpec order) is " + size +
" bits which is not secure enough for the " + name() + " algorithm. The JWT " +
"JWA Specification (RFC 7518, Section 3.4) states that keys used with " +
name() + " MUST have a size >= " + this.minKeyLength +
" bits. Consider using the " + Keys.class.getName() + " class's " +
"'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " +
"to be secure enough for " + name() + ". See " +
"https://tools.ietf.org/html/rfc7518#section-3.4 for more information.";
throw new WeakKeyException(msg);
}
} else { //RSA
if (!(key instanceof RSAKey)) {
String msg = familyName + " " + keyType(signing) + " keys must be RSAKey instances.";
throw new InvalidKeyException(msg);
}
RSAKey rsaKey = (RSAKey)key;
int size = rsaKey.getModulus().bitLength();
if (size < this.minKeyLength) {
String section = name().startsWith("P") ? "3.5" : "3.3";
String msg = "The " + keyType(signing) + " key's size is " + size + " bits which is not secure " +
"enough for the " + name() + " algorithm. The JWT JWA Specification (RFC 7518, Section " +
section + ") states that keys used with " + name() + " MUST have a size >= " +
this.minKeyLength + " bits. Consider using the " + Keys.class.getName() + " class's " +
"'keyPairFor(SignatureAlgorithm." + name() + ")' method to create a key pair guaranteed " +
"to be secure enough for " + name() + ". See " +
"https://tools.ietf.org/html/rfc7518#section-" + section + " for more information.";
throw new WeakKeyException(msg);
}
}
}
}
/**

View File

@ -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);

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.security;
/**
* @since 0.10.0
*/
public class InvalidKeyException extends KeyException {
public InvalidKeyException(String message) {
super(message);
}
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.security;
/**
* @since 0.10.0
*/
public class KeyException extends SecurityException {
public KeyException(String message) {
super(message);
}
}

View File

@ -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}.
*

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
package io.jsonwebtoken.security;
/**
* @since 0.10.0
*/
public class WeakKeyException extends InvalidKeyException {
public WeakKeyException(String message) {
super(message);
}
}

View File

@ -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
}
}
}

View File

@ -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).

View File

@ -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()
}
}

View File

@ -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;

View File

@ -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() +

View File

@ -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;

View File

@ -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 =

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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:

View File

@ -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;

View File

@ -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;

View File

@ -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 {
/**

View File

@ -15,7 +15,7 @@
*/
package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.security.SignatureException;
public interface Signer {

View File

@ -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

View File

@ -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

View File

@ -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<Claims> jws = Jwts.parser().setSigningKey(pair.public).parseClaimsJws(compact)

View File

@ -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)

View File

@ -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')

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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."

View File

@ -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."

View File

@ -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 {

View File

@ -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

View File

@ -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)
}
}

View File

@ -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];

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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;

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {