Skip to content

Commit fd22eeb

Browse files
committed
Added a builder method to sign/encrypt as a text document rather than binary data (#60).
1 parent 3523ca7 commit fd22eeb

File tree

4 files changed

+77
-17
lines changed

4 files changed

+77
-17
lines changed

src/main/java/name/neuhalfen/projects/crypto/bouncycastle/openpgp/BuildEncryptionOutputStreamAPI.java

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public final class BuildEncryptionOutputStreamAPI {
5151
private String signWith;
5252
private Set<PGPPublicKey> recipients;
5353
private boolean armorOutput;
54+
private boolean textMode;
5455

5556
// Signature
5657

@@ -99,6 +100,16 @@ OutputStream andWriteTo(OutputStream sinkForEncryptedData)
99100
throws PGPException, SignatureException, NoSuchAlgorithmException, NoSuchProviderException, IOException;
100101
}
101102

103+
public interface BuildWithTextMode extends Build {
104+
105+
/**
106+
* Emulates GnuPG's {@code --textmode} flag, which encodes data as text literal
107+
* packets rather than binary literal packets.
108+
* @return next step
109+
*/
110+
Build textMode();
111+
}
112+
102113

103114
public interface WithAlgorithmSuite {
104115

@@ -222,14 +233,14 @@ interface Armor {
222233
*
223234
* @return next step
224235
*/
225-
Build binaryOutput();
236+
BuildWithTextMode binaryOutput();
226237

227238
/**
228239
* Ascii armor the output, e.g. for usage in text protocols.
229240
*
230241
* @return next step
231242
*/
232-
Build armorAsciiOutput();
243+
BuildWithTextMode armorAsciiOutput();
233244
}
234245
}
235246
}
@@ -486,21 +497,21 @@ public Armor andDoNotSign() {
486497
public final class ArmorImpl implements Armor {
487498

488499
@Override
489-
public Build binaryOutput() {
500+
public BuildWithTextMode binaryOutput() {
490501
BuildEncryptionOutputStreamAPI.this.armorOutput = false;
491502
LOGGER.trace("binary output");
492-
return new Builder();
503+
return new BuilderWithTextMode();
493504
}
494505

495506
@Override
496-
public Build armorAsciiOutput() {
507+
public BuildWithTextMode armorAsciiOutput() {
497508
BuildEncryptionOutputStreamAPI.this.armorOutput = true;
498509
LOGGER.trace("ascii armor output");
499-
return new Builder();
510+
return new BuilderWithTextMode();
500511
}
501512

502513

503-
public final class Builder implements Build {
514+
public class Builder implements Build {
504515

505516
@Override
506517
public OutputStream andWriteTo(OutputStream sinkForEncryptedData)
@@ -513,10 +524,21 @@ public OutputStream andWriteTo(OutputStream sinkForEncryptedData)
513524
BuildEncryptionOutputStreamAPI.this.sinkForEncryptedData,
514525
getKeySelectionStrategy(),
515526
BuildEncryptionOutputStreamAPI.this.armorOutput,
516-
BuildEncryptionOutputStreamAPI.this.recipients);
527+
BuildEncryptionOutputStreamAPI.this.recipients,
528+
BuildEncryptionOutputStreamAPI.this.textMode);
517529

518530
}
519531
}
532+
533+
public final class BuilderWithTextMode extends Builder implements BuildWithTextMode {
534+
535+
@Override
536+
public Build textMode() {
537+
BuildEncryptionOutputStreamAPI.this.textMode = true;
538+
LOGGER.trace("text mode");
539+
return this;
540+
}
541+
}
520542
}
521543
}
522544
}

src/main/java/name/neuhalfen/projects/crypto/bouncycastle/openpgp/encrypting/PGPEncryptingStream.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ private PGPEncryptingStream(final KeyringConfig config, final PGPAlgorithmSuite
7878
* @param keySelectionStrategy selection strategy
7979
* @param armor armor the file (true) or use binary.
8080
* @param encryptTo encrypt to
81+
* @param textMode simulates GnuPG's {@code --textmode} flag
8182
*
8283
* @return stream where plaintext gets written into
8384
*
@@ -93,7 +94,8 @@ public static OutputStream create(final KeyringConfig config,
9394
final OutputStream cipherTextSink,
9495
final KeySelectionStrategy keySelectionStrategy,
9596
final boolean armor,
96-
final Set<PGPPublicKey> encryptTo)
97+
final Set<PGPPublicKey> encryptTo,
98+
final boolean textMode)
9799
throws IOException, PGPException, NoSuchAlgorithmException, NoSuchProviderException {
98100

99101
requireNonNull(config, "callback must not be null");
@@ -109,7 +111,7 @@ public static OutputStream create(final KeyringConfig config,
109111
}
110112

111113
final PGPEncryptingStream encryptingStream = new PGPEncryptingStream(config, algorithmSuite);
112-
encryptingStream.setup(cipherTextSink, signingUid, encryptTo, keySelectionStrategy, armor);
114+
encryptingStream.setup(cipherTextSink, signingUid, encryptTo, keySelectionStrategy, armor, textMode);
113115
return encryptingStream;
114116
}
115117

