Merge pull request #28 from jwtk/elliptic-curve

Elliptic Curve algorithms finished!
This commit is contained in:
Les Hazlewood 2015-05-07 23:42:11 -07:00
commit d74a921487
34 changed files with 1452 additions and 116 deletions

View File

@ -75,7 +75,7 @@ try {
* Creating and parsing plaintext compact JWTs
* Creating, parsing and verifying digitally signed compact JWTs (aka JWSs) with the following algorithms:
* Creating, parsing and verifying digitally signed compact JWTs (aka JWSs) with all standard JWS algorithms:
* HS256: HMAC using SHA-256
* HS384: HMAC using SHA-384
* HS512: HMAC using SHA-512
@ -85,11 +85,13 @@ try {
* PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256
* PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384
* PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512
* ES256: ECDSA using P-256 and SHA-256
* ES384: ECDSA using P-384 and SHA-384
* ES512: ECDSA using P-512 and SHA-512
## Currently Unsupported Features
* [Non-compact](https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-7.2) serialization and parsing.
* Elliptic Curve signature algorithms `ES256`, `ES384` and `ES512`.
* JWE (Encryption for JWT)
These feature sets will be implemented in a future release when possible. Community contributions are welcome!

View File

@ -102,7 +102,7 @@
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
<scope>runtime</scope> <!-- Not required at compile time -->
<scope>compile</scope>
<optional>true</optional>
</dependency>

View File

@ -29,7 +29,9 @@ import java.util.Map;
*
* @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

View File

@ -26,67 +26,67 @@ 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", null, false),
NONE("none", "No digital signature or MAC performed", "None", null, false),
/** 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} */
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} */
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} */
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} */
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} */
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
* 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", "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
* 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", "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
* 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", "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
* 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", "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
* 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", "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
* 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", "SHA512withRSAandMGF1", false);
PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "SHA512withRSAandMGF1", false);
static {
RuntimeEnvironment.enableBouncyCastleIfPossible();
@ -94,12 +94,14 @@ public enum SignatureAlgorithm {
private final String value;
private final String description;
private final String familyName;
private final String jcaName;
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.description = description;
this.familyName = familyName;
this.jcaName = jcaName;
this.jdkStandard = jdkStandard;
}
@ -122,6 +124,78 @@ public enum SignatureAlgorithm {
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.
*

View File

@ -310,14 +310,18 @@ public class DefaultJwtBuilder implements JwtBuilder {
return new DefaultJwtSigner(alg, key);
}
public static String base64UrlEncode(Object o, String errMsg) {
protected String base64UrlEncode(Object o, String errMsg) {
String s;
try {
s = OBJECT_MAPPER.writeValueAsString(o);
s = toJson(o);
} catch (JsonProcessingException e) {
throw new IllegalStateException(errMsg, e);
}
return TextCodec.BASE64URL.encode(s);
}
protected String toJson(Object o) throws JsonProcessingException {
return OBJECT_MAPPER.writeValueAsString(o);
}
}

View File

@ -242,7 +242,7 @@ public class DefaultJwtParser implements JwtParser {
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() +
" may not be used to verify " + algName + " signatures. Because the specified " +
" may not be used to validate " + algName + " signatures. Because the specified " +
"signing key reflects a specific and expected algorithm, and the JWT does not reflect " +
"this algorithm, it is likely that the JWT was not expected and therefore should not be " +
"trusted. Another possibility is that the parser was configured with the incorrect " +

View File

@ -15,6 +15,8 @@
*/
package io.jsonwebtoken.impl;
import io.jsonwebtoken.lang.Assert;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
@ -30,9 +32,7 @@ public class JwtMap implements Map<String,Object> {
}
public JwtMap(Map<String, Object> map) {
if (map == null) {
throw new IllegalArgumentException("Map argument cannot be null.");
}
Assert.notNull(map, "Map argument cannot be null.");
this.map = map;
}

View File

@ -30,8 +30,6 @@ public class DefaultSignatureValidatorFactory implements SignatureValidatorFacto
Assert.notNull(key, "Signing Key cannot be null.");
switch (alg) {
case NONE:
throw new IllegalArgumentException("The 'NONE' algorithm cannot be used for signing.");
case HS256:
case HS384:
case HS512:
@ -46,11 +44,9 @@ public class DefaultSignatureValidatorFactory implements SignatureValidatorFacto
case ES256:
case ES384:
case ES512:
throw new UnsupportedOperationException("Elliptic Curve digests are not yet supported.");
return new EllipticCurveSignatureValidator(alg, key);
default:
String msg = "Unrecognized algorithm '" + alg.name() + "'. This is a bug. Please submit a ticket " +
"via the project issue tracker.";
throw new IllegalStateException(msg);
throw new IllegalArgumentException("The '" + alg.name() + "' algorithm cannot be used for signing.");
}
}
}

View File

@ -30,8 +30,6 @@ public class DefaultSignerFactory implements SignerFactory {
Assert.notNull(key, "Signing Key cannot be null.");
switch (alg) {
case NONE:
throw new IllegalArgumentException("The 'NONE' algorithm cannot be used for signing.");
case HS256:
case HS384:
case HS512:
@ -46,11 +44,9 @@ public class DefaultSignerFactory implements SignerFactory {
case ES256:
case ES384:
case ES512:
throw new UnsupportedOperationException("Elliptic Curve digests are not yet supported.");
return new EllipticCurveSigner(alg, key);
default:
String msg = "Unrecognized algorithm '" + alg.name() + "'. This is a bug. Please submit a ticket " +
"via the project issue tracker.";
throw new IllegalStateException(msg);
throw new IllegalArgumentException("The '" + alg.name() + "' algorithm cannot be used for signing.");
}
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.lang.Assert;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
public abstract class EllipticCurveProvider extends SignatureProvider {
private static final Map<SignatureAlgorithm, String> EC_CURVE_NAMES = createEcCurveNames();
private static Map<SignatureAlgorithm, String> createEcCurveNames() {
Map<SignatureAlgorithm, String> m = new HashMap<SignatureAlgorithm, String>(); //alg to ASN1 OID name
m.put(SignatureAlgorithm.ES256, "secp256r1");
m.put(SignatureAlgorithm.ES384, "secp384r1");
m.put(SignatureAlgorithm.ES512, "secp521r1");
return m;
}
protected EllipticCurveProvider(SignatureAlgorithm alg, Key key) {
super(alg, key);
Assert.isTrue(alg.isEllipticCurve(), "SignatureAlgorithm must be an Elliptic Curve algorithm.");
}
public static KeyPair generateKeyPair() {
return generateKeyPair(SignatureAlgorithm.ES512);
}
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 {
KeyPairGenerator g = KeyPairGenerator.getInstance(jcaAlgorithmName, jcaProviderName);
String paramSpecCurveName = EC_CURVE_NAMES.get(alg);
g.initialize(org.bouncycastle.jce.ECNamedCurveTable.getParameterSpec(paramSpecCurveName), random);
return g.generateKeyPair();
} catch (Exception e) {
throw new IllegalStateException("Unable to generate Elliptic Curve KeyPair: " + e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,62 @@
/*
* 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 io.jsonwebtoken.lang.Assert;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
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) {
super(alg, key);
Assert.isTrue(!(key instanceof ECPrivateKey), NO_EC_PRIVATE_KEY_MSG);
Assert.isTrue(key instanceof ECPublicKey, EC_PUBLIC_KEY_REQD_MSG);
}
@Override
public boolean isValid(byte[] data, byte[] signature) {
Signature sig = createSignatureInstance();
PublicKey publicKey = (PublicKey) key;
try {
return doVerify(sig, publicKey, data, signature);
} catch (Exception e) {
String msg = "Unable to verify Elliptic Curve signature using configured ECPublicKey. " + e.getMessage();
throw new SignatureException(msg, e);
}
}
protected boolean doVerify(Signature sig, PublicKey publicKey, byte[] data, byte[] signature)
throws InvalidKeyException, java.security.SignatureException {
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(signature);
}
}

View File

@ -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 java.security.InvalidKeyException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.interfaces.ECPrivateKey;
public class EllipticCurveSigner extends EllipticCurveProvider implements Signer {
public EllipticCurveSigner(SignatureAlgorithm alg, Key key) {
super(alg, key);
if (!(key instanceof ECPrivateKey)) {
String msg = "Elliptic Curve signatures must be computed using an ECPrivateKey. The specified key of " +
"type " + key.getClass().getName() + " is not an ECPrivateKey.";
throw new IllegalArgumentException(msg);
}
}
@Override
public byte[] sign(byte[] data) {
try {
return doSign(data);
} catch (InvalidKeyException e) {
throw new SignatureException("Invalid Elliptic Curve PrivateKey. " + e.getMessage(), e);
} catch (java.security.SignatureException e) {
throw new SignatureException("Unable to calculate signature using Elliptic Curve PrivateKey. " + e.getMessage(), e);
}
}
protected byte[] doSign(byte[] data) throws InvalidKeyException, java.security.SignatureException {
PrivateKey privateKey = (PrivateKey)key;
Signature sig = createSignatureInstance();
sig.initSign(privateKey);
sig.update(data);
return sig.sign();
}
}

View File

@ -18,12 +18,46 @@ package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.lang.Assert;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
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) {
super(alg, key);
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());
}
}

View File

@ -21,12 +21,38 @@ import io.jsonwebtoken.lang.Assert;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.spec.MGF1ParameterSpec;
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) {
super(alg, key);
@ -35,58 +61,51 @@ abstract class RsaProvider extends SignatureProvider {
protected Signature createSignatureInstance() {
Signature sig = newSignatureInstance();
Signature sig = super.createSignatureInstance();
if (alg.name().startsWith("PS")) {
MGF1ParameterSpec paramSpec;
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);
PSSParameterSpec spec = PSS_PARAMETER_SPECS.get(alg);
if (spec != null) {
setParameter(sig, spec);
}
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) {
try {
sig.setParameter(spec);
doSetParameter(sig, spec);
} catch (InvalidAlgorithmParameterException e) {
String msg = "Unsupported RSASSA-PSS parameter '" + spec + "': " + e.getMessage();
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();
}
}

View File

@ -16,14 +16,27 @@
package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.RuntimeEnvironment;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Signature;
import java.util.Random;
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 Key key;
protected final Key key;
protected SignatureProvider(SignatureAlgorithm alg, Key key) {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
@ -31,4 +44,24 @@ abstract class SignatureProvider {
this.alg = alg;
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;
}
}

View File

@ -25,6 +25,8 @@ public class RuntimeEnvironment {
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() {
if (bcLoaded.get()) {

View File

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

View File

@ -19,21 +19,27 @@ import com.fasterxml.jackson.databind.ObjectMapper
import io.jsonwebtoken.impl.DefaultHeader
import io.jsonwebtoken.impl.DefaultJwsHeader
import io.jsonwebtoken.impl.TextCodec
import io.jsonwebtoken.impl.crypto.EllipticCurveProvider
import io.jsonwebtoken.impl.crypto.MacProvider
import io.jsonwebtoken.impl.crypto.RsaProvider
import org.testng.annotations.Test
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.nio.charset.Charset
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.PrivateKey
import java.security.PublicKey
import java.security.SecureRandom
import static org.testng.Assert.*
class JwtsTest {
@Test
void testSubclass() {
new Jwts()
}
@Test
void testHeaderWithNoArgs() {
def header = Jwts.header()
@ -374,14 +380,37 @@ class JwtsTest {
testRsa(SignatureAlgorithm.RS512, 1024, true);
}
@Test
void testES256() {
testEC(SignatureAlgorithm.ES256)
}
@Test
void testES384() {
testEC(SignatureAlgorithm.ES384)
}
@Test
void testES512() {
testEC(SignatureAlgorithm.ES512)
}
@Test
void testES256WithPrivateKeyValidation() {
try {
testEC(SignatureAlgorithm.ES256, true)
fail("EC private keys cannot be used to validate EC signatures.")
} catch (UnsupportedJwtException e) {
assertEquals e.cause.message, "Elliptic Curve signature validation requires an ECPublicKey. ECPrivateKeys may not be used."
}
}
//Asserts correct/expected behavior discussed in https://github.com/jwtk/jjwt/issues/20
@Test
void testParseClaimsJwsWithUnsignedJwt() {
//create random signing key for testing:
Random random = new SecureRandom();
byte[] key = new byte[64];
random.nextBytes(key);
byte[] key = MacProvider.generateKey().getEncoded()
String notSigned = Jwts.builder().setSubject("Foo").compact();
@ -398,9 +427,7 @@ class JwtsTest {
void testForgedTokenWithSwappedHeaderUsingNoneAlgorithm() {
//create random signing key for testing:
Random random = new SecureRandom();
byte[] key = new byte[64];
random.nextBytes(key);
byte[] key = MacProvider.generateKey().getEncoded()
//this is a 'real', valid JWT:
String compact = Jwts.builder().setSubject("Joe").signWith(SignatureAlgorithm.HS256, key).compact();
@ -432,9 +459,7 @@ class JwtsTest {
void testParseForgedRsaPublicKeyAsHmacTokenVerifiedWithTheRsaPrivateKey() {
//Create a legitimate RSA public and private key pair:
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
keyGenerator.initialize(1024);
KeyPair kp = keyGenerator.genKeyPair();
KeyPair kp = RsaProvider.generateKeyPair(1024)
PublicKey publicKey = kp.getPublic();
PrivateKey privateKey = kp.getPrivate();
@ -467,11 +492,9 @@ class JwtsTest {
void testParseForgedRsaPublicKeyAsHmacTokenVerifiedWithTheRsaPublicKey() {
//Create a legitimate RSA public and private key pair:
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
keyGenerator.initialize(1024);
KeyPair kp = keyGenerator.genKeyPair();
KeyPair kp = RsaProvider.generateKeyPair(1024)
PublicKey publicKey = kp.getPublic();
PrivateKey privateKey = kp.getPrivate();
//PrivateKey privateKey = kp.getPrivate();
ObjectMapper om = new ObjectMapper()
String header = TextCodec.BASE64URL.encode(om.writeValueAsString(['alg': 'HS256']))
@ -497,16 +520,9 @@ class JwtsTest {
}
}
static void testRsa(SignatureAlgorithm alg) {
testRsa(alg, 1024, false);
}
static void testRsa(SignatureAlgorithm alg, int keySize=1024, boolean verifyWithPrivateKey=false) {
static void testRsa(SignatureAlgorithm alg, int keySize, boolean verifyWithPrivateKey) {
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
keyGenerator.initialize(keySize);
KeyPair kp = keyGenerator.genKeyPair();
KeyPair kp = RsaProvider.generateKeyPair(keySize)
PublicKey publicKey = kp.getPublic();
PrivateKey privateKey = kp.getPrivate();
@ -529,9 +545,7 @@ class JwtsTest {
static void testHmac(SignatureAlgorithm alg) {
//create random signing key for testing:
Random random = new SecureRandom();
byte[] key = new byte[64];
random.nextBytes(key);
byte[] key = MacProvider.generateKey().encoded
def claims = [iss: 'joe', exp: later(), 'http://example.com/is_root':true]
@ -543,5 +557,27 @@ class JwtsTest {
assertEquals token.body, claims
}
static void testEC(SignatureAlgorithm alg, boolean verifyWithPrivateKey=false) {
KeyPair pair = EllipticCurveProvider.generateKeyPair(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();
def key = publicKey;
if (verifyWithPrivateKey) {
key = privateKey;
}
def token = Jwts.parser().setSigningKey(key).parse(jwt);
assertEquals token.header, [alg: alg.name()]
assertEquals token.body, claims
}
}

View File

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

View File

@ -54,6 +54,15 @@ class SignatureAlgorithmTest {
}
}
@Test
void testHmacFamilyName() {
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {
if (alg.name().startsWith("HS")) {
assertEquals alg.getFamilyName(), "HMAC"
}
}
}
@Test
void testIsRsa() {
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
void testIsEllipticCurve() {
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
void testIsJdkStandard() {
for(SignatureAlgorithm alg : SignatureAlgorithm.values()) {

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,120 @@
/*
* 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)
}
@Test
void testValues() {
def m = new JwtMap()
m.putAll([a: 'b', c: 'd'])
assertEquals( m.values(), ['b', 'd'] as Set)
}
}

View File

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

View File

@ -24,19 +24,13 @@ import static org.testng.Assert.*
class DefaultSignerFactoryTest {
private static final Random rng = new Random(); //doesn't need to be secure - we're just testing
@Test
void testCreateSignerWithNoneAlgorithm() {
byte[] keyBytes = new byte[32];
rng.nextBytes(keyBytes);
SecretKeySpec key = new SecretKeySpec(keyBytes, "foo");
def factory = new DefaultSignerFactory();
try {
factory.createSigner(SignatureAlgorithm.NONE, key);
factory.createSigner(SignatureAlgorithm.NONE, MacProvider.generateKey());
fail();
} catch (IllegalArgumentException iae) {
assertEquals iae.message, "The 'NONE' algorithm cannot be used for signing."

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,7 +28,6 @@ import java.security.PublicKey
import static org.testng.Assert.*
class RsaSignerTest {
private static final Random rng = new Random(); //doesn't need to be secure - we're just testing

View File

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