Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting name and modification date for literal data packet; BC … #78

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## V 2.x.x (NEXT)

* Enh: Bump Bouncy Castle to 1.77 and jdk18on for security fixes
* New: Provide a way to change name and modification date in literal data packet

## V 2.3.0 Bugfix Release

This releases fixes a security issue (#50) where encrypted, but not signed archives could be modified.
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ check.dependsOn integrationTest


dependencies {
compile 'org.bouncycastle:bcprov-jdk15on:1.67'
compile 'org.bouncycastle:bcpg-jdk15on:1.67'
compile 'org.bouncycastle:bcprov-jdk18on:1.77'
compile 'org.bouncycastle:bcpg-jdk18on:1.77'

compile 'org.slf4j:slf4j-api:1.7.30'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public final class BuildEncryptionOutputStreamAPI {
private Set<PGPPublicKey> recipients;
private boolean armorOutput;

private NameProvider nameProvider;
private ModificationDateProvider modificationDateProvider;

// Signature


Expand Down Expand Up @@ -95,6 +98,21 @@ private KeySelectionStrategy getKeySelectionStrategy() {

public interface Build {

/**
* Set name a modification date providers which will be used to set name and modification date
* of literal data (literal packet in the output data).
* <p/>
* This allows acts like we are actually working with a file, not just unnamed, just-in-time stream of data.
* <p/>
* If not provided ({@code null}), default values will be used - empty string ({@code ""}) as a name
* anc current date ({@code new Date()}) as a modification date.
*
* @param nameProvider literal data name provider (nullable)
* @param modificationDateProvider literal data modification date provider (nullable)
* @return this
*/
Build withProviders(NameProvider nameProvider, ModificationDateProvider modificationDateProvider);

OutputStream andWriteTo(OutputStream sinkForEncryptedData)
throws PGPException, SignatureException, NoSuchAlgorithmException, NoSuchProviderException, IOException;
}
Expand Down Expand Up @@ -502,6 +520,13 @@ public Build armorAsciiOutput() {

public final class Builder implements Build {

@Override
public Build withProviders(NameProvider nameProvider, ModificationDateProvider modificationDateProvider) {
BuildEncryptionOutputStreamAPI.this.nameProvider = nameProvider;
BuildEncryptionOutputStreamAPI.this.modificationDateProvider = modificationDateProvider;
return this;
}

@Override
public OutputStream andWriteTo(OutputStream sinkForEncryptedData)
throws PGPException, SignatureException, NoSuchAlgorithmException, NoSuchProviderException, IOException {
Expand All @@ -513,7 +538,9 @@ public OutputStream andWriteTo(OutputStream sinkForEncryptedData)
BuildEncryptionOutputStreamAPI.this.sinkForEncryptedData,
getKeySelectionStrategy(),
BuildEncryptionOutputStreamAPI.this.armorOutput,
BuildEncryptionOutputStreamAPI.this.recipients);
BuildEncryptionOutputStreamAPI.this.recipients,
BuildEncryptionOutputStreamAPI.this.nameProvider,
BuildEncryptionOutputStreamAPI.this.modificationDateProvider);

}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package name.neuhalfen.projects.crypto.bouncycastle.openpgp;

import java.io.OutputStream;
import java.util.Date;

/**
* Modification date provider.
*
* @see org.bouncycastle.openpgp.PGPLiteralDataGenerator#open(OutputStream, char, String, Date, byte[])
*/
public interface ModificationDateProvider {

/**
* Default instance which provides modification date as a current date.
*/
ModificationDateProvider DEFAULT_INSTANCE = Date::new;

/**
* Provide desired modification date.
*
* @return modification date (<b>never {@code null}</b>)
*/
Date getModificationDate();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package name.neuhalfen.projects.crypto.bouncycastle.openpgp;

import java.io.OutputStream;
import java.util.Date;

/**
* Name provider.
*
* @see org.bouncycastle.openpgp.PGPLiteralDataGenerator#open(OutputStream, char, String, Date, byte[])
*/
public interface NameProvider {

/**
* Default instance which provides an empty string ({@code ""}) as a name.
*/
NameProvider DEFAULT_INSTANCE = () -> "";

/**
* Provide desired name.
*
* @return name (never <b>never {@code null}</b>)
*/
String getName();
}


Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Date;
import java.util.Iterator;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.ModificationDateProvider;
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.NameProvider;
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PGPAlgorithmSuite;
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.PGPUtilities;
import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.KeySelectionStrategy;
Expand Down Expand Up @@ -95,6 +97,40 @@ public static OutputStream create(final KeyringConfig config,
final boolean armor,
final Set<PGPPublicKey> encryptTo)
throws IOException, PGPException, NoSuchAlgorithmException, NoSuchProviderException {
return create(config, algorithmSuite, signingUid, cipherTextSink, keySelectionStrategy, armor, encryptTo, null, null);
}

/**
* Return a stream that, when written plaintext into, writes the ciphertext into cipherTextSink.
*
* @param config key configuration
* @param algorithmSuite algorithm suite to use.
* @param signingUid sign with this uid (optionally)
* @param cipherTextSink write the ciphertext in here
* @param keySelectionStrategy selection strategy
* @param armor armor the file (true) or use binary.
* @param encryptTo encrypt to
* @param nameProvider a name provider for literal data packet (default to {@code ""})
* @param modificationDateProvider a modification date for literal data packet (default to current date)
*
* @return stream where plaintext gets written into
*
* @throws IOException streams, IO, ...
* @throws PGPException pgp error
* @throws NoSuchAlgorithmException algorithmSuite not supported
* @throws NoSuchProviderException bouncy castle not registered
* @see name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.DefaultPGPAlgorithmSuites
*/
public static OutputStream create(final KeyringConfig config,
final PGPAlgorithmSuite algorithmSuite,
@Nullable final String signingUid,
final OutputStream cipherTextSink,
final KeySelectionStrategy keySelectionStrategy,
final boolean armor,
final Set<PGPPublicKey> encryptTo,
final NameProvider nameProvider,
final ModificationDateProvider modificationDateProvider)
throws IOException, PGPException, NoSuchAlgorithmException, NoSuchProviderException {

requireNonNull(config, "callback must not be null");
requireNonNull(cipherTextSink, "cipherTextSink must not be null");
Expand All @@ -109,7 +145,7 @@ public static OutputStream create(final KeyringConfig config,
}

final PGPEncryptingStream encryptingStream = new PGPEncryptingStream(config, algorithmSuite);
encryptingStream.setup(cipherTextSink, signingUid, encryptTo, keySelectionStrategy, armor);
encryptingStream.setup(cipherTextSink, signingUid, encryptTo, keySelectionStrategy, armor, nameProvider, modificationDateProvider);
return encryptingStream;
}

Expand All @@ -134,7 +170,9 @@ private void setup(final OutputStream cipherTextSink,
@Nullable final String signingUid,
final Set<PGPPublicKey> pubEncKeys,
final KeySelectionStrategy keySelectionStrategy,
final boolean armor) throws
final boolean armor,
final NameProvider nameProvider,
final ModificationDateProvider modificationDateProvider) throws
IOException, PGPException {
isDoSign = signingUid != null;

Expand Down Expand Up @@ -192,13 +230,13 @@ private void setup(final OutputStream cipherTextSink,
if (userIDs.hasNext()) {
final PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();

spGen.setSignerUserID(false, (String) userIDs.next());
spGen.addSignerUserID(false, (String) userIDs.next());
signatureGenerator.setHashedSubpackets(spGen.generate());
}
}

compressionStreamGenerator = new PGPCompressedDataGenerator(
algorithmSuite.getCompressionEncryptionAlgorithmCode().getAlgorithmId());
algorithmSuite.getCompressionAlgorithmCode().getAlgorithmId());
compressionStream = new BCPGOutputStream(
compressionStreamGenerator.open(outerEncryptionStream));

Expand All @@ -208,7 +246,11 @@ private void setup(final OutputStream cipherTextSink,

encryptionDataStreamGenerator = new PGPLiteralDataGenerator();
encryptionDataStream = encryptionDataStreamGenerator
.open(compressionStream, PGPLiteralData.BINARY, "", new Date(), new byte[1 << 16]);
.open(compressionStream,
PGPLiteralData.BINARY,
Optional.ofNullable(nameProvider).orElse(NameProvider.DEFAULT_INSTANCE).getName(),
Optional.ofNullable(modificationDateProvider).orElse(ModificationDateProvider.DEFAULT_INSTANCE).getModificationDate(),
new byte[1 << 16]);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,21 +182,14 @@ protected Predicate<PGPPublicKey> hasPrivateKey(final PGPSecretKeyRingCollection
return pubKey -> {
requireNonNull(pubKey, "pubKey must not be null");

try {
final boolean hasPrivateKey = secretKeyRings.contains(pubKey.getKeyID());

if (!hasPrivateKey) {
LOGGER.trace("Skipping pubkey {} (no private key found)",
Long.toHexString(pubKey.getKeyID()));
}

return hasPrivateKey;
} catch (PGPException e) {
// ignore this for filtering
LOGGER.debug("Failed to test for private key for pubkey " + pubKey//NOPMD:GuardLogStatement
.getKeyID());
return false;
final boolean hasPrivateKey = secretKeyRings.contains(pubKey.getKeyID());

if (!hasPrivateKey) {
LOGGER.trace("Skipping pubkey {} (no private key found)",
Long.toHexString(pubKey.getKeyID()));
}

return hasPrivateKey;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
Expand Down