All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.jsonwebtoken.impl.security.ECCurve Maven / Gradle / Ivy

/*
 * Copyright (C) 2021 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.CheckedFunction;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.security.KeyPairBuilder;

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 AbstractCurve {

    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 VALUES = Collections.setOf(P256, P384, P521);
    private static final Map BY_ID = new LinkedHashMap<>(3);
    private static final Map 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 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) {
        super(id, jcaName);
        JcaTemplate template = new JcaTemplate(KEY_PAIR_GENERATOR_JCA_NAME);
        this.spec = template.withAlgorithmParameters(new CheckedFunction() {
            @Override
            public ECParameterSpec apply(AlgorithmParameters params) throws Exception {
                params.init(new ECGenParameterSpec(getJcaName()));
                return params.getParameterSpec(ECParameterSpec.class);
            }
        });
    }

    public ECParameterSpec toParameterSpec() {
        return this.spec;
    }

    @Override
    public KeyPairBuilder keyPair() {
        return new DefaultKeyPairBuilder(KEY_PAIR_GENERATOR_JCA_NAME, toParameterSpec());
    }

    @Override
    public 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 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:
     * 

* y2 = x3 + ax + b *

* * @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 curve contains the specified {@code point}, {@code false} otherwise. */ @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); } /** * 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); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy