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

io.jsonwebtoken.impl.security.EcSignatureAlgorithm 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.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;
import io.jsonwebtoken.security.SecureRequest;
import io.jsonwebtoken.security.SignatureAlgorithm;
import io.jsonwebtoken.security.SignatureException;
import io.jsonwebtoken.security.VerifySecureDigestRequest;

import java.io.InputStream;
import java.math.BigInteger;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.ECKey;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

// @since 0.12.0
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 ES256_OID = "1.2.840.10045.4.3.2";
    private static final String ES384_OID = "1.2.840.10045.4.3.3";
    private static final String ES512_OID = "1.2.840.10045.4.3.4";

    private static final Set KEY_ALG_NAMES = Collections.setOf("EC", "ECDSA", ES256_OID, ES384_OID, ES512_OID);

    private final ECGenParameterSpec KEY_PAIR_GEN_PARAMS;

    private final int orderBitLength;

    private final String OID;

    /**
     * JWA EC (concat formatted) length in bytes for this instance's {@link #orderBitLength}.
     */
    private final int signatureByteLength;
    private final int sigFieldByteLength;

    private static int shaSize(int orderBitLength) {
        return orderBitLength == 521 ? 512 : orderBitLength;
    }

    /**
     * Returns {@code true} for Order bit lengths defined in the JWA specification, {@code false} otherwise.
     * Specifically, returns {@code true} only for values of {@code 256}, {@code 384} and {@code 521}.  See
     * RFC 7518, Section 3.4 for more.
     *
     * @param orderBitLength the EC key Order bit length to check
     * @return {@code true} for Order bit lengths defined in the JWA specification, {@code false} otherwise.
     */
    private static boolean isSupportedOrderBitLength(int orderBitLength) {
        // This implementation supports only those defined in the JWA specification.
        return orderBitLength == 256 || orderBitLength == 384 || orderBitLength == 521;
    }

    static final EcSignatureAlgorithm ES256 = new EcSignatureAlgorithm(256, ES256_OID);
    static final EcSignatureAlgorithm ES384 = new EcSignatureAlgorithm(384, ES384_OID);
    static final EcSignatureAlgorithm ES512 = new EcSignatureAlgorithm(521, ES512_OID);

    private static final Map BY_OID = new LinkedHashMap<>(3);

    static {
        for (EcSignatureAlgorithm alg : Collections.of(ES256, ES384, ES512)) {
            BY_OID.put(alg.OID, alg);
        }
    }

    static SignatureAlgorithm findByKey(Key key) {

        String algName = KeysBridge.findAlgorithm(key);
        if (!Strings.hasText(algName)) {
            return null;
        }
        algName = algName.toUpperCase(Locale.ENGLISH);

        SignatureAlgorithm alg = BY_OID.get(algName);
        if (alg != null) {
            return alg;
        }

        if ("EC".equalsIgnoreCase(algName) || "ECDSA".equalsIgnoreCase(algName)) {
            // some PKCS11 keystores and HSMs won't expose the RSAKey interface, so we can't assume it:
            final int bitLength = KeysBridge.findBitLength(key); // returns -1 if we're unable to find out
            if (bitLength == ES512.orderBitLength) {
                return ES512;
            } else if (bitLength == ES384.orderBitLength) {
                return ES384;
            } else if (bitLength == ES256.orderBitLength) {
                return ES256;
            }
        }

        return null;
    }

    private EcSignatureAlgorithm(int orderBitLength, String oid) {
        super("ES" + shaSize(orderBitLength), "SHA" + shaSize(orderBitLength) + "withECDSA");
        Assert.isTrue(isSupportedOrderBitLength(orderBitLength), REQD_ORDER_BIT_LENGTH_MSG);
        this.OID = Assert.hasText(oid, "Invalid OID.");
        String curveName = "secp" + orderBitLength + "r1";
        this.KEY_PAIR_GEN_PARAMS = new ECGenParameterSpec(curveName);
        this.orderBitLength = orderBitLength;
        this.sigFieldByteLength = Bytes.length(this.orderBitLength);
        this.signatureByteLength = this.sigFieldByteLength * 2; // R bytes + S bytes = concat signature bytes
    }

    @Override
    public KeyPairBuilder keyPair() {
        return new DefaultKeyPairBuilder(ECCurve.KEY_PAIR_GENERATOR_JCA_NAME, this.KEY_PAIR_GEN_PARAMS)
                .random(Randoms.secureRandom());
    }

    @Override
    protected void validateKey(Key key, boolean signing) {
        super.validateKey(key, signing);
        if (!KEY_ALG_NAMES.contains(KeysBridge.findAlgorithm(key))) {
            throw new InvalidKeyException("Unrecognized EC key algorithm name.");
        }
        int size = KeysBridge.findBitLength(key);
        if (size < 0) return; // likely PKCS11 or HSM key, can't get the data we need
        int sigFieldByteLength = Bytes.length(size);
        int concatByteLength = sigFieldByteLength * 2;
        if (concatByteLength != this.signatureByteLength) {
            String msg = "The provided Elliptic Curve " + keyType(signing) +
                    " key size (aka order bit length) is " + Bytes.bitsMsg(size) + ", but the '" +
                    getId() + "' 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);
        }
    }

    @Override
    protected byte[] doDigest(final SecureRequest request) {
        return jca(request).withSignature(new CheckedFunction() {
            @Override
            public byte[] apply(Signature sig) throws Exception {
                sig.initSign(KeysBridge.root(request));
                byte[] signature = sign(sig, request.getPayload());
                return transcodeDERToConcat(signature, signatureByteLength);
            }
        });
    }

    boolean isValidRAndS(PublicKey key, byte[] concatSignature) {
        if (key instanceof ECKey) { //Some PKCS11 providers and HSMs won't expose the ECKey interface, so we have to check first
            ECKey ecKey = (ECKey) key;
            BigInteger order = ecKey.getParams().getOrder();
            BigInteger r = new BigInteger(1, Arrays.copyOfRange(concatSignature, 0, sigFieldByteLength));
            BigInteger s = new BigInteger(1, Arrays.copyOfRange(concatSignature, sigFieldByteLength, concatSignature.length));
            return r.signum() >= 1 && s.signum() >= 1 && r.compareTo(order) < 0 && s.compareTo(order) < 0;
        }
        return true;
    }

    @Override
    protected boolean doVerify(final VerifySecureDigestRequest request) {

        final PublicKey key = request.getKey();

        return jca(request).withSignature(new CheckedFunction() {
            @Override
            public Boolean apply(Signature sig) {
                byte[] concatSignature = request.getDigest();
                byte[] derSignature;
                try {
                    // mandated per https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4 :
                    if (signatureByteLength != concatSignature.length) {
                        /*
                         * If the expected size is not valid for JOSE, fall back to ASN.1 DER signature IFF the application
                         * is configured to do so.  This fallback is for backwards compatibility ONLY (to support tokens
                         * generated by early versions of jjwt) and backwards compatibility will be removed in a future
                         * version of this library.  This fallback is only enabled if the system property is set to 'true' due to
                         * the risk of CVE-2022-21449 attacks on early JVM versions 15, 17 and 18.
                         */
                        // TODO: remove for 1.0 (DER-encoding support is not in the JWT RFCs)
                        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).";
                            throw new SignatureException(msg);
                        }
                    } else {
                        //guard for JVM security bug CVE-2022-21449:
                        if (!isValidRAndS(key, concatSignature)) {
                            return false;
                        }

                        // Convert from concat to DER encoding since
                        // 1) SHAXXXWithECDSAInP1363Format algorithms are only available on >= JDK 9 and
                        // 2) the SignatureAlgorithm enum JCA alg names are all SHAXXXwithECDSA (which expects DER formatting)
                        derSignature = transcodeConcatToDER(concatSignature);
                    }

                    sig.initVerify(key);
                    return verify(sig, request.getPayload(), derSignature);

                } catch (Exception e) {
                    String msg = "Unable to verify Elliptic Curve signature using provided ECPublicKey: " + e.getMessage();
                    throw new SignatureException(msg, e);
                }
            }
        });
    }

    /**
     * Transcodes the JCA ASN.1/DER-encoded signature into the concatenated
     * R + S format expected by ECDSA JWS.
     *
     * @param derSignature The ASN1./DER-encoded. Must not be {@code null}.
     * @param outputLength The expected length of the ECDSA JWS signature.
     * @return The ECDSA JWS encoded signature.
     * @throws JwtException If the ASN.1/DER signature format is invalid.
     * @author Martin Treurnicht via 61510dfca58dd40b4b32c708935126785dcff48c
     */
    public static byte[] transcodeDERToConcat(final byte[] derSignature, int outputLength) throws JwtException {

        if (derSignature.length < 8 || derSignature[0] != 48) {
            throw new JwtException("Invalid ECDSA signature format");
        }

        int offset;
        if (derSignature[1] > 0) {
            offset = 2;
        } else if (derSignature[1] == (byte) 0x81) {
            offset = 3;
        } else {
            throw new JwtException("Invalid ECDSA signature format");
        }

        byte rLength = derSignature[offset + 1];

        int i = rLength;
        while ((i > 0) && (derSignature[(offset + 2 + rLength) - i] == 0)) {
            i--;
        }

        byte sLength = derSignature[offset + 2 + rLength + 1];

        int j = sLength;
        while ((j > 0) && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0)) {
            j--;
        }

        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) {
            throw new JwtException("Invalid ECDSA signature format");
        }

        final byte[] concatSignature = new byte[2 * rawLen];

        System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i);
        System.arraycopy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j);

        return concatSignature;
    }

    /**
     * Transcodes the ECDSA JWS signature into ASN.1/DER format for use by the JCA verifier.
     *
     * @param jwsSignature The JWS signature, consisting of the concatenated R and S values. Must not be {@code null}.
     * @return The ASN.1/DER encoded signature.
     * @throws JwtException If the ECDSA JWS signature format is invalid.
     */
    public static byte[] transcodeConcatToDER(byte[] jwsSignature) throws JwtException {
        try {
            return concatToDER(jwsSignature);
        } catch (Exception e) { // CVE-2022-21449 guard
            String msg = "Invalid ECDSA signature format.";
            throw new SignatureException(msg, e);
        }
    }

    /**
     * Converts the specified concat-encoded signature to a DER-encoded signature.
     *
     * @param jwsSignature concat-encoded signature
     * @return correpsonding DER-encoded signature
     * @throws ArrayIndexOutOfBoundsException if the signature cannot be converted
     * @author Martin Treurnicht via 61510dfca58dd40b4b32c708935126785dcff48c
     */
    private static byte[] concatToDER(byte[] jwsSignature) throws ArrayIndexOutOfBoundsException {

        int rawLen = jwsSignature.length / 2;

        int i = rawLen;

        while ((i > 0) && (jwsSignature[rawLen - i] == 0)) {
            i--;
        }

        int j = i;

        if (jwsSignature[rawLen - i] < 0) {
            j += 1;
        }

        int k = rawLen;

        while ((k > 0) && (jwsSignature[2 * rawLen - k] == 0)) {
            k--;
        }

        int l = k;

        if (jwsSignature[2 * rawLen - k] < 0) {
            l += 1;
        }

        int len = 2 + j + 2 + l;

        if (len > 255) {
            throw new JwtException("Invalid ECDSA signature format");
        }

        int offset;

        final byte[] derSignature;

        if (len < 128) {
            derSignature = new byte[2 + 2 + j + 2 + l];
            offset = 1;
        } else {
            derSignature = new byte[3 + 2 + j + 2 + l];
            derSignature[1] = (byte) 0x81;
            offset = 2;
        }

        derSignature[0] = 48;
        derSignature[offset++] = (byte) len;
        derSignature[offset++] = 2;
        derSignature[offset++] = (byte) j;

        System.arraycopy(jwsSignature, rawLen - i, derSignature, (offset + j) - i, i);

        offset += j;

        derSignature[offset++] = 2;
        derSignature[offset++] = (byte) l;

        System.arraycopy(jwsSignature, 2 * rawLen - k, derSignature, (offset + l) - k, k);

        return derSignature;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy