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

com.amazonaws.encryptionsdk.internal.TrailingSignatureAlgorithm Maven / Gradle / Ivy

There is a newer version: 3.0.1
Show newest version
package com.amazonaws.encryptionsdk.internal;

import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
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.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;

import com.amazonaws.encryptionsdk.CryptoAlgorithm;

import static com.amazonaws.encryptionsdk.internal.Utils.bigIntegerToByteArray;
import static com.amazonaws.encryptionsdk.internal.Utils.encodeBase64String;
import static java.math.BigInteger.ONE;
import static java.math.BigInteger.ZERO;
import static org.apache.commons.lang3.Validate.isInstanceOf;
import static org.apache.commons.lang3.Validate.notNull;

/**
 * Provides a consistent interface across various trailing signature algorithms.
 *
 * NOTE: This is not a stable API and may undergo breaking changes in the future.
 */
public abstract class TrailingSignatureAlgorithm {

    private TrailingSignatureAlgorithm() {
        /* Do not allow arbitrary subclasses */
    }

    public abstract String getMessageDigestAlgorithm();
    public abstract String getRawSignatureAlgorithm();
    public abstract String getHashAndSignAlgorithm();
    public abstract PublicKey deserializePublicKey(String keyString);
    public abstract String serializePublicKey(PublicKey key);
    public abstract KeyPair generateKey() throws GeneralSecurityException;

    /* Standards for Efficient Cryptography over a prime field */
    private static final String SEC_PRIME_FIELD_PREFIX = "secp";

    private static final class ECDSASignatureAlgorithm extends TrailingSignatureAlgorithm {
        private final ECGenParameterSpec ecSpec;
        private final ECParameterSpec ecParameterSpec;
        private final String messageDigestAlgorithm;
        private final String hashAndSignAlgorithm;
        private static final String ELLIPTIC_CURVE_ALGORITHM = "EC";
        /* Constants used by SEC-1 v2 point compression and decompression algorithms */
        private static final BigInteger TWO = BigInteger.valueOf(2);
        private static final BigInteger THREE = BigInteger.valueOf(3);
        private static final BigInteger FOUR = BigInteger.valueOf(4);

        private ECDSASignatureAlgorithm(ECGenParameterSpec ecSpec, String messageDigestAlgorithm, String hashAndSignAlgorithm) {
            if (!ecSpec.getName().startsWith(SEC_PRIME_FIELD_PREFIX)) {
                throw new IllegalStateException("Non-prime curves are not supported at this time");
            }

            this.ecSpec = ecSpec;
            this.messageDigestAlgorithm = messageDigestAlgorithm;
            this.hashAndSignAlgorithm = hashAndSignAlgorithm;

            try {
                final AlgorithmParameters parameters = AlgorithmParameters.getInstance(ELLIPTIC_CURVE_ALGORITHM);
                parameters.init(ecSpec);
                this.ecParameterSpec = parameters.getParameterSpec(ECParameterSpec.class);
            } catch (NoSuchAlgorithmException | InvalidParameterSpecException e) {
                throw new IllegalStateException("Invalid algorithm", e);
            }
        }

        @Override
        public String toString() {
            return "ECDSASignatureAlgorithm(curve=" + ecSpec.getName() + ")";
        }

        @Override
        public String getMessageDigestAlgorithm() {
            return messageDigestAlgorithm;
        }

        @Override
        public String getRawSignatureAlgorithm() {
            return "NONEwithECDSA";
        }

        @Override
        public String getHashAndSignAlgorithm() {
            return hashAndSignAlgorithm;
        }

        /**
         * Decodes a compressed elliptic curve point as described in SEC-1 v2 section 2.3.4
         *
         * @param keyString The serialized and compressed public key
         * @return The PublicKey
         * @see http://www.secg.org/sec1-v2.pdf
         */
        @Override
        public PublicKey deserializePublicKey(String keyString) {
            notNull(keyString, "keyString is required");

            final byte[] decodedKey = Utils.decodeBase64String(keyString);
            final BigInteger x = new BigInteger(1, Arrays.copyOfRange(decodedKey, 1, decodedKey.length));

            final byte compressedY = decodedKey[0];
            final BigInteger yOrder;

            if (compressedY == TWO.byteValue()) {
                yOrder = ZERO;
            } else if (compressedY == THREE.byteValue()) {
                yOrder = ONE;
            } else {
                throw new IllegalArgumentException("Compressed y value was invalid");
            }

            final BigInteger p = ((ECFieldFp) ecParameterSpec.getCurve().getField()).getP();
            final BigInteger a = ecParameterSpec.getCurve().getA();
            final BigInteger b = ecParameterSpec.getCurve().getB();

            //alpha must be equal to y^2, this is validated below
            final BigInteger alpha = x.modPow(THREE, p)
                    .add(a.multiply(x).mod(p))
                    .add(b)
                    .mod(p);

            final BigInteger beta;
            if (p.mod(FOUR).equals(THREE)) {
                beta = alpha.modPow(p.add(ONE).divide(FOUR), p);
            } else {
                throw new IllegalArgumentException("Curve not supported at this time");
            }

            final BigInteger y = beta.mod(TWO).equals(yOrder) ? beta : p.subtract(beta);

            //Validate that Y is a root of Y^2 to prevent invalid point attacks
            if (!alpha.equals(y.modPow(TWO, p))) {
                throw new IllegalArgumentException("Y was invalid");
            }

            try {
                return KeyFactory.getInstance(ELLIPTIC_CURVE_ALGORITHM).generatePublic(
                        new ECPublicKeySpec(new ECPoint(x, y), ecParameterSpec));
            } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
                throw new IllegalStateException("Invalid algorithm", e);
            }
        }

        /**
         * Encodes a compressed elliptic curve point as described in SEC-1 v2 section 2.3.3
         *
         * @param key The Elliptic Curve public key to compress and serialize
         * @return The serialized and compressed public key
         * @see http://www.secg.org/sec1-v2.pdf
         */
        @Override
        public String serializePublicKey(PublicKey key) {
            notNull(key, "key is required");
            isInstanceOf(ECPublicKey.class, key, "key must be an instance of ECPublicKey");

            final BigInteger x = ((ECPublicKey) key).getW().getAffineX();
            final BigInteger y = ((ECPublicKey) key).getW().getAffineY();
            final BigInteger compressedY = y.mod(TWO).equals(ZERO) ? TWO : THREE;

            final byte[] xBytes = bigIntegerToByteArray(x,
                    ecParameterSpec.getCurve().getField().getFieldSize() / Byte.SIZE);

            final byte[] compressedKey = new byte[xBytes.length + 1];
            System.arraycopy(xBytes, 0, compressedKey, 1, xBytes.length);
            compressedKey[0] = compressedY.byteValue();

            return encodeBase64String(compressedKey);
        }

        @Override
        public KeyPair generateKey() throws GeneralSecurityException {
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ELLIPTIC_CURVE_ALGORITHM);
            keyGen.initialize(ecSpec, Utils.getSecureRandom());

            return keyGen.generateKeyPair();
        }
    }

    private static final ECDSASignatureAlgorithm SHA256_ECDSA_P256
            = new ECDSASignatureAlgorithm(new ECGenParameterSpec(SEC_PRIME_FIELD_PREFIX + "256r1"), "SHA-256", "SHA256withECDSA");
    private static final ECDSASignatureAlgorithm SHA384_ECDSA_P384
            = new ECDSASignatureAlgorithm(new ECGenParameterSpec(SEC_PRIME_FIELD_PREFIX + "384r1"), "SHA-384", "SHA384withECDSA");

    public static TrailingSignatureAlgorithm forCryptoAlgorithm(CryptoAlgorithm algorithm) {
        switch (algorithm) {
            case ALG_AES_128_GCM_IV12_TAG16_HKDF_SHA256_ECDSA_P256:
                return SHA256_ECDSA_P256;
            case ALG_AES_192_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384:
            case ALG_AES_256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384:
            case ALG_AES_256_GCM_HKDF_SHA512_COMMIT_KEY_ECDSA_P384:
                return SHA384_ECDSA_P384;
            default:
                throw new IllegalStateException("Algorithm does not support trailing signature");
        }
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy