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

Java tutorial

Introduction

Here is the source code for org.pgptool.gui.encryption.implpgp.KeyFilesOperationsPgpImpl.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.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.sql.Date;
import java.util.Iterator;
import java.util.Stack;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.pgptool.gui.encryption.api.KeyFilesOperations;
import org.pgptool.gui.encryption.api.dto.Key;
import org.pgptool.gui.encryption.api.dto.KeyInfo;
import org.pgptool.gui.encryption.api.dto.KeyTypeEnum;
import org.pgptool.gui.tools.IoStreamUtils;
import org.springframework.util.StringUtils;
import org.summerb.approaches.security.api.exceptions.InvalidPasswordException;
import org.summerb.approaches.validation.FieldValidationException;
import org.summerb.approaches.validation.ValidationError;

import com.google.common.base.Preconditions;

public class KeyFilesOperationsPgpImpl implements KeyFilesOperations {
    private static Logger log = Logger.getLogger(KeyFilesOperationsPgpImpl.class);

    /**
     * Considering this as not a violation to DI since I don't see scenarios when
     * we'll need to change this
     */
    protected final static BcKeyFingerprintCalculator fingerprintCalculator = new BcKeyFingerprintCalculator();

    @SuppressWarnings("deprecation")
    @Override
    public Key readKeyFromFile(String filePathName) {
        try {
            KeyDataPgp keyData = readKeyData(filePathName);

            Key key = new Key();
            key.setKeyData(keyData);
            if (keyData.getSecretKeyRing() != null) {
                key.setKeyInfo(buildKeyInfoFromSecret(keyData.getSecretKeyRing()));
            } else {
                key.setKeyInfo(buildKeyInfoFromPublic(keyData.getPublicKeyRing()));
            }
            return key;
        } catch (Throwable t) {
            throw new RuntimeException("Can't read key file", t);
        }
    }

    private KeyInfo buildKeyInfoFromPublic(PGPPublicKeyRing publicKeyRing) throws PGPException {
        KeyInfo ret = new KeyInfo();
        ret.setKeyType(KeyTypeEnum.Public);
        PGPPublicKey key = publicKeyRing.getPublicKey();
        ret.setUser(buildUser(key.getUserIDs()));

        ret.setKeyId(KeyDataPgp.buildKeyIdStr(key.getKeyID()));
        fillDates(ret, key);
        fillAlgorithmName(ret, key);
        return ret;
    }

    private static void fillDates(KeyInfo ret, PGPPublicKey key) {
        ret.setCreatedOn(new Date(key.getCreationTime().getTime()));
        if (key.getValidSeconds() != 0) {
            java.util.Date expiresAt = DateUtils.addSeconds(key.getCreationTime(), (int) key.getValidSeconds());
            ret.setExpiresAt(new Date(expiresAt.getTime()));
        }
    }

    private static void fillAlgorithmName(KeyInfo ret, PGPPublicKey key) throws PGPException {
        String alg = resolveAlgorithm(key);
        if (alg == null) {
            ret.setKeyAlgorithm("unresolved");
        } else {
            ret.setKeyAlgorithm(alg + " " + key.getBitStrength() + "bit");
        }
    }

    @SuppressWarnings("rawtypes")
    private static String resolveAlgorithm(PGPPublicKey key) throws PGPException {
        for (Iterator iter = key.getSignatures(); iter.hasNext();) {
            PGPSignature sig = (PGPSignature) iter.next();
            return PGPUtil.getSignatureName(sig.getKeyAlgorithm(), sig.getHashAlgorithm());
        }
        return null;
    }

    protected static KeyInfo buildKeyInfoFromSecret(PGPSecretKeyRing secretKeyRing) throws PGPException {
        KeyInfo ret = new KeyInfo();
        ret.setKeyType(KeyTypeEnum.KeyPair);
        PGPPublicKey key = secretKeyRing.getPublicKey();
        ret.setUser(buildUser(key.getUserIDs()));

        ret.setKeyId(KeyDataPgp.buildKeyIdStr(key.getKeyID()));
        fillDates(ret, key);
        fillAlgorithmName(ret, key);
        return ret;
    }

    @SuppressWarnings("rawtypes")
    private static String buildUser(Iterator userIDs) {
        Object ret = userIDs.next();
        return (String) ret;
    }

    @SuppressWarnings("rawtypes")
    public static KeyDataPgp readKeyData(String filePathName) {
        KeyDataPgp data = new KeyDataPgp();

        try (FileInputStream stream = new FileInputStream(new File(filePathName))) {
            PGPObjectFactory factory = new PGPObjectFactory(PGPUtil.getDecoderStream(stream),
                    fingerprintCalculator);
            for (Iterator iter = factory.iterator(); iter.hasNext();) {
                Object section = iter.next();
                log.debug("Section found: " + section);

                if (section instanceof PGPSecretKeyRing) {
                    data.setSecretKeyRing((PGPSecretKeyRing) section);
                } else if (section instanceof PGPPublicKeyRing) {
                    data.setPublicKeyRing((PGPPublicKeyRing) section);
                } else {
                    log.error("Unknown section enountered in a key file: " + section);
                }
            }
        } catch (Throwable t) {
            throw new RuntimeException("Error happenedd while parsing key file", t);
        }

        if (data.getPublicKeyRing() == null && data.getSecretKeyRing() == null) {
            throw new RuntimeException("Neither Secret nor Public keys were found in the input file");
        }

        return data;
    }

    @Override
    public void exportPublicKey(Key key, String targetFilePathname) {
        Preconditions.checkArgument(key != null && key.getKeyData() != null && key.getKeyInfo() != null,
                "Key must be providedand fully described");
        Preconditions.checkArgument(StringUtils.hasText(targetFilePathname), "targetFilePathname must be provided");
        Stack<OutputStream> os = new Stack<>();
        try {
            os.push(new FileOutputStream(targetFilePathname));
            if ("asc".equalsIgnoreCase(FilenameUtils.getExtension(targetFilePathname))) {
                os.push(new ArmoredOutputStream(os.peek()));
            }
            KeyDataPgp keyDataPgp = KeyDataPgp.get(key);
            if (keyDataPgp.getPublicKeyRing() != null) {
                keyDataPgp.getPublicKeyRing().encode(os.peek());
            } else {
                keyDataPgp.getSecretKeyRing().getPublicKey().encode(os.peek());
            }
        } catch (Throwable t) {
            throw new RuntimeException(
                    "Failed to export public key " + key.getKeyInfo().getUser() + " to " + targetFilePathname, t);
        } finally {
            while (!os.isEmpty()) {
                IoStreamUtils.safeClose(os.pop());
            }
        }
    }

    @Override
    public void exportPrivateKey(Key key, String targetFilePathname) {
        Preconditions.checkArgument(key != null && key.getKeyData() != null && key.getKeyInfo() != null,
                "Key must be providedand fully described");
        KeyDataPgp keyDataPgp = KeyDataPgp.get(key);
        Preconditions.checkArgument(keyDataPgp.getSecretKeyRing() != null, "KeyPair key wasn't provided");
        Preconditions.checkArgument(StringUtils.hasText(targetFilePathname), "targetFilePathname must be provided");
        Stack<OutputStream> os = new Stack<>();
        try {
            os.push(new FileOutputStream(targetFilePathname));
            if ("asc".equalsIgnoreCase(FilenameUtils.getExtension(targetFilePathname))) {
                os.push(new ArmoredOutputStream(os.peek()));
            }
            keyDataPgp.getSecretKeyRing().encode(os.peek());
            if (keyDataPgp.getPublicKeyRing() != null) {
                keyDataPgp.getPublicKeyRing().encode(os.peek());
            }
        } catch (Throwable t) {
            throw new RuntimeException(
                    "Failed to export private key " + key.getKeyInfo().getUser() + " to " + targetFilePathname, t);
        } finally {
            while (!os.isEmpty()) {
                IoStreamUtils.safeClose(os.pop());
            }
        }
    }

    @SuppressWarnings("deprecation")
    @Override
    public void validateDecryptionKeyPassword(String requestedKeyId, Key key, String password)
            throws FieldValidationException {
        try {
            PGPSecretKey secretKey = KeyDataPgp.get(key).findSecretKeyById(requestedKeyId);
            Preconditions.checkArgument(secretKey != null, "Matching secret key wasn't found");
            PGPPrivateKey privateKey = getPrivateKey(password, secretKey);
            Preconditions.checkArgument(privateKey != null, "Failed to extract private key");
        } catch (InvalidPasswordException pe) {
            throw new FieldValidationException(new ValidationError(pe.getMessageCode(), FN_PASSWORD));
        } catch (Throwable t) {
            throw new RuntimeException("Failed to verify key password", t);
        }
    }

    private PGPPrivateKey getPrivateKey(String passphrase, PGPSecretKey secretKey) throws InvalidPasswordException {
        try {
            PBESecretKeyDecryptor decryptorFactory = new BcPBESecretKeyDecryptorBuilder(
                    new BcPGPDigestCalculatorProvider()).build(passphrase.toCharArray());
            PGPPrivateKey privateKey = secretKey.extractPrivateKey(decryptorFactory);
            return privateKey;
        } catch (Throwable t) {
            log.warn("Failed to extract private key. Most likely it because of incorrect passphrase provided", t);
            throw new InvalidPasswordException();
        }
    }

}