mirror of https://github.com/jwtk/jjwt.git
#27: finished EC implementations. Also added test cases to get to 100% code coverage on all code except the lang package.
This commit is contained in:
parent
8d86973b4b
commit
d644d8f9ee
|
@ -29,7 +29,9 @@ import java.util.Map;
|
||||||
*
|
*
|
||||||
* @since 0.1
|
* @since 0.1
|
||||||
*/
|
*/
|
||||||
public class Jwts {
|
public final class Jwts {
|
||||||
|
|
||||||
|
private Jwts(){}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link Header} instance suitable for <em>plaintext</em> (not digitally signed) JWTs. As this
|
* Creates a new {@link Header} instance suitable for <em>plaintext</em> (not digitally signed) JWTs. As this
|
||||||
|
|
|
@ -26,67 +26,67 @@ import io.jsonwebtoken.lang.RuntimeEnvironment;
|
||||||
public enum SignatureAlgorithm {
|
public enum SignatureAlgorithm {
|
||||||
|
|
||||||
/** JWA name for {@code No digital signature or MAC performed} */
|
/** JWA name for {@code No digital signature or MAC performed} */
|
||||||
NONE("none", "No digital signature or MAC performed", null, false),
|
NONE("none", "No digital signature or MAC performed", "None", null, false),
|
||||||
|
|
||||||
/** JWA algorithm name for {@code HMAC using SHA-256} */
|
/** JWA algorithm name for {@code HMAC using SHA-256} */
|
||||||
HS256("HS256", "HMAC using SHA-256", "HmacSHA256", true),
|
HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true),
|
||||||
|
|
||||||
/** JWA algorithm name for {@code HMAC using SHA-384} */
|
/** JWA algorithm name for {@code HMAC using SHA-384} */
|
||||||
HS384("HS384", "HMAC using SHA-384", "HmacSHA384", true),
|
HS384("HS384", "HMAC using SHA-384", "HMAC", "HmacSHA384", true),
|
||||||
|
|
||||||
/** JWA algorithm name for {@code HMAC using SHA-512} */
|
/** JWA algorithm name for {@code HMAC using SHA-512} */
|
||||||
HS512("HS512", "HMAC using SHA-512", "HmacSHA512", true),
|
HS512("HS512", "HMAC using SHA-512", "HMAC", "HmacSHA512", true),
|
||||||
|
|
||||||
/** JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-256} */
|
/** JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-256} */
|
||||||
RS256("RS256", "RSASSA-PKCS-v1_5 using SHA-256", "SHA256withRSA", true),
|
RS256("RS256", "RSASSA-PKCS-v1_5 using SHA-256", "RSA", "SHA256withRSA", true),
|
||||||
|
|
||||||
/** JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-384} */
|
/** JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-384} */
|
||||||
RS384("RS384", "RSASSA-PKCS-v1_5 using SHA-384", "SHA384withRSA", true),
|
RS384("RS384", "RSASSA-PKCS-v1_5 using SHA-384", "RSA", "SHA384withRSA", true),
|
||||||
|
|
||||||
/** JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-512} */
|
/** JWA algorithm name for {@code RSASSA-PKCS-v1_5 using SHA-512} */
|
||||||
RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "SHA512withRSA", true),
|
RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "RSA", "SHA512withRSA", true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWA algorithm name for {@code ECDSA using P-256 and SHA-256}. <b>This is not a JDK standard algorithm and
|
* JWA algorithm name for {@code ECDSA using P-256 and SHA-256}. <b>This is not a JDK standard algorithm and
|
||||||
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
|
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
|
||||||
* automatically if found in the runtime classpath.
|
* automatically if found in the runtime classpath.
|
||||||
*/
|
*/
|
||||||
ES256("ES256", "ECDSA using P-256 and SHA-256", "secp256r1", false),
|
ES256("ES256", "ECDSA using P-256 and SHA-256", "Elliptic Curve", "SHA256withECDSA", false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWA algorithm name for {@code ECDSA using P-384 and SHA-384}. <b>This is not a JDK standard algorithm and
|
* JWA algorithm name for {@code ECDSA using P-384 and SHA-384}. <b>This is not a JDK standard algorithm and
|
||||||
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
|
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
|
||||||
* automatically if found in the runtime classpath.
|
* automatically if found in the runtime classpath.
|
||||||
*/
|
*/
|
||||||
ES384("ES384", "ECDSA using P-384 and SHA-384", "secp384r1", false),
|
ES384("ES384", "ECDSA using P-384 and SHA-384", "Elliptic Curve", "SHA384withECDSA", false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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-512 and SHA-512}. <b>This is not a JDK standard algorithm and
|
||||||
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
|
* requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle will be used
|
||||||
* automatically if found in the runtime classpath.
|
* automatically if found in the runtime classpath.
|
||||||
*/
|
*/
|
||||||
ES512("ES512", "ECDSA using P-512 and SHA-512", "secp521r1", false),
|
ES512("ES512", "ECDSA using P-512 and SHA-512", "Elliptic Curve", "SHA512withECDSA", false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWA algorithm name for {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256}. <b>This is not a JDK standard
|
* JWA algorithm name for {@code RSASSA-PSS using SHA-256 and MGF1 with SHA-256}. <b>This is not a JDK standard
|
||||||
* algorithm and requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle
|
* algorithm and requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle
|
||||||
* will be used automatically if found in the runtime classpath.
|
* will be used automatically if found in the runtime classpath.
|
||||||
*/
|
*/
|
||||||
PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "SHA256withRSAandMGF1", false),
|
PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "RSA", "SHA256withRSAandMGF1", false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWA algorithm name for {@code RSASSA-PSS using SHA-384 and MGF1 with SHA-384}. <b>This is not a JDK standard
|
* JWA algorithm name for {@code RSASSA-PSS using SHA-384 and MGF1 with SHA-384}. <b>This is not a JDK standard
|
||||||
* algorithm and requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle
|
* algorithm and requires that a JCA provider like BouncyCastle be in the runtime classpath.</b> BouncyCastle
|
||||||
* will be used automatically if found in the runtime classpath.
|
* will be used automatically if found in the runtime classpath.
|
||||||
*/
|
*/
|
||||||
PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "SHA384withRSAandMGF1", false),
|
PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "RSA", "SHA384withRSAandMGF1", false),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWA algorithm name for {@code RSASSA-PSS using SHA-512 and MGF1 with SHA-512}. <b>This is not a JDK standard
|
* JWA algorithm name for {@code RSASSA-PSS using SHA-512 and MGF1 with SHA-512}. <b>This is not a JDK standard
|
||||||
* algorithm and requires that a JCA provider like BouncyCastle be in the classpath.</b> BouncyCastle will be used
|
* algorithm and requires that a JCA provider like BouncyCastle be in the classpath.</b> BouncyCastle will be used
|
||||||
* automatically if found in the runtime classpath.
|
* automatically if found in the runtime classpath.
|
||||||
*/
|
*/
|
||||||
PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "SHA512withRSAandMGF1", false);
|
PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "SHA512withRSAandMGF1", false);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
RuntimeEnvironment.enableBouncyCastleIfPossible();
|
RuntimeEnvironment.enableBouncyCastleIfPossible();
|
||||||
|
@ -94,12 +94,14 @@ public enum SignatureAlgorithm {
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
private final String description;
|
private final String description;
|
||||||
|
private final String familyName;
|
||||||
private final String jcaName;
|
private final String jcaName;
|
||||||
private final boolean jdkStandard;
|
private final boolean jdkStandard;
|
||||||
|
|
||||||
private SignatureAlgorithm(String value, String description, String jcaName, boolean jdkStandard) {
|
SignatureAlgorithm(String value, String description, String familyName, String jcaName, boolean jdkStandard) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
this.familyName = familyName;
|
||||||
this.jcaName = jcaName;
|
this.jcaName = jcaName;
|
||||||
this.jdkStandard = jdkStandard;
|
this.jdkStandard = jdkStandard;
|
||||||
}
|
}
|
||||||
|
@ -122,6 +124,78 @@ public enum SignatureAlgorithm {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cryptographic family name of the signature algorithm. The value returned is according to the
|
||||||
|
* following table:
|
||||||
|
*
|
||||||
|
* <table>
|
||||||
|
* <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>
|
||||||
|
* </table>
|
||||||
|
*
|
||||||
|
* @return Returns the cryptographic family name of the signature algorithm.
|
||||||
|
*
|
||||||
|
* @since 0.5
|
||||||
|
*/
|
||||||
|
public String getFamilyName() {
|
||||||
|
return familyName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the name of the JCA algorithm used to compute the signature.
|
* Returns the name of the JCA algorithm used to compute the signature.
|
||||||
*
|
*
|
||||||
|
|
|
@ -310,14 +310,18 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
||||||
return new DefaultJwtSigner(alg, key);
|
return new DefaultJwtSigner(alg, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String base64UrlEncode(Object o, String errMsg) {
|
protected String base64UrlEncode(Object o, String errMsg) {
|
||||||
String s;
|
String s;
|
||||||
try {
|
try {
|
||||||
s = OBJECT_MAPPER.writeValueAsString(o);
|
s = toJson(o);
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
throw new IllegalStateException(errMsg, e);
|
throw new IllegalStateException(errMsg, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TextCodec.BASE64URL.encode(s);
|
return TextCodec.BASE64URL.encode(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String toJson(Object o) throws JsonProcessingException {
|
||||||
|
return OBJECT_MAPPER.writeValueAsString(o);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.impl;
|
package io.jsonwebtoken.impl;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
@ -30,9 +32,7 @@ public class JwtMap implements Map<String,Object> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public JwtMap(Map<String, Object> map) {
|
public JwtMap(Map<String, Object> map) {
|
||||||
if (map == null) {
|
Assert.notNull(map, "Map argument cannot be null.");
|
||||||
throw new IllegalArgumentException("Map argument cannot be null.");
|
|
||||||
}
|
|
||||||
this.map = map;
|
this.map = map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,6 @@ public class DefaultSignatureValidatorFactory implements SignatureValidatorFacto
|
||||||
Assert.notNull(key, "Signing Key cannot be null.");
|
Assert.notNull(key, "Signing Key cannot be null.");
|
||||||
|
|
||||||
switch (alg) {
|
switch (alg) {
|
||||||
case NONE:
|
|
||||||
throw new IllegalArgumentException("The 'NONE' algorithm cannot be used for signing.");
|
|
||||||
case HS256:
|
case HS256:
|
||||||
case HS384:
|
case HS384:
|
||||||
case HS512:
|
case HS512:
|
||||||
|
@ -48,9 +46,7 @@ public class DefaultSignatureValidatorFactory implements SignatureValidatorFacto
|
||||||
case ES512:
|
case ES512:
|
||||||
return new EllipticCurveSignatureValidator(alg, key);
|
return new EllipticCurveSignatureValidator(alg, key);
|
||||||
default:
|
default:
|
||||||
String msg = "Unrecognized algorithm '" + alg.name() + "'. This is a bug. Please submit a ticket " +
|
throw new IllegalArgumentException("The '" + alg.name() + "' algorithm cannot be used for signing.");
|
||||||
"via the project issue tracker.";
|
|
||||||
throw new IllegalStateException(msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,6 @@ public class DefaultSignerFactory implements SignerFactory {
|
||||||
Assert.notNull(key, "Signing Key cannot be null.");
|
Assert.notNull(key, "Signing Key cannot be null.");
|
||||||
|
|
||||||
switch (alg) {
|
switch (alg) {
|
||||||
case NONE:
|
|
||||||
throw new IllegalArgumentException("The 'NONE' algorithm cannot be used for signing.");
|
|
||||||
case HS256:
|
case HS256:
|
||||||
case HS384:
|
case HS384:
|
||||||
case HS512:
|
case HS512:
|
||||||
|
@ -48,9 +46,7 @@ public class DefaultSignerFactory implements SignerFactory {
|
||||||
case ES512:
|
case ES512:
|
||||||
return new EllipticCurveSigner(alg, key);
|
return new EllipticCurveSigner(alg, key);
|
||||||
default:
|
default:
|
||||||
String msg = "Unrecognized algorithm '" + alg.name() + "'. This is a bug. Please submit a ticket " +
|
throw new IllegalArgumentException("The '" + alg.name() + "' algorithm cannot be used for signing.");
|
||||||
"via the project issue tracker.";
|
|
||||||
throw new IllegalStateException(msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,25 +16,24 @@
|
||||||
package io.jsonwebtoken.impl.crypto;
|
package io.jsonwebtoken.impl.crypto;
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.SignatureException;
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.KeyPair;
|
||||||
import java.security.Signature;
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
abstract class EllipticCurveProvider extends SignatureProvider {
|
public abstract class EllipticCurveProvider extends SignatureProvider {
|
||||||
|
|
||||||
private static final Map<SignatureAlgorithm, String> EC_SIG_ALG_NAMES = createEcSigAlgNames();
|
private static final Map<SignatureAlgorithm, String> EC_CURVE_NAMES = createEcCurveNames();
|
||||||
|
|
||||||
private static Map<SignatureAlgorithm, String> createEcSigAlgNames() {
|
private static Map<SignatureAlgorithm, String> createEcCurveNames() {
|
||||||
Map<SignatureAlgorithm, String> m =
|
Map<SignatureAlgorithm, String> m = new HashMap<SignatureAlgorithm, String>(); //alg to ASN1 OID name
|
||||||
new HashMap<SignatureAlgorithm, String>(); //EC alg name to EC alg signature name
|
m.put(SignatureAlgorithm.ES256, "secp256r1");
|
||||||
m.put(SignatureAlgorithm.ES256, "SHA256withECDSA");
|
m.put(SignatureAlgorithm.ES384, "secp384r1");
|
||||||
m.put(SignatureAlgorithm.ES384, "SHA384withECDSA");
|
m.put(SignatureAlgorithm.ES512, "secp521r1");
|
||||||
m.put(SignatureAlgorithm.ES512, "SHA512withECDSA");
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,24 +42,27 @@ abstract class EllipticCurveProvider extends SignatureProvider {
|
||||||
Assert.isTrue(alg.isEllipticCurve(), "SignatureAlgorithm must be an Elliptic Curve algorithm.");
|
Assert.isTrue(alg.isEllipticCurve(), "SignatureAlgorithm must be an Elliptic Curve algorithm.");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Signature createSignatureInstance() {
|
public static KeyPair generateKeyPair() {
|
||||||
return newSignatureInstance();
|
return generateKeyPair(SignatureAlgorithm.ES512);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Signature newSignatureInstance() {
|
public static KeyPair generateKeyPair(SignatureAlgorithm alg) {
|
||||||
|
return generateKeyPair(alg, SignatureProvider.DEFAULT_SECURE_RANDOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyPair generateKeyPair(SignatureAlgorithm alg, SecureRandom random) {
|
||||||
|
return generateKeyPair("ECDSA", "BC", alg, random);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyPair generateKeyPair(String jcaAlgorithmName, String jcaProviderName, SignatureAlgorithm alg, SecureRandom random) {
|
||||||
|
Assert.isTrue(alg != null && alg.isEllipticCurve(), "SignatureAlgorithm argument must represent an Elliptic Curve algorithm.");
|
||||||
try {
|
try {
|
||||||
String sigAlgName = EC_SIG_ALG_NAMES.get(alg);
|
KeyPairGenerator g = KeyPairGenerator.getInstance(jcaAlgorithmName, jcaProviderName);
|
||||||
if (sigAlgName == null) {
|
String paramSpecCurveName = EC_CURVE_NAMES.get(alg);
|
||||||
throw new NoSuchAlgorithmException("No EllipticCurve signature algorithm for algorithm " + alg +
|
g.initialize(org.bouncycastle.jce.ECNamedCurveTable.getParameterSpec(paramSpecCurveName), random);
|
||||||
". This is a bug. Please report this to the project issue tracker.");
|
return g.generateKeyPair();
|
||||||
}
|
} catch (Exception e) {
|
||||||
return Signature.getInstance(sigAlgName);
|
throw new IllegalStateException("Unable to generate Elliptic Curve KeyPair: " + e.getMessage(), e);
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
String msg = "Unavailable Elliptic Curve Signature algorithm.";
|
|
||||||
if (!alg.isJdkStandard()) {
|
|
||||||
msg += " This is not a standard JDK algorithm. Try including BouncyCastle in the runtime classpath.";
|
|
||||||
}
|
|
||||||
throw new SignatureException(msg, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,12 +28,16 @@ import java.security.interfaces.ECPublicKey;
|
||||||
|
|
||||||
public class EllipticCurveSignatureValidator extends EllipticCurveProvider implements SignatureValidator {
|
public class EllipticCurveSignatureValidator extends EllipticCurveProvider implements SignatureValidator {
|
||||||
|
|
||||||
|
private static final String NO_EC_PRIVATE_KEY_MSG =
|
||||||
|
"Elliptic Curve signature validation requires an ECPublicKey. ECPrivateKeys may not be used.";
|
||||||
|
|
||||||
|
private static final String EC_PUBLIC_KEY_REQD_MSG =
|
||||||
|
"Elliptic Curve Signature validation requires either an ECPublicKey instance.";
|
||||||
|
|
||||||
public EllipticCurveSignatureValidator(SignatureAlgorithm alg, Key key) {
|
public EllipticCurveSignatureValidator(SignatureAlgorithm alg, Key key) {
|
||||||
super(alg, key);
|
super(alg, key);
|
||||||
Assert.isTrue(!(key instanceof ECPrivateKey),
|
Assert.isTrue(!(key instanceof ECPrivateKey), NO_EC_PRIVATE_KEY_MSG);
|
||||||
"Elliptic Curve signature validation requires an ECPublicKey. ECPrivateKeys may not be used.");
|
Assert.isTrue(key instanceof ECPublicKey, EC_PUBLIC_KEY_REQD_MSG);
|
||||||
Assert.isTrue(key instanceof ECPublicKey,
|
|
||||||
"Elliptic Curve Signature validation requires either an ECPublicKey instance.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,12 +18,46 @@ package io.jsonwebtoken.impl.crypto;
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.Signature;
|
||||||
|
|
||||||
abstract class MacProvider extends SignatureProvider {
|
public abstract class MacProvider extends SignatureProvider {
|
||||||
|
|
||||||
protected MacProvider(SignatureAlgorithm alg, Key key) {
|
protected MacProvider(SignatureAlgorithm alg, Key key) {
|
||||||
super(alg, key);
|
super(alg, key);
|
||||||
Assert.isTrue(alg.isHmac(), "SignatureAlgorithm must be a HMAC SHA algorithm.");
|
Assert.isTrue(alg.isHmac(), "SignatureAlgorithm must be a HMAC SHA algorithm.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SecretKey generateKey() {
|
||||||
|
return generateKey(SignatureAlgorithm.HS512);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SecretKey generateKey(SignatureAlgorithm alg) {
|
||||||
|
return generateKey(alg, SignatureProvider.DEFAULT_SECURE_RANDOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SecretKey generateKey(SignatureAlgorithm alg, SecureRandom random) {
|
||||||
|
|
||||||
|
Assert.isTrue(alg.isHmac(), "SignatureAlgorithm argument must represent an HMAC algorithm.");
|
||||||
|
|
||||||
|
byte[] bytes;
|
||||||
|
|
||||||
|
switch(alg) {
|
||||||
|
case HS256:
|
||||||
|
bytes = new byte[32];
|
||||||
|
break;
|
||||||
|
case HS384:
|
||||||
|
bytes = new byte[48];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
bytes = new byte[64];
|
||||||
|
}
|
||||||
|
|
||||||
|
random.nextBytes(bytes);
|
||||||
|
|
||||||
|
return new SecretKeySpec(bytes, alg.getJcaName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,12 +21,38 @@ import io.jsonwebtoken.lang.Assert;
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.security.Signature;
|
import java.security.Signature;
|
||||||
import java.security.spec.MGF1ParameterSpec;
|
import java.security.spec.MGF1ParameterSpec;
|
||||||
import java.security.spec.PSSParameterSpec;
|
import java.security.spec.PSSParameterSpec;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
abstract class RsaProvider extends SignatureProvider {
|
public abstract class RsaProvider extends SignatureProvider {
|
||||||
|
|
||||||
|
private static final Map<SignatureAlgorithm, PSSParameterSpec> PSS_PARAMETER_SPECS = createPssParameterSpecs();
|
||||||
|
|
||||||
|
private static Map<SignatureAlgorithm, PSSParameterSpec> createPssParameterSpecs() {
|
||||||
|
|
||||||
|
Map<SignatureAlgorithm, PSSParameterSpec> m = new HashMap<SignatureAlgorithm, PSSParameterSpec>();
|
||||||
|
|
||||||
|
MGF1ParameterSpec ps = MGF1ParameterSpec.SHA256;
|
||||||
|
PSSParameterSpec spec = new PSSParameterSpec(ps.getDigestAlgorithm(), "MGF1", ps, 32, 1);
|
||||||
|
m.put(SignatureAlgorithm.PS256, spec);
|
||||||
|
|
||||||
|
ps = MGF1ParameterSpec.SHA384;
|
||||||
|
spec = new PSSParameterSpec(ps.getDigestAlgorithm(), "MGF1", ps, 48, 1);
|
||||||
|
m.put(SignatureAlgorithm.PS384, spec);
|
||||||
|
|
||||||
|
ps = MGF1ParameterSpec.SHA512;
|
||||||
|
spec = new PSSParameterSpec(ps.getDigestAlgorithm(), "MGF1", ps, 64, 1);
|
||||||
|
m.put(SignatureAlgorithm.PS512, spec);
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
protected RsaProvider(SignatureAlgorithm alg, Key key) {
|
protected RsaProvider(SignatureAlgorithm alg, Key key) {
|
||||||
super(alg, key);
|
super(alg, key);
|
||||||
|
@ -35,58 +61,51 @@ abstract class RsaProvider extends SignatureProvider {
|
||||||
|
|
||||||
protected Signature createSignatureInstance() {
|
protected Signature createSignatureInstance() {
|
||||||
|
|
||||||
Signature sig = newSignatureInstance();
|
Signature sig = super.createSignatureInstance();
|
||||||
|
|
||||||
if (alg.name().startsWith("PS")) {
|
PSSParameterSpec spec = PSS_PARAMETER_SPECS.get(alg);
|
||||||
|
if (spec != null) {
|
||||||
MGF1ParameterSpec paramSpec;
|
setParameter(sig, spec);
|
||||||
int saltLength;
|
|
||||||
|
|
||||||
switch (alg) {
|
|
||||||
case PS256:
|
|
||||||
paramSpec = MGF1ParameterSpec.SHA256;
|
|
||||||
saltLength = 32;
|
|
||||||
break;
|
|
||||||
case PS384:
|
|
||||||
paramSpec = MGF1ParameterSpec.SHA384;
|
|
||||||
saltLength = 48;
|
|
||||||
break;
|
|
||||||
case PS512:
|
|
||||||
paramSpec = MGF1ParameterSpec.SHA512;
|
|
||||||
saltLength = 64;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unsupported RSASSA-PSS algorithm: " + alg);
|
|
||||||
}
|
|
||||||
|
|
||||||
PSSParameterSpec pssParamSpec =
|
|
||||||
new PSSParameterSpec(paramSpec.getDigestAlgorithm(), "MGF1", paramSpec, saltLength, 1);
|
|
||||||
|
|
||||||
setParameter(sig, pssParamSpec);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sig;
|
return sig;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Signature newSignatureInstance() {
|
|
||||||
try {
|
|
||||||
return Signature.getInstance(alg.getJcaName());
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
String msg = "Unavailable RSA Signature algorithm.";
|
|
||||||
if (!alg.isJdkStandard()) {
|
|
||||||
msg += " This is not a standard JDK algorithm. Try including BouncyCastle in the runtime classpath.";
|
|
||||||
}
|
|
||||||
throw new SignatureException(msg, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setParameter(Signature sig, PSSParameterSpec spec) {
|
protected void setParameter(Signature sig, PSSParameterSpec spec) {
|
||||||
try {
|
try {
|
||||||
sig.setParameter(spec);
|
doSetParameter(sig, spec);
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
String msg = "Unsupported RSASSA-PSS parameter '" + spec + "': " + e.getMessage();
|
String msg = "Unsupported RSASSA-PSS parameter '" + spec + "': " + e.getMessage();
|
||||||
throw new SignatureException(msg, e);
|
throw new SignatureException(msg, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void doSetParameter(Signature sig, PSSParameterSpec spec) throws InvalidAlgorithmParameterException {
|
||||||
|
sig.setParameter(spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyPair generateKeyPair() {
|
||||||
|
return generateKeyPair(4096);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyPair generateKeyPair(int keySizeInBits) {
|
||||||
|
return generateKeyPair(keySizeInBits, SignatureProvider.DEFAULT_SECURE_RANDOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyPair generateKeyPair(int keySizeInBits, SecureRandom random) {
|
||||||
|
return generateKeyPair("RSA", keySizeInBits, random);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static KeyPair generateKeyPair(String jcaAlgName, int keySizeInBits, SecureRandom random) {
|
||||||
|
KeyPairGenerator keyGenerator;
|
||||||
|
try {
|
||||||
|
keyGenerator = KeyPairGenerator.getInstance(jcaAlgName);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException("Unable to obtain an RSA KeyPairGenerator: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
keyGenerator.initialize(keySizeInBits, random);
|
||||||
|
|
||||||
|
return keyGenerator.genKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,27 @@
|
||||||
package io.jsonwebtoken.impl.crypto;
|
package io.jsonwebtoken.impl.crypto;
|
||||||
|
|
||||||
import io.jsonwebtoken.SignatureAlgorithm;
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import io.jsonwebtoken.SignatureException;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.lang.RuntimeEnvironment;
|
||||||
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
abstract class SignatureProvider {
|
abstract class SignatureProvider {
|
||||||
|
|
||||||
|
public static final SecureRandom DEFAULT_SECURE_RANDOM;
|
||||||
|
|
||||||
|
static {
|
||||||
|
DEFAULT_SECURE_RANDOM = new SecureRandom();
|
||||||
|
DEFAULT_SECURE_RANDOM.nextBytes(new byte[64]);
|
||||||
|
}
|
||||||
|
|
||||||
protected final SignatureAlgorithm alg;
|
protected final SignatureAlgorithm alg;
|
||||||
protected final Key key;
|
protected final Key key;
|
||||||
|
|
||||||
protected SignatureProvider(SignatureAlgorithm alg, Key key) {
|
protected SignatureProvider(SignatureAlgorithm alg, Key key) {
|
||||||
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
|
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
|
||||||
|
@ -31,4 +44,24 @@ abstract class SignatureProvider {
|
||||||
this.alg = alg;
|
this.alg = alg;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Signature createSignatureInstance() {
|
||||||
|
try {
|
||||||
|
return getSignatureInstance();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
String msg = "Unavailable " + alg.getFamilyName() + " Signature algorithm '" + alg.getJcaName() + "'.";
|
||||||
|
if (!alg.isJdkStandard() && !isBouncyCastleAvailable()) {
|
||||||
|
msg += " This is not a standard JDK algorithm. Try including BouncyCastle in the runtime classpath.";
|
||||||
|
}
|
||||||
|
throw new SignatureException(msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Signature getSignatureInstance() throws NoSuchAlgorithmException {
|
||||||
|
return Signature.getInstance(alg.getJcaName());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isBouncyCastleAvailable() {
|
||||||
|
return RuntimeEnvironment.BOUNCY_CASTLE_AVAILABLE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,8 @@ public class RuntimeEnvironment {
|
||||||
|
|
||||||
private static final AtomicBoolean bcLoaded = new AtomicBoolean(false);
|
private static final AtomicBoolean bcLoaded = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
public static final boolean BOUNCY_CASTLE_AVAILABLE = Classes.isAvailable(BC_PROVIDER_CLASS_NAME);
|
||||||
|
|
||||||
public static void enableBouncyCastleIfPossible() {
|
public static void enableBouncyCastleIfPossible() {
|
||||||
|
|
||||||
if (bcLoaded.get()) {
|
if (bcLoaded.get()) {
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken
|
||||||
|
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
class ExpiredJwtExceptionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testOverloadedConstructor() {
|
||||||
|
def header = Jwts.header()
|
||||||
|
def claims = Jwts.claims()
|
||||||
|
def msg = 'foo'
|
||||||
|
def cause = new NullPointerException()
|
||||||
|
|
||||||
|
def ex = new ExpiredJwtException(header, claims, msg, cause)
|
||||||
|
|
||||||
|
assertSame ex.header, header
|
||||||
|
assertSame ex.claims, claims
|
||||||
|
assertEquals ex.message, msg
|
||||||
|
assertSame ex.cause, cause
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,23 +19,26 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import io.jsonwebtoken.impl.DefaultHeader
|
import io.jsonwebtoken.impl.DefaultHeader
|
||||||
import io.jsonwebtoken.impl.DefaultJwsHeader
|
import io.jsonwebtoken.impl.DefaultJwsHeader
|
||||||
import io.jsonwebtoken.impl.TextCodec
|
import io.jsonwebtoken.impl.TextCodec
|
||||||
import org.bouncycastle.jce.ECNamedCurveTable
|
import io.jsonwebtoken.impl.crypto.EllipticCurveProvider
|
||||||
|
import io.jsonwebtoken.impl.crypto.MacProvider
|
||||||
|
import io.jsonwebtoken.impl.crypto.RsaProvider
|
||||||
import org.testng.annotations.Test
|
import org.testng.annotations.Test
|
||||||
|
|
||||||
import javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.KeyPairGenerator
|
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.security.SecureRandom
|
|
||||||
|
|
||||||
import static org.testng.Assert.*
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
class JwtsTest {
|
class JwtsTest {
|
||||||
|
|
||||||
private static final SecureRandom RANDOM = new SecureRandom();
|
@Test
|
||||||
|
void testSubclass() {
|
||||||
|
new Jwts()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHeaderWithNoArgs() {
|
void testHeaderWithNoArgs() {
|
||||||
|
@ -398,7 +401,7 @@ class JwtsTest {
|
||||||
testEC(SignatureAlgorithm.ES256, true)
|
testEC(SignatureAlgorithm.ES256, true)
|
||||||
fail("EC private keys cannot be used to validate EC signatures.")
|
fail("EC private keys cannot be used to validate EC signatures.")
|
||||||
} catch (UnsupportedJwtException e) {
|
} catch (UnsupportedJwtException e) {
|
||||||
assertEquals e.cause.message, "Elliptic Curve signature validation requires an ECPublicKey. ECPrivateKeys may not be used."
|
assertEquals e.cause.message, "Elliptic Curve signature validation requires an ECPublicKey. ECPrivateKeys may not be used."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,8 +410,7 @@ class JwtsTest {
|
||||||
void testParseClaimsJwsWithUnsignedJwt() {
|
void testParseClaimsJwsWithUnsignedJwt() {
|
||||||
|
|
||||||
//create random signing key for testing:
|
//create random signing key for testing:
|
||||||
byte[] key = new byte[64];
|
byte[] key = MacProvider.generateKey().getEncoded()
|
||||||
RANDOM.nextBytes(key);
|
|
||||||
|
|
||||||
String notSigned = Jwts.builder().setSubject("Foo").compact();
|
String notSigned = Jwts.builder().setSubject("Foo").compact();
|
||||||
|
|
||||||
|
@ -425,8 +427,7 @@ class JwtsTest {
|
||||||
void testForgedTokenWithSwappedHeaderUsingNoneAlgorithm() {
|
void testForgedTokenWithSwappedHeaderUsingNoneAlgorithm() {
|
||||||
|
|
||||||
//create random signing key for testing:
|
//create random signing key for testing:
|
||||||
byte[] key = new byte[64];
|
byte[] key = MacProvider.generateKey().getEncoded()
|
||||||
RANDOM.nextBytes(key);
|
|
||||||
|
|
||||||
//this is a 'real', valid JWT:
|
//this is a 'real', valid JWT:
|
||||||
String compact = Jwts.builder().setSubject("Joe").signWith(SignatureAlgorithm.HS256, key).compact();
|
String compact = Jwts.builder().setSubject("Joe").signWith(SignatureAlgorithm.HS256, key).compact();
|
||||||
|
@ -458,9 +459,7 @@ class JwtsTest {
|
||||||
void testParseForgedRsaPublicKeyAsHmacTokenVerifiedWithTheRsaPrivateKey() {
|
void testParseForgedRsaPublicKeyAsHmacTokenVerifiedWithTheRsaPrivateKey() {
|
||||||
|
|
||||||
//Create a legitimate RSA public and private key pair:
|
//Create a legitimate RSA public and private key pair:
|
||||||
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
|
KeyPair kp = RsaProvider.generateKeyPair(1024)
|
||||||
keyGenerator.initialize(1024);
|
|
||||||
KeyPair kp = keyGenerator.genKeyPair();
|
|
||||||
PublicKey publicKey = kp.getPublic();
|
PublicKey publicKey = kp.getPublic();
|
||||||
PrivateKey privateKey = kp.getPrivate();
|
PrivateKey privateKey = kp.getPrivate();
|
||||||
|
|
||||||
|
@ -493,11 +492,9 @@ class JwtsTest {
|
||||||
void testParseForgedRsaPublicKeyAsHmacTokenVerifiedWithTheRsaPublicKey() {
|
void testParseForgedRsaPublicKeyAsHmacTokenVerifiedWithTheRsaPublicKey() {
|
||||||
|
|
||||||
//Create a legitimate RSA public and private key pair:
|
//Create a legitimate RSA public and private key pair:
|
||||||
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
|
KeyPair kp = RsaProvider.generateKeyPair(1024)
|
||||||
keyGenerator.initialize(1024);
|
|
||||||
KeyPair kp = keyGenerator.genKeyPair();
|
|
||||||
PublicKey publicKey = kp.getPublic();
|
PublicKey publicKey = kp.getPublic();
|
||||||
PrivateKey privateKey = kp.getPrivate();
|
//PrivateKey privateKey = kp.getPrivate();
|
||||||
|
|
||||||
ObjectMapper om = new ObjectMapper()
|
ObjectMapper om = new ObjectMapper()
|
||||||
String header = TextCodec.BASE64URL.encode(om.writeValueAsString(['alg': 'HS256']))
|
String header = TextCodec.BASE64URL.encode(om.writeValueAsString(['alg': 'HS256']))
|
||||||
|
@ -525,10 +522,7 @@ class JwtsTest {
|
||||||
|
|
||||||
static void testRsa(SignatureAlgorithm alg, int keySize=1024, boolean verifyWithPrivateKey=false) {
|
static void testRsa(SignatureAlgorithm alg, int keySize=1024, boolean verifyWithPrivateKey=false) {
|
||||||
|
|
||||||
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
|
KeyPair kp = RsaProvider.generateKeyPair(keySize)
|
||||||
keyGenerator.initialize(keySize);
|
|
||||||
|
|
||||||
KeyPair kp = keyGenerator.genKeyPair();
|
|
||||||
PublicKey publicKey = kp.getPublic();
|
PublicKey publicKey = kp.getPublic();
|
||||||
PrivateKey privateKey = kp.getPrivate();
|
PrivateKey privateKey = kp.getPrivate();
|
||||||
|
|
||||||
|
@ -551,8 +545,7 @@ class JwtsTest {
|
||||||
|
|
||||||
static void testHmac(SignatureAlgorithm alg) {
|
static void testHmac(SignatureAlgorithm alg) {
|
||||||
//create random signing key for testing:
|
//create random signing key for testing:
|
||||||
byte[] key = new byte[64];
|
byte[] key = MacProvider.generateKey().encoded
|
||||||
RANDOM.nextBytes(key);
|
|
||||||
|
|
||||||
def claims = [iss: 'joe', exp: later(), 'http://example.com/is_root':true]
|
def claims = [iss: 'joe', exp: later(), 'http://example.com/is_root':true]
|
||||||
|
|
||||||
|
@ -566,9 +559,8 @@ class JwtsTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void testEC(SignatureAlgorithm alg, boolean verifyWithPrivateKey=false) {
|
static void testEC(SignatureAlgorithm alg, boolean verifyWithPrivateKey=false) {
|
||||||
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC");
|
|
||||||
g.initialize(ECNamedCurveTable.getParameterSpec(alg.getJcaName()), RANDOM);
|
KeyPair pair = EllipticCurveProvider.generateKeyPair(alg)
|
||||||
KeyPair pair = g.generateKeyPair();
|
|
||||||
PublicKey publicKey = pair.getPublic()
|
PublicKey publicKey = pair.getPublic()
|
||||||
PrivateKey privateKey = pair.getPrivate()
|
PrivateKey privateKey = pair.getPrivate()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken
|
||||||
|
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
class PrematureJwtExceptionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testOverloadedConstructor() {
|
||||||
|
def header = Jwts.header()
|
||||||
|
def claims = Jwts.claims()
|
||||||
|
def msg = 'foo'
|
||||||
|
def cause = new NullPointerException()
|
||||||
|
|
||||||
|
def ex = new PrematureJwtException(header, claims, msg, cause)
|
||||||
|
|
||||||
|
assertSame ex.header, header
|
||||||
|
assertSame ex.claims, claims
|
||||||
|
assertEquals ex.message, msg
|
||||||
|
assertSame ex.cause, cause
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,6 +54,15 @@ class SignatureAlgorithmTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHmacFamilyName() {
|
||||||
|
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
|
if (alg.name().startsWith("HS")) {
|
||||||
|
assertEquals alg.getFamilyName(), "HMAC"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testIsRsa() {
|
void testIsRsa() {
|
||||||
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
|
@ -65,6 +74,15 @@ class SignatureAlgorithmTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRsaFamilyName() {
|
||||||
|
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
|
if (alg.name().startsWith("RS") || alg.name().startsWith("PS")) {
|
||||||
|
assertEquals alg.getFamilyName(), "RSA"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testIsEllipticCurve() {
|
void testIsEllipticCurve() {
|
||||||
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
|
@ -76,6 +94,15 @@ class SignatureAlgorithmTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEllipticCurveFamilyName() {
|
||||||
|
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
|
if (alg.name().startsWith("ES")) {
|
||||||
|
assertEquals alg.getFamilyName(), "Elliptic Curve"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testIsJdkStandard() {
|
void testIsJdkStandard() {
|
||||||
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken.impl
|
||||||
|
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
class DefaultHeaderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testType() {
|
||||||
|
|
||||||
|
def h = new DefaultHeader()
|
||||||
|
|
||||||
|
h.setType('foo')
|
||||||
|
assertEquals h.getType(), 'foo'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testContentType() {
|
||||||
|
|
||||||
|
def h = new DefaultHeader()
|
||||||
|
|
||||||
|
h.setContentType('bar')
|
||||||
|
assertEquals h.getContentType(), 'bar'
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken.impl
|
||||||
|
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
class DefaultJwsHeaderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testKeyId() {
|
||||||
|
|
||||||
|
def h = new DefaultJwsHeader()
|
||||||
|
|
||||||
|
h.setKeyId('foo')
|
||||||
|
assertEquals h.getKeyId(), 'foo'
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken.impl
|
||||||
|
|
||||||
|
import io.jsonwebtoken.JwsHeader
|
||||||
|
import io.jsonwebtoken.Jwts
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
class DefaultJwsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConstructor() {
|
||||||
|
|
||||||
|
JwsHeader header = Jwts.jwsHeader()
|
||||||
|
def jws = new DefaultJws<String>(header, 'foo', 'sig')
|
||||||
|
|
||||||
|
assertSame jws.getHeader(), header
|
||||||
|
assertEquals jws.getBody(), 'foo'
|
||||||
|
assertEquals jws.getSignature(), 'sig'
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken.impl
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException
|
||||||
|
import com.fasterxml.jackson.databind.JsonMappingException
|
||||||
|
import io.jsonwebtoken.Jwts
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import io.jsonwebtoken.impl.crypto.MacProvider
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
class DefaultJwtBuilderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetHeader() {
|
||||||
|
def h = Jwts.header()
|
||||||
|
def b = new DefaultJwtBuilder()
|
||||||
|
b.setHeader(h)
|
||||||
|
assertSame b.header, h
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetHeaderFromMap() {
|
||||||
|
def m = [foo: 'bar']
|
||||||
|
def b = new DefaultJwtBuilder()
|
||||||
|
b.setHeader(m)
|
||||||
|
assertNotNull b.header
|
||||||
|
assertEquals b.header.size(), 1
|
||||||
|
assertEquals b.header.foo, 'bar'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetHeaderParams() {
|
||||||
|
def m = [a: 'b', c: 'd']
|
||||||
|
def b = new DefaultJwtBuilder()
|
||||||
|
b.setHeaderParams(m)
|
||||||
|
assertNotNull b.header
|
||||||
|
assertEquals b.header.size(), 2
|
||||||
|
assertEquals b.header.a, 'b'
|
||||||
|
assertEquals b.header.c, 'd'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetHeaderParam() {
|
||||||
|
def b = new DefaultJwtBuilder()
|
||||||
|
b.setHeaderParam('foo', 'bar')
|
||||||
|
assertNotNull b.header
|
||||||
|
assertEquals b.header.size(), 1
|
||||||
|
assertEquals b.header.foo, 'bar'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSetClaims() {
|
||||||
|
def b = new DefaultJwtBuilder()
|
||||||
|
def c = Jwts.claims()
|
||||||
|
b.setClaims(c)
|
||||||
|
assertNotNull b.claims
|
||||||
|
assertSame b.claims, c
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testClaim() {
|
||||||
|
def b = new DefaultJwtBuilder()
|
||||||
|
b.claim('foo', 'bar')
|
||||||
|
assertNotNull b.claims
|
||||||
|
assertEquals b.claims.size(), 1
|
||||||
|
assertEquals b.claims.foo, 'bar'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testExistingClaimsAndSetClaim() {
|
||||||
|
def b = new DefaultJwtBuilder()
|
||||||
|
def c = Jwts.claims()
|
||||||
|
b.setClaims(c)
|
||||||
|
b.claim('foo', 'bar')
|
||||||
|
assertSame b.claims, c
|
||||||
|
assertEquals b.claims.size(), 1
|
||||||
|
assertEquals c.size(), 1
|
||||||
|
assertEquals b.claims.foo, 'bar'
|
||||||
|
assertEquals c.foo, 'bar'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRemoveClaimBySettingNullValue() {
|
||||||
|
def b = new DefaultJwtBuilder()
|
||||||
|
b.claim('foo', 'bar')
|
||||||
|
assertNotNull b.claims
|
||||||
|
assertEquals b.claims.size(), 1
|
||||||
|
assertEquals b.claims.foo, 'bar'
|
||||||
|
|
||||||
|
b.claim('foo', null)
|
||||||
|
assertNotNull b.claims
|
||||||
|
assertNull b.claims.foo
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompactWithoutBody() {
|
||||||
|
def b = new DefaultJwtBuilder()
|
||||||
|
try {
|
||||||
|
b.compact()
|
||||||
|
fail()
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
assertEquals ise.message, "Either 'payload' or 'claims' must be specified."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompactWithBothPayloadAndClaims() {
|
||||||
|
def b = new DefaultJwtBuilder()
|
||||||
|
b.setPayload('foo')
|
||||||
|
b.claim('a', 'b')
|
||||||
|
try {
|
||||||
|
b.compact()
|
||||||
|
fail()
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
assertEquals ise.message, "Both 'payload' and 'claims' cannot both be specified. Choose either one."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompactWithBothKeyAndKeyBytes() {
|
||||||
|
def b = new DefaultJwtBuilder()
|
||||||
|
b.setPayload('foo')
|
||||||
|
def key = MacProvider.generateKey()
|
||||||
|
b.signWith(SignatureAlgorithm.HS256, key)
|
||||||
|
b.signWith(SignatureAlgorithm.HS256, key.encoded)
|
||||||
|
try {
|
||||||
|
b.compact()
|
||||||
|
fail()
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
assertEquals ise.message, "A key object and key bytes cannot both be specified. Choose either one."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompactWithJwsHeader() {
|
||||||
|
def b = new DefaultJwtBuilder()
|
||||||
|
b.setHeader(Jwts.jwsHeader().setKeyId('a'))
|
||||||
|
b.setPayload('foo')
|
||||||
|
def key = MacProvider.generateKey()
|
||||||
|
b.signWith(SignatureAlgorithm.HS256, key)
|
||||||
|
b.compact()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBase64UrlEncodeError() {
|
||||||
|
|
||||||
|
def b = new DefaultJwtBuilder() {
|
||||||
|
@Override
|
||||||
|
protected String toJson(Object o) throws JsonProcessingException {
|
||||||
|
throw new JsonMappingException('foo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
b.setPayload('foo').compact()
|
||||||
|
fail()
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
assertEquals ise.cause.message, 'foo'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken.impl
|
||||||
|
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
class JwtMapTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToDateFromDate() {
|
||||||
|
|
||||||
|
def d = new Date()
|
||||||
|
|
||||||
|
Date date = JwtMap.toDate(d, 'foo')
|
||||||
|
|
||||||
|
assertSame date, d
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToDateFromString() {
|
||||||
|
|
||||||
|
Date d = new Date(2015, 1, 1, 12, 0, 0)
|
||||||
|
|
||||||
|
String s = (d.getTime() / 1000) + '' //JWT timestamps are in seconds - need to strip millis
|
||||||
|
|
||||||
|
Date date = JwtMap.toDate(s, 'foo')
|
||||||
|
|
||||||
|
assertEquals date, d
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToDateFromNonDateObject() {
|
||||||
|
try {
|
||||||
|
JwtMap.toDate(new Object() { @Override public String toString() {return 'hi'} }, 'foo')
|
||||||
|
fail()
|
||||||
|
} catch (IllegalStateException iae) {
|
||||||
|
assertEquals iae.message, "Cannot convert 'foo' value [hi] to Date instance."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testContainsKey() {
|
||||||
|
def m = new JwtMap()
|
||||||
|
m.put('foo', 'bar')
|
||||||
|
assertTrue m.containsKey('foo')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testContainsValue() {
|
||||||
|
def m = new JwtMap()
|
||||||
|
m.put('foo', 'bar')
|
||||||
|
assertTrue m.containsValue('bar')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRemoveByPuttingNull() {
|
||||||
|
def m = new JwtMap()
|
||||||
|
m.put('foo', 'bar')
|
||||||
|
assertTrue m.containsKey('foo')
|
||||||
|
assertTrue m.containsValue('bar')
|
||||||
|
m.put('foo', null)
|
||||||
|
assertFalse m.containsKey('foo')
|
||||||
|
assertFalse m.containsValue('bar')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPutAll() {
|
||||||
|
def m = new JwtMap();
|
||||||
|
m.putAll([a: 'b', c: 'd'])
|
||||||
|
assertEquals m.size(), 2
|
||||||
|
assertEquals m.a, 'b'
|
||||||
|
assertEquals m.c, 'd'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPutAllWithNullArgument() {
|
||||||
|
def m = new JwtMap();
|
||||||
|
m.putAll((Map)null)
|
||||||
|
assertEquals m.size(), 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testClear() {
|
||||||
|
def m = new JwtMap();
|
||||||
|
m.put('foo', 'bar')
|
||||||
|
assertEquals m.size(), 1
|
||||||
|
m.clear()
|
||||||
|
assertEquals m.size(), 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testKeySet() {
|
||||||
|
def m = new JwtMap()
|
||||||
|
m.putAll([a: 'b', c: 'd'])
|
||||||
|
assertEquals( m.keySet(), ['a', 'c'] as Set)
|
||||||
|
}
|
||||||
|
|
||||||
|
void testEntrySet() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testValues() {
|
||||||
|
def m = new JwtMap()
|
||||||
|
m.putAll([a: 'b', c: 'd'])
|
||||||
|
assertEquals( m.values(), ['b', 'd'] as Set)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
class DefaultSignatureValidatorFactoryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNoneAlgorithm() {
|
||||||
|
try {
|
||||||
|
new DefaultSignatureValidatorFactory().createSignatureValidator(SignatureAlgorithm.NONE, MacProvider.generateKey())
|
||||||
|
fail()
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
assertEquals iae.message, "The 'NONE' algorithm cannot be used for signing."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,19 +24,13 @@ import static org.testng.Assert.*
|
||||||
|
|
||||||
class DefaultSignerFactoryTest {
|
class DefaultSignerFactoryTest {
|
||||||
|
|
||||||
private static final Random rng = new Random(); //doesn't need to be secure - we're just testing
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCreateSignerWithNoneAlgorithm() {
|
void testCreateSignerWithNoneAlgorithm() {
|
||||||
|
|
||||||
byte[] keyBytes = new byte[32];
|
|
||||||
rng.nextBytes(keyBytes);
|
|
||||||
SecretKeySpec key = new SecretKeySpec(keyBytes, "foo");
|
|
||||||
|
|
||||||
def factory = new DefaultSignerFactory();
|
def factory = new DefaultSignerFactory();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
factory.createSigner(SignatureAlgorithm.NONE, key);
|
factory.createSigner(SignatureAlgorithm.NONE, MacProvider.generateKey());
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException iae) {
|
} catch (IllegalArgumentException iae) {
|
||||||
assertEquals iae.message, "The 'NONE' algorithm cannot be used for signing."
|
assertEquals iae.message, "The 'NONE' algorithm cannot be used for signing."
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.security.NoSuchProviderException
|
||||||
|
import java.security.interfaces.ECPrivateKey
|
||||||
|
import java.security.interfaces.ECPublicKey
|
||||||
|
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
|
||||||
|
class EllipticCurveProviderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGenerateKeyPair() {
|
||||||
|
KeyPair pair = EllipticCurveProvider.generateKeyPair()
|
||||||
|
assertNotNull pair
|
||||||
|
assertTrue pair.public instanceof ECPublicKey
|
||||||
|
assertTrue pair.private instanceof ECPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGenerateKeyPairWithInvalidProviderName() {
|
||||||
|
try {
|
||||||
|
EllipticCurveProvider.generateKeyPair("ECDSA", "Foo", SignatureAlgorithm.ES256, null)
|
||||||
|
fail()
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
assertEquals ise.message, "Unable to generate Elliptic Curve KeyPair: no such provider: Foo"
|
||||||
|
assertTrue ise.cause instanceof NoSuchProviderException
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import io.jsonwebtoken.SignatureException
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
|
||||||
|
import java.security.InvalidKeyException
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.security.Signature
|
||||||
|
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
class EllipticCurveSignatureValidatorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDoVerifyWithInvalidKeyException() {
|
||||||
|
|
||||||
|
String msg = 'foo'
|
||||||
|
final InvalidKeyException ex = new InvalidKeyException(msg)
|
||||||
|
|
||||||
|
def v = new EllipticCurveSignatureValidator(SignatureAlgorithm.ES512, EllipticCurveProvider.generateKeyPair().public) {
|
||||||
|
@Override
|
||||||
|
protected boolean doVerify(Signature sig, PublicKey pk, byte[] data, byte[] signature) throws InvalidKeyException, java.security.SignatureException {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = new byte[16]
|
||||||
|
byte[] signature = new byte[16]
|
||||||
|
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(bytes)
|
||||||
|
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(signature)
|
||||||
|
|
||||||
|
try {
|
||||||
|
v.isValid(bytes, signature)
|
||||||
|
fail();
|
||||||
|
} catch (SignatureException se) {
|
||||||
|
assertEquals se.message, 'Unable to verify Elliptic Curve signature using configured ECPublicKey. ' + msg
|
||||||
|
assertSame se.cause, ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import io.jsonwebtoken.SignatureException
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
|
||||||
|
import java.security.InvalidKeyException
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.security.PrivateKey
|
||||||
|
import java.security.PublicKey
|
||||||
|
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
class EllipticCurveSignerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConstructorWithoutECAlg() {
|
||||||
|
try {
|
||||||
|
new EllipticCurveSigner(SignatureAlgorithm.HS256, MacProvider.generateKey());
|
||||||
|
fail('EllipticCurveSigner should reject non ECPrivateKeys');
|
||||||
|
} catch (IllegalArgumentException expected) {
|
||||||
|
assertEquals expected.message, 'SignatureAlgorithm must be an Elliptic Curve algorithm.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConstructorWithoutECPrivateKey() {
|
||||||
|
def key = MacProvider.generateKey()
|
||||||
|
try {
|
||||||
|
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 ECPrivateKey. The specified key of " +
|
||||||
|
"type " + key.getClass().getName() + " is not an ECPrivateKey.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDoSignWithInvalidKeyException() {
|
||||||
|
|
||||||
|
KeyPair kp = EllipticCurveProvider.generateKeyPair()
|
||||||
|
PublicKey publicKey = kp.getPublic();
|
||||||
|
PrivateKey privateKey = kp.getPrivate();
|
||||||
|
|
||||||
|
String msg = 'foo'
|
||||||
|
final InvalidKeyException ex = new InvalidKeyException(msg)
|
||||||
|
|
||||||
|
def signer = new EllipticCurveSigner(SignatureAlgorithm.ES256, privateKey) {
|
||||||
|
@Override
|
||||||
|
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException {
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = new byte[16]
|
||||||
|
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(bytes)
|
||||||
|
|
||||||
|
try {
|
||||||
|
signer.sign(bytes)
|
||||||
|
fail();
|
||||||
|
} catch (SignatureException se) {
|
||||||
|
assertEquals se.message, 'Invalid Elliptic Curve PrivateKey. ' + msg
|
||||||
|
assertSame se.cause, ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDoSignWithJdkSignatureException() {
|
||||||
|
|
||||||
|
KeyPair kp = EllipticCurveProvider.generateKeyPair()
|
||||||
|
PublicKey publicKey = kp.getPublic();
|
||||||
|
PrivateKey privateKey = kp.getPrivate();
|
||||||
|
|
||||||
|
String msg = 'foo'
|
||||||
|
final java.security.SignatureException ex = new java.security.SignatureException(msg)
|
||||||
|
|
||||||
|
def signer = new EllipticCurveSigner(SignatureAlgorithm.ES256, privateKey) {
|
||||||
|
@Override
|
||||||
|
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException {
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] bytes = new byte[16]
|
||||||
|
SignatureProvider.DEFAULT_SECURE_RANDOM.nextBytes(bytes)
|
||||||
|
|
||||||
|
try {
|
||||||
|
signer.sign(bytes)
|
||||||
|
fail();
|
||||||
|
} catch (SignatureException se) {
|
||||||
|
assertEquals se.message, 'Unable to calculate signature using Elliptic Curve PrivateKey. ' + msg
|
||||||
|
assertSame se.cause, ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
|
||||||
|
class MacProviderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDefault() {
|
||||||
|
byte[] bytes = MacProvider.generateKey().encoded
|
||||||
|
assertEquals 64, bytes.length
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHS256() {
|
||||||
|
byte[] bytes = MacProvider.generateKey(SignatureAlgorithm.HS256).encoded
|
||||||
|
assertEquals 32, bytes.length
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHS384() {
|
||||||
|
byte[] bytes = MacProvider.generateKey(SignatureAlgorithm.HS384).encoded
|
||||||
|
assertEquals 48, bytes.length
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHS512() {
|
||||||
|
byte[] bytes = MacProvider.generateKey(SignatureAlgorithm.HS512).encoded
|
||||||
|
assertEquals 64, bytes.length
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import io.jsonwebtoken.SignatureException
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
|
||||||
|
import java.security.InvalidAlgorithmParameterException
|
||||||
|
import java.security.KeyPair
|
||||||
|
import java.security.Signature
|
||||||
|
import java.security.interfaces.RSAPrivateKey
|
||||||
|
import java.security.interfaces.RSAPublicKey
|
||||||
|
import java.security.spec.PSSParameterSpec
|
||||||
|
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
class RsaProviderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGenerateKeyPair() {
|
||||||
|
KeyPair pair = RsaProvider.generateKeyPair()
|
||||||
|
assertNotNull pair
|
||||||
|
assertTrue pair.public instanceof RSAPublicKey
|
||||||
|
assertTrue pair.private instanceof RSAPrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGenerateKeyPairWithInvalidProviderName() {
|
||||||
|
try {
|
||||||
|
RsaProvider.generateKeyPair('foo', 1024, SignatureProvider.DEFAULT_SECURE_RANDOM)
|
||||||
|
fail()
|
||||||
|
} catch (IllegalStateException ise) {
|
||||||
|
assertTrue ise.message.startsWith("Unable to obtain an RSA KeyPairGenerator: ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateSignatureInstanceWithInvalidPSSParameterSpecAlgorithm() {
|
||||||
|
|
||||||
|
def p = new RsaProvider(SignatureAlgorithm.PS256, RsaProvider.generateKeyPair(512).public) {
|
||||||
|
@Override
|
||||||
|
protected void doSetParameter(Signature sig, PSSParameterSpec spec) throws InvalidAlgorithmParameterException {
|
||||||
|
throw new InvalidAlgorithmParameterException('foo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
p.createSignatureInstance()
|
||||||
|
fail()
|
||||||
|
} catch (SignatureException se) {
|
||||||
|
assertTrue se.message.startsWith('Unsupported RSASSA-PSS parameter')
|
||||||
|
assertEquals se.cause.message, 'foo'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,6 @@ import java.security.PublicKey
|
||||||
|
|
||||||
import static org.testng.Assert.*
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
|
||||||
class RsaSignerTest {
|
class RsaSignerTest {
|
||||||
|
|
||||||
private static final Random rng = new Random(); //doesn't need to be secure - we're just testing
|
private static final Random rng = new Random(); //doesn't need to be secure - we're just testing
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 jsonwebtoken.io
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package io.jsonwebtoken.impl.crypto
|
||||||
|
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import io.jsonwebtoken.SignatureException
|
||||||
|
import org.testng.annotations.Test
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
|
import java.security.Signature
|
||||||
|
|
||||||
|
import static org.testng.Assert.*
|
||||||
|
|
||||||
|
class SignatureProviderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateSignatureInstanceNoSuchAlgorithm() {
|
||||||
|
|
||||||
|
def p = new SignatureProvider(SignatureAlgorithm.HS256, MacProvider.generateKey()) {
|
||||||
|
@Override
|
||||||
|
protected Signature getSignatureInstance() throws NoSuchAlgorithmException {
|
||||||
|
throw new NoSuchAlgorithmException('foo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
p.createSignatureInstance()
|
||||||
|
fail()
|
||||||
|
} catch (SignatureException se) {
|
||||||
|
assertEquals se.cause.message, 'foo'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateSignatureInstanceNoSuchAlgorithmNonStandardAlgorithm() {
|
||||||
|
|
||||||
|
def p = new SignatureProvider(SignatureAlgorithm.ES512, EllipticCurveProvider.generateKeyPair().getPublic()) {
|
||||||
|
@Override
|
||||||
|
protected Signature getSignatureInstance() throws NoSuchAlgorithmException {
|
||||||
|
throw new NoSuchAlgorithmException('foo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
p.createSignatureInstance()
|
||||||
|
fail()
|
||||||
|
} catch (SignatureException se) {
|
||||||
|
assertEquals se.cause.message, 'foo'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateSignatureInstanceNoSuchAlgorithmNonStandardAlgorithmWithoutBouncyCastle() {
|
||||||
|
|
||||||
|
def p = new SignatureProvider(SignatureAlgorithm.ES512, EllipticCurveProvider.generateKeyPair().getPublic()) {
|
||||||
|
@Override
|
||||||
|
protected Signature getSignatureInstance() throws NoSuchAlgorithmException {
|
||||||
|
throw new NoSuchAlgorithmException('foo')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isBouncyCastleAvailable() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
p.createSignatureInstance()
|
||||||
|
fail()
|
||||||
|
} catch (SignatureException se) {
|
||||||
|
assertTrue se.message.contains('Try including BouncyCastle in the runtime classpath')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue