org.pgptool.gui.encryption.implpgp.KeyGeneratorServicePgpImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.pgptool.gui.encryption.implpgp.KeyGeneratorServicePgpImpl.java

Source

/*******************************************************************************
 * PGPTool is a desktop application for pgp encryption/decryption
 * Copyright (C) 2017 Sergey Karpushin
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *******************************************************************************/
package org.pgptool.gui.encryption.implpgp;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import javax.crypto.spec.DHParameterSpec;

import org.apache.log4j.Logger;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
import org.pgptool.gui.encryption.api.KeyGeneratorService;
import org.pgptool.gui.encryption.api.dto.CreateKeyParams;
import org.pgptool.gui.encryption.api.dto.Key;
import org.springframework.beans.factory.annotation.Autowired;
import org.summerb.approaches.validation.FieldValidationException;
import org.summerb.approaches.validation.ValidationContext;

import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;

public class KeyGeneratorServicePgpImpl implements KeyGeneratorService {
    private static Logger log = Logger.getLogger(KeyGeneratorServicePgpImpl.class);

    @Autowired
    private ExecutorService executorService;

    // TODO: Shouldn't I generate it each time. Is it safe to have it hardcoded?
    BigInteger g = new BigInteger(
            "153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc",
            16);
    BigInteger p = new BigInteger(
            "9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b",
            16);
    private static DsaKeyPairParams DEFAULT_DSA_KEY_PARAMETERS = new DsaKeyPairParams("DSA", "BC", 2048);

    private Map<DsaKeyPairParams, Future<KeyPair>> pregeneratedDsaKeyPairs = new ConcurrentHashMap<>();

    public KeyGeneratorServicePgpImpl() {
        KeyRingServicePgpImpl.touch();
    }

    @Override
    public Key createNewKey(CreateKeyParams params) throws FieldValidationException {
        try {
            Preconditions.checkArgument(params != null, "params must not be null");
            assertParamsValid(params);

            // Create KeyPairs
            KeyPair dsaKp = getOrGenerateDsaKeyPair(DEFAULT_DSA_KEY_PARAMETERS);
            KeyPairGenerator elgKpg = KeyPairGenerator.getInstance("ELGAMAL", "BC");
            DHParameterSpec elParams = new DHParameterSpec(p, g);
            elgKpg.initialize(elParams);
            KeyPair elgKp = elgKpg.generateKeyPair();

            // Now let do some crazy stuff (I HAVE NO IDEA WHAT I AM DOING
            // HERE). BouncyCastle guys are not helping by changing API from
            // one version to another so often!!!!!!!
            PGPKeyPair dsaKeyPair = new JcaPGPKeyPair(PGPPublicKey.DSA, dsaKp, new Date());
            PGPKeyPair elgKeyPair = new JcaPGPKeyPair(PGPPublicKey.ELGAMAL_ENCRYPT, elgKp, new Date());

            // PGPContentSignerBuilde
            // JCA
            // JcaPGPContentSignerBuilder keySignerBuilder = new
            // JcaPGPContentSignerBuilder(
            // dsaKeyPair.getPublicKey().getAlgorithm(),
            // HashAlgorithmTags.SHA256);

            // BC
            BcPGPContentSignerBuilder keySignerBuilderBC = new BcPGPContentSignerBuilder(
                    dsaKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA256);

            // PGPDigestCalculator
            // JCA
            // PGPDigestCalculator sha1Calc = new
            // JcaPGPDigestCalculatorProviderBuilder().build()
            // .get(HashAlgorithmTags.SHA256);

            // BC
            PGPDigestCalculator sha1CalcBC = new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1);

            // keyEncryptor
            // BC
            BcPBESecretKeyEncryptorBuilder encryptorBuilderBC = new BcPBESecretKeyEncryptorBuilder(
                    PGPEncryptedData.AES_256, sha1CalcBC);
            PBESecretKeyEncryptor keyEncryptorBC = encryptorBuilderBC.build(params.getPassphrase().toCharArray());

            // JCA
            // JcePBESecretKeyEncryptorBuilder encryptorBuilder = new
            // JcePBESecretKeyEncryptorBuilder(
            // PGPEncryptedData.AES_256, sha1Calc).setProvider("BC");
            // PBESecretKeyEncryptor keyEncryptor =
            // encryptorBuilder.build(params.getPassphrase().toCharArray());

            // keyRingGen
            String userName = params.getFullName() + " <" + params.getEmail() + ">";
            // JCA
            // PGPKeyRingGenerator keyRingGen = new
            // PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
            // dsaKeyPair,
            // userName, sha1Calc, null, null, keySignerBuilder,
            // keyEncryptor);

            // BC
            PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION,
                    dsaKeyPair, userName, sha1CalcBC, null, null, keySignerBuilderBC, keyEncryptorBC);

            keyRingGen.addSubKey(elgKeyPair);
            // building ret
            Key ret = buildKey(keyRingGen);
            return ret;
        } catch (Throwable t) {
            Throwables.propagateIfInstanceOf(t, FieldValidationException.class);
            throw new RuntimeException("Failed to generate key", t);
        }
    }

    @SuppressWarnings("deprecation")
    private Key buildKey(PGPKeyRingGenerator keyRingGen) throws PGPException {
        Key ret = new Key();
        KeyDataPgp keyData = new KeyDataPgp();
        keyData.setPublicKeyRing(keyRingGen.generatePublicKeyRing());
        keyData.setSecretKeyRing(keyRingGen.generateSecretKeyRing());
        ret.setKeyData(keyData);
        ret.setKeyInfo(KeyFilesOperationsPgpImpl.buildKeyInfoFromSecret(keyData.getSecretKeyRing()));
        return ret;
    }

    /**
     * NOTE: It feels like a little over-engineered thing since generation takes
     * like 1 second not that long as it was advertised. So perhaps we might decide
     * to get rid of it and run it on demand
     * 
     * @param params
     * @return
     * @throws Exception
     */
    private KeyPair getOrGenerateDsaKeyPair(DsaKeyPairParams params) throws Exception {
        Future<KeyPair> future = pregeneratedDsaKeyPairs.remove(params);
        if (future == null) {
            return PrecalculateDsaKeyPair.generateDsaKeyPair(params);
        }
        return future.get();
    }

    private void assertParamsValid(CreateKeyParams params) throws FieldValidationException {
        ValidationContext ctx = new ValidationContext();

        ctx.validateNotEmpty(params.getFullName(), CreateKeyParams.FN_FULL_NAME);
        if (ctx.validateNotEmpty(params.getEmail(), CreateKeyParams.FN_EMAIL)) {
            ctx.validateEmailFormat(params.getEmail(), CreateKeyParams.FN_EMAIL);
        }
        if (ctx.validateNotEmpty(params.getPassphrase(), CreateKeyParams.FN_PASSPHRASE)
                && ctx.validateNotEmpty(params.getPassphraseAgain(), CreateKeyParams.FN_PASSPHRASE_AGAIN)) {
            ctx.equals(params.getPassphrase(), "term." + CreateKeyParams.FN_PASSPHRASE, params.getPassphraseAgain(),
                    "term." + CreateKeyParams.FN_PASSPHRASE_AGAIN, CreateKeyParams.FN_PASSPHRASE_AGAIN);
        }

        if (ctx.getHasErrors()) {
            throw new FieldValidationException(ctx.getErrors());
        }
    }

    @Override
    public void expectNewKeyCreation() {
        precalculateKeyPair(DEFAULT_DSA_KEY_PARAMETERS);
    }

    private void precalculateKeyPair(DsaKeyPairParams params) {
        Future<KeyPair> future = executorService.submit(new PrecalculateDsaKeyPair(params));
        pregeneratedDsaKeyPairs.put(params, future);
    }

    public static class PrecalculateDsaKeyPair implements Callable<KeyPair> {
        private DsaKeyPairParams dsaKeyPairParams;

        public PrecalculateDsaKeyPair(DsaKeyPairParams dsaKeyPairParams) {
            this.dsaKeyPairParams = dsaKeyPairParams;
        }

        @Override
        public KeyPair call() throws Exception {
            log.debug("Invoking pregeneration of DSA key pair " + dsaKeyPairParams);
            try {
                return generateDsaKeyPair(dsaKeyPairParams);
            } finally {
                log.debug("Invocation completed " + dsaKeyPairParams);
            }
        }

        public static KeyPair generateDsaKeyPair(DsaKeyPairParams dsaKeyPairParams) throws Exception {
            try {
                log.debug("Calculating DSA KeyPair " + dsaKeyPairParams);
                KeyPairGenerator dsaKpg = KeyPairGenerator.getInstance(dsaKeyPairParams.algorithm,
                        dsaKeyPairParams.provider);
                dsaKpg.initialize(dsaKeyPairParams.keysize);
                log.info("Started key generation");
                KeyPair dsaKp = dsaKpg.generateKeyPair();
                log.info("Key generation is complete");
                return dsaKp;
            } catch (Throwable t) {
                log.error("Failed to generate DSA keypair " + dsaKeyPairParams, t);
                throw new Exception("Failed to generate DSA keypair" + dsaKeyPairParams, t);
            }
        }
    }

    public static class DsaKeyPairParams {
        String algorithm;
        String provider;
        int keysize;

        public DsaKeyPairParams(String algorithm, String provider, int keysize) {
            this.algorithm = algorithm;
            this.provider = provider;
            this.keysize = keysize;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((algorithm == null) ? 0 : algorithm.hashCode());
            result = prime * result + keysize;
            result = prime * result + ((provider == null) ? 0 : provider.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            DsaKeyPairParams other = (DsaKeyPairParams) obj;
            if (algorithm == null) {
                if (other.algorithm != null) {
                    return false;
                }
            } else if (!algorithm.equals(other.algorithm)) {
                return false;
            }
            if (keysize != other.keysize) {
                return false;
            }
            if (provider == null) {
                if (other.provider != null) {
                    return false;
                }
            } else if (!provider.equals(other.provider)) {
                return false;
            }
            return true;
        }

        @Override
        public String toString() {
            return "DsaKeyPairParams [algorithm=" + algorithm + ", provider=" + provider + ", keysize=" + keysize
                    + "]";
        }
    }

}