/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.converter.crypto;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SignatureException;
import java.util.Date;
import org.apache.camel.Exchange;
import org.apache.camel.spi.DataFormat;
import org.apache.camel.util.ExchangeHelper;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.ObjectHelper;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.util.io.Streams;
/**
* <code>PGPDataFormat</code> uses the <a href="http://www.bouncycastle.org/java.htm">bouncy castle</a>
* libraries to enable encryption and decryption in the PGP format.
*/
public class PGPDataFormat implements DataFormat {
public static final String KEY_FILE_NAME = "CamelPGPDataFormatKeyFileName";
public static final String KEY_USERID = "CamelPGPDataFormatKeyUserid";
public static final String KEY_PASSWORD = "CamelPGPDataFormatKeyPassword";
public static final String SIGNATURE_KEY_FILE_NAME = "CamelPGPDataFormatSignatureKeyFileName";
public static final String SIGNATURE_KEY_USERID = "CamelPGPDataFormatSignatureKeyUserid";
public static final String SIGNATURE_KEY_PASSWORD = "CamelPGPDataFormatSignatureKeyPassword";
private static final int BUFFER_SIZE = 16 * 1024;
// encryption / decryption key info (required)
private String keyUserid;
private String password;
private String keyFileName;
// signature / verification key info (optional)
private String signatureKeyUserid;
private String signaturePassword;
private String signatureKeyFileName;
private boolean armored;
private boolean integrity = true;
public PGPDataFormat() {
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
protected String findKeyFileName(Exchange exchange) {
return exchange.getIn().getHeader(KEY_FILE_NAME, keyFileName, String.class);
}
protected String findKeyUserid(Exchange exchange) {
return exchange.getIn().getHeader(KEY_USERID, keyUserid, String.class);
}
protected String findKeyPassword(Exchange exchange) {
return exchange.getIn().getHeader(KEY_PASSWORD, password, String.class);
}
protected String findSignatureKeyFileName(Exchange exchange) {
return exchange.getIn().getHeader(SIGNATURE_KEY_FILE_NAME, signatureKeyFileName, String.class);
}
protected String findSignatureKeyUserid(Exchange exchange) {
return exchange.getIn().getHeader(SIGNATURE_KEY_USERID, signatureKeyUserid, String.class);
}
protected String findSignatureKeyPassword(Exchange exchange) {
return exchange.getIn().getHeader(SIGNATURE_KEY_PASSWORD, signaturePassword, String.class);
}
public void marshal(Exchange exchange, Object graph, OutputStream outputStream) throws Exception {
PGPPublicKey key = PGPDataFormatUtil.findPublicKey(exchange.getContext(), findKeyFileName(exchange), findKeyUserid(exchange), true);
if (key == null) {
throw new IllegalArgumentException("Public key is null, cannot proceed");
}
InputStream input = ExchangeHelper.convertToMandatoryType(exchange, InputStream.class, graph);
if (armored) {
outputStream = new ArmoredOutputStream(outputStream);
}
PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(PGPEncryptedData.CAST5, integrity, new SecureRandom(), "BC");
encGen.addMethod(key);
OutputStream encOut = encGen.open(outputStream, new byte[BUFFER_SIZE]);
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
OutputStream comOut = new BufferedOutputStream(comData.open(encOut));
PGPSignatureGenerator sigGen = createSignatureGenerator(exchange, comOut);
PGPLiteralDataGenerator litData = new PGPLiteralDataGenerator();
String fileName = exchange.getIn().getHeader(Exchange.FILE_NAME, String.class);
if (ObjectHelper.isEmpty(fileName)) {
// This marks the file as For Your Eyes Only... may cause problems for the receiver if they use
// an automated process to decrypt as the filename is appended with _CONSOLE
fileName = PGPLiteralData.CONSOLE;
}
OutputStream litOut = litData.open(comOut, PGPLiteralData.BINARY, fileName, new Date(), new byte[BUFFER_SIZE]);
try {
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
litOut.write(buffer, 0, bytesRead);
if (sigGen != null) {
sigGen.update(buffer, 0, bytesRead);
}
litOut.flush();
}
} finally {
IOHelper.close(litOut);
if (sigGen != null) {
sigGen.generate().encode(comOut);
}
IOHelper.close(comOut, encOut, outputStream, input);
}
}
protected PGPSignatureGenerator createSignatureGenerator(Exchange exchange, OutputStream out)
throws IOException, PGPException, NoSuchProviderException, NoSuchAlgorithmException {
String sigKeyFileName = findSignatureKeyFileName(exchange);
String sigKeyUserid = findSignatureKeyUserid(exchange);
String sigKeyPassword = findSignatureKeyPassword(exchange);
if (sigKeyFileName == null || sigKeyUserid == null || sigKeyPassword == null) {
return null;
}
PGPSecretKey sigSecretKey = PGPDataFormatUtil.findSecretKey(exchange.getContext(), sigKeyFileName, sigKeyPassword);
if (sigSecretKey == null) {
throw new IllegalArgumentException("Signature secret key is null, cannot proceed");
}
PGPPrivateKey sigPrivateKey = sigSecretKey.extractPrivateKey(sigKeyPassword.toCharArray(), "BC");
if (sigPrivateKey == null) {
throw new IllegalArgumentException("Signature private key is null, cannot proceed");
}
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
spGen.setSignerUserID(false, sigKeyUserid);
int algorithm = sigSecretKey.getPublicKey().getAlgorithm();
PGPSignatureGenerator sigGen = new PGPSignatureGenerator(algorithm, HashAlgorithmTags.SHA1, "BC");
sigGen.initSign(PGPSignature.BINARY_DOCUMENT, sigPrivateKey);
sigGen.setHashedSubpackets(spGen.generate());
sigGen.generateOnePassVersion(false).encode(out);
return sigGen;
}
public Object unmarshal(Exchange exchange, InputStream encryptedStream) throws Exception {
if (encryptedStream == null) {
return null;
}
PGPPrivateKey key = PGPDataFormatUtil.findPrivateKey(exchange.getContext(), findKeyFileName(exchange), encryptedStream, findKeyPassword(exchange));
if (key == null) {
throw new IllegalArgumentException("Private key is null, cannot proceed");
}
InputStream in;
try {
byte[] encryptedData = IOUtils.toByteArray(encryptedStream);
InputStream byteStream = new ByteArrayInputStream(encryptedData);
in = PGPUtil.getDecoderStream(byteStream);
} finally {
IOUtils.closeQuietly(encryptedStream);
}
PGPObjectFactory pgpFactory = new PGPObjectFactory(in);
Object o = pgpFactory.nextObject();
// the first object might be a PGP marker packet
PGPEncryptedDataList enc;
if (o instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) o;
} else {
enc = (PGPEncryptedDataList) pgpFactory.nextObject();
}
IOHelper.close(in);
PGPPublicKeyEncryptedData pbe = (PGPPublicKeyEncryptedData) enc.get(0);
InputStream encData = pbe.getDataStream(key, "BC");
pgpFactory = new PGPObjectFactory(encData);
PGPCompressedData comData = (PGPCompressedData) pgpFactory.nextObject();
pgpFactory = new PGPObjectFactory(comData.getDataStream());
Object object = pgpFactory.nextObject();
PGPOnePassSignature signature;
if (object instanceof PGPOnePassSignatureList) {
signature = getSignature(exchange, (PGPOnePassSignatureList) object);
object = pgpFactory.nextObject();
} else {
signature = null;
}
PGPLiteralData ld = (PGPLiteralData) object;
InputStream litData = ld.getInputStream();
byte[] answer;
try {
answer = Streams.readAll(litData);
} finally {
IOHelper.close(litData, encData, in);
}
if (signature != null) {
signature.update(answer);
PGPSignatureList sigList = (PGPSignatureList) pgpFactory.nextObject();
if (!signature.verify(sigList.get(0))) {
throw new SignatureException("Cannot verify PGP signature");
}
}
return answer;
}
protected PGPOnePassSignature getSignature(Exchange exchange, PGPOnePassSignatureList signatureList)
throws IOException, PGPException, NoSuchProviderException {
PGPPublicKey sigPublicKey = PGPDataFormatUtil.findPublicKey(exchange.getContext(), findSignatureKeyFileName(exchange), findSignatureKeyUserid(exchange), false);
if (sigPublicKey == null) {
throw new IllegalArgumentException("Signature public key is null, cannot proceed");
}
PGPOnePassSignature signature = signatureList.get(0);
signature.initVerify(sigPublicKey, "BC");
return signature;
}
/**
* Sets if the encrypted file should be written in ascii visible text
*/
public void setArmored(boolean armored) {
this.armored = armored;
}
public boolean getArmored() {
return this.armored;
}
/**
* Whether or not to add a integrity check/sign to the encrypted file
*/
public void setIntegrity(boolean integrity) {
this.integrity = integrity;
}
public boolean getIntegrity() {
return this.integrity;
}
/**
* Userid of the key used to encrypt/decrypt
*/
public void setKeyUserid(String keyUserid) {
this.keyUserid = keyUserid;
}
public String getKeyUserid() {
return keyUserid;
}
/**
* filename of the keyring that will be used, classpathResource
*/
public void setKeyFileName(String keyFileName) {
this.keyFileName = keyFileName;
}
public String getKeyFileName() {
return keyFileName;
}
/**
* Password used to open the private keyring
*/
public void setPassword(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
/**
* Userid of the signature key used to sign/verify
*/
public void setSignatureKeyUserid(String signatureKeyUserid) {
this.signatureKeyUserid = signatureKeyUserid;
}
public String getSignatureKeyUserid() {
return signatureKeyUserid;
}
/**
* filename of the signature keyring that will be used, classpathResource
*/
public void setSignatureKeyFileName(String signatureKeyFileName) {
this.signatureKeyFileName = signatureKeyFileName;
}
public String getSignatureKeyFileName() {
return signatureKeyFileName;
}
/**
* Password used to open the signature private keyring
*/
public void setSignaturePassword(String signaturePassword) {
this.signaturePassword = signaturePassword;
}
public String getSignaturePassword() {
return signaturePassword;
}
}