Java tutorial
/* 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(); } }