@@ -122,6 +124,7 @@ public static OutputStream create(final KeyringConfig config,
122124
* @param pubEncKeys the pub enc keys
123125
* @param keySelectionStrategy key selection strategy (for signatures)
124126
* @param armor if OutputStream should be "armored", that means base64 encoded
127+
* @param textMode simulates GnuPG's {@code --textmode} flag
125128
*
126129
* @throws IOException Signals that an I/O exception has occurred.
127130
* @throws PGPException the pGP exception
@@ -134,7 +137,8 @@ private void setup(final OutputStream cipherTextSink,
134137
@Nullable final String signingUid,
135138
final Set<PGPPublicKey> pubEncKeys,
136139
final KeySelectionStrategy keySelectionStrategy,
137-
final boolean armor) throws
140+
final boolean armor,
141+
final boolean textMode) throws
138142
IOException, PGPException {
139143
isDoSign = signingUid != null;
140144

@@ -186,7 +190,7 @@ private void setup(final OutputStream cipherTextSink,
186190
new BcPGPContentSignerBuilder(pgpSec.getPublicKey().getAlgorithm(),
187191
algorithmSuite.getHashAlgorithmCode().getAlgorithmId()));
188192

189-
signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
193+
signatureGenerator.init(textMode ? PGPSignature.CANONICAL_TEXT_DOCUMENT : PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
190194

191195
final Iterator<?> userIDs = pgpSec.getPublicKey().getUserIDs();
192196
if (userIDs.hasNext()) {
@@ -208,7 +212,7 @@ private void setup(final OutputStream cipherTextSink,
208212

209213
encryptionDataStreamGenerator = new PGPLiteralDataGenerator();
210214
encryptionDataStream = encryptionDataStreamGenerator
211-
.open(compressionStream, PGPLiteralData.BINARY, "", new Date(), new byte[1 << 16]);
215+
.open(compressionStream, textMode ? PGPLiteralData.TEXT : PGPLiteralData.BINARY, "", new Date(), new byte[1 << 16]);
212216
}
213217

214218
@Override
@@ -268,4 +272,4 @@ public void close() throws IOException {
268272
isClosed = true;
269273
}
270274
}
271-
}
275+
}

src/test/java/name/neuhalfen/projects/crypto/bouncycastle/openpgp/encrypting/EncryptWithOpenPGPTestDriver.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,10 @@ void encryptAndSign(final InputStream in, OutputStream out, final Set<PGPPublicK
9191

9292
try (final OutputStream encryptionStream = PGPEncryptingStream
9393
.create(config, algorithmSuite, signatureUid, out, keySelectionStrategy, armor,
94-
pubEncKeys)) {
94+
pubEncKeys, false)) {
9595
Streams.pipeAll(in, encryptionStream);
9696
encryptionStream.flush();
9797
}
9898
out.flush();
9999
}
100-
}
100+
}

src/test/java/name/neuhalfen/projects/crypto/bouncycastle/openpgp/roundtrip/EncryptionDecryptionRoundtripIntegrationTest.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import java.io.IOException;
1212
import java.io.InputStream;
1313
import java.io.OutputStream;
14-
import java.nio.charset.StandardCharsets;
1514
import java.security.NoSuchAlgorithmException;
1615
import java.security.NoSuchProviderException;
1716
import java.security.SignatureException;
@@ -127,6 +126,41 @@ public void encryptAndSignBinary_thenDecryptAndVerify_yieldsOriginalPlaintext()
127126
assertArrayEquals(expectedPlaintext, decryptedPlaintext);
128127
}
129128

129+
@Test
130+
public void encryptAndSignTextModeBinary_thenDecryptAndVerify_yieldsOriginalPlaintext()
131+
throws IOException, PGPException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException {
132+
final byte[] expectedPlaintext = ExampleMessages.IMPORTANT_QUOTE_TEXT.getBytes("US-ASCII");
133+
134+
ByteArrayOutputStream cipherText = new ByteArrayOutputStream();
135+
136+
final OutputStream encryptionStream = BouncyGPG
137+
.encryptToStream()
138+
.withConfig(Configs.keyringConfigFromFilesForSender())
139+
.withAlgorithms(algorithmSuite)
140+
.toRecipient("recipient@example.com")
141+
.andSignWith("sender@example.com")
142+
.binaryOutput()
143+
.textMode()
144+
.andWriteTo(cipherText);
145+
146+
encryptionStream.write(expectedPlaintext);
147+
encryptionStream.close();
148+
cipherText.close();
149+
150+
ByteArrayInputStream cipherTextAsSource = new ByteArrayInputStream(cipherText.toByteArray());
151+
152+
// Decrypt
153+
final InputStream decryptedPlaintextStream = BouncyGPG
154+
.decryptAndVerifyStream()
155+
.withConfig(Configs.keyringConfigFromResourceForRecipient())
156+
.andRequireSignatureFromAllKeys("sender@example.com")
157+
.fromEncryptedInputStream(cipherTextAsSource);
158+
159+
final byte[] decryptedPlaintext = Streams.readAll(decryptedPlaintextStream);
160+
161+
assertArrayEquals(expectedPlaintext, decryptedPlaintext);
162+
}
163+
130164

131165
@Test
132166
public void encryptAndSignBinaryWithSHA256_AES256_thenDecryptAndVerify_yieldsOriginalPlaintext()

0 commit comments

Comments
 (0)