Updated Branches: refs/heads/master f832af138 -> a346c1c1c
CAMEL-7002. Support for serveral signatures. Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/a346c1c1 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/a346c1c1 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/a346c1c1 Branch: refs/heads/master Commit: a346c1c1c66d0f2132b8ff9b244c8050cdcd1292 Parents: f832af1 Author: Hadrian Zbarcea <[email protected]> Authored: Fri Feb 7 15:09:09 2014 -0500 Committer: Hadrian Zbarcea <[email protected]> Committed: Fri Feb 7 15:09:29 2014 -0500 ---------------------------------------------------------------------- .../camel/converter/crypto/PGPDataFormat.java | 243 +++++++++++++++---- .../converter/crypto/PGPDataFormatUtil.java | 131 ++++++---- .../crypto/PGPDataFormatDynamicTest.java | 10 + .../converter/crypto/PGPDataFormatTest.java | 149 +++++++++++- 4 files changed, 438 insertions(+), 95 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/a346c1c1/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormat.java ---------------------------------------------------------------------- diff --git a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormat.java b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormat.java index 985e80d..0157088 100644 --- a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormat.java +++ b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormat.java @@ -29,9 +29,13 @@ import java.security.SignatureException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.apache.camel.Exchange; +import org.apache.camel.converter.crypto.PGPDataFormatUtil.PGPSecretKeyAndPrivateKeyAndUserId; import org.apache.camel.converter.stream.CachedOutputStream; import org.apache.camel.spi.DataFormat; import org.apache.camel.support.ServiceSupport; @@ -56,7 +60,6 @@ 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; @@ -64,7 +67,6 @@ import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider; -import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; @@ -86,6 +88,7 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { public static final String SIGNATURE_KEY_FILE_NAME = "CamelPGPDataFormatSignatureKeyFileName"; public static final String SIGNATURE_KEY_RING = "CamelPGPDataFormatSignatureKeyRing"; public static final String SIGNATURE_KEY_USERID = "CamelPGPDataFormatSignatureKeyUserid"; + public static final String SIGNATURE_KEY_USERIDS = "CamelPGPDataFormatSignatureKeyUserids"; public static final String SIGNATURE_KEY_PASSWORD = "CamelPGPDataFormatSignatureKeyPassword"; public static final String ENCRYPTION_ALGORITHM = "CamelPGPDataFormatEncryptionAlgorithm"; public static final String SIGNATURE_HASH_ALGORITHM = "CamelPGPDataFormatSignatureHashAlgorithm"; @@ -109,8 +112,10 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { private byte[] encryptionKeyRing; // signature / verification key info (optional) - private String signatureKeyUserid; // for encryption - private String signaturePassword; // for encryption + 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 String signaturePassword; //only for signing, optional if you have several signature keys, then you should use passphaseAccessor private String signatureKeyFileName; // alternatively to the signature key file name you can specify the signature key ring as byte array private byte[] signatureKeyRing; @@ -124,7 +129,7 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { private int compressionAlgorithm = CompressionAlgorithmTags.ZIP; // for encryption - private PGPPassphraseAccessor passphraseAccessor; + private PGPPassphraseAccessor passphraseAccessor; // for signing and decryption with multiple keys public PGPDataFormat() { } @@ -168,6 +173,11 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { 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 String findSignatureKeyPassword(Exchange exchange) { String sigPassword = exchange.getIn().getHeader(SIGNATURE_KEY_PASSWORD, getSignaturePassword(), String.class); if (sigPassword != null) { @@ -218,7 +228,7 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(findCompressionAlgorithm(exchange)); OutputStream comOut = new BufferedOutputStream(comData.open(encOut)); - PGPSignatureGenerator sigGen = createSignatureGenerator(exchange, comOut); + List<PGPSignatureGenerator> sigGens = createSignatureGenerator(exchange, comOut); PGPLiteralDataGenerator litData = new PGPLiteralDataGenerator(); String fileName = exchange.getIn().getHeader(Exchange.FILE_NAME, String.class); @@ -234,21 +244,29 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { int bytesRead; while ((bytesRead = input.read(buffer)) != -1) { litOut.write(buffer, 0, bytesRead); - if (sigGen != null) { - sigGen.update(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 (sigGen != null) { - sigGen.generate().encode(comOut); + 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); } } - public List<String> determineEncryptionUserIds(Exchange exchange) { + protected List<String> determineEncryptionUserIds(Exchange exchange) { String userid = findKeyUserid(exchange); List<String> userids = findKeyUserids(exchange); // merge them together @@ -270,44 +288,94 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { return result; } - protected PGPSignatureGenerator createSignatureGenerator(Exchange exchange, OutputStream out) throws IOException, PGPException, + 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<String>(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 IOException, PGPException, NoSuchProviderException, NoSuchAlgorithmException { String sigKeyFileName = findSignatureKeyFileName(exchange); - String sigKeyUserid = findSignatureKeyUserid(exchange); + List<String> sigKeyUserids = determineSignaturenUserIds(exchange); String sigKeyPassword = findSignatureKeyPassword(exchange); byte[] sigKeyRing = findSignatureKeyRing(exchange); - if ((sigKeyFileName == null && sigKeyRing == null) || sigKeyUserid == null || sigKeyPassword == null) { + if ((sigKeyFileName == null && sigKeyRing == null) || sigKeyUserids == null || sigKeyUserids.isEmpty() + || (sigKeyPassword == null && passphraseAccessor == null)) { return null; } - PGPSecretKey sigSecretKey = PGPDataFormatUtil.findSecretKey(exchange.getContext(), sigKeyFileName, sigKeyRing, sigKeyPassword, - sigKeyUserid, getProvider()); - if (sigSecretKey == null) { + List<PGPSecretKeyAndPrivateKeyAndUserId> sigSecretKeysWithPrivateKeyAndUserId = determineSecretKeysWithPrivateKeyAndUserId( + exchange, sigKeyFileName, sigKeyUserids, sigKeyPassword, sigKeyRing); + + List<PGPSignatureGenerator> sigGens = new ArrayList<PGPSignatureGenerator>(); + 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 List<PGPSecretKeyAndPrivateKeyAndUserId> determineSecretKeysWithPrivateKeyAndUserId(Exchange exchange, String sigKeyFileName, + List<String> sigKeyUserids, String sigKeyPassword, byte[] sigKeyRing) throws IOException, PGPException, NoSuchProviderException { + + Map<String, String> sigKeyUserId2Password = determineSignatureKeyUserId2Password(sigKeyUserids, sigKeyPassword); + + List<PGPSecretKeyAndPrivateKeyAndUserId> sigSecretKeysWithPrivateKeyAndUserId = PGPDataFormatUtil + .findSecretKeysWithPrivateKeyAndUserId(exchange.getContext(), sigKeyFileName, sigKeyRing, sigKeyUserId2Password, + getProvider()); + + if (sigSecretKeysWithPrivateKeyAndUserId.isEmpty()) { throw new IllegalArgumentException( String.format( - "Cannot PGP encrypt message. No secret key found for User ID %s. Either add a key with this User ID to the secret keyring or change the configured User ID.", - sigKeyUserid)); + "Cannot PGP sign message. No secret key found for User IDs %s. Either add keys with this User IDs to the secret keyring or change the configured User IDs.", + sigKeyUserids)); } + return sigSecretKeysWithPrivateKeyAndUserId; + } - PGPPrivateKey sigPrivateKey = sigSecretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider(getProvider()) - .build(sigKeyPassword.toCharArray())); - if (sigPrivateKey == null) { - // this exception will never happen - throw new IllegalArgumentException("Signature private key is null, cannot proceed"); + public Map<String, String> determineSignatureKeyUserId2Password(List<String> sigKeyUserids, String sigKeyPassword) { + // we want to keep the order of the entries, therefore we use LinkedHashMap + Map<String, String> sigKeyUserId2Password = new LinkedHashMap<String, String>(sigKeyUserids.size()); + for (String sigKeyUserid : sigKeyUserids) { + if (sigKeyPassword == null) { + sigKeyPassword = passphraseAccessor.getPassphrase(sigKeyUserid); + } + if (sigKeyPassword == null) { + throw new IllegalArgumentException( + String.format( + "No passphrase specified for signature key user ID %s. Either specify a passphrase or remove this user ID from the configuration.", + sigKeyUserid)); + } + sigKeyUserId2Password.put(sigKeyUserid, sigKeyPassword); } - - PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator(); - spGen.setSignerUserID(false, sigKeyUserid); - - int algorithm = sigSecretKey.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); - return sigGen; + return sigKeyUserId2Password; } @SuppressWarnings("resource") @@ -333,6 +401,10 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { pbe = (PGPPublicKeyEncryptedData) enc.get(i); key = PGPDataFormatUtil.findPrivateKeyWithKeyId(exchange.getContext(), findKeyFileName(exchange), findEncryptionKeyRing(exchange), pbe.getKeyID(), findKeyPassword(exchange), getPassphraseAccessor(), getProvider()); + if (key != null) { + // take the first key + break; + } } if (key == null) { throw new PGPException("Provided input is encrypted with unknown pair of keys."); @@ -368,7 +440,7 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { bos = new ByteArrayOutputStream(); os = bos; } - + try { byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead; @@ -389,7 +461,7 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { throw new SignatureException("Cannot verify PGP signature"); } } - + if (cos != null) { return cos.newStreamCache(); } else { @@ -410,6 +482,7 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { protected PGPOnePassSignature getSignature(Exchange exchange, PGPOnePassSignatureList signatureList) throws IOException, PGPException, NoSuchProviderException { + List<String> allowedUserIds = determineSignaturenUserIds(exchange); for (int i = 0; i < signatureList.size(); i++) { PGPOnePassSignature signature = signatureList.get(i); // Determine public key from signature keyId @@ -418,9 +491,11 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { if (sigPublicKey == null) { continue; } - // choose that signature for which a public key exists! - signature.init(new JcaPGPContentVerifierBuilderProvider().setProvider(getProvider()), sigPublicKey); - return signature; + if (isAllowedVerifyingKey(allowedUserIds, sigPublicKey)) { + // choose that signature for which a public key exists! + signature.init(new JcaPGPContentVerifierBuilderProvider().setProvider(getProvider()), sigPublicKey); + return signature; + } } if (signatureList.isEmpty()) { return null; @@ -430,6 +505,31 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { } + public boolean isAllowedVerifyingKey(List<String> allowedUserIds, PGPPublicKey verifyingPublicKey) { + + if (allowedUserIds == null || allowedUserIds.isEmpty()) { + // no restrictions specified + return true; + } + String keyUserId = null; + for (@SuppressWarnings("unchecked") + Iterator<String> iterator = verifyingPublicKey.getUserIDs(); iterator.hasNext();) { + keyUserId = iterator.next(); + for (String userid : allowedUserIds) { + if (keyUserId != null && keyUserId.contains(userid)) { + LOG.debug( + "Public key with user ID {} fulfills the User ID restriction {}. Therefore this key will be used for the signature verification. ", + keyUserId, allowedUserIds); + return true; + } + } + } + LOG.warn( + "Public key with User ID {} does not fulfill the User ID restriction {}. Therefore this key will not be used for the signature verification.", + keyUserId, allowedUserIds); + return false; + } + /** * Sets if the encrypted file should be written in ascii visible text (for * marshaling). @@ -455,11 +555,8 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { } /** - * Userid of the key used to encrypt. If you want to encrypt with several - * keys then use the method {@link #setKeyUserids(List<String>)}. The User - * ID of this method and the User IDs of the method {@link - * #setKeyUserids(List<String>)} will be merged together and the - * corresponding public keys will be used for the encryption. + * 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; @@ -474,11 +571,15 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { } /** - * KeyUserIds used to determine the public keys for encryption. If you just - * have one User ID, then you can also use the method - * {@link #setKeyUserid(String)} or this method. The User ID specified in - * {@link #setKeyUserid(String)} and in this method will be merged together - * and the corresponding public keys will be used for the encryption. + * 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; @@ -511,7 +612,9 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { } /** - * Userid of the signature key used to sign (marshal). + * 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; @@ -521,6 +624,44 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { 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; + } + /** * Filename of the signature keyring that will be used, classpathResource. */ http://git-wip-us.apache.org/repos/asf/camel/blob/a346c1c1/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormatUtil.java ---------------------------------------------------------------------- diff --git a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormatUtil.java b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormatUtil.java index 0753957..3408a4a 100644 --- a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormatUtil.java +++ b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormatUtil.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.camel.CamelContext; @@ -43,6 +44,8 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static org.bouncycastle.bcpg.PublicKeyAlgorithmTags.DSA; import static org.bouncycastle.bcpg.PublicKeyAlgorithmTags.ECDSA; @@ -51,6 +54,8 @@ import static org.bouncycastle.bcpg.PublicKeyAlgorithmTags.RSA_GENERAL; import static org.bouncycastle.bcpg.PublicKeyAlgorithmTags.RSA_SIGN; public final class PGPDataFormatUtil { + + private static final Logger LOG = LoggerFactory.getLogger(PGPDataFormatUtil.class); private PGPDataFormatUtil() { } @@ -81,7 +86,7 @@ public final class PGPDataFormatUtil { } public static List<PGPPublicKey> findPublicKeys(CamelContext context, String filename, byte[] keyRing, List<String> userids, - boolean forEncryption) throws IOException, PGPException, NoSuchProviderException { + boolean forEncryption) throws IOException, PGPException, NoSuchProviderException { InputStream is = determineKeyRingInputStream(context, filename, keyRing, forEncryption); try { return findPublicKeys(is, userids, forEncryption); @@ -91,7 +96,7 @@ public final class PGPDataFormatUtil { } public static PGPPublicKey findPublicKeyWithKeyId(CamelContext context, String filename, byte[] keyRing, long keyid, - boolean forEncryption) throws IOException, PGPException, NoSuchProviderException { + boolean forEncryption) throws IOException, PGPException, NoSuchProviderException { InputStream is = determineKeyRingInputStream(context, filename, keyRing, forEncryption); PGPPublicKey pubKey; try { @@ -103,8 +108,8 @@ public final class PGPDataFormatUtil { } public static PGPPrivateKey findPrivateKeyWithKeyId(CamelContext context, String filename, byte[] secretKeyRing, long keyid, - String passphrase, PGPPassphraseAccessor passpraseAccessor, String provider) throws IOException, PGPException, - NoSuchProviderException { + String passphrase, PGPPassphraseAccessor passpraseAccessor, String provider) throws IOException, PGPException, + NoSuchProviderException { InputStream is = determineKeyRingInputStream(context, filename, secretKeyRing, true); try { return findPrivateKeyWithKeyId(is, keyid, passphrase, passpraseAccessor, provider); @@ -115,7 +120,7 @@ public final class PGPDataFormatUtil { @SuppressWarnings("unchecked") private static PGPPrivateKey findPrivateKeyWithKeyId(InputStream keyringInput, long keyid, String passphrase, - PGPPassphraseAccessor passphraseAccessor, String provider) throws IOException, PGPException { + PGPPassphraseAccessor passphraseAccessor, String provider) throws IOException, PGPException { PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyringInput)); for (Iterator<?> i = pgpSec.getKeyRings(); i.hasNext();) { Object data = i.next(); @@ -168,25 +173,24 @@ public final class PGPDataFormatUtil { @SuppressWarnings("unchecked") private static PGPPublicKey findPublicKeyWithKeyId(InputStream input, long keyid) throws IOException, PGPException, - NoSuchProviderException { + NoSuchProviderException { PGPPublicKeyRingCollection pgpSec = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(input)); for (Iterator<PGPPublicKeyRing> keyRingIter = pgpSec.getKeyRings(); keyRingIter.hasNext();) { - PGPPublicKeyRing keyRing = keyRingIter.next(); - for (Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys(); keyIter.hasNext();) { - PGPPublicKey key = keyIter.next(); - if (keyid == key.getKeyID()) { - return key; - } + PGPPublicKeyRing keyRing = keyRingIter.next(); + PGPPublicKey key = keyRing.getPublicKey(keyid); + if (key != null) { + return key; } - } + } + return null; } @SuppressWarnings("unchecked") private static List<PGPPublicKey> findPublicKeys(InputStream input, List<String> userids, boolean forEncryption) throws IOException, - PGPException, NoSuchProviderException { + PGPException, NoSuchProviderException { List<PGPPublicKey> result = new ArrayList<PGPPublicKey>(3); PGPPublicKeyRingCollection pgpSec = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(input)); @@ -196,13 +200,17 @@ public final class PGPDataFormatUtil { Set<String> keyUserIds = getUserIds(keyRing); for (Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys(); keyIter.hasNext();) { PGPPublicKey key = keyIter.next(); - for (String userid : userids) { + for (String useridPart : userids) { for (String keyUserId : keyUserIds) { - if (keyUserId != null && keyUserId.contains(userid)) { + if (keyUserId != null && keyUserId.contains(useridPart)) { if (forEncryption && key.isEncryptionKey()) { result.add(key); + LOG.debug("Public key with key user ID {} and key ID {} found for specified user ID part {}", new Object[] { + keyUserId, Long.toString(key.getKeyID()), useridPart }); } else if (!forEncryption && isSignatureKey(key)) { result.add(key); + LOG.debug("Public key with key user ID {} and key ID {} found for specified user ID part {}", new Object[] { + keyUserId, Long.toString(key.getKeyID()), useridPart }); } } } @@ -243,14 +251,14 @@ public final class PGPDataFormatUtil { @Deprecated public static PGPPrivateKey findPrivateKey(CamelContext context, String keychainFilename, byte[] secKeyRing, - InputStream encryptedInput, String passphrase, String provider) throws IOException, PGPException, NoSuchProviderException { + InputStream encryptedInput, String passphrase, String provider) throws IOException, PGPException, NoSuchProviderException { return findPrivateKey(context, keychainFilename, secKeyRing, encryptedInput, passphrase, null, provider); } @Deprecated public static PGPPrivateKey findPrivateKey(CamelContext context, String keychainFilename, byte[] secKeyRing, - InputStream encryptedInput, String passphrase, PGPPassphraseAccessor passphraseAccessor, String provider) throws IOException, - PGPException, NoSuchProviderException { + InputStream encryptedInput, String passphrase, PGPPassphraseAccessor passphraseAccessor, String provider) throws IOException, + PGPException, NoSuchProviderException { InputStream keyChainInputStream = determineKeyRingInputStream(context, keychainFilename, secKeyRing, true); PGPPrivateKey privKey = null; @@ -264,7 +272,7 @@ public final class PGPDataFormatUtil { @Deprecated private static PGPPrivateKey findPrivateKey(InputStream keyringInput, InputStream encryptedInput, String passphrase, - PGPPassphraseAccessor passphraseAccessor, String provider) throws IOException, PGPException, NoSuchProviderException { + PGPPassphraseAccessor passphraseAccessor, String provider) throws IOException, PGPException, NoSuchProviderException { PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyringInput)); PGPObjectFactory factory = new PGPObjectFactory(PGPUtil.getDecoderStream(encryptedInput)); PGPEncryptedDataList enc; @@ -305,60 +313,101 @@ public final class PGPDataFormatUtil { @Deprecated public static PGPSecretKey findSecretKey(CamelContext context, String keychainFilename, String passphrase) throws IOException, - PGPException, NoSuchProviderException { + PGPException, NoSuchProviderException { return findSecretKey(context, keychainFilename, null, passphrase, "BC"); } + @Deprecated public static PGPSecretKey findSecretKey(CamelContext context, String keychainFilename, byte[] secKeyRing, String passphrase, - String userId, String provider) throws IOException, PGPException, NoSuchProviderException { + String userId, String provider) throws IOException, PGPException, NoSuchProviderException { InputStream keyChainInputStream = determineKeyRingInputStream(context, keychainFilename, secKeyRing, false); - PGPSecretKey secKey = null; try { - secKey = findSecretKey(keyChainInputStream, passphrase, userId, provider); + List<PGPSecretKeyAndPrivateKeyAndUserId> secKeys = findSecretKeysWithPrivateKeyAndUserId(keyChainInputStream, + Collections.singletonMap(userId, passphrase), provider); + if (!secKeys.isEmpty()) { + return secKeys.get(0).getSecretKey(); + } + return null; + } finally { + IOHelper.close(keyChainInputStream); + } + } + + public static List<PGPSecretKeyAndPrivateKeyAndUserId> findSecretKeysWithPrivateKeyAndUserId(CamelContext context, + String keychainFilename, byte[] secKeyRing, Map<String, String> sigKeyUserId2Password, String provider) throws IOException, + PGPException, NoSuchProviderException { + InputStream keyChainInputStream = determineKeyRingInputStream(context, keychainFilename, secKeyRing, false); + try { + return findSecretKeysWithPrivateKeyAndUserId(keyChainInputStream, sigKeyUserId2Password, provider); } finally { IOHelper.close(keyChainInputStream); } - return secKey; } @Deprecated public static PGPSecretKey findSecretKey(CamelContext context, String keychainFilename, byte[] secKeyRing, String passphrase, - String provider) throws IOException, PGPException, NoSuchProviderException { + String provider) throws IOException, PGPException, NoSuchProviderException { return findSecretKey(context, keychainFilename, secKeyRing, passphrase, null, provider); } @SuppressWarnings("unchecked") - private static PGPSecretKey findSecretKey(InputStream keyringInput, String passphrase, String userId, String provider) - throws IOException, PGPException, NoSuchProviderException { - PGPSecretKey pgpSecKey = null; + private static List<PGPSecretKeyAndPrivateKeyAndUserId> findSecretKeysWithPrivateKeyAndUserId(InputStream keyringInput, + Map<String, String> sigKeyUserId2Password, String provider) throws IOException, PGPException, NoSuchProviderException { + List<PGPSecretKeyAndPrivateKeyAndUserId> result = new ArrayList<PGPDataFormatUtil.PGPSecretKeyAndPrivateKeyAndUserId>( + sigKeyUserId2Password.size()); PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyringInput)); - for (Iterator<?> i = pgpSec.getKeyRings(); i.hasNext() && pgpSecKey == null;) { + for (Iterator<?> i = pgpSec.getKeyRings(); i.hasNext();) { Object data = i.next(); if (data instanceof PGPSecretKeyRing) { PGPSecretKeyRing keyring = (PGPSecretKeyRing) data; PGPSecretKey secKey = keyring.getSecretKey(); - if (userId != null) { + for (String userIdPart : sigKeyUserId2Password.keySet()) { for (Iterator<String> iterator = secKey.getUserIDs(); iterator.hasNext();) { String keyUserId = iterator.next(); // there can be serveral user IDs! - if (keyUserId != null && keyUserId.contains(userId)) { + if (keyUserId != null && keyUserId.contains(userIdPart)) { PGPPrivateKey privateKey = secKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider(provider) - .build(passphrase.toCharArray())); + .build(sigKeyUserId2Password.get(userIdPart).toCharArray())); if (privateKey != null) { - return secKey; + result.add(new PGPSecretKeyAndPrivateKeyAndUserId(secKey, privateKey, keyUserId)); + LOG.debug("Private key with key user ID {} and key ID {} found for specified user ID part {}", + new Object[] {keyUserId, Long.toString(privateKey.getKeyID()), userIdPart}); + } } } - } else { - PGPPrivateKey privateKey = secKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider(provider).build( - passphrase.toCharArray())); - if (privateKey != null) { - pgpSecKey = secKey; - } } } } - return pgpSecKey; + return result; + } + + public static class PGPSecretKeyAndPrivateKeyAndUserId { + + private final PGPSecretKey secretKey; + + private final PGPPrivateKey privateKey; + + private final String userId; + + public PGPSecretKeyAndPrivateKeyAndUserId(PGPSecretKey secretKey, PGPPrivateKey privateKey, String userId) { + this.secretKey = secretKey; + this.privateKey = privateKey; + this.userId = userId; + } + + public PGPSecretKey getSecretKey() { + return secretKey; + } + + public PGPPrivateKey getPrivateKey() { + return privateKey; + } + + public String getUserId() { + return userId; + } + } } http://git-wip-us.apache.org/repos/asf/camel/blob/a346c1c1/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatDynamicTest.java ---------------------------------------------------------------------- diff --git a/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatDynamicTest.java b/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatDynamicTest.java index 144b655..6250695 100644 --- a/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatDynamicTest.java +++ b/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatDynamicTest.java @@ -41,6 +41,15 @@ public class PGPDataFormatDynamicTest extends PGPDataFormatTest { userids.add(getKeyUserId()); return userids; } + + // setup a wrong signature userids + @Override + protected List<String> getSignatureKeyUserIds() { + List<String> userids = new ArrayList<String>(2); + userids.add("wrong1"); + userids.add(getKeyUserId()); + return userids; + } // setup a wrong password @Override @@ -77,6 +86,7 @@ public class PGPDataFormatDynamicTest extends PGPDataFormatTest { headers.put(PGPDataFormat.ENCRYPTION_ALGORITHM, SymmetricKeyAlgorithmTags.AES_128); headers.put(PGPDataFormat.SIGNATURE_HASH_ALGORITHM, HashAlgorithmTags.SHA512); headers.put(PGPDataFormat.COMPRESSION_ALGORITHM, CompressionAlgorithmTags.ZLIB); + headers.put(PGPDataFormat.SIGNATURE_KEY_USERIDS, Collections.singletonList("second")); return headers; } } http://git-wip-us.apache.org/repos/asf/camel/blob/a346c1c1/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatTest.java ---------------------------------------------------------------------- diff --git a/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatTest.java b/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatTest.java index 50af476..b815882 100644 --- a/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatTest.java +++ b/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatTest.java @@ -20,11 +20,15 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.camel.Exchange; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.util.IOHelper; import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.bouncycastle.bcpg.HashAlgorithmTags; @@ -55,6 +59,13 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { return userids; } + protected List<String> getSignatureKeyUserIds() { + List<String> userids = new ArrayList<String>(2); + userids.add("second"); + userids.add(getKeyUserId()); + return userids; + } + protected String getKeyPassword() { return "sdude"; } @@ -105,9 +116,62 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { doRoundTripEncryptionTests("direct:sign-key-ring-byte-array"); } + @Test + public void testSeveralSignerKeys() throws Exception { + doRoundTripEncryptionTests("direct:several-signer-keys"); + } + + @Test + public void testOneUserIdWithServeralKeys() throws Exception { + doRoundTripEncryptionTests("direct:one-userid-several-keys"); + } + + @Test + public void testVerifyExceptionNoPublicKeyFoundCorrespondingToSignatureUserIds() throws Exception { + setupExpectations(context, 1, "mock:encrypted"); + MockEndpoint exception = setupExpectations(context, 1, "mock:exception"); + + String payload = "Hi Alice, Be careful Eve is listening, signed Bob"; + Map<String, Object> headers = getHeaders(); + template.sendBodyAndHeaders("direct:verify_exception_sig_userids", payload, headers); + assertMockEndpointsSatisfied(); + + //check exception text + Exception e = (Exception) exception.getExchanges().get(0).getProperty(Exchange.EXCEPTION_CAUGHT); + assertNotNull("Expected excpetion missing", e); + assertTrue(e.getMessage().contains("No public key found fitting to the signature key Id")); + + } + + + @Test + public void testVerifyExceptionNoPassphraseSpecifiedForSignatureKeyUserId() throws Exception { + MockEndpoint exception = setupExpectations(context, 1, "mock:exception"); + + String payload = "Hi Alice, Be careful Eve is listening, signed Bob"; + Map<String, Object> headers = new HashMap<String, Object>(); + // add signature user id which does not have a passphrase + headers.put(PGPDataFormat.SIGNATURE_KEY_USERID, "userIDWithNoPassphrase"); + // the following entry is necessary for the dynamic test + headers.put(PGPDataFormat.KEY_USERID, "second"); + template.sendBodyAndHeaders("direct:several-signer-keys", payload, headers); + assertMockEndpointsSatisfied(); + + //check exception text + Exception e = (Exception) exception.getExchanges().get(0).getProperty(Exchange.EXCEPTION_CAUGHT); + assertNotNull("Expected excpetion missing", e); + assertTrue(e.getMessage().contains("No passphrase specified for signature key user ID")); + + } + + + protected RouteBuilder createRouteBuilder() { return new RouteBuilder() { public void configure() throws Exception { + + onException(IllegalArgumentException.class).handled(true).to("mock:exception"); + // START SNIPPET: pgp-format // Public Key FileName String keyFileName = getKeyFileName(); @@ -147,7 +211,7 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { pgpSignAndEncrypt.setKeyUserid(keyUserid); pgpSignAndEncrypt.setSignatureKeyFileName(keyFileNameSec); PGPPassphraseAccessor passphraseAccessor = getPassphraseAccessor(); - pgpSignAndEncrypt.setSignatureKeyUserid(keyUserid); + pgpSignAndEncrypt.setSignatureKeyUserid("Super <[email protected]>"); // must be the exact user Id because passphrase is searched in accessor pgpSignAndEncrypt.setPassphraseAccessor(passphraseAccessor); pgpSignAndEncrypt.setProvider(getProvider()); pgpSignAndEncrypt.setAlgorithm(getAlgorithm()); @@ -159,10 +223,18 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { pgpVerifyAndDecrypt.setPassword(keyPassword); pgpVerifyAndDecrypt.setSignatureKeyFileName(keyFileName); pgpVerifyAndDecrypt.setProvider(getProvider()); + pgpVerifyAndDecrypt.setSignatureKeyUserid(keyUserid); // restrict verification to public keys with certain User ID from("direct:inline-sign").marshal(pgpSignAndEncrypt).to("mock:encrypted").unmarshal(pgpVerifyAndDecrypt) .to("mock:unencrypted"); // END SNIPPET: pgp-format-signature + + // test verifying exception, no public key found corresponding to signature key userIds + from("direct:verify_exception_sig_userids").marshal(pgpSignAndEncrypt).to("mock:encrypted") + .setHeader(PGPDataFormat.SIGNATURE_KEY_USERIDS).constant(Arrays.asList(new String[] {"wrong1", "wrong2"})) + .setHeader(PGPDataFormat.SIGNATURE_KEY_USERID).constant("wrongUserID").unmarshal(pgpVerifyAndDecrypt) + .to("mock:unencrypted"); + /* ---- key ring as byte array -- */ // START SNIPPET: pgp-format-key-ring-byte-array PGPDataFormat pgpEncryptByteArray = new PGPDataFormat(); @@ -177,8 +249,8 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { pgpDecryptByteArray.setPassphraseAccessor(passphraseAccessor); pgpDecryptByteArray.setProvider(getProvider()); - from("direct:key-ring-byte-array").streamCaching().marshal(pgpEncryptByteArray).to("mock:encrypted").unmarshal(pgpDecryptByteArray) - .to("mock:unencrypted"); + from("direct:key-ring-byte-array").streamCaching().marshal(pgpEncryptByteArray).to("mock:encrypted") + .unmarshal(pgpDecryptByteArray).to("mock:unencrypted"); // END SNIPPET: pgp-format-key-ring-byte-array // START SNIPPET: pgp-format-signature-key-ring-byte-array @@ -196,6 +268,8 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { pgpVerifyAndDecryptByteArray.setPassphraseAccessor(passphraseAccessor); pgpVerifyAndDecryptByteArray.setEncryptionKeyRing(getSecKeyRing()); pgpVerifyAndDecryptByteArray.setProvider(getProvider()); + // restrict verification to public keys with certain User ID + pgpVerifyAndDecryptByteArray.setSignatureKeyUserids(getSignatureKeyUserIds()); from("direct:sign-key-ring-byte-array").streamCaching() // encryption key ring can also be set as header @@ -207,7 +281,76 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { // it is recommended to remove the header immediately when it is no longer needed .removeHeader(PGPDataFormat.SIGNATURE_KEY_RING).to("mock:unencrypted"); // END SNIPPET: pgp-format-signature-key-ring-byte-array + + // START SNIPPET: pgp-format-several-signer-keys + PGPDataFormat pgpSignAndEncryptSeveralSignerKeys = new PGPDataFormat(); + pgpSignAndEncryptSeveralSignerKeys.setKeyUserid(keyUserid); + pgpSignAndEncryptSeveralSignerKeys.setEncryptionKeyRing(getPublicKeyRing()); + pgpSignAndEncryptSeveralSignerKeys.setSignatureKeyRing(getSecKeyRing()); + + List<String> signerUserIds = new ArrayList<String>(); + signerUserIds.add("Third (comment third) <[email protected]>"); + signerUserIds.add("Second <[email protected]>"); + pgpSignAndEncryptSeveralSignerKeys.setSignatureKeyUserids(signerUserIds); + + Map<String, String> userId2Passphrase = new HashMap<String, String>(); + userId2Passphrase.put("Third (comment third) <[email protected]>", "sdude"); + userId2Passphrase.put("Second <[email protected]>", "sdude"); + PGPPassphraseAccessor passphraseAccessorSeveralKeys = new PGPPassphraseAccessorDefault(userId2Passphrase); + pgpSignAndEncryptSeveralSignerKeys.setPassphraseAccessor(passphraseAccessorSeveralKeys); + + PGPDataFormat pgpVerifyAndDecryptSeveralSignerKeys = new PGPDataFormat(); + pgpVerifyAndDecryptSeveralSignerKeys.setPassphraseAccessor(passphraseAccessor); + pgpVerifyAndDecryptSeveralSignerKeys.setEncryptionKeyRing(getSecKeyRing()); + pgpVerifyAndDecryptSeveralSignerKeys.setSignatureKeyRing(getPublicKeyRing()); + pgpVerifyAndDecryptSeveralSignerKeys.setProvider(getProvider()); + // only specify one expected signature + List<String> expectedSigUserIds = new ArrayList<String>(); + expectedSigUserIds.add("Second <[email protected]>"); + pgpVerifyAndDecryptSeveralSignerKeys.setSignatureKeyUserids(expectedSigUserIds); + from("direct:several-signer-keys").streamCaching().marshal(pgpSignAndEncryptSeveralSignerKeys).to("mock:encrypted") + .unmarshal(pgpVerifyAndDecryptSeveralSignerKeys).to("mock:unencrypted"); + // END SNIPPET: pgp-format-several-signer-keys + + // test encryption by several key and signing by serveral keys where the keys are specified by one User ID part + PGPDataFormat pgpSignAndEncryptOneUserIdWithServeralKeys = new PGPDataFormat(); + pgpSignAndEncryptOneUserIdWithServeralKeys.setEncryptionKeyRing(getPublicKeyRing()); + pgpSignAndEncryptOneUserIdWithServeralKeys.setSignatureKeyRing(getSecKeyRing()); + // the two private keys have the same password therefore we do not need a passphrase accessor + pgpSignAndEncryptOneUserIdWithServeralKeys.setPassword(getKeyPassword()); + + PGPDataFormat pgpVerifyAndDecryptOneUserIdWithServeralKeys = new PGPDataFormat(); + pgpVerifyAndDecryptOneUserIdWithServeralKeys.setPassword(getKeyPassword()); + pgpVerifyAndDecryptOneUserIdWithServeralKeys.setEncryptionKeyRing(getSecKeyRing()); + pgpVerifyAndDecryptOneUserIdWithServeralKeys.setSignatureKeyRing(getPublicKeyRing()); + pgpVerifyAndDecryptOneUserIdWithServeralKeys.setProvider(getProvider()); + pgpVerifyAndDecryptOneUserIdWithServeralKeys.setSignatureKeyUserids(expectedSigUserIds); + from("direct:one-userid-several-keys") + // there are two keys which have a User ID which contains the string "econd" + .setHeader(PGPDataFormat.KEY_USERID) + .constant("econd") + .setHeader(PGPDataFormat.SIGNATURE_KEY_USERID) + .constant("econd") + .marshal(pgpSignAndEncryptOneUserIdWithServeralKeys) + // it is recommended to remove the header immediately when it is no longer needed + .removeHeader(PGPDataFormat.KEY_USERID) + .removeHeader(PGPDataFormat.SIGNATURE_KEY_USERID) + .to("mock:encrypted") + // only specify one expected signature key, to check the first signature + .setHeader(PGPDataFormat.SIGNATURE_KEY_USERID) + .constant("Second <[email protected]>") + .unmarshal(pgpVerifyAndDecryptOneUserIdWithServeralKeys) + // do it again but now check the second signature key + // there are two keys which have a User ID which contains the string "econd" + .setHeader(PGPDataFormat.KEY_USERID).constant("econd").setHeader(PGPDataFormat.SIGNATURE_KEY_USERID) + .constant("econd").marshal(pgpSignAndEncryptOneUserIdWithServeralKeys) + // it is recommended to remove the header immediately when it is no longer needed + .removeHeader(PGPDataFormat.KEY_USERID).removeHeader(PGPDataFormat.SIGNATURE_KEY_USERID) + // only specify one expected signature key, to check the second signature + .setHeader(PGPDataFormat.SIGNATURE_KEY_USERID).constant("Third (comment third) <[email protected]>") + .unmarshal(pgpVerifyAndDecryptOneUserIdWithServeralKeys).to("mock:unencrypted"); } + }; }
