mirror of https://github.com/jwtk/jjwt.git
#27: initial implementation of Elliptic Curve signatures with tests. Still have a bit more code coverage to add before I'm happy.
This commit is contained in:
parent
1bda828a0d
commit
8d86973b4b
2
pom.xml
2
pom.xml
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 " +
|
||||
|
|
|
@ -46,7 +46,7 @@ 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.";
|
||||
|
|
|
@ -46,7 +46,7 @@ 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.";
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.Key;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Signature;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
abstract class EllipticCurveProvider extends SignatureProvider {
|
||||
|
||||
private static final Map<SignatureAlgorithm, String> EC_SIG_ALG_NAMES = createEcSigAlgNames();
|
||||
|
||||
private static Map<SignatureAlgorithm, String> createEcSigAlgNames() {
|
||||
Map<SignatureAlgorithm, String> m =
|
||||
new HashMap<SignatureAlgorithm, String>(); //EC alg name to EC alg signature name
|
||||
m.put(SignatureAlgorithm.ES256, "SHA256withECDSA");
|
||||
m.put(SignatureAlgorithm.ES384, "SHA384withECDSA");
|
||||
m.put(SignatureAlgorithm.ES512, "SHA512withECDSA");
|
||||
return m;
|
||||
}
|
||||
|
||||
protected EllipticCurveProvider(SignatureAlgorithm alg, Key key) {
|
||||
super(alg, key);
|
||||
Assert.isTrue(alg.isEllipticCurve(), "SignatureAlgorithm must be an Elliptic Curve algorithm.");
|
||||
}
|
||||
|
||||
protected Signature createSignatureInstance() {
|
||||
return newSignatureInstance();
|
||||
}
|
||||
|
||||
protected Signature newSignatureInstance() {
|
||||
try {
|
||||
String sigAlgName = EC_SIG_ALG_NAMES.get(alg);
|
||||
if (sigAlgName == null) {
|
||||
throw new NoSuchAlgorithmException("No EllipticCurve signature algorithm for algorithm " + alg +
|
||||
". This is a bug. Please report this to the project issue tracker.");
|
||||
}
|
||||
return Signature.getInstance(sigAlgName);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 {
|
||||
|
||||
public EllipticCurveSignatureValidator(SignatureAlgorithm alg, Key key) {
|
||||
super(alg, key);
|
||||
Assert.isTrue(!(key instanceof ECPrivateKey),
|
||||
"Elliptic Curve signature validation requires an ECPublicKey. ECPrivateKeys may not be used.");
|
||||
Assert.isTrue(key instanceof ECPublicKey,
|
||||
"Elliptic Curve Signature validation requires either an ECPublicKey instance.");
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
|||
import io.jsonwebtoken.impl.DefaultHeader
|
||||
import io.jsonwebtoken.impl.DefaultJwsHeader
|
||||
import io.jsonwebtoken.impl.TextCodec
|
||||
import org.bouncycastle.jce.ECNamedCurveTable
|
||||
import org.testng.annotations.Test
|
||||
|
||||
import javax.crypto.Mac
|
||||
|
@ -34,6 +35,8 @@ import static org.testng.Assert.*
|
|||
|
||||
class JwtsTest {
|
||||
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
@Test
|
||||
void testHeaderWithNoArgs() {
|
||||
def header = Jwts.header()
|
||||
|
@ -374,14 +377,38 @@ 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);
|
||||
RANDOM.nextBytes(key);
|
||||
|
||||
String notSigned = Jwts.builder().setSubject("Foo").compact();
|
||||
|
||||
|
@ -398,9 +425,8 @@ class JwtsTest {
|
|||
void testForgedTokenWithSwappedHeaderUsingNoneAlgorithm() {
|
||||
|
||||
//create random signing key for testing:
|
||||
Random random = new SecureRandom();
|
||||
byte[] key = new byte[64];
|
||||
random.nextBytes(key);
|
||||
RANDOM.nextBytes(key);
|
||||
|
||||
//this is a 'real', valid JWT:
|
||||
String compact = Jwts.builder().setSubject("Joe").signWith(SignatureAlgorithm.HS256, key).compact();
|
||||
|
@ -497,11 +523,7 @@ class JwtsTest {
|
|||
}
|
||||
}
|
||||
|
||||
static void testRsa(SignatureAlgorithm alg) {
|
||||
testRsa(alg, 1024, false);
|
||||
}
|
||||
|
||||
static void testRsa(SignatureAlgorithm alg, int keySize, boolean verifyWithPrivateKey) {
|
||||
static void testRsa(SignatureAlgorithm alg, int keySize=1024, boolean verifyWithPrivateKey=false) {
|
||||
|
||||
KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
keyGenerator.initialize(keySize);
|
||||
|
@ -529,9 +551,8 @@ 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);
|
||||
RANDOM.nextBytes(key);
|
||||
|
||||
def claims = [iss: 'joe', exp: later(), 'http://example.com/is_root':true]
|
||||
|
||||
|
@ -543,5 +564,28 @@ class JwtsTest {
|
|||
|
||||
assertEquals token.body, claims
|
||||
}
|
||||
|
||||
static void testEC(SignatureAlgorithm alg, boolean verifyWithPrivateKey=false) {
|
||||
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC");
|
||||
g.initialize(ECNamedCurveTable.getParameterSpec(alg.getJcaName()), RANDOM);
|
||||
KeyPair pair = g.generateKeyPair();
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue