Move EC curve utility functions (#803)

- Changed EC point multiplication montgomery ladder iteration to be a little faster (still has fixed number of operations of course)
- Moved Weierstrass calc/utility methods to ECCurve instead of AbstractEcJwkFactory
- Removed unnecessary Curves.java since StandardCurves is the preferred implementation
- Renamed CurvesTest to StandardCurvesTest
This commit is contained in:
lhazlewood 2023-08-27 16:56:15 -07:00 committed by GitHub
parent 095f446c37
commit d6dac16042
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 616 additions and 481 deletions

View File

@ -15,54 +15,26 @@
*/
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.impl.lang.Converters;
import io.jsonwebtoken.impl.lang.Field;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Curve;
import io.jsonwebtoken.security.Jwk;
import io.jsonwebtoken.security.UnsupportedKeyException;
import java.math.BigInteger;
import java.security.Key;
import java.security.KeyFactory;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidKeySpecException;
import java.util.Set;
abstract class AbstractEcJwkFactory<K extends Key & ECKey, J extends Jwk<K>> extends AbstractFamilyJwkFactory<K, J> {
private static final BigInteger TWO = BigInteger.valueOf(2);
private static final BigInteger THREE = BigInteger.valueOf(3);
private static final String UNSUPPORTED_CURVE_MSG = "The specified ECKey curve does not match a JWA standard curve id.";
protected static ECParameterSpec getCurveByJwaId(String jwaCurveId) {
ECParameterSpec spec = null;
Curve curve = Curves.findById(jwaCurveId);
if (curve instanceof ECCurve) {
ECCurve ecCurve = (ECCurve) curve;
spec = ecCurve.toParameterSpec();
}
if (spec == null) {
String msg = "Unrecognized JWA curve id '" + jwaCurveId + "'";
protected static ECCurve getCurveByJwaId(String jwaCurveId) {
ECCurve curve = ECCurve.findById(jwaCurveId);
if (curve == null) {
String msg = "Unrecognized JWA EC curve id '" + jwaCurveId + "'";
throw new UnsupportedKeyException(msg);
}
return spec;
}
protected static String getJwaIdByCurve(EllipticCurve curve) {
ECCurve c = Curves.findBy(curve);
if (c == null) {
throw new UnsupportedKeyException(UNSUPPORTED_CURVE_MSG);
}
return c.getId();
return curve;
}
/**
@ -85,146 +57,7 @@ abstract class AbstractEcJwkFactory<K extends Key & ECKey, J extends Jwk<K>> ext
return Encoders.BASE64URL.encode(bytes);
}
/**
* Returns {@code true} if a given elliptic {@code curve} contains the specified {@code point}, {@code false}
* otherwise. Assumes elliptic curves over finite fields adhering to the reduced (a.k.a short or narrow)
* Weierstrass form:
* <p>
* <code>y<sup>2</sup> = x<sup>3</sup> + ax + b</code>
* </p>
*
* @param curve the Elliptic Curve to check
* @param point a point that may or may not be defined on the specified elliptic curve
* @return {@code true} if a given elliptic curve contains the specified {@code point}, {@code false} otherwise.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
static boolean contains(EllipticCurve curve, ECPoint point) {
if (ECPoint.POINT_INFINITY.equals(point)) {
return false;
}
final BigInteger a = curve.getA();
final BigInteger b = curve.getB();
final BigInteger x = point.getAffineX();
final BigInteger y = point.getAffineY();
// The reduced Weierstrass form y^2 = x^3 + ax + b reflects an elliptic curve E over any field K (e.g. all real
// numbers or all complex numbers, etc). For computational simplicity, cryptographic (e.g. NIST) elliptic curves
// restrict K to be a field of integers modulo a prime number 'p'. As such, we apply modulo p (the field prime)
// to the equation to account for the restricted field. For a nice overview of the math behind EC curves and
// their application in cryptography, see
// https://web.northeastern.edu/dummit/docs/cryptography_5_elliptic_curves_in_cryptography.pdf
final BigInteger p = ((ECFieldFp) curve.getField()).getP();
// Verify the point coordinates are in field range:
if (x.compareTo(BigInteger.ZERO) < 0 || x.compareTo(p) >= 0 ||
y.compareTo(BigInteger.ZERO) < 0 || y.compareTo(p) >= 0) {
return false;
}
// Finally, assert Weierstrass form equality:
final BigInteger lhs = y.modPow(TWO, p); //mod p to account for field prime
final BigInteger rhs = x.modPow(THREE, p).add(a.multiply(x)).add(b).mod(p); //mod p to account for field prime
return lhs.equals(rhs);
}
/**
* Multiply a point {@code p} by scalar {@code s} on the curve identified by {@code spec}.
*
* @param p the Elliptic Curve point to multiply
* @param s the scalar value to multiply
* @param spec the domain parameters that identify the Elliptic Curve containing point {@code p}.
*/
private static ECPoint multiply(ECPoint p, BigInteger s, ECParameterSpec spec) {
if (ECPoint.POINT_INFINITY.equals(p)) {
return p;
}
EllipticCurve curve = spec.getCurve();
BigInteger n = spec.getOrder();
BigInteger k = s.mod(n);
ECPoint r0 = ECPoint.POINT_INFINITY;
ECPoint r1 = p;
// Montgomery Ladder implementation to mitigate side-channel attacks (i.e. an 'add' operation and a 'double'
// operation is calculated for every loop iteration, regardless if the 'add'' is needed or not)
// See: https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
while (k.compareTo(BigInteger.ZERO) > 0) {
ECPoint temp = add(r0, r1, curve);
r0 = k.testBit(0) ? temp : r0;
r1 = doublePoint(r1, curve);
k = k.shiftRight(1);
}
return r0;
}
private static ECPoint add(ECPoint P, ECPoint Q, EllipticCurve curve) {
if (ECPoint.POINT_INFINITY.equals(P)) {
return Q;
} else if (ECPoint.POINT_INFINITY.equals(Q)) {
return P;
} else if (P.equals(Q)) {
return doublePoint(P, curve);
}
final BigInteger Px = P.getAffineX();
final BigInteger Py = P.getAffineY();
final BigInteger Qx = Q.getAffineX();
final BigInteger Qy = Q.getAffineY();
final BigInteger prime = ((ECFieldFp) curve.getField()).getP();
final BigInteger slope = Qy.subtract(Py).multiply(Qx.subtract(Px).modInverse(prime)).mod(prime);
final BigInteger Rx = slope.pow(2).subtract(Px).subtract(Qx).mod(prime);
final BigInteger Ry = slope.multiply(Px.subtract(Rx)).subtract(Py).mod(prime);
return new ECPoint(Rx, Ry);
}
private static ECPoint doublePoint(ECPoint P, EllipticCurve curve) {
if (ECPoint.POINT_INFINITY.equals(P)) {
return P;
}
final BigInteger Px = P.getAffineX();
final BigInteger Py = P.getAffineY();
final BigInteger p = ((ECFieldFp) curve.getField()).getP();
final BigInteger a = curve.getA();
final BigInteger s = THREE.multiply(Px.pow(2)).add(a).mod(p).multiply(TWO.multiply(Py).modInverse(p)).mod(p);
final BigInteger x = s.pow(2).subtract(TWO.multiply(Px)).mod(p);
final BigInteger y = s.multiply(Px.subtract(x)).subtract(Py).mod(p);
return new ECPoint(x, y);
}
AbstractEcJwkFactory(Class<K> keyType, Set<Field<?>> fields) {
super(DefaultEcPublicJwk.TYPE_VALUE, keyType, fields);
}
// visible for testing
protected ECPublicKey derivePublic(KeyFactory keyFactory, ECPublicKeySpec spec) throws InvalidKeySpecException {
return (ECPublicKey) keyFactory.generatePublic(spec);
}
protected ECPublicKey derivePublic(final JwkContext<ECPrivateKey> ctx) {
final ECPrivateKey key = ctx.getKey();
final ECParameterSpec params = key.getParams();
final ECPoint w = multiply(params.getGenerator(), key.getS(), params);
final ECPublicKeySpec spec = new ECPublicKeySpec(w, params);
return generateKey(ctx, ECPublicKey.class, new CheckedFunction<KeyFactory, ECPublicKey>() {
@Override
public ECPublicKey apply(KeyFactory kf) {
try {
return derivePublic(kf, spec);
} catch (Exception e) {
String msg = "Unable to derive ECPublicKey from ECPrivateKey: " + e.getMessage();
throw new UnsupportedKeyException(msg, e);
}
}
});
}
}

View File

@ -1,79 +0,0 @@
/*
* Copyright (C) 2022 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.security;
import io.jsonwebtoken.impl.lang.DefaultRegistry;
import io.jsonwebtoken.impl.lang.Function;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Registry;
import io.jsonwebtoken.security.Curve;
import java.security.spec.EllipticCurve;
import java.util.Collection;
import java.util.LinkedHashSet;
public final class Curves {
public static final Curve P_256 = new ECCurve("P-256", "secp256r1"); // JDK standard
public static final Curve P_384 = new ECCurve("P-384", "secp384r1"); // JDK standard
public static final Curve P_521 = new ECCurve("P-521", "secp521r1"); // JDK standard
private static final Collection<ECCurve> EC_CURVES = Collections.setOf((ECCurve) P_256, (ECCurve) P_384, (ECCurve) P_521);
static final Collection<Curve> VALUES = new LinkedHashSet<>();
static {
VALUES.addAll(EC_CURVES);
VALUES.addAll(EdwardsCurve.VALUES);
}
private static final Registry<String, Curve> CURVES_BY_ID = new IdRegistry<>("Elliptic Curve", VALUES);
private static final Registry<String, Curve> CURVES_BY_JCA_NAME = new DefaultRegistry<>(
"Elliptic Curve", "JCA name", VALUES, new Function<Curve, String>() {
@Override
public String apply(Curve curve) {
return ((DefaultCurve) curve).getJcaName();
}
});
private static final Registry<EllipticCurve, ECCurve> CURVES_BY_JCA_CURVE = new DefaultRegistry<>(
"Elliptic Curve", "ECCurve instance", EC_CURVES, new Function<ECCurve, EllipticCurve>() {
@Override
public EllipticCurve apply(ECCurve curve) {
return curve.toParameterSpec().getCurve();
}
});
//prevent instantiation
private Curves() {
}
public static Curve findById(String jwaId) {
Assert.hasText(jwaId, "jwaId cannot be null or empty.");
return CURVES_BY_ID.get(jwaId);
}
public static Curve findByJcaName(String jcaName) {
Assert.hasText(jcaName, "jcaName cannot be null or empty.");
return CURVES_BY_JCA_NAME.get(jcaName);
}
public static ECCurve findBy(EllipticCurve curve) {
Assert.notNull(curve, "EllipticCurve argument cannot be null.");
return CURVES_BY_JCA_CURVE.get(curve);
}
}

View File

@ -16,17 +16,104 @@
package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.KeyPairBuilder;
import io.jsonwebtoken.security.UnsupportedKeyException;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECFieldFp;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
public class ECCurve extends DefaultCurve {
private static final BigInteger TWO = BigInteger.valueOf(2);
private static final BigInteger THREE = BigInteger.valueOf(3);
static final String KEY_PAIR_GENERATOR_JCA_NAME = "EC";
public static final ECCurve P256 = new ECCurve("P-256", "secp256r1"); // JDK standard
public static final ECCurve P384 = new ECCurve("P-384", "secp384r1"); // JDK standard
public static final ECCurve P521 = new ECCurve("P-521", "secp521r1"); // JDK standard
public static final Collection<ECCurve> VALUES = Collections.setOf(P256, P384, P521);
private static final Map<String, ECCurve> BY_ID = new LinkedHashMap<>(3);
private static final Map<EllipticCurve, ECCurve> BY_JCA_CURVE = new LinkedHashMap<>(3);
static {
for (ECCurve curve : VALUES) {
BY_ID.put(curve.getId(), curve);
}
for (ECCurve curve : VALUES) {
BY_JCA_CURVE.put(curve.spec.getCurve(), curve);
}
}
static EllipticCurve assertJcaCurve(ECKey key) {
Assert.notNull(key, "ECKey cannot be null.");
ECParameterSpec spec = Assert.notNull(key.getParams(), "ECKey params() cannot be null.");
return Assert.notNull(spec.getCurve(), "ECKey params().getCurve() cannot be null.");
}
static ECCurve findById(String id) {
return BY_ID.get(id);
}
static ECCurve findByJcaCurve(EllipticCurve curve) {
return BY_JCA_CURVE.get(curve);
}
static ECCurve findByKey(Key key) {
if (!(key instanceof ECKey)) {
return null;
}
ECKey ecKey = (ECKey) key;
ECParameterSpec spec = ecKey.getParams();
if (spec == null) {
return null;
}
EllipticCurve jcaCurve = spec.getCurve();
ECCurve curve = BY_JCA_CURVE.get(jcaCurve);
if (curve != null && key instanceof ECPublicKey) {
ECPublicKey pub = (ECPublicKey) key;
ECPoint w = pub.getW();
if (w == null || !curve.contains(w)) { // don't support keys with a point not on its indicated curve
curve = null;
}
}
return curve;
}
static ECCurve forKey(Key key) {
ECCurve curve = findByKey(key);
if (curve == null) {
String msg = "Unable to determine JWA-standard Elliptic Curve for specified key: " +
KeysBridge.toString(key);
throw new UnsupportedKeyException(msg);
}
return curve;
}
static ECPublicKeySpec publicKeySpec(ECPrivateKey key) throws IllegalArgumentException {
EllipticCurve jcaCurve = assertJcaCurve(key);
ECCurve curve = BY_JCA_CURVE.get(jcaCurve);
Assert.notNull(curve, "There is no JWA-standard Elliptic Curve for specified ECPrivateKey.");
final ECPoint w = curve.multiply(key.getS());
return new ECPublicKeySpec(w, curve.spec);
}
private final ECParameterSpec spec;
public ECCurve(String id, String jcaName) {
@ -45,23 +132,161 @@ public class ECCurve extends DefaultCurve {
return this.spec;
}
@Override
public KeyPairBuilder keyPair() {
return new DefaultKeyPairBuilder(KEY_PAIR_GENERATOR_JCA_NAME, toParameterSpec());
}
boolean contains(Key key) {
if (key instanceof ECPublicKey) {
ECPublicKey pub = (ECPublicKey) key;
ECParameterSpec pubSpec = pub.getParams();
return pubSpec != null &&
this.spec.getCurve().equals(pubSpec.getCurve()) &&
contains(pub.getW());
}
return false;
}
boolean contains(ECPoint point) {
return contains(this.spec.getCurve(), point);
}
/**
* Returns {@code true} if this elliptic curve contains the specified {@code point}, {@code false}
* otherwise. Assumes elliptic curves over finite fields adhering to the reduced (a.k.a short or narrow)
* Returns {@code true} if the specified curve contains the specified {@code point}, {@code false} otherwise.
* Assumes elliptic curves over finite fields adhering to the reduced (a.k.a short or narrow)
* Weierstrass form:
* <p>
* <code>y<sup>2</sup> = x<sup>3</sup> + ax + b</code>
* </p>
*
* @param curve the EllipticCurve to check
* @param point a point that may or may not be defined on this elliptic curve
* @return {@code true} if this elliptic curve contains the specified {@code point}, {@code false} otherwise.
* @return {@code true} if this curve contains the specified {@code point}, {@code false} otherwise.
*/
public boolean contains(ECPoint point) {
return AbstractEcJwkFactory.contains(spec.getCurve(), point);
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
static boolean contains(EllipticCurve curve, ECPoint point) {
if (point == null || ECPoint.POINT_INFINITY.equals(point)) {
return false;
}
final BigInteger a = curve.getA();
final BigInteger b = curve.getB();
final BigInteger x = point.getAffineX();
final BigInteger y = point.getAffineY();
// The reduced Weierstrass form y^2 = x^3 + ax + b reflects an elliptic curve E over any field K (e.g. all real
// numbers or all complex numbers, etc). For computational simplicity, cryptographic (e.g. NIST) elliptic curves
// restrict K to be a field of integers modulo a prime number 'p'. As such, we apply modulo p (the field prime)
// to the equation to account for the restricted field. For a nice overview of the math behind EC curves and
// their application in cryptography, see
// https://web.northeastern.edu/dummit/docs/cryptography_5_elliptic_curves_in_cryptography.pdf
final BigInteger p = ((ECFieldFp) curve.getField()).getP();
// Verify the point coordinates are in field range:
if (x.compareTo(BigInteger.ZERO) < 0 || x.compareTo(p) >= 0 ||
y.compareTo(BigInteger.ZERO) < 0 || y.compareTo(p) >= 0) {
return false;
}
// Finally, assert Weierstrass form equality:
final BigInteger lhs = y.modPow(TWO, p); //mod p to account for field prime
final BigInteger rhs = x.modPow(THREE, p).add(a.multiply(x)).add(b).mod(p); //mod p to account for field prime
return lhs.equals(rhs);
}
@Override
public KeyPairBuilder keyPair() {
return new DefaultKeyPairBuilder(KEY_PAIR_GENERATOR_JCA_NAME, toParameterSpec());
/**
* Multiply this curve's generator (aka 'base point') by scalar {@code s} on the curve.
*
* @param s the scalar value to multiply
*/
private ECPoint multiply(BigInteger s) {
return multiply(this.spec.getGenerator(), s);
}
/**
* Multiply a point {@code p} by scalar {@code s} on the curve.
*
* @param p the Elliptic Curve point to multiply
* @param s the scalar value to multiply
*/
private ECPoint multiply(ECPoint p, BigInteger s) {
if (ECPoint.POINT_INFINITY.equals(p)) {
return p;
}
final BigInteger n = this.spec.getOrder();
final BigInteger k = s.mod(n);
ECPoint r0 = ECPoint.POINT_INFINITY;
ECPoint r1 = p;
// Montgomery Ladder implementation to mitigate side-channel attacks (i.e. an 'add' operation and a 'double'
// operation is calculated for every loop iteration, regardless if the 'add'' is needed or not)
// See: https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
// while (k.compareTo(BigInteger.ZERO) > 0) {
// ECPoint temp = add(r0, r1, curve);
// r0 = k.testBit(0) ? temp : r0;
// r1 = doublePoint(r1, curve);
// k = k.shiftRight(1);
// }
// above implementation (k.compareTo/k.shiftRight) works correctly , but this is a little faster:
for (int i = k.bitLength() - 1; i >= 0; i--) {
if (k.testBit(i)) { // bit == 1
r0 = add(r0, r1);
r1 = doublePoint(r1);
} else { // bit == 0
r1 = add(r0, r1);
r0 = doublePoint(r0);
}
}
return r0;
}
private ECPoint add(ECPoint P, ECPoint Q) {
if (ECPoint.POINT_INFINITY.equals(P)) {
return Q;
} else if (ECPoint.POINT_INFINITY.equals(Q)) {
return P;
} else if (P.equals(Q)) {
return doublePoint(P);
}
final EllipticCurve curve = this.spec.getCurve();
final BigInteger Px = P.getAffineX();
final BigInteger Py = P.getAffineY();
final BigInteger Qx = Q.getAffineX();
final BigInteger Qy = Q.getAffineY();
final BigInteger prime = ((ECFieldFp) curve.getField()).getP();
final BigInteger slope = Qy.subtract(Py).multiply(Qx.subtract(Px).modInverse(prime)).mod(prime);
final BigInteger Rx = slope.pow(2).subtract(Px).subtract(Qx).mod(prime);
final BigInteger Ry = slope.multiply(Px.subtract(Rx)).subtract(Py).mod(prime);
return new ECPoint(Rx, Ry);
}
private ECPoint doublePoint(ECPoint P) {
if (ECPoint.POINT_INFINITY.equals(P)) {
return P;
}
final EllipticCurve curve = this.spec.getCurve();
final BigInteger Px = P.getAffineX();
final BigInteger Py = P.getAffineY();
final BigInteger p = ((ECFieldFp) curve.getField()).getP();
final BigInteger a = curve.getA();
final BigInteger s = THREE.multiply(Px.pow(2)).add(a).mod(p).multiply(TWO.multiply(Py).modInverse(p)).mod(p);
final BigInteger x = s.pow(2).subtract(TWO.multiply(Px)).mod(p);
final BigInteger y = s.multiply(Px.subtract(x)).subtract(Py).mod(p);
return new ECPoint(x, y);
}
}

View File

@ -22,14 +22,16 @@ import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.EcPrivateJwk;
import io.jsonwebtoken.security.EcPublicJwk;
import io.jsonwebtoken.security.UnsupportedKeyException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
class EcPrivateJwkFactory extends AbstractEcJwkFactory<ECPrivateKey, EcPrivateJwk> {
@ -46,6 +48,27 @@ class EcPrivateJwkFactory extends AbstractEcJwkFactory<ECPrivateKey, EcPrivateJw
return super.supportsKeyValues(ctx) && ctx.containsKey(DefaultEcPrivateJwk.D.getId());
}
// visible for testing
protected ECPublicKey derivePublic(KeyFactory keyFactory, ECPublicKeySpec spec) throws InvalidKeySpecException {
return (ECPublicKey) keyFactory.generatePublic(spec);
}
protected ECPublicKey derivePublic(final JwkContext<ECPrivateKey> ctx) {
final ECPrivateKey key = ctx.getKey();
return generateKey(ctx, ECPublicKey.class, new CheckedFunction<KeyFactory, ECPublicKey>() {
@Override
public ECPublicKey apply(KeyFactory kf) {
try {
ECPublicKeySpec spec = ECCurve.publicKeySpec(key);
return derivePublic(kf, spec);
} catch (Exception e) {
String msg = "Unable to derive ECPublicKey from ECPrivateKey: " + e.getMessage();
throw new UnsupportedKeyException(msg, e);
}
}
});
}
@Override
protected EcPrivateJwk createJwkFromKey(JwkContext<ECPrivateKey> ctx) {
@ -93,9 +116,8 @@ class EcPrivateJwkFactory extends AbstractEcJwkFactory<ECPrivateKey, EcPrivateJw
JwkContext<ECPublicKey> pubCtx = new DefaultJwkContext<>(DefaultEcPublicJwk.FIELDS, ctx);
EcPublicJwk pubJwk = EcPublicJwkFactory.INSTANCE.createJwk(pubCtx);
ECParameterSpec spec = getCurveByJwaId(curveId);
final ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(d, spec);
ECCurve curve = getCurveByJwaId(curveId);
final ECPrivateKeySpec privateSpec = new ECPrivateKeySpec(d, curve.toParameterSpec());
ECPrivateKey key = generateKey(ctx, new CheckedFunction<KeyFactory, ECPrivateKey>() {
@Override
public ECPrivateKey apply(KeyFactory kf) throws Exception {

View File

@ -21,6 +21,7 @@ import io.jsonwebtoken.impl.lang.RequiredFieldReader;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.EcPublicJwk;
import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.UnsupportedKeyException;
import java.math.BigInteger;
import java.security.KeyFactory;
@ -33,6 +34,8 @@ import java.util.Map;
class EcPublicJwkFactory extends AbstractEcJwkFactory<ECPublicKey, EcPublicJwk> {
private static final String UNSUPPORTED_CURVE_MSG = "The specified ECKey curve does not match a JWA standard curve id.";
static final EcPublicJwkFactory INSTANCE = new EcPublicJwkFactory();
EcPublicJwkFactory() {
@ -54,6 +57,14 @@ class EcPublicJwkFactory extends AbstractEcJwkFactory<ECPublicKey, EcPublicJwk>
return String.format(fmt, curveId, jwk);
}
protected static String getJwaIdByCurve(EllipticCurve curve) {
ECCurve c = ECCurve.findByJcaCurve(curve);
if (c == null) {
throw new UnsupportedKeyException(UNSUPPORTED_CURVE_MSG);
}
return c.getId();
}
@Override
protected EcPublicJwk createJwkFromKey(JwkContext<ECPublicKey> ctx) {
@ -64,7 +75,7 @@ class EcPublicJwkFactory extends AbstractEcJwkFactory<ECPublicKey, EcPublicJwk>
ECPoint point = key.getW();
String curveId = getJwaIdByCurve(curve);
if (!contains(curve, point)) {
if (!ECCurve.contains(curve, point)) {
String msg = keyContainsErrorMessage(curveId);
throw new InvalidKeyException(msg);
}
@ -89,16 +100,15 @@ class EcPublicJwkFactory extends AbstractEcJwkFactory<ECPublicKey, EcPublicJwk>
BigInteger x = reader.get(DefaultEcPublicJwk.X);
BigInteger y = reader.get(DefaultEcPublicJwk.Y);
ECParameterSpec spec = getCurveByJwaId(curveId);
ECCurve curve = getCurveByJwaId(curveId);
ECPoint point = new ECPoint(x, y);
if (!contains(spec.getCurve(), point)) {
if (!curve.contains(point)) {
String msg = jwkContainsErrorMessage(curveId, ctx);
throw new InvalidKeyException(msg);
}
final ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, spec);
final ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, curve.toParameterSpec());
ECPublicKey key = generateKey(ctx, new CheckedFunction<KeyFactory, ECPublicKey>() {
@Override
public ECPublicKey apply(KeyFactory kf) throws Exception {

View File

@ -19,6 +19,7 @@ import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.impl.lang.Bytes;
import io.jsonwebtoken.impl.lang.CheckedFunction;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.InvalidKeyException;
import io.jsonwebtoken.security.KeyPairBuilder;
@ -44,8 +45,7 @@ final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
private static final String REQD_ORDER_BIT_LENGTH_MSG = "orderBitLength must equal 256, 384, or 521.";
private static final String DER_ENCODING_SYS_PROPERTY_NAME =
"io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator.derEncodingSupported";
private static final String DER_ENCODING_SYS_PROPERTY_NAME = "io.jsonwebtoken.impl.crypto.EllipticCurveSignatureValidator.derEncodingSupported";
private static final String ES256_OID = "1.2.840.10045.4.3.2";
private static final String ES384_OID = "1.2.840.10045.4.3.3";
@ -104,13 +104,12 @@ final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
static final EcSignatureAlgorithm ES384 = new EcSignatureAlgorithm(384, ES384_OID);
static final EcSignatureAlgorithm ES512 = new EcSignatureAlgorithm(521, ES512_OID);
private static final Map<String, SignatureAlgorithm> ALGS_BY_OID;
private static final Map<String, SignatureAlgorithm> BY_OID = new LinkedHashMap<>(3);
static {
ALGS_BY_OID = new LinkedHashMap<>(3);
ALGS_BY_OID.put(ES256_OID, ES256);
ALGS_BY_OID.put(ES384_OID, ES384);
ALGS_BY_OID.put(ES512_OID, ES512);
for (EcSignatureAlgorithm alg : Collections.of(ES256, ES384, ES512)) {
BY_OID.put(alg.OID, alg);
}
}
static SignatureAlgorithm findByKey(Key key) {
@ -121,7 +120,7 @@ final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
}
algName = algName.toUpperCase(Locale.ENGLISH);
SignatureAlgorithm alg = ALGS_BY_OID.get(algName);
SignatureAlgorithm alg = BY_OID.get(algName);
if (alg != null) {
return alg;
}
@ -154,8 +153,7 @@ final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
@Override
public KeyPairBuilder keyPair() {
return new DefaultKeyPairBuilder(ECCurve.KEY_PAIR_GENERATOR_JCA_NAME, this.KEY_PAIR_GEN_PARAMS)
.random(Randoms.secureRandom());
return new DefaultKeyPairBuilder(ECCurve.KEY_PAIR_GENERATOR_JCA_NAME, this.KEY_PAIR_GEN_PARAMS).random(Randoms.secureRandom());
}
@Override
@ -172,10 +170,7 @@ final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
int concatByteLength = sigFieldByteLength * 2;
if (concatByteLength != this.signatureByteLength) {
String msg = "The provided Elliptic Curve " + keyType(signing) + " key's size (aka Order bit length) is " +
Bytes.bitsMsg(orderBitLength) + ", but the '" + name + "' algorithm requires EC Keys with " +
Bytes.bitsMsg(this.orderBitLength) + " per " +
"[RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4).";
String msg = "The provided Elliptic Curve " + keyType(signing) + " key's size (aka Order bit length) is " + Bytes.bitsMsg(orderBitLength) + ", but the '" + name + "' algorithm requires EC Keys with " + Bytes.bitsMsg(this.orderBitLength) + " per " + "[RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4).";
throw new InvalidKeyException(msg);
}
}
@ -229,9 +224,7 @@ final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
if (concatSignature[0] == 0x30 && "true".equalsIgnoreCase(System.getProperty(DER_ENCODING_SYS_PROPERTY_NAME))) {
derSignature = concatSignature;
} else {
String msg = "Provided signature is " + Bytes.bytesMsg(concatSignature.length) + " but " +
getId() + " signatures must be exactly " + Bytes.bytesMsg(signatureByteLength) + " per " +
"[RFC 7518, Section 3.4 (validation)](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4).";
String msg = "Provided signature is " + Bytes.bytesMsg(concatSignature.length) + " but " + getId() + " signatures must be exactly " + Bytes.bytesMsg(signatureByteLength) + " per " + "[RFC 7518, Section 3.4 (validation)](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4).";
throw new SignatureException(msg);
}
} else {
@ -300,10 +293,7 @@ final class EcSignatureAlgorithm extends AbstractSignatureAlgorithm {
int rawLen = Math.max(i, j);
rawLen = Math.max(rawLen, outputLength / 2);
if ((derSignature[offset - 1] & 0xff) != derSignature.length - offset
|| (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength
|| derSignature[offset] != 2
|| derSignature[offset + 2 + rLength] != 2) {
if ((derSignature[offset - 1] & 0xff) != derSignature.length - offset || (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength || derSignature[offset] != 2 || derSignature[offset + 2 + rLength] != 2) {
throw new JwtException("Invalid ECDSA signature format");
}

View File

@ -24,6 +24,7 @@ import io.jsonwebtoken.impl.lang.RequiredFieldReader;
import io.jsonwebtoken.lang.Arrays;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.security.AeadAlgorithm;
import io.jsonwebtoken.security.Curve;
import io.jsonwebtoken.security.DecryptionKeyRequest;
import io.jsonwebtoken.security.DynamicJwkBuilder;
import io.jsonwebtoken.security.EcPublicJwk;
@ -50,9 +51,6 @@ import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
/**
* @since JJWT_RELEASE_VERSION
@ -70,9 +68,9 @@ class EcdhKeyAlgorithm extends CryptoAlgorithm implements KeyAlgorithm<PublicKey
private static final String CONCAT_KDF_HASH_ALG_NAME = "SHA-256";
private static final ConcatKDF CONCAT_KDF = new ConcatKDF(CONCAT_KDF_HASH_ALG_NAME);
public static final String KEK_TYPE_MESSAGE = "Key Encryption Key must be a " + ECKey.class.getName() +
" or valid Edwards Curve PublicKey instance.";
" or Edwards Curve PublicKey on a supported curve.";
public static final String KDK_TYPE_MESSAGE = "Key Decryption Key must be a " + ECKey.class.getName() +
" or valid Edwards Curve PrivateKey instance.";
" or Edwards Curve PrivateKey on a supported curve.";
private final KeyAlgorithm<SecretKey, SecretKey> WRAP_ALG;
@ -91,16 +89,8 @@ class EcdhKeyAlgorithm extends CryptoAlgorithm implements KeyAlgorithm<PublicKey
this.WRAP_ALG = Assert.notNull(wrapAlg, "Wrap algorithm cannot be null.");
}
//visible for testing, for non-Edwards elliptic curves
protected KeyPair generateKeyPair(final Request<?> request, final ECParameterSpec spec) {
Assert.notNull(spec, "request key params cannot be null.");
JcaTemplate template = new JcaTemplate(ECCurve.KEY_PAIR_GENERATOR_JCA_NAME, request.getProvider(),
ensureSecureRandom(request));
return template.generateKeyPair(spec);
}
//visible for testing, for Edwards elliptic curves
protected KeyPair generateKeyPair(SecureRandom random, EdwardsCurve curve, Provider provider) {
protected KeyPair generateKeyPair(Curve curve, Provider provider, SecureRandom random) {
return curve.keyPair().provider(provider).random(random).build();
}
@ -170,8 +160,9 @@ class EcdhKeyAlgorithm extends CryptoAlgorithm implements KeyAlgorithm<PublicKey
EdwardsCurve curve;
try {
curve = EdwardsCurve.forKey(key);
} catch (Exception e) {
throw new UnsupportedKeyException(exMsg + " Cause: " + e.getMessage(), e);
} catch (Throwable t) {
String msg = exMsg + " Cause: " + t.getMessage();
throw new UnsupportedKeyException(msg, t);
}
Assert.stateNotNull(curve, "EdwardsCurve instance cannot be null.");
if (curve.isSignatureCurve()) {
@ -182,34 +173,38 @@ class EcdhKeyAlgorithm extends CryptoAlgorithm implements KeyAlgorithm<PublicKey
return curve;
}
private static ECCurve assertEcCurve(Key key, String exMsg) {
ECCurve curve;
try {
curve = ECCurve.forKey(key);
} catch (Throwable t) {
String msg = exMsg + " Cause: " + t.getMessage();
throw new UnsupportedKeyException(msg, t);
}
return Assert.stateNotNull(curve, "ECCurve instance cannot be null.");
}
@Override
public KeyResult getEncryptionKey(KeyRequest<PublicKey> request) throws SecurityException {
Assert.notNull(request, "Request cannot be null.");
JweHeader header = Assert.notNull(request.getHeader(), "Request JweHeader cannot be null.");
PublicKey publicKey = Assert.notNull(request.getPayload(), "Encryption PublicKey cannot be null.");
KeyPair pair; // generated (ephemeral) key pair
final SecureRandom random = ensureSecureRandom(request);
DynamicJwkBuilder<?, ?> jwkBuilder = Jwks.builder().random(random).provider(request.getProvider());
Curve curve;
if (publicKey instanceof ECKey) {
ECKey ecPublicKey = (ECKey) publicKey;
ECParameterSpec spec = Assert.notNull(ecPublicKey.getParams(),
"Encryption PublicKey params cannot be null.");
curve = assertEcCurve(publicKey, KEK_TYPE_MESSAGE);
// note: we don't need to validate if specified key's point is on a supported curve here
// because that will automatically be asserted when using Jwks.builder().... below
pair = generateKeyPair(request, spec);
// assert pair key types:
KeyPairs.getKey(pair, ECPublicKey.class);
KeyPairs.getKey(pair, ECPrivateKey.class);
} else { // it must be an edwards curve key
EdwardsCurve curve = assertAgreement(publicKey, KEK_TYPE_MESSAGE);
Provider provider = request.getProvider();
request = new DefaultKeyRequest<>(request.getPayload(), provider, random,
request.getHeader(), request.getEncryptionAlgorithm());
pair = generateKeyPair(random, curve, provider);
jwkBuilder.provider(provider);
curve = assertAgreement(publicKey, KEK_TYPE_MESSAGE);
}
Assert.stateNotNull(curve, "Internal implementation state: Curve cannot be null.");
// Generate our ephemeral key pair:
final SecureRandom random = ensureSecureRandom(request);
final Provider provider = request.getProvider();
DynamicJwkBuilder<?, ?> jwkBuilder = Jwks.builder().random(random).provider(provider);
KeyPair pair = generateKeyPair(curve, provider, random);
Assert.stateNotNull(pair, "Internal implementation state: KeyPair cannot be null.");
@ -227,6 +222,15 @@ class EcdhKeyAlgorithm extends CryptoAlgorithm implements KeyAlgorithm<PublicKey
return result;
}
private <T> T assertEpk(Class<T> clazz, Object epk) {
if (!clazz.isInstance(epk)) {
String msg = "JWE Header " + DefaultJweHeader.EPK + " value is not a supported Elliptic Curve " +
"Public JWK. Value: " + epk;
throw new UnsupportedKeyException(msg);
}
return clazz.cast(epk);
}
@Override
public SecretKey getDecryptionKey(DecryptionKeyRequest<PrivateKey> request) throws SecurityException {
@ -237,27 +241,17 @@ class EcdhKeyAlgorithm extends CryptoAlgorithm implements KeyAlgorithm<PublicKey
PublicJwk<?> epk = reader.get(DefaultJweHeader.EPK);
if (privateKey instanceof ECKey) {
ECKey ecPrivateKey = (ECKey) privateKey;
if (!(epk instanceof EcPublicJwk)) {
String msg = "JWE Header " + DefaultJweHeader.EPK + " value is not a supported Elliptic Curve " +
"Public JWK. Value: " + epk;
throw new UnsupportedKeyException(msg);
}
EcPublicJwk ecEpk = (EcPublicJwk) epk;
ECCurve curve = assertEcCurve(privateKey, KDK_TYPE_MESSAGE);
EcPublicJwk ecEpk = assertEpk(EcPublicJwk.class, epk);
// While the EPK might be on a JWA-supported NIST curve, it must be on the private key's exact curve:
if (!EcPublicJwkFactory.contains(ecPrivateKey.getParams().getCurve(), ecEpk.toKey().getW())) {
if (!curve.contains(ecEpk.toKey())) {
String msg = "JWE Header " + DefaultJweHeader.EPK + " value does not represent " +
"a point on the expected curve.";
throw new InvalidKeyException(msg);
}
} else { // it must be an Edwards Curve key
EdwardsCurve privateKeyCurve = assertAgreement(privateKey, KDK_TYPE_MESSAGE);
if (!(epk instanceof OctetPublicJwk)) {
String msg = "JWE Header " + DefaultJweHeader.EPK + " value is not a supported Elliptic Curve " +
"Public JWK. Value: " + epk;
throw new UnsupportedKeyException(msg);
}
OctetPublicJwk<?> oEpk = (OctetPublicJwk<?>) epk;
OctetPublicJwk<?> oEpk = assertEpk(OctetPublicJwk.class, epk);
EdwardsCurve epkCurve = EdwardsCurve.forKey(oEpk.toKey());
if (!privateKeyCurve.equals(epkCurve)) {
String msg = "JWE Header " + DefaultJweHeader.EPK + " value does not represent a point " +

View File

@ -17,11 +17,28 @@ package io.jsonwebtoken.impl.security;
import io.jsonwebtoken.impl.lang.DelegatingRegistry;
import io.jsonwebtoken.impl.lang.IdRegistry;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.Curve;
@SuppressWarnings("unused") // used via reflection in io.jsonwebtoken.Jwks.CRV
public final class StandardCurves extends DelegatingRegistry<String, Curve> {
public StandardCurves() {
super(new IdRegistry<>("Elliptic Curve", Curves.VALUES, false));
super(new IdRegistry<>("Elliptic Curve", Collections.<Curve>of(
ECCurve.P256,
ECCurve.P384,
ECCurve.P521,
EdwardsCurve.X25519,
EdwardsCurve.X448,
EdwardsCurve.Ed25519,
EdwardsCurve.Ed448
), false));
}
// public static Curve findByKey(Key key) {
// Curve curve = ECCurve.findByKey(key);
// if (curve == null) {
// curve = EdwardsCurve.findByKey(key);
// }
// return curve;
// }
}

View File

@ -15,20 +15,27 @@
*/
package io.jsonwebtoken
import io.jsonwebtoken.impl.security.Curves
import io.jsonwebtoken.impl.security.ECCurve
import io.jsonwebtoken.impl.security.EdwardsCurve
import io.jsonwebtoken.impl.security.StandardCurves
import io.jsonwebtoken.security.Jwks
import org.junit.Test
import static org.junit.Assert.assertSame
import static org.junit.Assert.assertTrue
class JwksCRVTest {
@Test
void testRegistry() {
assertTrue Jwks.CRV.get() instanceof StandardCurves
}
@Test
void testInstances() {
assertSame Curves.P_256, Jwks.CRV.P256
assertSame Curves.P_384, Jwks.CRV.P384
assertSame Curves.P_521, Jwks.CRV.P521
assertSame ECCurve.P256, Jwks.CRV.P256
assertSame ECCurve.P384, Jwks.CRV.P384
assertSame ECCurve.P521, Jwks.CRV.P521
assertSame EdwardsCurve.X25519, Jwks.CRV.X25519
assertSame EdwardsCurve.X448, Jwks.CRV.X448
assertSame EdwardsCurve.Ed25519, Jwks.CRV.Ed25519

View File

@ -15,16 +15,10 @@
*/
package io.jsonwebtoken.impl.security
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Jwk
import io.jsonwebtoken.security.UnsupportedKeyException
import org.junit.Test
import java.security.KeyFactory
import java.security.interfaces.ECPrivateKey
import java.security.interfaces.ECPublicKey
import java.security.spec.*
import static org.junit.Assert.assertEquals
import static org.junit.Assert.fail
@ -37,98 +31,8 @@ class AbstractEcJwkFactoryTest {
AbstractEcJwkFactory.getCurveByJwaId(id)
fail()
} catch (UnsupportedKeyException e) {
String msg = "Unrecognized JWA curve id '$id'"
String msg = "Unrecognized JWA EC curve id '$id'"
assertEquals msg, e.getMessage()
}
}
@Test
void testUnsupportedCurve() {
def curve = new EllipticCurve(new TestECField(fieldSize: 1), BigInteger.ONE, BigInteger.TEN)
try {
AbstractEcJwkFactory.getJwaIdByCurve(curve)
fail()
} catch (UnsupportedKeyException e) {
assertEquals AbstractEcJwkFactory.UNSUPPORTED_CURVE_MSG, e.getMessage()
}
}
@Test
void testMultiplyInfinity() {
ECParameterSpec spec = AbstractEcJwkFactory.getCurveByJwaId('P-256')
def result = AbstractEcJwkFactory.multiply(ECPoint.POINT_INFINITY, BigInteger.valueOf(1), spec)
assertEquals ECPoint.POINT_INFINITY, result
}
@Test
void testDoubleInfinity() {
ECParameterSpec spec = AbstractEcJwkFactory.getCurveByJwaId('P-256')
def curve = spec.getCurve()
def result = AbstractEcJwkFactory.doublePoint(ECPoint.POINT_INFINITY, curve)
assertEquals ECPoint.POINT_INFINITY, result
}
@Test
void testAddInfinity() {
ECParameterSpec spec = AbstractEcJwkFactory.getCurveByJwaId('P-256')
def curve = spec.getCurve()
ECPoint point = new ECPoint(BigInteger.valueOf(1), BigInteger.valueOf(2)) // any point is fine for this test
def result = AbstractEcJwkFactory.add(ECPoint.POINT_INFINITY, point, curve)
//adding infinity to a point should return the point:
assertEquals point, result
//adding a point to infinity should return the point:
result = AbstractEcJwkFactory.add(point, ECPoint.POINT_INFINITY, curve)
assertEquals point, result
}
@Test
void testAddSamePointDoublesIt() {
def pair = Jwts.SIG.ES256.keyPair().build()
def pub = pair.getPublic() as ECPublicKey
def spec = pub.getParams()
def curve = spec.getCurve()
def point = pub.getW()
def doubled = AbstractEcJwkFactory.doublePoint(point, curve)
def added = AbstractEcJwkFactory.add(point, point, curve)
assertEquals doubled, added
}
@Test
void testDerivePublicFails() {
def pair = Jwts.SIG.ES256.keyPair().build()
def priv = pair.getPrivate() as ECPrivateKey
final def context = new DefaultJwkContext(DefaultEcPrivateJwk.FIELDS)
context.setKey(priv)
def ex = new InvalidKeySpecException("invalid")
def factory = new AbstractEcJwkFactory(ECPrivateKey.class, DefaultEcPrivateJwk.FIELDS) {
@Override
protected Jwk createJwkFromKey(JwkContext ctx) {
return null
}
@Override
protected Jwk createJwkFromValues(JwkContext ctx) {
return null
}
@Override
protected ECPublicKey derivePublic(KeyFactory keyFactory, ECPublicKeySpec spec) throws InvalidKeySpecException {
throw ex
}
}
try {
factory.derivePublic(context)
fail()
} catch (UnsupportedKeyException expected) {
String msg = 'Unable to derive ECPublicKey from ECPrivateKey: invalid'
assertEquals msg, expected.getMessage()
}
}
}

View File

@ -15,19 +15,80 @@
*/
package io.jsonwebtoken.impl.security
import io.jsonwebtoken.security.UnsupportedKeyException
import org.junit.Test
import java.security.PublicKey
import java.security.interfaces.ECPublicKey
import java.security.spec.ECParameterSpec
import java.security.spec.ECPoint
import java.security.spec.EllipticCurve
import static org.junit.Assert.assertFalse
import static org.junit.Assert.assertTrue
import static org.junit.Assert.*
class ECCurveTest {
static void assertContains(ECCurve curve, PublicKey pub) {
assertTrue(curve.contains(pub))
}
@Test
void testContainsKeyTrue() {
assertContains(ECCurve.P256, TestKeys.ES256.pair.public)
assertContains(ECCurve.P384, TestKeys.ES384.pair.public)
assertContains(ECCurve.P521, TestKeys.ES512.pair.public)
}
@Test
void testContainsKeyNull() {
ECCurve.VALUES.each {
assertFalse(it.contains(null))
}
}
@Test
void testContainsNonECPublicKey() {
ECCurve.VALUES.each {
assertFalse it.contains(TestKeys.HS256)
}
}
@Test
void testContainsKeyNullParams() {
ECCurve.VALUES.each {
assertFalse it.contains(new TestECPublicKey())
}
}
@Test
void testContainsKeyNullJcaCurve() {
def src = TestKeys.ES256.pair.public.getParams() as ECParameterSpec
def spec = new ECParameterSpec(src.curve, src.generator, src.order, src.cofactor) {
@Override
EllipticCurve getCurve() {
return null
}
}
def key = new TestECKey(params: spec)
ECCurve.VALUES.each {
assertFalse it.contains(key)
}
}
@Test
void testContainsKeyBadWPoint() {
ECCurve.VALUES.each {
def src = it.keyPair().build().public
def spec = src.getParams() as ECParameterSpec
def key = new TestECPublicKey(params: spec, w: new ECPoint(BigInteger.ONE, BigInteger.ONE))
assertFalse it.contains(key)
}
}
@Test
void testContainsTrue() {
ECCurve curve = (ECCurve) Curves.P_256
ECCurve curve = ECCurve.P256
def pair = curve.keyPair().build()
ECPublicKey ecPub = (ECPublicKey) pair.getPublic()
assertTrue(curve.contains(ecPub.getW()))
@ -35,6 +96,109 @@ class ECCurveTest {
@Test
void testContainsFalse() {
assertFalse(((ECCurve) Curves.P_256).contains(new ECPoint(BigInteger.ONE, BigInteger.ONE)))
assertFalse(ECCurve.P256.contains(new ECPoint(BigInteger.ONE, BigInteger.ONE)))
}
@Test
void testFindByJcaEllipticCurve() {
ECCurve.VALUES.each {
it.equals(ECCurve.findByJcaCurve(it.toParameterSpec().getCurve()))
}
}
@Test
void testMultiplyInfinity() {
ECCurve.VALUES.each {
def result = it.multiply(ECPoint.POINT_INFINITY, BigInteger.valueOf(1))
assertEquals ECPoint.POINT_INFINITY, result
}
}
@Test
void testDoubleInfinity() {
ECCurve.VALUES.each {
def result = it.doublePoint(ECPoint.POINT_INFINITY)
assertEquals ECPoint.POINT_INFINITY, result
}
}
@Test
void testAddInfinity() {
ECCurve.VALUES.each {
def curve = it.spec.getCurve()
ECPoint point = new ECPoint(BigInteger.valueOf(1), BigInteger.valueOf(2)) // any point is fine for this test
def result = it.add(ECPoint.POINT_INFINITY, point)
//adding infinity to a point should return the point:
assertEquals point, result
//adding a point to infinity should return the point:
result = it.add(point, ECPoint.POINT_INFINITY)
assertEquals point, result
}
}
@Test
void testAddSamePointDoublesIt() {
ECCurve.VALUES.each {
def pair = it.keyPair().build()
def pub = pair.getPublic() as ECPublicKey
def point = pub.getW()
def doubled = it.doublePoint(point)
def added = it.add(point, point)
assertEquals doubled, added
}
}
@Test
void testFindByKeyNull() {
assertNull ECCurve.findByKey(null)
}
@Test
void testFindByKeyNotECKey() {
assertNull ECCurve.findByKey(TestKeys.HS256)
}
@Test
void testFindByKeyNullParams() {
assertNull ECCurve.findByKey(new TestECKey())
}
@Test
void testFindByKeyNullJcaCurve() {
def src = TestKeys.ES256.pair.public.getParams() as ECParameterSpec
def spec = new ECParameterSpec(src.curve, src.generator, src.order, src.cofactor) {
@Override
EllipticCurve getCurve() {
return null
}
}
assertNull ECCurve.findByKey(new TestECKey(params: spec))
}
@Test
void testFindByKeyWithNullWPoint() {
def spec = TestKeys.ES256.pair.public.getParams() as ECParameterSpec
assertNull ECCurve.findByKey(new TestECPublicKey(params: spec))
}
@Test
void testFindByKeyWithWPointNotOnCurve() {
def spec = TestKeys.ES256.pair.public.getParams() as ECParameterSpec
def key = new TestECPublicKey(params: spec, w: new ECPoint(BigInteger.ONE, BigInteger.ONE))
assertNull ECCurve.findByKey(key)
}
@Test
void testForKeyWithNonECPublicKey() {
def key = TestKeys.HS256
try {
ECCurve.forKey(key)
fail()
} catch (UnsupportedKeyException expected) {
String msg = "Unable to determine JWA-standard Elliptic Curve for specified key: " +
"${KeysBridge.toString(key)}"
assertEquals msg, expected.getMessage()
}
}
}

View File

@ -15,9 +15,17 @@
*/
package io.jsonwebtoken.impl.security
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.MalformedKeyException
import io.jsonwebtoken.security.UnsupportedKeyException
import org.junit.Test
import java.security.KeyFactory
import java.security.interfaces.ECPrivateKey
import java.security.interfaces.ECPublicKey
import java.security.spec.ECPublicKeySpec
import java.security.spec.InvalidKeySpecException
import static org.junit.Assert.assertEquals
import static org.junit.Assert.fail
@ -36,4 +44,31 @@ class EcPrivateJwkFactoryTest {
assertEquals msg, expected.getMessage()
}
}
@Test
void testDerivePublicFails() {
def pair = Jwts.SIG.ES256.keyPair().build()
def priv = pair.getPrivate() as ECPrivateKey
final def context = new DefaultJwkContext(DefaultEcPrivateJwk.FIELDS)
context.setKey(priv)
def ex = new InvalidKeySpecException("invalid")
def factory = new EcPrivateJwkFactory() {
@Override
protected ECPublicKey derivePublic(KeyFactory keyFactory, ECPublicKeySpec spec) throws InvalidKeySpecException {
throw ex
}
}
try {
factory.derivePublic(context)
fail()
} catch (UnsupportedKeyException expected) {
String msg = 'Unable to derive ECPublicKey from ECPrivateKey: invalid'
assertEquals msg, expected.getMessage()
}
}
}

View File

@ -18,8 +18,11 @@ package io.jsonwebtoken.impl.security
import io.jsonwebtoken.security.InvalidKeyException
import io.jsonwebtoken.security.Jwks
import io.jsonwebtoken.security.MalformedKeyException
import io.jsonwebtoken.security.UnsupportedKeyException
import org.junit.Test
import java.security.spec.EllipticCurve
import static org.junit.Assert.assertEquals
import static org.junit.Assert.fail
@ -72,4 +75,15 @@ class EcPublicJwkFactoryTest {
assertEquals msg, expected.getMessage()
}
}
@Test
void testUnsupportedCurve() {
def curve = new EllipticCurve(new TestECField(fieldSize: 1), BigInteger.ONE, BigInteger.TEN)
try {
EcPublicJwkFactory.getJwaIdByCurve(curve)
fail()
} catch (UnsupportedKeyException e) {
assertEquals EcPublicJwkFactory.UNSUPPORTED_CURVE_MSG, e.getMessage()
}
}
}

View File

@ -62,7 +62,7 @@ class EcSignatureAlgorithmTest {
@Test
void testFindOidKeys() {
for(def alg : EcSignatureAlgorithm.ALGS_BY_OID.values()) {
for(def alg : EcSignatureAlgorithm.BY_OID.values()) {
String name = "${alg.getId()}_OID"
String oid = EcSignatureAlgorithm.metaClass.getAttribute(EcSignatureAlgorithm, name) as String
assertEquals oid, alg.OID

View File

@ -124,9 +124,9 @@ class EcdhKeyAlgorithmTest {
alg.getEncryptionKey(request)
fail()
} catch (UnsupportedKeyException expected) {
String msg = 'Key Encryption Key must be a java.security.interfaces.ECKey or valid Edwards Curve ' +
'PublicKey instance. Cause: sun.security.rsa.RSAPublicKeyImpl with algorithm \'RSA\' is not a ' +
'recognized Edwards Curve key.'
String msg = 'Key Encryption Key must be a java.security.interfaces.ECKey or Edwards Curve ' +
'PublicKey on a supported curve. Cause: sun.security.rsa.RSAPublicKeyImpl with ' +
'algorithm \'RSA\' is not a recognized Edwards Curve key.'
assertEquals msg, expected.getMessage()
}
}
@ -142,9 +142,9 @@ class EcdhKeyAlgorithmTest {
alg.getDecryptionKey(request)
fail()
} catch (UnsupportedKeyException expected) {
String msg = 'Key Decryption Key must be a java.security.interfaces.ECKey or valid Edwards Curve ' +
'PrivateKey instance. Cause: sun.security.rsa.RSAPrivateCrtKeyImpl with algorithm \'RSA\' is ' +
'not a recognized Edwards Curve key.'
String msg = 'Key Decryption Key must be a java.security.interfaces.ECKey or Edwards Curve ' +
'PrivateKey on a supported curve. Cause: sun.security.rsa.RSAPrivateCrtKeyImpl with ' +
'algorithm \'RSA\' is not a recognized Edwards Curve key.'
assertEquals msg, expected.getMessage()
}
}
@ -221,4 +221,17 @@ class EcdhKeyAlgorithmTest {
assertEquals msg, expected.getMessage()
}
}
@Test
void testAssertEcCurveFails() {
def key = TestKeys.HS256
try {
EcdhKeyAlgorithm.assertEcCurve(key, 'foo.')
fail()
} catch (UnsupportedKeyException expected) {
String msg = "foo. Cause: Unable to determine JWA-standard Elliptic Curve for specified " +
"key: ${KeysBridge.toString(key)}"
assertEquals msg, expected.getMessage()
}
}
}

View File

@ -26,7 +26,8 @@ import org.junit.Test
import java.nio.charset.StandardCharsets
import java.security.KeyPair
import java.security.spec.ECParameterSpec
import java.security.Provider
import java.security.SecureRandom
import static org.junit.Assert.*
@ -98,7 +99,7 @@ class RFC7518AppendixCTest {
//ensure keypair reflects required RFC test value:
@Override
protected KeyPair generateKeyPair(Request request, ECParameterSpec spec) {
protected KeyPair generateKeyPair(Curve curve, Provider provider, SecureRandom random) {
return aliceJwk.toKeyPair().toJavaKeyPair()
}

View File

@ -30,8 +30,9 @@ import javax.crypto.SecretKey
import javax.crypto.spec.SecretKeySpec
import java.nio.charset.StandardCharsets
import java.security.KeyPair
import java.security.Provider
import java.security.SecureRandom
import java.security.interfaces.RSAPublicKey
import java.security.spec.ECParameterSpec
import static org.junit.Assert.assertEquals
@ -634,7 +635,7 @@ class RFC7520Section5Test {
def RFC_EPK = Jwks.parser().build().parse(FIGURE_111) as EcPrivateJwk
def alg = new EcdhKeyAlgorithm(wrapAlg) {
@Override
protected KeyPair generateKeyPair(Request request, ECParameterSpec spec) {
protected KeyPair generateKeyPair(Curve curve, Provider provider, SecureRandom random) {
return new KeyPair(RFC_EPK.toPublicJwk().toKey(), RFC_EPK.toKey()) // ensure RFC value
}
}

View File

@ -17,6 +17,7 @@ package io.jsonwebtoken.impl.security
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.impl.RfcTests
import io.jsonwebtoken.security.Curve
import io.jsonwebtoken.security.Jwks
import io.jsonwebtoken.security.OctetPrivateJwk
import io.jsonwebtoken.security.OctetPublicJwk
@ -154,7 +155,7 @@ class RFC8037AppendixATest {
// ensure this is used during key algorithm execution per the RFC test case:
def alg = new EcdhKeyAlgorithm(Jwts.KEY.A128KW) {
@Override
protected KeyPair generateKeyPair(SecureRandom random, EdwardsCurve curve, Provider provider) {
protected KeyPair generateKeyPair(Curve curve, Provider provider, SecureRandom random) {
return ephemJwk.toKeyPair().toJavaKeyPair()
}
}
@ -245,7 +246,7 @@ class RFC8037AppendixATest {
// ensure this is used during key algorithm execution per the RFC test case:
def alg = new EcdhKeyAlgorithm(Jwts.KEY.A256KW) {
@Override
protected KeyPair generateKeyPair(SecureRandom random, EdwardsCurve curve, Provider provider) {
protected KeyPair generateKeyPair(Curve curve, Provider provider, SecureRandom random) {
return ephemJwk.toKeyPair().toJavaKeyPair()
}
}

View File

@ -15,42 +15,25 @@
*/
package io.jsonwebtoken.impl.security
import io.jsonwebtoken.security.Jwks
import org.junit.Test
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertTrue
import static org.junit.Assert.*
class CurvesTest {
class StandardCurvesTest {
@Test
void testCtor() {
new Curves() // test coverage only
}
static final StandardCurves curves = (StandardCurves) Jwks.CRV.get()
@Test
void testFindById() {
Curves.VALUES.each {
it.equals(Curves.findById(it.getId()))
}
}
@Test
void testFindByJcaName() {
Curves.VALUES.each {
it.equals(Curves.findByJcaName(it.getJcaName()))
}
}
@Test
void testFindByEllipticCurve() {
Curves.EC_CURVES.each {
it.equals(Curves.findBy(it.toParameterSpec().getCurve()))
curves.values().each {
assertSame it, curves.get(it.getId())
}
}
@Test
void testKeyPairBuilders() {
Curves.VALUES.each {
curves.values().each {
def pair = it.keyPair().build()
if (it instanceof ECCurve) {
assertEquals ECCurve.KEY_PAIR_GENERATOR_JCA_NAME, pair.getPublic().getAlgorithm()