org.opentestsystem.delivery.testreg.transformer.GPGEncryptor.java Source code

Java tutorial

Introduction

Here is the source code for org.opentestsystem.delivery.testreg.transformer.GPGEncryptor.java

Source

/*
Educational Online Test Delivery System Copyright (c) 2013 American Institutes for Research
    
Distributed under the AIR Open Source License, Version 1.0 See accompanying file AIR-License-1_0.txt or at
http://www.smarterapp.org/documents/American_Institutes_for_Research_Open_Source_Software_License.pdf
 */

package org.opentestsystem.delivery.testreg.transformer;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SignatureException;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import org.opentestsystem.delivery.testreg.transformer.domain.DwConfigs;
import org.opentestsystem.delivery.testreg.transformer.domain.DwConfigs.DwConfigType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.Message;
import org.springframework.integration.annotation.Header;
import org.springframework.integration.annotation.Transformer;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.stereotype.Component;

@Component("gpgEncryptor")
public class GPGEncryptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(GPGEncryptor.class);

    @Value("${gpgKeyring.public.location}")
    private String publicKeyringLocation;

    @Value("${gpgKeyring.secret.location}")
    private String secretKeyringLocation;

    @Value("${testreg.secret.key.userid}")
    private String testRegSecretKeyUserId;

    // This must be a full path to a highly secured file
    @Value("#{ systemProperties['testreg.secret.passphrase.file'] }")
    private String passphraseFilePath;

    @Autowired
    private DwConfigs dwConfigs;

    private static final int BUFFER_SIZE = 4096;

    static {
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * Uses the Legion of the Bouncy Castle (aka BouncyCastle) PGP API to encrypt, compress, and sign the input.
     * 
     * The configured landing zone public key is used to encrypt the input. Only the landing zone private key will be
     * able to decrypt.
     * 
     * The configured test registration private key is used to sign the input. This can be verified by the landing zone
     * to prove that this specific test registration instance created the data.
     * 
     * @param input
     *            A byte array
     * @return A byte array comprised of a PGP/GPG compatible binary encrypted and signed output
     */
    @Transformer
    public Message<File> encryptStream(final File input, final @Header("dwBatchUuid") String dwBatchUuid,
            final @Header("fileSuffix") String fileSuffix, final @Header("recordsSent") int recordsSent,
            final @Header("tempPaths") List<Path> tempPaths,
            final @Header("dwConfigType") DwConfigType dwConfigType) {

        String debugPrefix = dwConfigType + " DW Config: ";

        long curTime = System.currentTimeMillis();
        File tmpEncFile;

        try {

            PGPPublicKey landingZonePubKey = findLandingZonePublicKey(dwConfigType);
            PGPSecretKey testRegSecretKey = findTestRegSecretKey();

            PGPPrivateKey testRegPrivateKey = testRegSecretKey.extractPrivateKey(
                    new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(getPassphrase()));

            // ////////////////////
            // setup encryptor

            PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
                    new JcePGPDataEncryptorBuilder(PGPEncryptedData.AES_256).setWithIntegrityPacket(true)
                            .setSecureRandom(new SecureRandom()).setProvider("BC"));

            encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(landingZonePubKey).setProvider("BC"));

            // This outputstream, encryptedSignedOutputStream is the ultimate target that will contain the encrypted and
            // signed output
            Path tempEncPath = Files.createTempFile(DwBatchHandler.DW_ENC_TMP_PREFIX,
                    (dwConfigType == DwConfigType.SBAC ? DwBatchHandler.SBAC_DW_NAME : DwBatchHandler.LOCAL_DW_NAME)
                            + fileSuffix);
            tempPaths.add(tempEncPath);
            tmpEncFile = tempEncPath.toFile();
            FileOutputStream encryptedSignedOutputStream = new FileOutputStream(tmpEncFile);

            LOGGER.debug(debugPrefix + "Created temp encrypted output file " + tmpEncFile.getAbsolutePath());

            OutputStream encryptOutStream = encGen.open(encryptedSignedOutputStream, new byte[BUFFER_SIZE]);

            // ////////////////////////////
            // setup data compression

            PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
            OutputStream compressOutStream = comData.open(encryptOutStream);

            // /////////////////////
            // sign encrypted file with test reg private key

            // create a signature generator
            PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
                    new JcaPGPContentSignerBuilder(testRegSecretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1)
                            .setProvider("BC"));
            signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, testRegPrivateKey);

            @SuppressWarnings("unchecked")
            Iterator<String> it = testRegSecretKey.getPublicKey().getUserIDs();

            if (it.hasNext()) {
                PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
                spGen.setSignerUserID(false, it.next());
                signatureGenerator.setHashedSubpackets(spGen.generate());
            }

            // setup signature generator to encode the contents of the compressed output stream
            signatureGenerator.generateOnePassVersion(false).encode(compressOutStream);

            // create a PGP Literal Data Generator and open it to wrap the compression output stream
            PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();

            OutputStream signedOutStream = lGen.open(compressOutStream, PGPLiteralData.BINARY, "",
                    new java.util.Date(), new byte[BUFFER_SIZE]);

            // create an input stream out of the input bytes
            FileInputStream clearInputStream = new FileInputStream(input);

            // read the input and write all data to the signing output stream, also update the signature
            // generator with the same input data
            byte[] buf = new byte[BUFFER_SIZE];
            int len;
            while ((len = clearInputStream.read(buf)) > 0) {
                signedOutStream.write(buf, 0, len);
                signatureGenerator.update(buf, 0, len);
            }

            // close everything and generate the final signature
            signedOutStream.close();
            lGen.close();
            signatureGenerator.generate().encode(compressOutStream);
            compressOutStream.close();
            comData.close();
            encryptOutStream.close();
            encGen.close();
            clearInputStream.close();

            encryptedSignedOutputStream.close();

        } catch (IOException | PGPException | SignatureException e) {
            throw new GPGEncryptionException(debugPrefix + "Failure to encrypt and sign input", e);
        }

        LOGGER.debug(debugPrefix + "Generated encrypted data in " + (System.currentTimeMillis() - curTime));

        return MessageBuilder.withPayload(tmpEncFile).setHeader("dwBatchUuid", dwBatchUuid)
                .setHeader("fileSuffix", fileSuffix).setHeader("recordsSent", recordsSent)
                .setHeader("tempPaths", tempPaths).setHeader("dwConfigType", dwConfigType).build();
    }

    public InputStream getStreamForPath(final String path) {

        InputStream theStream = null;

        try {
            theStream = new BufferedInputStream(new FileInputStream(path));
        } catch (FileNotFoundException e) {
            // if we got here, then perhaps the path was a relative path somewhere in the classpath?
            theStream = new BufferedInputStream(this.getClass().getClassLoader().getResourceAsStream(path));
        }

        return theStream;
    }

    @SuppressWarnings("unchecked")
    public PGPPublicKey findLandingZonePublicKey(DwConfigType configType) throws PGPException, IOException {
        // get public key for the landing zone

        String lzPubKeyUserId = null;

        if (configType == DwConfigType.SBAC) {
            lzPubKeyUserId = dwConfigs.getSbacConfig().getLzPubKeyUserid();
        } else {
            lzPubKeyUserId = dwConfigs.getLocalConfig().getLzPubKeyUserid();
        }

        InputStream publicKeyringInputStream = getStreamForPath(publicKeyringLocation);

        PGPPublicKeyRingCollection pgpPubkeyringCollection = new PGPPublicKeyRingCollection(
                PGPUtil.getDecoderStream(publicKeyringInputStream));

        Iterator<PGPPublicKeyRing> pubkeyringItr = pgpPubkeyringCollection.getKeyRings(lzPubKeyUserId, true);

        PGPPublicKey landingZonePubKey = null;

        while (pubkeyringItr.hasNext()) {

            PGPPublicKeyRing keyring = pubkeyringItr.next();
            Iterator<PGPPublicKey> pubkeyItr = keyring.getPublicKeys();

            while (pubkeyItr.hasNext()) {

                PGPPublicKey pubkey = pubkeyItr.next();

                if (pubkey.isEncryptionKey()) {
                    landingZonePubKey = pubkey;
                    break;
                }

            }

        }

        return landingZonePubKey;
    }

    @SuppressWarnings("unchecked")
    public PGPSecretKey findTestRegSecretKey() throws IOException, PGPException {

        // get private key for the running test reg
        InputStream secretKeyringInputStream = getStreamForPath(secretKeyringLocation);

        PGPSecretKeyRingCollection pgpSecretkeyringCollection = new PGPSecretKeyRingCollection(
                PGPUtil.getDecoderStream(secretKeyringInputStream));

        Iterator<PGPSecretKeyRing> secretKeyRingItr = pgpSecretkeyringCollection.getKeyRings(testRegSecretKeyUserId,
                true);

        PGPSecretKey testRegSecretKey = null;

        while (secretKeyRingItr.hasNext()) {
            PGPSecretKeyRing secretKeyRing = secretKeyRingItr.next();

            Iterator<PGPSecretKey> secretKeyItr = secretKeyRing.getSecretKeys();

            while (secretKeyItr.hasNext()) {
                PGPSecretKey secretKey = secretKeyItr.next();

                if (secretKey.isSigningKey()) {
                    testRegSecretKey = secretKey;
                    break;
                }
            }

        }

        return testRegSecretKey;
    }

    private char[] getPassphrase() {

        // load props file and convert the passphrase to bytes
        Properties props = new Properties();

        FileInputStream fis = null;

        try {
            fis = new FileInputStream(passphraseFilePath);
            props.load(fis);
        } catch (IOException e) {
            return new char[] {};
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                // probably don't need to do anything here
            }
        }

        String passphrase = props.getProperty("testreg.secret.passphrase");

        char[] passphraseChars = passphrase.toCharArray();

        // really shouldn't have to do the assign to null here. these vars _should_ be eligible for GC
        passphrase = null;
        props = null;
        fis = null;

        // passphraseChars should be out of scope here and eligible for GC

        return passphraseChars.clone();
    }
}