Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move EC curve utility functions #803

Merged
merged 3 commits into from
Aug 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand All @@ -85,146 +57,7 @@ static String toOctetString(int fieldSize, BigInteger coordinate) {
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);
}
}
});
}
}
79 changes: 0 additions & 79 deletions impl/src/main/java/io/jsonwebtoken/impl/security/Curves.java

This file was deleted.

Loading