Repository: camel Updated Branches: refs/heads/master 4be7f3630 -> e8c9f630b
CAMEL-7283: PGP Data Format: Signature Verification Options. Thanks to Franz Forsthofer for the patch. Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/e8c9f630 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/e8c9f630 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/e8c9f630 Branch: refs/heads/master Commit: e8c9f630b482f4687274cd2f184a0e16238ed94c Parents: 4be7f36 Author: Claus Ibsen <davscl...@apache.org> Authored: Thu Mar 13 10:42:03 2014 +0100 Committer: Claus Ibsen <davscl...@apache.org> Committed: Thu Mar 13 10:42:03 2014 +0100 ---------------------------------------------------------------------- .../converter/crypto/PGPDataFormatUtil.java | 148 ++++++------- .../crypto/PGPKeyAccessDataFormat.java | 222 +++++++++++++------ .../converter/crypto/PGPDataFormatTest.java | 93 ++++++-- 3 files changed, 303 insertions(+), 160 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/e8c9f630/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 b8849db..8713c3e 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 @@ -24,10 +24,8 @@ import java.security.NoSuchProviderException; import java.util.ArrayList; 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; import org.apache.camel.util.IOHelper; @@ -112,7 +110,7 @@ public final class PGPDataFormatUtil { } return pubKey; } - + public static PGPPublicKeyRingCollection getPublicKeyRingCollection(CamelContext context, String filename, byte[] keyRing, boolean forEncryption) throws IOException, PGPException { InputStream is = determineKeyRingInputStream(context, filename, keyRing, forEncryption); try { @@ -204,35 +202,37 @@ public final class PGPDataFormatUtil { return findPublicKeys(userids, forEncryption, pgpSec); } - @SuppressWarnings("unchecked") - public static List<PGPPublicKey> findPublicKeys(List<String> userids, boolean forEncryption, PGPPublicKeyRingCollection pgpPublicKeyringCollection) { - List<PGPPublicKey> result = new ArrayList<PGPPublicKey>(userids.size()); + public static List<PGPPublicKey> findPublicKeys(List<String> useridParts, boolean forEncryption, PGPPublicKeyRingCollection pgpPublicKeyringCollection) { + List<PGPPublicKey> result = new ArrayList<PGPPublicKey>(useridParts.size()); for (Iterator<PGPPublicKeyRing> keyRingIter = pgpPublicKeyringCollection.getKeyRings(); keyRingIter.hasNext();) { PGPPublicKeyRing keyRing = keyRingIter.next(); - Set<String> keyUserIds = getUserIds(keyRing); + PGPPublicKey primaryKey = keyRing.getPublicKey(); + String[] foundKeyUserIdForUserIdPart = findFirstKeyUserIdContainingOneOfTheParts(useridParts, primaryKey); + if (foundKeyUserIdForUserIdPart == null) { + LOG.debug("No User ID found in primary key with key ID {} containing one of the parts {}", primaryKey.getKeyID(), + useridParts); + continue; + } + LOG.debug("User ID {} found in primary key with key ID {} containing one of the parts {}", new Object[] { + foundKeyUserIdForUserIdPart[0], primaryKey.getKeyID(), useridParts }); + // add adequate keys to the result for (Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys(); keyIter.hasNext();) { PGPPublicKey key = keyIter.next(); - for (String useridPart : userids) { - for (String keyUserId : keyUserIds) { - if (keyUserId != null && keyUserId.contains(useridPart)) { - if (forEncryption) { - if (isEncryptionKey(key)) { - LOG.debug( - "Public encryption key with key user ID {} and key ID {} found for specified user ID part {}", - new Object[] {keyUserId, Long.toString(key.getKeyID()), useridPart }); - result.add(key); - } - } else if (!forEncryption && isSignatureKey(key)) { - // not used! - 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 }); - } - } + if (forEncryption) { + if (isEncryptionKey(key)) { + LOG.debug("Public encryption key with key user ID {} and key ID {} added to the encryption keys", + foundKeyUserIdForUserIdPart[0], Long.toString(key.getKeyID())); + result.add(key); } + } else if (!forEncryption && isSignatureKey(key)) { + // not used! + result.add(key); + LOG.debug("Public key with key user ID {} and key ID {} added to the signing keys", foundKeyUserIdForUserIdPart[0], + Long.toString(key.getKeyID())); } } + } return result; @@ -247,7 +247,7 @@ public final class PGPDataFormatUtil { if (hasEncryptionKeyFlags != null && !hasEncryptionKeyFlags) { LOG.debug( "Public key with key key ID {} found for specified user ID. But this key will not be used for the encryption, because its key flags are not encryption key flags.", - new Object[] {Long.toString(key.getKeyID()) }); + Long.toString(key.getKeyID())); return false; } else { // also without keyflags (hasEncryptionKeyFlags = null), true is returned! @@ -261,31 +261,18 @@ public final class PGPDataFormatUtil { // the user IDs of the master / primary key. The master / primary key is the first key in // the keyring, and the rest of the keys are subkeys. // http://bouncy-castle.1462172.n4.nabble.com/How-to-find-PGP-subkeys-td1465289.html - @SuppressWarnings("unchecked") - private static Set<String> getUserIds(PGPPublicKeyRing keyRing) { - Set<String> userIds = new LinkedHashSet<String>(3); - for (Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys(); keyIter.hasNext();) { - PGPPublicKey key = keyIter.next(); - for (Iterator<String> iterator = key.getUserIDs(); iterator.hasNext();) { - userIds.add(iterator.next()); + private static String[] findFirstKeyUserIdContainingOneOfTheParts(List<String> useridParts, PGPPublicKey primaryKey) { + String[] foundKeyUserIdForUserIdPart = null; + for (@SuppressWarnings("unchecked") + Iterator<String> iterator = primaryKey.getUserIDs(); iterator.hasNext();) { + String keyUserId = iterator.next(); + for (String userIdPart : useridParts) { + if (keyUserId.contains(userIdPart)) { + foundKeyUserIdForUserIdPart = new String[] {keyUserId, userIdPart }; + } } } - return userIds; - } - - // Within a secret keyring, the master / primary key has the user ID(s); the subkeys don't - // have user IDs associated directly to them, but the subkeys are implicitly associated with - // the user IDs of the master / primary key. The master / primary key is the first key in - // the keyring, and the rest of the keys are subkeys. - // http://bouncy-castle.1462172.n4.nabble.com/How-to-find-PGP-subkeys-td1465289.html - @SuppressWarnings("unchecked") - private static Set<String> getUserIds(PGPSecretKeyRing keyRing) { - Set<String> userIds = new LinkedHashSet<String>(3); - PGPSecretKey key = keyRing.getSecretKey(); - for (Iterator<String> iterator = key.getUserIDs(); iterator.hasNext();) { - userIds.add(iterator.next()); - } - return userIds; + return foundKeyUserIdForUserIdPart; } private static boolean isSignatureKey(PGPPublicKey key) { @@ -409,31 +396,33 @@ public final class PGPDataFormatUtil { public static List<PGPSecretKeyAndPrivateKeyAndUserId> findSecretKeysWithPrivateKeyAndUserId(Map<String, String> sigKeyUserId2Password, String provider, PGPSecretKeyRingCollection pgpSec) throws PGPException { - List<PGPSecretKeyAndPrivateKeyAndUserId> result = new ArrayList<PGPSecretKeyAndPrivateKeyAndUserId>( - sigKeyUserId2Password.size()); + List<PGPSecretKeyAndPrivateKeyAndUserId> result = new ArrayList<PGPSecretKeyAndPrivateKeyAndUserId>(sigKeyUserId2Password.size()); for (Iterator<?> i = pgpSec.getKeyRings(); i.hasNext();) { Object data = i.next(); if (data instanceof PGPSecretKeyRing) { PGPSecretKeyRing keyring = (PGPSecretKeyRing) data; - Set<String> keyUserIds = getUserIds(keyring); - - for (String userIdPart : sigKeyUserId2Password.keySet()) { - for (String keyUserId : keyUserIds) { - if (keyUserId.contains(userIdPart)) { - for (@SuppressWarnings("unchecked") - Iterator<PGPSecretKey> iterKey = keyring.getSecretKeys(); iterKey.hasNext();) { - PGPSecretKey secKey = iterKey.next(); - if (isSigningKey(secKey)) { - PGPPrivateKey privateKey = secKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider( - provider).build(sigKeyUserId2Password.get(userIdPart).toCharArray())); - if (privateKey != null) { - 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 }); - - } - } - } + PGPSecretKey primaryKey = keyring.getSecretKey(); + List<String> useridParts = new ArrayList<String>(sigKeyUserId2Password.keySet()); + String[] foundKeyUserIdForUserIdPart = findFirstKeyUserIdContainingOneOfTheParts(useridParts, primaryKey.getPublicKey()); + if (foundKeyUserIdForUserIdPart == null) { + LOG.debug("No User ID found in primary key with key ID {} containing one of the parts {}", primaryKey.getKeyID(), + useridParts); + continue; + } + LOG.debug("User ID {} found in primary key with key ID {} containing one of the parts {}", new Object[] { + foundKeyUserIdForUserIdPart[0], primaryKey.getKeyID(), useridParts }); + // add all signing keys + for (@SuppressWarnings("unchecked") + Iterator<PGPSecretKey> iterKey = keyring.getSecretKeys(); iterKey.hasNext();) { + PGPSecretKey secKey = iterKey.next(); + if (isSigningKey(secKey)) { + PGPPrivateKey privateKey = secKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider(provider) + .build(sigKeyUserId2Password.get(foundKeyUserIdForUserIdPart[1]).toCharArray())); + if (privateKey != null) { + result.add(new PGPSecretKeyAndPrivateKeyAndUserId(secKey, privateKey, foundKeyUserIdForUserIdPart[0])); + LOG.debug("Private key with user ID {} and key ID {} added to the signing keys", + foundKeyUserIdForUserIdPart[0], Long.toString(privateKey.getKeyID())); + } } } @@ -493,14 +482,19 @@ public final class PGPDataFormatUtil { } return null; // no key flag } - - + /** - * Determines a public key from the keyring collection which has a certain key ID and which has a User ID which contains at least one of the User ID parts. + * Determines a public key from the keyring collection which has a certain + * key ID and which has a User ID which contains at least one of the User ID + * parts. * - * @param keyId key ID - * @param userIdParts user ID parts, can be empty, than no filter on the User ID is executed - * @param publicKeyringCollection keyring collection + * @param keyId + * key ID + * @param userIdParts + * user ID parts, can be empty, than no filter on the User ID is + * executed + * @param publicKeyringCollection + * keyring collection * @return public key or <code>null</code> if no fitting key is found * @throws PGPException */ @@ -519,7 +513,7 @@ public final class PGPDataFormatUtil { return null; } } - + private static boolean isAllowedKey(List<String> allowedUserIds, Iterator<String> verifyingPublicKeyUserIds) { if (allowedUserIds == null || allowedUserIds.isEmpty()) { @@ -543,5 +537,5 @@ public final class PGPDataFormatUtil { keyUserId, allowedUserIds); return false; } - + } http://git-wip-us.apache.org/repos/asf/camel/blob/e8c9f630/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPKeyAccessDataFormat.java ---------------------------------------------------------------------- diff --git a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPKeyAccessDataFormat.java b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPKeyAccessDataFormat.java index 92663c4..179a4d1 100644 --- a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPKeyAccessDataFormat.java +++ b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPKeyAccessDataFormat.java @@ -18,12 +18,14 @@ package org.apache.camel.converter.crypto; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; +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; @@ -76,6 +78,10 @@ import org.slf4j.LoggerFactory; * array or file, then you should use the class {@link PGPDataFormat}. * */ +/** + * @author D023101 + * + */ public class PGPKeyAccessDataFormat extends ServiceSupport implements DataFormat { public static final String KEY_USERID = "CamelPGPDataFormatKeyUserid"; @@ -87,10 +93,40 @@ public class PGPKeyAccessDataFormat extends ServiceSupport implements DataFormat public static final String COMPRESSION_ALGORITHM = "CamelPGPDataFormatCompressionAlgorithm"; /** - * During encryption the number of asymmectirc encryption keys is set to - * this header parameter. The Value is of type Integer. + * 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 @@ -99,11 +135,13 @@ public class PGPKeyAccessDataFormat extends ServiceSupport implements DataFormat 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; @@ -130,6 +168,8 @@ public class PGPKeyAccessDataFormat extends ServiceSupport implements DataFormat private int compressionAlgorithm = CompressionAlgorithmTags.ZIP; // for encryption + private String signatureVerificationOption = "optional"; + public PGPKeyAccessDataFormat() { } @@ -308,52 +348,9 @@ public class PGPKeyAccessDataFormat extends ServiceSupport implements DataFormat return null; } InputStream in = PGPUtil.getDecoderStream(encryptedStream); - PGPObjectFactory pgpFactory = new PGPObjectFactory(in); - Object firstObject = pgpFactory.nextObject(); - // the first object might be a PGP marker packet - PGPEncryptedDataList enc; - if (firstObject instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList) firstObject; - } else { - Object secondObject = pgpFactory.nextObject(); - if (secondObject instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList)secondObject; - } else { - enc = null; - } - } - - 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("Message is encrypted with a key which could not be found in the Secret Key Ring."); - } - - InputStream encData = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider(getProvider()).build(key)); - pgpFactory = new PGPObjectFactory(encData); - Object compObj = pgpFactory.nextObject(); - if (!(compObj instanceof PGPCompressedData)) { - throw getFormatException(); - } - PGPCompressedData comData = (PGPCompressedData)compObj; - pgpFactory = new PGPObjectFactory(comData.getDataStream()); + InputStream encData = getDecryptedData(exchange, in); + InputStream uncompressedData = getUncompressedData(encData); + PGPObjectFactory pgpFactory = new PGPObjectFactory(uncompressedData); Object object = pgpFactory.nextObject(); PGPOnePassSignature signature; @@ -361,7 +358,12 @@ public class PGPKeyAccessDataFormat extends ServiceSupport implements DataFormat 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; @@ -400,12 +402,7 @@ public class PGPKeyAccessDataFormat extends ServiceSupport implements DataFormat IOHelper.close(os, litData, encData, in); } - if (signature != null) { - PGPSignatureList sigList = (PGPSignatureList) pgpFactory.nextObject(); - if (!signature.verify(getSignatureWithKeyId(signature.getKeyID(), sigList))) { - throw new SignatureException("Cannot verify PGP signature"); - } - } + verifySignature(pgpFactory, signature); if (cos != null) { return cos.newStreamCache(); @@ -414,11 +411,79 @@ public class PGPKeyAccessDataFormat extends ServiceSupport implements DataFormat } } + private InputStream getUncompressedData(InputStream encData) throws IOException, PGPException { + PGPObjectFactory pgpFactory = new PGPObjectFactory(encData); + Object compObj = pgpFactory.nextObject(); + if (!(compObj instanceof PGPCompressedData)) { + throw getFormatException(); + } + PGPCompressedData comData = (PGPCompressedData) compObj; + InputStream uncompressedData = comData.getDataStream(); + return uncompressedData; + } + + private InputStream getDecryptedData(Exchange exchange, InputStream encryptedStream) throws Exception, PGPException { + PGPObjectFactory pgpFactory = new PGPObjectFactory(encryptedStream); + 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 encData; + } + + 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 ...,)"); + 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) { @@ -432,7 +497,13 @@ public class PGPKeyAccessDataFormat extends ServiceSupport implements DataFormat } 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); @@ -448,7 +519,8 @@ public class PGPKeyAccessDataFormat extends ServiceSupport implements DataFormat if (signatureList.isEmpty()) { return null; } else { - throw new IllegalArgumentException("No public key found fitting to the signature key Id; cannot verify the signature."); + 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."); } } @@ -626,6 +698,32 @@ public class PGPKeyAccessDataFormat extends ServiceSupport implements DataFormat this.secretKeyAccessor = secretKeyAccessor; } + public String getSignatureVerificationOption() { + return signatureVerificationOption; + } + + /** + * 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 IllegalArgument + * exception 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"); + } + } + @Override protected void doStart() throws Exception { if (Security.getProvider(BC) == null && BC.equals(getProvider())) { http://git-wip-us.apache.org/repos/asf/camel/blob/e8c9f630/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 5237710..98f9121 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 @@ -64,6 +64,7 @@ import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerat import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; +import org.junit.Before; import org.junit.Test; public class PGPDataFormatTest extends AbstractPGPDataFormatTest { @@ -72,6 +73,26 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { private static final String SEC_KEY_RING_FILE_NAME = "org/apache/camel/component/crypto/secring.gpg"; private static final String PUB_KEY_RING_FILE_NAME = "org/apache/camel/component/crypto/pubring.gpg"; + PGPDataFormat encryptor = new PGPDataFormat(); + PGPDataFormat decryptor = new PGPDataFormat(); + + @Before + public void setUpEncryptorAndDecryptor() { + + // the following keyring contains a primary key with KeyFlag "Certify" and a subkey for signing and a subkey for encryption + encryptor.setKeyFileName(PUB_KEY_RING_SUBKEYS_FILE_NAME); + encryptor.setSignatureKeyFileName("org/apache/camel/component/crypto/secringSubKeys.gpg"); + encryptor.setSignaturePassword("Abcd1234"); + encryptor.setKeyUserid("keyflag"); + encryptor.setSignatureKeyUserid("keyflag"); + + // the following keyring contains a primary key with KeyFlag "Certify" and a subkey for signing and a subkey for encryption + decryptor.setKeyFileName("org/apache/camel/component/crypto/secringSubKeys.gpg"); + decryptor.setSignatureKeyFileName(PUB_KEY_RING_SUBKEYS_FILE_NAME); + decryptor.setPassword("Abcd1234"); + decryptor.setSignatureKeyUserid("keyflag"); + } + protected String getKeyFileName() { return PUB_KEY_RING_FILE_NAME; } @@ -173,7 +194,7 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { template.sendBodyAndHeaders("direct:verify_exception_sig_userids", payload, headers); assertMockEndpointsSatisfied(); - checkThrownException(exception, IllegalArgumentException.class, null, "No public key found fitting to the signature key Id"); + checkThrownException(exception, IllegalArgumentException.class, null, "No public key found for the key ID(s)"); } @@ -299,7 +320,7 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { assertMockEndpointsSatisfied(); checkThrownException(mock, PGPException.class, null, - "Message is encrypted with a key which could not be found in the Secret Key Ring"); + "PGP message is encrypted with a key which could not be found in the Secret Keyring"); } void createEncryptedNonCompressedData(ByteArrayOutputStream bos, String keyringPath) throws Exception, IOException, PGPException, @@ -419,6 +440,49 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { checkThrownException(mock, IllegalArgumentException.class, null, "The input message body has an invalid format."); } + @Test + public void testExceptionForSignatureVerificationOptionNoSignatureAllowed() throws Exception { + + decryptor.setSignatureVerificationOption(PGPDataFormat.SIGNATURE_VERIFICATION_OPTION_NO_SIGNATURE_ALLOWED); + + MockEndpoint mock = getMockEndpoint("mock:exception"); + mock.expectedMessageCount(1); + template.sendBody("direct:subkey", "Test Message"); + assertMockEndpointsSatisfied(); + + checkThrownException(mock, PGPException.class, null, "PGP message contains a signature although a signature is not expected"); + } + + @Test + public void testExceptionForSignatureVerificationOptionRequired() throws Exception { + + encryptor.setSignatureKeyUserid(null); // no signature + decryptor.setSignatureVerificationOption(PGPDataFormat.SIGNATURE_VERIFICATION_OPTION_REQUIRED); + + MockEndpoint mock = getMockEndpoint("mock:exception"); + mock.expectedMessageCount(1); + template.sendBody("direct:subkey", "Test Message"); + assertMockEndpointsSatisfied(); + + checkThrownException(mock, PGPException.class, null, "PGP message does not contain any signatures although a signature is expected"); + } + + @Test + public void testSignatureVerificationOptionIgnore() throws Exception { + + // encryptor is sending a PGP message with signature! Decryptor is ignoreing the signature + decryptor.setSignatureVerificationOption(PGPDataFormat.SIGNATURE_VERIFICATION_OPTION_IGNORE); + decryptor.setSignatureKeyUserids(null); + decryptor.setSignatureKeyFileName(null); // no public keyring! --> no signature validation possible + + String payload = "Test Message"; + MockEndpoint mock = getMockEndpoint("mock:unencrypted"); + mock.expectedBodiesReceived(payload); + template.sendBody("direct:subkey", payload); + assertMockEndpointsSatisfied(); + + } + protected RouteBuilder[] createRouteBuilders() { return new RouteBuilder[] {new RouteBuilder() { public void configure() throws Exception { @@ -451,6 +515,7 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { pgpDecrypt.setKeyFileName(keyFileNameSec); pgpDecrypt.setPassword(keyPassword); pgpDecrypt.setProvider(getProvider()); + pgpDecrypt.setSignatureVerificationOption(PGPDataFormat.SIGNATURE_VERIFICATION_OPTION_NO_SIGNATURE_ALLOWED); from("direct:inline2").marshal(pgpEncrypt).to("mock:encrypted").unmarshal(pgpDecrypt).to("mock:unencrypted"); @@ -523,6 +588,7 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { pgpVerifyAndDecryptByteArray.setProvider(getProvider()); // restrict verification to public keys with certain User ID pgpVerifyAndDecryptByteArray.setSignatureKeyUserids(getSignatureKeyUserIds()); + pgpVerifyAndDecryptByteArray.setSignatureVerificationOption(PGPDataFormat.SIGNATURE_VERIFICATION_OPTION_REQUIRED); from("direct:sign-key-ring-byte-array").streamCaching() // encryption key ring can also be set as header @@ -609,28 +675,13 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { public void configure() throws Exception { onException(Exception.class).handled(true).to("mock:exception"); - // keyflag test - PGPDataFormat pgpKeyFlag = new PGPDataFormat(); - // the following keyring contains a primary key with KeyFlag "Certify" and a subkey for signing and a subkey for encryption - pgpKeyFlag.setKeyFileName(PUB_KEY_RING_SUBKEYS_FILE_NAME); - pgpKeyFlag.setSignatureKeyFileName("org/apache/camel/component/crypto/secringSubKeys.gpg"); - pgpKeyFlag.setSignaturePassword("Abcd1234"); - pgpKeyFlag.setKeyUserid("keyflag"); - pgpKeyFlag.setSignatureKeyUserid("keyflag"); - - from("direct:keyflag").marshal(pgpKeyFlag).to("mock:encrypted_keyflag"); - - PGPDataFormat pgpDecryptVerifySubkey = new PGPDataFormat(); - // the following keyring contains a primary key with KeyFlag "Certify" and a subkey for signing and a subkey for encryption - pgpDecryptVerifySubkey.setKeyFileName("org/apache/camel/component/crypto/secringSubKeys.gpg"); - pgpDecryptVerifySubkey.setSignatureKeyFileName(PUB_KEY_RING_SUBKEYS_FILE_NAME); - pgpDecryptVerifySubkey.setPassword("Abcd1234"); - pgpDecryptVerifySubkey.setSignatureKeyUserid("keyflag"); + + from("direct:keyflag").marshal(encryptor).to("mock:encrypted_keyflag"); // test that the correct subkey is selected during decrypt and verify - from("direct:subkey").marshal(pgpKeyFlag).to("mock:encrypted").unmarshal(pgpDecryptVerifySubkey).to("mock:unencrypted"); + from("direct:subkey").marshal(encryptor).to("mock:encrypted").unmarshal(decryptor).to("mock:unencrypted"); - from("direct:subkeyUnmarshal").unmarshal(pgpDecryptVerifySubkey).to("mock:unencrypted"); + from("direct:subkeyUnmarshal").unmarshal(decryptor).to("mock:unencrypted"); } }, new RouteBuilder() { public void configure() throws Exception {