Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.bouncycastle.bcpg.AEADAlgorithmTags;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.KeyIdentifier;
import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites;
Expand Down Expand Up @@ -53,6 +54,7 @@ public class OpenPGPMessageGenerator
private boolean isArmored = true;
public boolean isAllowPadding = true;
private final List<OpenPGPCertificate.OpenPGPComponentKey> encryptionKeys = new ArrayList<OpenPGPCertificate.OpenPGPComponentKey>();
private final List<KeyIdentifier> anonymousRecipients = new ArrayList<KeyIdentifier>();
private final List<char[]> messagePassphrases = new ArrayList<char[]>();

// Literal Data metadata
Expand Down Expand Up @@ -88,7 +90,25 @@ public OpenPGPMessageGenerator(OpenPGPImplementation implementation, OpenPGPPoli
public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recipientCertificate)
throws InvalidEncryptionKeyException
{
return addEncryptionCertificate(recipientCertificate, encryptionKeySelector);
return addEncryptionCertificate(recipientCertificate, false);
}

/**
* Add a recipients certificate to the set of encryption keys.
* Subkeys will be selected using the default {@link SubkeySelector}, which can be replaced by calling
* {@link #setEncryptionKeySelector(SubkeySelector)}.
* The recipient will be able to decrypt the message using their corresponding secret key.
* If anonymousRecipient is true, the recipients key-identifier will be obfuscated via a wildcard key-identifier.
*
* @param recipientCertificate recipient certificate (public key)
* @param anonymousRecipient whether to obfuscate the recipients identity within the message
* @return this
*/
public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recipientCertificate,
boolean anonymousRecipient)
throws InvalidEncryptionKeyException
{
return addEncryptionCertificate(recipientCertificate, encryptionKeySelector, anonymousRecipient);
}

/**
Expand All @@ -103,15 +123,39 @@ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recip
*/
public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recipientCertificate,
SubkeySelector subkeySelector)
throws InvalidEncryptionKeyException
throws InvalidEncryptionKeyException
{
return addEncryptionCertificate(recipientCertificate, subkeySelector, false);
}

/**
* Add a recipients certificate to the set of encryption keys.
* Subkeys will be selected using the provided {@link SubkeySelector}.
* The recipient will be able to decrypt the message using their corresponding secret key.
* If anonymous recipient is true, the identity of the recipient will be obfuscated via a wildcard key-identifier.
*
* @param recipientCertificate recipient certificate (public key)
* @param subkeySelector selector for encryption subkeys
* @param anonymousRecipient whether to obfuscate the recipients identity within the message
* @return this
* @throws InvalidEncryptionKeyException if the certificate is not capable of encryption
*/
public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recipientCertificate,
SubkeySelector subkeySelector,
boolean anonymousRecipient)
throws InvalidEncryptionKeyException
{
List<OpenPGPCertificate.OpenPGPComponentKey> subkeys =
subkeySelector.select(recipientCertificate, policy);
subkeySelector.select(recipientCertificate, policy);
if (subkeys.isEmpty())
{
throw new InvalidEncryptionKeyException(recipientCertificate);
}
this.encryptionKeys.addAll(subkeys);

for (OpenPGPCertificate.OpenPGPComponentKey subkey : subkeys)
{
addEncryptionCertificate(subkey, anonymousRecipient);
}
return this;
}

Expand All @@ -124,13 +168,34 @@ public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate recip
* @throws InvalidEncryptionKeyException if the key is not capable of encryption
*/
public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate.OpenPGPComponentKey encryptionKey)
throws InvalidEncryptionKeyException
throws InvalidEncryptionKeyException
{
return addEncryptionCertificate(encryptionKey, false);
}

/**
* Add a (sub-)key to the set of recipient encryption keys.
* The recipient will be able to decrypt the message using their corresponding secret key.
* If anonymous recipient is true, the identity of the recipient will be obfuscated via a wildcard key-identifier.
*
* @param encryptionKey encryption capable subkey
* @param anonymousRecipient whether to obfuscate the recipients identity within the message
* @return this
* @throws InvalidEncryptionKeyException if the key is not capable of encryption
*/
public OpenPGPMessageGenerator addEncryptionCertificate(OpenPGPCertificate.OpenPGPComponentKey encryptionKey,
boolean anonymousRecipient)
throws InvalidEncryptionKeyException
{
if (!encryptionKey.isEncryptionKey())
{
throw new InvalidEncryptionKeyException(encryptionKey);
}
encryptionKeys.add(encryptionKey);
if (anonymousRecipient)
{
anonymousRecipients.add(encryptionKey.getKeyIdentifier());
}
return this;
}

Expand Down Expand Up @@ -282,6 +347,8 @@ private void applyOptionalEncryption(
{
PublicKeyKeyEncryptionMethodGenerator method = implementation.publicKeyKeyEncryptionMethodGenerator(
encryptionSubkey.getPGPPublicKey());
// optionally mark recipient as anonymous
method.setUseWildcardRecipient(anonymousRecipients.contains(encryptionSubkey.getKeyIdentifier()));
encGen.addMethod(method);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ Decrypted decrypt(PGPEncryptedDataList encDataList)

List<PGPPBEEncryptedData> skesks = skesks(encDataList);
List<PGPPublicKeyEncryptedData> pkesks = pkesks(encDataList);
List<PGPPublicKeyEncryptedData> anonPkesks = anonPkesks(pkesks);

PGPException exception = null;

Expand Down Expand Up @@ -384,6 +385,57 @@ Decrypted decrypt(PGPEncryptedDataList encDataList)
}
}

// Edge-case: There are anonymous (wildcard) key identifiers
if (!anonPkesks.isEmpty())
{
for (PGPPublicKeyEncryptedData pkesk : anonPkesks)
{
for (OpenPGPKey key : configuration.keyPool.getAllItems())
{
for (OpenPGPCertificate.OpenPGPComponentKey encryptionKey : key.getEncryptionKeys())
{
OpenPGPKey.OpenPGPSecretKey decryptionKey = key.getSecretKey(encryptionKey);
if (decryptionKey == null)
{
// Missing (potentially stripped) secret key
continue;
}

try
{
char[] keyPassphrase = configuration.keyPassphraseProvider.getKeyPassword(decryptionKey);
PGPKeyPair unlockedKey = decryptionKey.unlock(keyPassphrase).getKeyPair();
if (unlockedKey == null)
{
throw new KeyPassphraseException(decryptionKey, new PGPException("Cannot unlock secret key."));
}

// Decrypt the message session key using the private key
PublicKeyDataDecryptorFactory pkDecryptorFactory =
implementation.publicKeyDataDecryptorFactory(unlockedKey.getPrivateKey());
PGPSessionKey decryptedSessionKey = pkesk.getSessionKey(pkDecryptorFactory);

// Decrypt the message using the decrypted session key
SessionKeyDataDecryptorFactory skDecryptorFactory =
implementation.sessionKeyDataDecryptorFactory(decryptedSessionKey);
PGPSessionKeyEncryptedData encData = encDataList.extractSessionKeyEncryptedData();
InputStream decryptedIn = encData.getDataStream(skDecryptorFactory);
IntegrityProtectedInputStream verifyingIn = new IntegrityProtectedInputStream(decryptedIn, encData);
Decrypted decrypted = new Decrypted(encData, decryptedSessionKey, verifyingIn);
decrypted.decryptionKey = decryptionKey;
return decrypted;
}
catch (PGPException e)
{
onException(e);
// cache first exception, then continue to try next skesk if present
exception = exception != null ? exception : e;
}
}
}
}
}

// And lastly, we'll prompt the user dynamically for a message passphrase
if (!skesks.isEmpty() && configuration.missingMessagePassphraseCallback != null)
{
Expand Down Expand Up @@ -463,6 +515,19 @@ private List<PGPPublicKeyEncryptedData> pkesks(PGPEncryptedDataList encDataList)
return list;
}

private List<PGPPublicKeyEncryptedData> anonPkesks(List<PGPPublicKeyEncryptedData> pkesks)
{
List<PGPPublicKeyEncryptedData> list = new ArrayList<PGPPublicKeyEncryptedData>();
for (PGPPublicKeyEncryptedData pkesk : pkesks)
{
if (pkesk.getKeyIdentifier().isWildcard())
{
list.add(pkesk);
}
}
return list;
}

OpenPGPCertificate provideCertificate(KeyIdentifier identifier)
{
return configuration.certificatePool.provide(identifier);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
package org.bouncycastle.openpgp.api.test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.bcpg.BCPGInputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.openpgp.OpenPGPTestKeys;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.api.OpenPGPApi;
import org.bouncycastle.openpgp.api.OpenPGPCertificate;
import org.bouncycastle.openpgp.api.OpenPGPKey;
import org.bouncycastle.openpgp.api.OpenPGPMessageGenerator;
import org.bouncycastle.openpgp.api.OpenPGPMessageInputStream;
import org.bouncycastle.openpgp.api.OpenPGPMessageOutputStream;
import org.bouncycastle.openpgp.api.OpenPGPMessageProcessor;
import org.bouncycastle.openpgp.api.OpenPGPPolicy;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Hex;
Expand All @@ -38,6 +47,7 @@ protected void performTestWith(OpenPGPApi api)
seipd2EncryptedMessage(api);

seipd2EncryptedSignedMessage(api);
encryptWithWildcardKeyIdentifier(api);
}

private void armoredLiteralDataPacket(OpenPGPApi api)
Expand Down Expand Up @@ -186,6 +196,45 @@ private void seipd2EncryptedSignedMessage(OpenPGPApi api)
System.out.println(bOut);
}

public void encryptWithWildcardKeyIdentifier(OpenPGPApi api)
throws IOException, PGPException {
OpenPGPKey key = api.readKeyOrCertificate().parseKey(OpenPGPTestKeys.V6_KEY);

OpenPGPMessageGenerator gen = api.signAndOrEncryptMessage()
.setAllowPadding(true)
.setArmored(true)
.addSigningKey(key)
.addEncryptionCertificate(key, true);

ByteArrayOutputStream bOut = new ByteArrayOutputStream();
OutputStream encOut = gen.open(bOut);
encOut.write("Hello, World!\n".getBytes());
encOut.close();

ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray());
ArmoredInputStream aIn = ArmoredInputStream.builder()
.build(bIn);
BCPGInputStream pIn = BCPGInputStream.wrap(aIn);
PGPObjectFactory objFac = api.getImplementation().pgpObjectFactory(pIn);
PGPEncryptedDataList encList = (PGPEncryptedDataList) objFac.nextObject();
for (PGPEncryptedData encData : encList)
{
isTrue(encData instanceof PGPPublicKeyEncryptedData);
PGPPublicKeyEncryptedData pkesk = (PGPPublicKeyEncryptedData) encData;
isTrue(pkesk.getKeyIdentifier().isWildcard());
}

bIn = new ByteArrayInputStream(bOut.toByteArray());
OpenPGPMessageProcessor processor = api.decryptAndOrVerifyMessage()
.addDecryptionKey(key);

OpenPGPMessageInputStream mIn = processor.process(bIn);
bOut = new ByteArrayOutputStream();
org.bouncycastle.util.io.Streams.pipeAll(mIn, bOut);
mIn.close();
isEncodingEqual("Hello, World!\n".getBytes(), bOut.toByteArray());
}

public static void main(String[] args)
{
runTest(new OpenPGPMessageGeneratorTest());
Expand Down