Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| /** | |
| * 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.IOException; | |
| import java.io.InputStream; | |
| import java.io.OutputStream; | |
| import java.security.SecureRandom; | |
| import java.security.Security; | |
| import java.security.SignatureException; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collections; | |
| import java.util.Date; | |
| import java.util.List; | |
| import org.apache.camel.Exchange; | |
| import org.apache.camel.converter.stream.OutputStreamBuilder; | |
| import org.apache.camel.spi.DataFormat; | |
| import org.apache.camel.spi.DataFormatName; | |
| import org.apache.camel.support.ServiceSupport; | |
| import org.apache.camel.util.ExchangeHelper; | |
| import org.apache.camel.util.IOHelper; | |
| import org.bouncycastle.bcpg.ArmoredOutputStream; | |
| import org.bouncycastle.bcpg.CompressionAlgorithmTags; | |
| import org.bouncycastle.bcpg.HashAlgorithmTags; | |
| import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; | |
| import org.bouncycastle.jce.provider.BouncyCastleProvider; | |
| import org.bouncycastle.openpgp.PGPCompressedData; | |
| import org.bouncycastle.openpgp.PGPCompressedDataGenerator; | |
| 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.PGPSignature; | |
| import org.bouncycastle.openpgp.PGPSignatureGenerator; | |
| import org.bouncycastle.openpgp.PGPSignatureList; | |
| import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; | |
| import org.bouncycastle.openpgp.PGPUtil; | |
| import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; | |
| import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; | |
| import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; | |
| import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; | |
| import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; | |
| import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; | |
| import org.slf4j.Logger; | |
| import org.slf4j.LoggerFactory; | |
| /** | |
| * This PGP Data Format uses the interfaces {@link PGPPublicKeyAccessor} and | |
| * {@link PGPSecretKeyAccessor} to access the keys for encryption/signing and | |
| * decryption/signature verification. These interfaces allow caching of the keys | |
| * which can improve the performance. | |
| * <p> | |
| * If you want to provide the key access via keyrings in the format of a byte | |
| * array or file, then you should use the class {@link PGPDataFormat}. | |
| * | |
| */ | |
| public class PGPKeyAccessDataFormat extends ServiceSupport implements DataFormat, DataFormatName { | |
| public static final String KEY_USERID = "CamelPGPDataFormatKeyUserid"; | |
| public static final String KEY_USERIDS = "CamelPGPDataFormatKeyUserids"; | |
| public static final String SIGNATURE_KEY_USERID = "CamelPGPDataFormatSignatureKeyUserid"; | |
| public static final String SIGNATURE_KEY_USERIDS = "CamelPGPDataFormatSignatureKeyUserids"; | |
| public static final String ENCRYPTION_ALGORITHM = "CamelPGPDataFormatEncryptionAlgorithm"; | |
| public static final String SIGNATURE_HASH_ALGORITHM = "CamelPGPDataFormatSignatureHashAlgorithm"; | |
| public static final String COMPRESSION_ALGORITHM = "CamelPGPDataFormatCompressionAlgorithm"; | |
| /** | |
| * Signature verification option "optional": Used during unmarshaling. The | |
| * PGP message can or cannot contain signatures. If it does contain | |
| * signatures then one of them is verified. This is the default option. | |
| */ | |
| public static final String SIGNATURE_VERIFICATION_OPTION_OPTIONAL = "optional"; | |
| /** | |
| * Signature verification option "required": Used during unmarshaling. It is | |
| * checked that the PGP message does contain at least one signature. If this | |
| * is not the case a {@link PGPException} is thrown. One of the contained | |
| * signatures is verified. | |
| */ | |
| public static final String SIGNATURE_VERIFICATION_OPTION_REQUIRED = "required"; | |
| /** | |
| * Signature verification option "required": Used during unmarshaling. If | |
| * the PGP message contains signatures then they are ignored. No | |
| * verification takes place. | |
| */ | |
| public static final String SIGNATURE_VERIFICATION_OPTION_IGNORE = "ignore"; | |
| /** | |
| * Signature verification option "no signature allowed": Used during | |
| * unmarshaling. It is checked that the PGP message does contain not any | |
| * signatures. If this is not the case a {@link PGPException} is thrown. | |
| */ | |
| public static final String SIGNATURE_VERIFICATION_OPTION_NO_SIGNATURE_ALLOWED = "no_signature_allowed"; | |
| /** | |
| * During encryption the number of asymmetric encryption keys is set to this | |
| * header parameter. The Value is of type Integer. | |
| */ | |
| public static final String NUMBER_OF_ENCRYPTION_KEYS = "CamelPGPDataFormatNumberOfEncryptionKeys"; | |
| /** | |
| * During signing the number of signing keys is set to this header | |
| * parameter. This corresponds to the number of signatures. The Value is of | |
| * type Integer. | |
| */ | |
| public static final String NUMBER_OF_SIGNING_KEYS = "CamelPGPDataFormatNumberOfSigningKeys"; | |
| private static final Logger LOG = LoggerFactory.getLogger(PGPKeyAccessDataFormat.class); | |
| private static final List<String> SIGNATURE_VERIFICATION_OPTIONS = Arrays.asList(new String[] {SIGNATURE_VERIFICATION_OPTION_OPTIONAL, | |
| SIGNATURE_VERIFICATION_OPTION_REQUIRED, SIGNATURE_VERIFICATION_OPTION_IGNORE, SIGNATURE_VERIFICATION_OPTION_NO_SIGNATURE_ALLOWED }); | |
| private static final String BC = "BC"; | |
| private static final int BUFFER_SIZE = 16 * 1024; | |
| PGPPublicKeyAccessor publicKeyAccessor; | |
| PGPSecretKeyAccessor secretKeyAccessor; | |
| // Java Cryptography Extension provider, default is Bouncy Castle | |
| private String provider = BC; | |
| // encryption / decryption key info (required) | |
| private String keyUserid; // only for encryption | |
| //in addition you can specify further User IDs, in this case the symmetric key is encrypted by several public keys corresponding to the User Ids | |
| private List<String> keyUserids; //only for encryption; | |
| // signature / verification key info (optional) | |
| private String signatureKeyUserid; // for signing and verification (optional for verification) | |
| //For verification you can specify further User IDs in addition | |
| private List<String> signatureKeyUserids; //only for signing with several keys and verifying; | |
| private boolean armored; // for encryption | |
| private boolean integrity = true; // for encryption | |
| private int hashAlgorithm = HashAlgorithmTags.SHA1; // for signature | |
| private int algorithm = SymmetricKeyAlgorithmTags.CAST5; // for encryption | |
| private int compressionAlgorithm = CompressionAlgorithmTags.ZIP; // for encryption | |
| private boolean withCompressedDataPacket = true; // for encryption | |
| private String signatureVerificationOption = "optional"; | |
| /* | |
| * The default value "_CONSOLE" 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 | |
| */ | |
| private String fileName = PGPLiteralData.CONSOLE; | |
| public PGPKeyAccessDataFormat() { | |
| } | |
| @Override | |
| public String getDataFormatName() { | |
| return "pgp"; | |
| } | |
| protected String findKeyUserid(Exchange exchange) { | |
| return exchange.getIn().getHeader(KEY_USERID, getKeyUserid(), String.class); | |
| } | |
| @SuppressWarnings("unchecked") | |
| protected List<String> findKeyUserids(Exchange exchange) { | |
| return exchange.getIn().getHeader(KEY_USERIDS, getKeyUserids(), List.class); | |
| } | |
| protected String findSignatureKeyUserid(Exchange exchange) { | |
| return exchange.getIn().getHeader(SIGNATURE_KEY_USERID, getSignatureKeyUserid(), String.class); | |
| } | |
| @SuppressWarnings("unchecked") | |
| protected List<String> findSignatureKeyUserids(Exchange exchange) { | |
| return exchange.getIn().getHeader(SIGNATURE_KEY_USERIDS, getSignatureKeyUserids(), List.class); | |
| } | |
| protected int findCompressionAlgorithm(Exchange exchange) { | |
| return exchange.getIn().getHeader(COMPRESSION_ALGORITHM, getCompressionAlgorithm(), Integer.class); | |
| } | |
| protected int findAlgorithm(Exchange exchange) { | |
| return exchange.getIn().getHeader(ENCRYPTION_ALGORITHM, getAlgorithm(), Integer.class); | |
| } | |
| protected int findHashAlgorithm(Exchange exchange) { | |
| return exchange.getIn().getHeader(SIGNATURE_HASH_ALGORITHM, getHashAlgorithm(), Integer.class); | |
| } | |
| protected String findFileName(Exchange exchange) { | |
| return exchange.getIn().getHeader(Exchange.FILE_NAME, getFileName(), String.class); | |
| } | |
| public void marshal(Exchange exchange, Object graph, OutputStream outputStream) throws Exception { //NOPMD | |
| List<String> userids = determineEncryptionUserIds(exchange); | |
| List<PGPPublicKey> keys = publicKeyAccessor.getEncryptionKeys(exchange, userids); | |
| if (keys.isEmpty()) { | |
| throw new IllegalArgumentException("Cannot PGP encrypt message. No public encryption key found for the User Ids " + userids | |
| + " in the public keyring. Either specify other User IDs or add correct public keys to the keyring."); | |
| } | |
| exchange.getOut().setHeader(NUMBER_OF_ENCRYPTION_KEYS, Integer.valueOf(keys.size())); | |
| InputStream input = ExchangeHelper.convertToMandatoryType(exchange, InputStream.class, graph); | |
| if (armored) { | |
| outputStream = new ArmoredOutputStream(outputStream); | |
| } | |
| PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(findAlgorithm(exchange)) | |
| .setWithIntegrityPacket(integrity).setSecureRandom(new SecureRandom()).setProvider(getProvider())); | |
| // several keys can be added | |
| for (PGPPublicKey key : keys) { | |
| encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(key)); | |
| } | |
| OutputStream encOut = encGen.open(outputStream, new byte[BUFFER_SIZE]); | |
| OutputStream comOut; | |
| if (withCompressedDataPacket) { | |
| PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(findCompressionAlgorithm(exchange)); | |
| comOut = new BufferedOutputStream(comData.open(encOut)); | |
| } else { | |
| comOut = encOut; | |
| LOG.debug("No Compressed Data packet is added"); | |
| } | |
| List<PGPSignatureGenerator> sigGens = createSignatureGenerator(exchange, comOut); | |
| PGPLiteralDataGenerator litData = new PGPLiteralDataGenerator(); | |
| String fileName = findFileName(exchange); | |
| 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 (sigGens != null && !sigGens.isEmpty()) { | |
| for (PGPSignatureGenerator sigGen : sigGens) { | |
| // not nested therefore it is the same for all | |
| // can this be improved that we only do it for one sigGen and set the result on the others? | |
| sigGen.update(buffer, 0, bytesRead); | |
| } | |
| } | |
| litOut.flush(); | |
| } | |
| } finally { | |
| IOHelper.close(litOut); | |
| if (sigGens != null && !sigGens.isEmpty()) { | |
| // reverse order | |
| for (int i = sigGens.size() - 1; i > -1; i--) { | |
| PGPSignatureGenerator sigGen = sigGens.get(i); | |
| sigGen.generate().encode(comOut); | |
| } | |
| } | |
| IOHelper.close(comOut, encOut, outputStream, input); | |
| } | |
| } | |
| protected List<String> determineEncryptionUserIds(Exchange exchange) { | |
| String userid = findKeyUserid(exchange); | |
| List<String> userids = findKeyUserids(exchange); | |
| // merge them together | |
| List<String> result; | |
| if (userid != null) { | |
| if (userids == null || userids.isEmpty()) { | |
| result = Collections.singletonList(userid); | |
| } else { | |
| result = new ArrayList<>(userids.size() + 1); | |
| result.add(userid); | |
| result.addAll(userids); | |
| } | |
| } else { | |
| if (userids == null || userids.isEmpty()) { | |
| throw new IllegalStateException("Cannot PGP encrypt message. No User ID of the public key specified."); | |
| } | |
| result = userids; | |
| } | |
| return result; | |
| } | |
| protected List<String> determineSignaturenUserIds(Exchange exchange) { | |
| String userid = findSignatureKeyUserid(exchange); | |
| List<String> userids = findSignatureKeyUserids(exchange); | |
| // merge them together | |
| List<String> result; | |
| if (userid != null) { | |
| if (userids == null || userids.isEmpty()) { | |
| result = Collections.singletonList(userid); | |
| } else { | |
| result = new ArrayList<>(userids.size() + 1); | |
| result.add(userid); | |
| result.addAll(userids); | |
| } | |
| } else { | |
| // userids can be empty or null! | |
| result = userids; | |
| } | |
| return result; | |
| } | |
| protected List<PGPSignatureGenerator> createSignatureGenerator(Exchange exchange, OutputStream out) throws Exception { //NOPMD | |
| if (secretKeyAccessor == null) { | |
| return null; | |
| } | |
| List<String> sigKeyUserids = determineSignaturenUserIds(exchange); | |
| List<PGPSecretKeyAndPrivateKeyAndUserId> sigSecretKeysWithPrivateKeyAndUserId = secretKeyAccessor.getSignerKeys(exchange, | |
| sigKeyUserids); | |
| if (sigSecretKeysWithPrivateKeyAndUserId.isEmpty()) { | |
| return null; | |
| } | |
| exchange.getOut().setHeader(NUMBER_OF_SIGNING_KEYS, Integer.valueOf(sigSecretKeysWithPrivateKeyAndUserId.size())); | |
| List<PGPSignatureGenerator> sigGens = new ArrayList<>(); | |
| for (PGPSecretKeyAndPrivateKeyAndUserId sigSecretKeyWithPrivateKeyAndUserId : sigSecretKeysWithPrivateKeyAndUserId) { | |
| PGPPrivateKey sigPrivateKey = sigSecretKeyWithPrivateKeyAndUserId.getPrivateKey(); | |
| PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); | |
| spGen.setSignerUserID(false, sigSecretKeyWithPrivateKeyAndUserId.getUserId()); | |
| int algorithm = sigSecretKeyWithPrivateKeyAndUserId.getSecretKey().getPublicKey().getAlgorithm(); | |
| PGPSignatureGenerator sigGen = new PGPSignatureGenerator( | |
| new JcaPGPContentSignerBuilder(algorithm, findHashAlgorithm(exchange)).setProvider(getProvider())); | |
| sigGen.init(PGPSignature.BINARY_DOCUMENT, sigPrivateKey); | |
| sigGen.setHashedSubpackets(spGen.generate()); | |
| sigGen.generateOnePassVersion(false).encode(out); | |
| sigGens.add(sigGen); | |
| } | |
| return sigGens; | |
| } | |
| public Object unmarshal(Exchange exchange, InputStream encryptedStream) throws Exception { //NOPMD | |
| if (encryptedStream == null) { | |
| return null; | |
| } | |
| InputStream in = null; | |
| InputStream encData = null; | |
| InputStream uncompressedData = null; | |
| InputStream litData = null; | |
| OutputStreamBuilder osb = null; | |
| try { | |
| in = PGPUtil.getDecoderStream(encryptedStream); | |
| DecryptedDataAndPPublicKeyEncryptedData encDataAndPbe = getDecryptedData(exchange, in); | |
| encData = encDataAndPbe.getDecryptedData(); | |
| PGPObjectFactory pgpFactory = new PGPObjectFactory(encData, new BcKeyFingerprintCalculator()); | |
| Object object = pgpFactory.nextObject(); | |
| if (object instanceof PGPCompressedData) { | |
| PGPCompressedData comData = (PGPCompressedData) object; | |
| uncompressedData = comData.getDataStream(); | |
| pgpFactory = new PGPObjectFactory(uncompressedData, new BcKeyFingerprintCalculator()); | |
| object = pgpFactory.nextObject(); | |
| } else { | |
| LOG.debug("PGP Message does not contain a Compressed Data Packet"); | |
| } | |
| PGPOnePassSignature signature; | |
| if (object instanceof PGPOnePassSignatureList) { | |
| signature = getSignature(exchange, (PGPOnePassSignatureList) object); | |
| object = pgpFactory.nextObject(); | |
| } else { | |
| // no signature contained in PGP message | |
| signature = null; | |
| if (SIGNATURE_VERIFICATION_OPTION_REQUIRED.equals(getSignatureVerificationOption())) { | |
| throw new PGPException( | |
| "PGP message does not contain any signatures although a signature is expected. Either send a PGP message with signature or change the configuration of the PGP decryptor."); | |
| } | |
| } | |
| PGPLiteralData ld; | |
| if (object instanceof PGPLiteralData) { | |
| ld = (PGPLiteralData) object; | |
| } else { | |
| throw getFormatException(); | |
| } | |
| litData = ld.getInputStream(); | |
| osb = OutputStreamBuilder.withExchange(exchange); | |
| byte[] buffer = new byte[BUFFER_SIZE]; | |
| int bytesRead; | |
| while ((bytesRead = litData.read(buffer)) != -1) { | |
| osb.write(buffer, 0, bytesRead); | |
| if (signature != null) { | |
| signature.update(buffer, 0, bytesRead); | |
| } | |
| osb.flush(); | |
| } | |
| verifySignature(pgpFactory, signature); | |
| PGPPublicKeyEncryptedData pbe = encDataAndPbe.getPbe(); | |
| if (pbe.isIntegrityProtected()) { | |
| if (!pbe.verify()) { | |
| throw new PGPException("Message failed integrity check"); | |
| } | |
| } | |
| } finally { | |
| IOHelper.close(osb, litData, uncompressedData, encData, in, encryptedStream); | |
| } | |
| return osb.build(); | |
| } | |
| private DecryptedDataAndPPublicKeyEncryptedData getDecryptedData(Exchange exchange, InputStream encryptedStream) throws Exception, PGPException { | |
| PGPObjectFactory pgpFactory = new PGPObjectFactory(encryptedStream, new BcKeyFingerprintCalculator()); | |
| Object firstObject = pgpFactory.nextObject(); | |
| // the first object might be a PGP marker packet | |
| PGPEncryptedDataList enc = getEcryptedDataList(pgpFactory, firstObject); | |
| if (enc == null) { | |
| throw getFormatException(); | |
| } | |
| PGPPublicKeyEncryptedData pbe = null; | |
| PGPPrivateKey key = null; | |
| // find encrypted data for which a private key exists in the secret key ring | |
| for (int i = 0; i < enc.size() && key == null; i++) { | |
| Object encryptedData = enc.get(i); | |
| if (!(encryptedData instanceof PGPPublicKeyEncryptedData)) { | |
| throw getFormatException(); | |
| } | |
| pbe = (PGPPublicKeyEncryptedData) encryptedData; | |
| key = secretKeyAccessor.getPrivateKey(exchange, pbe.getKeyID()); | |
| if (key != null) { | |
| // take the first key | |
| break; | |
| } | |
| } | |
| if (key == null) { | |
| throw new PGPException("PGP message is encrypted with a key which could not be found in the Secret Keyring."); | |
| } | |
| InputStream encData = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider(getProvider()).build(key)); | |
| return new DecryptedDataAndPPublicKeyEncryptedData(encData, pbe); | |
| } | |
| private PGPEncryptedDataList getEcryptedDataList(PGPObjectFactory pgpFactory, Object firstObject) throws IOException { | |
| PGPEncryptedDataList enc; | |
| if (firstObject instanceof PGPEncryptedDataList) { | |
| enc = (PGPEncryptedDataList) firstObject; | |
| } else { | |
| Object secondObject = pgpFactory.nextObject(); | |
| if (secondObject instanceof PGPEncryptedDataList) { | |
| enc = (PGPEncryptedDataList) secondObject; | |
| } else { | |
| enc = null; | |
| } | |
| } | |
| return enc; | |
| } | |
| private void verifySignature(PGPObjectFactory pgpFactory, PGPOnePassSignature signature) throws IOException, PGPException, SignatureException { | |
| if (signature != null) { | |
| PGPSignatureList sigList = (PGPSignatureList) pgpFactory.nextObject(); | |
| if (!signature.verify(getSignatureWithKeyId(signature.getKeyID(), sigList))) { | |
| throw new SignatureException("Verification of the PGP signature with the key ID " + signature.getKeyID() + " failed. The PGP message may have been tampered."); | |
| } | |
| } | |
| } | |
| private IllegalArgumentException getFormatException() { | |
| return new IllegalArgumentException( | |
| "The input message body has an invalid format. The PGP decryption/verification processor expects a sequence of PGP packets of the form " | |
| + "(entries in brackets are optional and ellipses indicate repetition, comma represents sequential composition, and vertical bar separates alternatives): " | |
| + "Public Key Encrypted Session Key ..., Symmetrically Encrypted Data | Sym. Encrypted and Integrity Protected Data, (Compressed Data,) (One Pass Signature ...,) " | |
| + "Literal Data, (Signature ...,)"); | |
| } | |
| protected PGPSignature getSignatureWithKeyId(long keyID, PGPSignatureList sigList) { | |
| for (int i = 0; i < sigList.size(); i++) { | |
| PGPSignature signature = sigList.get(i); | |
| if (keyID == signature.getKeyID()) { | |
| return signature; | |
| } | |
| } | |
| throw new IllegalStateException("PGP signature is inconsistent"); | |
| } | |
| protected PGPOnePassSignature getSignature(Exchange exchange, PGPOnePassSignatureList signatureList) throws Exception { | |
| if (SIGNATURE_VERIFICATION_OPTION_IGNORE.equals(getSignatureVerificationOption())) { | |
| return null; | |
| } | |
| if (SIGNATURE_VERIFICATION_OPTION_NO_SIGNATURE_ALLOWED.equals(getSignatureVerificationOption())) { | |
| throw new PGPException( | |
| "PGP message contains a signature although a signature is not expected. Either change the configuration of the PGP decryptor or send a PGP message with no signature."); | |
| } | |
| List<String> allowedUserIds = determineSignaturenUserIds(exchange); | |
| for (int i = 0; i < signatureList.size(); i++) { | |
| PGPOnePassSignature signature = signatureList.get(i); | |
| // Determine public key from signature keyId | |
| PGPPublicKey sigPublicKey = publicKeyAccessor.getPublicKey(exchange, signature.getKeyID(), allowedUserIds); | |
| if (sigPublicKey == null) { | |
| continue; | |
| } | |
| // choose that signature for which a public key exists! | |
| signature.init(new JcaPGPContentVerifierBuilderProvider().setProvider(getProvider()), sigPublicKey); | |
| return signature; | |
| } | |
| if (signatureList.isEmpty()) { | |
| return null; | |
| } else { | |
| throw new IllegalArgumentException("Cannot verify the PGP signature: No public key found for the key ID(s) contained in the PGP signature(s). " | |
| + "Either the received PGP message contains a signature from an unexpected sender or the Public Keyring does not contain the public key of the sender."); | |
| } | |
| } | |
| /** | |
| * Sets if the encrypted file should be written in ascii visible text (for | |
| * marshaling). | |
| */ | |
| public void setArmored(boolean armored) { | |
| this.armored = armored; | |
| } | |
| public boolean getArmored() { | |
| return this.armored; | |
| } | |
| /** | |
| * Whether or not to add an integrity check/sign to the encrypted file for | |
| * marshaling. | |
| */ | |
| public void setIntegrity(boolean integrity) { | |
| this.integrity = integrity; | |
| } | |
| public boolean getIntegrity() { | |
| return this.integrity; | |
| } | |
| /** | |
| * User ID, or more precisely user ID part, of the key used for encryption. | |
| * See also {@link #setKeyUserids(List<String>)}. | |
| */ | |
| public void setKeyUserid(String keyUserid) { | |
| this.keyUserid = keyUserid; | |
| } | |
| public String getKeyUserid() { | |
| return keyUserid; | |
| } | |
| public List<String> getKeyUserids() { | |
| return keyUserids; | |
| } | |
| /** | |
| * Keys User IDs, or more precisely user ID parts, used for determining the | |
| * public keys for encryption. If you just have one User ID, then you can | |
| * also use the method {@link #setKeyUserid(String)}. The User ID specified | |
| * in {@link #setKeyUserid(String)} and in this method will be merged | |
| * together and the public keys which have a User ID which contain a value | |
| * of the specified User IDs the will be used for the encryption. Be aware | |
| * that you may get several public keys even if you specify only one User | |
| * Id, because there can be several public keys which have a User ID which | |
| * contains the specified User ID. | |
| */ | |
| public void setKeyUserids(List<String> keyUserids) { | |
| this.keyUserids = keyUserids; | |
| } | |
| /** | |
| * Userid, or more precisely user ID part, of the signature key used for | |
| * signing (marshal) and verifying (unmarshal). See also | |
| * {@link #setSignatureKeyUserids(List)}. | |
| */ | |
| public void setSignatureKeyUserid(String signatureKeyUserid) { | |
| this.signatureKeyUserid = signatureKeyUserid; | |
| } | |
| public String getSignatureKeyUserid() { | |
| return signatureKeyUserid; | |
| } | |
| public List<String> getSignatureKeyUserids() { | |
| return signatureKeyUserids; | |
| } | |
| /** | |
| * User IDs, or more precisely user ID parts, used for signing and | |
| * verification. | |
| * <p> | |
| * In the signing case, the User IDs specify the private keys which are used | |
| * for signing. If the result are several private keys then several | |
| * signatures will be created. If you just have one signature User ID, then | |
| * you can also use the method {@link #setSignatureKeyUserid(String)} or | |
| * this method. The User ID specified in | |
| * {@link #setSignatureKeyUserid(String)} and in this method will be merged | |
| * together and the private keys which have a User Id which contain one | |
| * value out of the specified UserIds will be used for the signature | |
| * creation. Be aware that you may get several private keys even if you | |
| * specify only one User Id, because there can be several private keys which | |
| * have a User ID which contains the specified User ID. | |
| * <p> | |
| * In the verification case the User IDs restrict the set of public keys | |
| * which can be used for verification. The public keys used for verification | |
| * must contain a User ID which contain one value of the User ID list. If | |
| * you neither specify in this method and nor specify in the method | |
| * {@link #setSignatureKeyUserid(String)} any value then any public key in | |
| * the public key ring will be taken into consideration for the | |
| * verification. | |
| * <p> | |
| * If you just have one User ID, then you can also use the method | |
| * {@link #setSignatureKeyUserid(String)}. The User ID specified in | |
| * {@link #setSignatureKeyUserid(String)} and in this method will be merged | |
| * together and the corresponding public keys represent the potential keys | |
| * for the verification of the message. | |
| */ | |
| public void setSignatureKeyUserids(List<String> signatureKeyUserids) { | |
| this.signatureKeyUserids = signatureKeyUserids; | |
| } | |
| public String getProvider() { | |
| return provider; | |
| } | |
| /** | |
| * Java Cryptography Extension (JCE) provider, default is Bouncy Castle | |
| * ("BC"). Alternatively you can use, for example, the IAIK JCE provider; in | |
| * this case the provider must be registered beforehand and the Bouncy | |
| * Castle provider must not be registered beforehand. The Sun JCE provider | |
| * does not work. | |
| */ | |
| public void setProvider(String provider) { | |
| this.provider = provider; | |
| } | |
| public int getCompressionAlgorithm() { | |
| return compressionAlgorithm; | |
| } | |
| /** | |
| * Compression algorithm used during marshaling. Possible values are defined | |
| * in {@link CompressionAlgorithmTags}. Default value is ZIP. | |
| */ | |
| public void setCompressionAlgorithm(int compressionAlgorithm) { | |
| this.compressionAlgorithm = compressionAlgorithm; | |
| } | |
| public int getHashAlgorithm() { | |
| return hashAlgorithm; | |
| } | |
| /** | |
| * Digest algorithm for signing (marshaling). Possible values are defined in | |
| * {@link HashAlgorithmTags}. Default value is SHA1. | |
| */ | |
| public void setHashAlgorithm(int hashAlgorithm) { | |
| this.hashAlgorithm = hashAlgorithm; | |
| } | |
| public int getAlgorithm() { | |
| return algorithm; | |
| } | |
| /** | |
| * Symmetric key algorithm for encryption (marshaling). Possible values are | |
| * defined in {@link SymmetricKeyAlgorithmTags}. Default value is CAST5. | |
| */ | |
| public void setAlgorithm(int algorithm) { | |
| this.algorithm = algorithm; | |
| } | |
| public PGPPublicKeyAccessor getPublicKeyAccessor() { | |
| return publicKeyAccessor; | |
| } | |
| public void setPublicKeyAccessor(PGPPublicKeyAccessor publicKeyAccessor) { | |
| this.publicKeyAccessor = publicKeyAccessor; | |
| } | |
| public PGPSecretKeyAccessor getSecretKeyAccessor() { | |
| return secretKeyAccessor; | |
| } | |
| public void setSecretKeyAccessor(PGPSecretKeyAccessor secretKeyAccessor) { | |
| this.secretKeyAccessor = secretKeyAccessor; | |
| } | |
| public String getSignatureVerificationOption() { | |
| return signatureVerificationOption; | |
| } | |
| public boolean isWithCompressedDataPacket() { | |
| return withCompressedDataPacket; | |
| } | |
| /** Indicator that Compressed Data packet shall be added during encryption. | |
| * The default value is true. | |
| * If <tt>false</tt> then the compression algorithm (see {@link #setCompressionAlgorithm(int)} is ignored. | |
| */ | |
| public void setWithCompressedDataPacket(boolean withCompressedDataPacket) { | |
| this.withCompressedDataPacket = withCompressedDataPacket; | |
| } | |
| /** | |
| * Signature verification option. Controls the behavior for the signature | |
| * verification during unmarshaling. Possible values are | |
| * {@link #SIGNATURE_VERIFICATION_OPTION_OPTIONAL}, | |
| * {@link #SIGNATURE_VERIFICATION_OPTION_REQUIRED}, | |
| * {@link #SIGNATURE_VERIFICATION_OPTION_NO_SIGNATURE_ALLOWED}, and | |
| * {@link #SIGNATURE_VERIFICATION_OPTION_IGNORE}. The default | |
| * value is {@link #SIGNATURE_VERIFICATION_OPTION_OPTIONAL} | |
| * | |
| * @param signatureVerificationOption | |
| * signature verification option | |
| * @throws IllegalArgumentException | |
| * if an invalid value is entered | |
| */ | |
| public void setSignatureVerificationOption(String signatureVerificationOption) { | |
| if (SIGNATURE_VERIFICATION_OPTIONS.contains(signatureVerificationOption)) { | |
| this.signatureVerificationOption = signatureVerificationOption; | |
| } else { | |
| throw new IllegalArgumentException(signatureVerificationOption + " is not a valid signature verification option"); | |
| } | |
| } | |
| /** | |
| * Returns the file name for the literal packet. Cannot be <code>null</code> | |
| * . | |
| * | |
| */ | |
| public String getFileName() { | |
| return fileName; | |
| } | |
| /** | |
| * Sets the file name for the literal data packet. Can be overwritten by the | |
| * header {@link Exchange#FILE_NAME}. The default value is "_CONSOLE". | |
| * "_CONSOLE" indicates that the message is considered to be | |
| * "for your eyes only". This advises that the message data is unusually | |
| * sensitive, and the receiving program should process it more carefully, | |
| * perhaps avoiding storing the received data to disk, for example. | |
| * <p> | |
| * Only used for marshaling. | |
| * | |
| * @param fileName | |
| * @throws IllegalArgumentException | |
| * if <tt>fileName</tt> is <code>null</code> | |
| */ | |
| public void setFileName(String fileName) { | |
| if (fileName == null) { | |
| throw new IllegalArgumentException("Parameter 'fileName' is null"); | |
| } | |
| this.fileName = fileName; | |
| } | |
| @Override | |
| protected void doStart() throws Exception { //NOPMD | |
| if (Security.getProvider(BC) == null && BC.equals(getProvider())) { | |
| LOG.debug("Adding BouncyCastleProvider as security provider"); | |
| Security.addProvider(new BouncyCastleProvider()); | |
| } else { | |
| LOG.debug("Using custom provider {} which is expected to be enlisted manually.", getProvider()); | |
| } | |
| } | |
| @Override | |
| protected void doStop() throws Exception { //NOPMD | |
| // noop | |
| } | |
| private static class DecryptedDataAndPPublicKeyEncryptedData { | |
| private final InputStream decryptedData; | |
| private final PGPPublicKeyEncryptedData pbe; | |
| DecryptedDataAndPPublicKeyEncryptedData(InputStream decryptedData, PGPPublicKeyEncryptedData pbe) { | |
| this.decryptedData = decryptedData; | |
| this.pbe = pbe; | |
| } | |
| public InputStream getDecryptedData() { | |
| return decryptedData; | |
| } | |
| public PGPPublicKeyEncryptedData getPbe() { | |
| return pbe; | |
| } | |
| } | |
| } |