Skip to content

Commit dca7a81

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

File tree

4 files changed

+72
-17
lines changed

4 files changed

+72
-17
lines changed

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

Lines changed: 25 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,11 @@ OutputStream andWriteTo(OutputStream sinkForEncryptedData)
99100
throws PGPException, SignatureException, NoSuchAlgorithmException, NoSuchProviderException, IOException;
100101
}
101102

103+
public interface BuildWithTextMode extends Build {
104+
105+
Build textMode();
106+
}
107+
102108

103109
public interface WithAlgorithmSuite {
104110

@@ -222,14 +228,14 @@ interface Armor {
222228
*
223229
* @return next step
224230
*/
225-
Build binaryOutput();
231+
BuildWithTextMode binaryOutput();
226232

227233
/**
228234
* Ascii armor the output, e.g. for usage in text protocols.
229235
*
230236
* @return next step
231237
*/
232-
Build armorAsciiOutput();
238+
BuildWithTextMode armorAsciiOutput();
233239
}
234240
}
235241
}
@@ -486,21 +492,21 @@ public Armor andDoNotSign() {
486492
public final class ArmorImpl implements Armor {
487493

488494
@Override
489-
public Build binaryOutput() {
495+
public BuildWithTextMode binaryOutput() {
490496
BuildEncryptionOutputStreamAPI.this.armorOutput = false;
491497
LOGGER.trace("binary output");
492-
return new Builder();
498+
return new BuilderWithTextMode();
493499
}
494500

495501
@Override
496-
public Build armorAsciiOutput() {
502+
public BuildWithTextMode armorAsciiOutput() {
497503
BuildEncryptionOutputStreamAPI.this.armorOutput = true;
498504
LOGGER.trace("ascii armor output");
499-
return new Builder();
505+
return new BuilderWithTextMode();
500506
}
501507

502508

503-
public final class Builder implements Build {
509+
public class Builder implements Build {
504510

505511
@Override
506512
public OutputStream andWriteTo(OutputStream sinkForEncryptedData)
@@ -513,8 +519,19 @@ public OutputStream andWriteTo(OutputStream sinkForEncryptedData)
513519
BuildEncryptionOutputStreamAPI.this.sinkForEncryptedData,
514520
getKeySelectionStrategy(),
515521
BuildEncryptionOutputStreamAPI.this.armorOutput,
516-
BuildEncryptionOutputStreamAPI.this.recipients);
522+
BuildEncryptionOutputStreamAPI.this.recipients,
523+
BuildEncryptionOutputStreamAPI.this.textMode);
524+
525+
}
526+
}
517527

528+
public final class BuilderWithTextMode extends Builder implements BuildWithTextMode {
529+
530+
@Override
531+
public Build textMode() {
532+
BuildEncryptionOutputStreamAPI.this.textMode = true;
533+
LOGGER.trace("text mode");
534+
return this;
518535
}
519536
}
520537
}

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)