• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pgpainless / sop-java / #54

25 Sep 2025 09:07PM UTC coverage: 58.308% (-0.1%) from 58.411%
#54

push

other

vanitasvitae
Remove unused import

2088 of 3581 relevant lines covered (58.31%)

0.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

26.89
/sop-java-testfixtures/src/main/java/sop/testsuite/operation/EncryptDecryptTest.java
1
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
2
//
3
// SPDX-License-Identifier: Apache-2.0
4

5
package sop.testsuite.operation;
6

7
import org.junit.jupiter.api.condition.EnabledIf;
8
import org.junit.jupiter.params.ParameterizedTest;
9
import org.junit.jupiter.params.provider.Arguments;
10
import org.junit.jupiter.params.provider.MethodSource;
11
import sop.ByteArrayAndResult;
12
import sop.DecryptionResult;
13
import sop.EncryptionResult;
14
import sop.Profile;
15
import sop.SOP;
16
import sop.SessionKey;
17
import sop.Verification;
18
import sop.enums.EncryptAs;
19
import sop.enums.SignatureMode;
20
import sop.exception.SOPGPException;
21
import sop.operation.Decrypt;
22
import sop.operation.Encrypt;
23
import sop.testsuite.TestData;
24
import sop.testsuite.assertions.VerificationListAssert;
25
import sop.util.Optional;
26
import sop.util.UTCUtil;
27

28
import java.io.IOException;
29
import java.nio.charset.StandardCharsets;
30
import java.text.ParseException;
31
import java.util.ArrayList;
32
import java.util.Date;
33
import java.util.List;
34
import java.util.stream.Stream;
35

36
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
37
import static org.junit.jupiter.api.Assertions.assertEquals;
38
import static org.junit.jupiter.api.Assertions.assertNotNull;
39
import static org.junit.jupiter.api.Assertions.assertThrows;
40

41
@EnabledIf("sop.testsuite.operation.AbstractSOPTest#hasBackends")
42
public class EncryptDecryptTest extends AbstractSOPTest {
1✔
43

44
    static Stream<Arguments> provideInstances() {
45
        return provideBackends();
1✔
46
    }
47

48
    @ParameterizedTest
49
    @MethodSource("provideInstances")
50
    public void encryptDecryptRoundTripPasswordTest(SOP sop) throws IOException {
51
        byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
1✔
52
        ByteArrayAndResult<EncryptionResult> encResult = assumeSupported(sop::encrypt)
1✔
53
                .withPassword("sw0rdf1sh")
1✔
54
                .plaintext(message)
1✔
55
                .toByteArrayAndResult();
×
56

57
        byte[] ciphertext = encResult.getBytes();
×
58
        Optional<SessionKey> encSessionKey = encResult.getResult().getSessionKey();
×
59

60
        ByteArrayAndResult<DecryptionResult> decResult = assumeSupported(sop::decrypt)
×
61
                .withPassword("sw0rdf1sh")
×
62
                .ciphertext(ciphertext)
×
63
                .toByteArrayAndResult();
×
64

65
        byte[] plaintext = decResult.getBytes();
×
66
        Optional<SessionKey> decSessionKey = decResult.getResult().getSessionKey();
×
67

68
        assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
×
69
        if (encSessionKey.isPresent() && decSessionKey.isPresent()) {
×
70
            assertEquals(encSessionKey.get(), decSessionKey.get(),
×
71
                    "Extracted Session Key mismatch.");
72
        }
73
    }
×
74

75
    @ParameterizedTest
76
    @MethodSource("provideInstances")
77
    public void encryptDecryptRoundTripAliceTest(SOP sop) throws IOException {
78
        byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
1✔
79
        byte[] ciphertext = assumeSupported(sop::encrypt)
1✔
80
                .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
1✔
81
                .plaintext(message)
1✔
82
                .toByteArrayAndResult()
×
83
                .getBytes();
×
84

85
        ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
×
86
                .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
×
87
                .ciphertext(ciphertext)
×
88
                .toByteArrayAndResult();
×
89

90
        byte[] plaintext = bytesAndResult.getBytes();
×
91
        assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
×
92

93
        DecryptionResult result = bytesAndResult.getResult();
×
94
        if (result.getSessionKey().isPresent()) {
×
95
            assertNotNull(result.getSessionKey().get(), "Session key MUST NOT be null.");
×
96
        }
97
    }
×
98

99
    @ParameterizedTest
100
    @MethodSource("provideInstances")
101
    public void encryptDecryptRoundTripBobTest(SOP sop) throws IOException {
102
        byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
1✔
103
        byte[] ciphertext = assumeSupported(sop::encrypt)
1✔
104
                .withCert(TestData.BOB_CERT.getBytes(StandardCharsets.UTF_8))
1✔
105
                .plaintext(message)
1✔
106
                .toByteArrayAndResult()
×
107
                .getBytes();
×
108

109
        byte[] plaintext = assumeSupported(sop::decrypt)
×
110
                .withKey(TestData.BOB_KEY.getBytes(StandardCharsets.UTF_8))
×
111
                .ciphertext(ciphertext)
×
112
                .toByteArrayAndResult()
×
113
                .getBytes();
×
114

115
        assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
×
116
    }
×
117

118
    @ParameterizedTest
119
    @MethodSource("provideInstances")
120
    public void encryptDecryptRoundTripCarolTest(SOP sop) throws IOException {
121
        byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
1✔
122
        byte[] ciphertext = assumeSupported(sop::encrypt)
1✔
123
                .withCert(TestData.CAROL_CERT.getBytes(StandardCharsets.UTF_8))
1✔
124
                .plaintext(message)
1✔
125
                .toByteArrayAndResult()
×
126
                .getBytes();
×
127

128
        byte[] plaintext = assumeSupported(sop::decrypt)
×
129
                .withKey(TestData.CAROL_KEY.getBytes(StandardCharsets.UTF_8))
×
130
                .ciphertext(ciphertext)
×
131
                .toByteArrayAndResult()
×
132
                .getBytes();
×
133

134
        assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
×
135
    }
×
136

137
    @ParameterizedTest
138
    @MethodSource("provideInstances")
139
    public void encryptNoArmorThenArmorThenDecryptRoundTrip(SOP sop) throws IOException {
140
        byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
1✔
141
        byte[] ciphertext = assumeSupported(sop::encrypt)
1✔
142
                .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
1✔
143
                .noArmor()
1✔
144
                .plaintext(message)
1✔
145
                .toByteArrayAndResult()
×
146
                .getBytes();
×
147

148
        byte[] armored = assumeSupported(sop::armor)
×
149
                .data(ciphertext)
×
150
                .getBytes();
×
151

152
        ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
×
153
                .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
×
154
                .ciphertext(armored)
×
155
                .toByteArrayAndResult();
×
156

157
        byte[] plaintext = bytesAndResult.getBytes();
×
158
        assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
×
159
    }
×
160

161
    @ParameterizedTest
162
    @MethodSource("provideInstances")
163
    public void encryptSignDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException {
164
        byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
1✔
165
        byte[] ciphertext = assumeSupported(sop::encrypt)
1✔
166
                .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
1✔
167
                .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
1✔
168
                .mode(EncryptAs.binary)
1✔
169
                .plaintext(message)
1✔
170
                .toByteArrayAndResult()
×
171
                .getBytes();
×
172

173
        ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
×
174
                .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
×
175
                .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
×
176
                .ciphertext(ciphertext)
×
177
                .toByteArrayAndResult();
×
178

179
        byte[] plaintext = bytesAndResult.getBytes();
×
180
        assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
×
181

182
        DecryptionResult result = bytesAndResult.getResult();
×
183
        if (result.getSessionKey().isPresent()) {
×
184
            assertNotNull(result.getSessionKey().get(), "Session key MUST NOT be null.");
×
185
        }
186

187
        List<Verification> verificationList = result.getVerifications();
×
188
        VerificationListAssert.assertThatVerificationList(verificationList)
×
189
                .isNotEmpty()
×
190
                .hasSingleItem()
×
191
                .issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT)
×
192
                .hasModeOrNull(SignatureMode.binary);
×
193
    }
×
194

195
    @ParameterizedTest
196
    @MethodSource("provideInstances")
197
    public void encryptSignAsTextDecryptVerifyRoundTripAliceTest(SOP sop) throws IOException {
198
        byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
1✔
199
        byte[] ciphertext = assumeSupported(sop::encrypt)
1✔
200
                .withCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
1✔
201
                .signWith(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
1✔
202
                .mode(EncryptAs.text)
1✔
203
                .plaintext(message)
1✔
204
                .toByteArrayAndResult()
×
205
                .getBytes();
×
206

207
        ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
×
208
                .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
×
209
                .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
×
210
                .ciphertext(ciphertext)
×
211
                .toByteArrayAndResult();
×
212

213
        byte[] plaintext = bytesAndResult.getBytes();
×
214
        assertArrayEquals(message, plaintext, "Decrypted plaintext does not match original plaintext.");
×
215

216
        DecryptionResult result = bytesAndResult.getResult();
×
217
        assertNotNull(result.getSessionKey().get());
×
218

219
        List<Verification> verificationList = result.getVerifications();
×
220
        VerificationListAssert.assertThatVerificationList(verificationList)
×
221
                .hasSingleItem()
×
222
                .issuedBy(TestData.ALICE_SIGNING_FINGERPRINT, TestData.ALICE_PRIMARY_FINGERPRINT)
×
223
                .hasModeOrNull(SignatureMode.text);
×
224
    }
×
225

226
    @ParameterizedTest
227
    @MethodSource("provideInstances")
228
    public void encryptSignDecryptVerifyRoundTripWithFreshEncryptedKeyTest(SOP sop) throws IOException {
229
        byte[] keyPassword = "sw0rdf1sh".getBytes(StandardCharsets.UTF_8);
1✔
230
        byte[] key = assumeSupported(sop::generateKey)
1✔
231
                .withKeyPassword(keyPassword)
1✔
232
                .userId("Alice <alice@openpgp.org>")
1✔
233
                .generate()
1✔
234
                .getBytes();
×
235
        byte[] cert = assumeSupported(sop::extractCert)
×
236
                .key(key)
×
237
                .getBytes();
×
238

239
        byte[] message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8);
×
240
        byte[] ciphertext = assumeSupported(sop::encrypt)
×
241
                .withCert(cert)
×
242
                .signWith(key)
×
243
                .withKeyPassword(keyPassword)
×
244
                .plaintext(message)
×
245
                .toByteArrayAndResult()
×
246
                .getBytes();
×
247

248
        ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
×
249
                .withKey(key)
×
250
                .withKeyPassword(keyPassword)
×
251
                .verifyWithCert(cert)
×
252
                .ciphertext(ciphertext)
×
253
                .toByteArrayAndResult();
×
254

255
        List<Verification> verifications = bytesAndResult.getResult().getVerifications();
×
256
        VerificationListAssert.assertThatVerificationList(verifications)
×
257
                .isNotEmpty()
×
258
                .hasSingleItem();
×
259
    }
×
260

261
    @ParameterizedTest
262
    @MethodSource("provideInstances")
263
    public void decryptVerifyNotAfterTest(SOP sop) throws ParseException {
264
        byte[] message = ("-----BEGIN PGP MESSAGE-----\n" +
1✔
265
                "\n" +
266
                "wV4DR2b2udXyHrYSAQdAwlOwwyxFDJta5+H9abgSj8jum9v7etUc9usdrElESmow\n" +
267
                "2Hka48AFVfOezYh0OFn9R8+DMcpuE+e4nw3XnnX5nKs/j3AC2IW6zRHUkRcF3ZCq\n" +
268
                "0sBNAfjnTYCMjuBmqdcCLzaZT4Hadnpg6neP1UecT/jP14maGfv8nwt0IDGR0Bik\n" +
269
                "0WC/UJLpWyJ/6TgRrA5hNfANVnfiFBzIiThiVBRWPT2StHr2cOAvFxQK4Uk07rK9\n" +
270
                "9aTUak8FpML+QA83U8I3qOk4QbzGVBP+IDJ+AKmvDz+0V+9kUhKp+8vyXsBmo9c3\n" +
271
                "SAXjhFSiPQkU7ORsc6gQHL9+KPOU+W2poPK87H3cmaGiusnXMeLXLIUbkBUJTswd\n" +
272
                "JNrA2yAkTTFP9QabsdcdTGoeYamq1c29kHF3GOTTcEqXw4WWXngcF7Kbcf435kkL\n" +
273
                "4iSJnCaxTPftKUxmiGqMqLef7ICVnq/lz3HrH1VD54s=\n" +
274
                "=Ebi3\n" +
275
                "-----END PGP MESSAGE-----").getBytes(StandardCharsets.UTF_8);
1✔
276
        Date signatureDate = UTCUtil.parseUTCDate("2023-01-13T16:09:32Z");
1✔
277

278
        Date beforeSignature = new Date(signatureDate.getTime() - 1000); // 1 sec before signing date
1✔
279

280
        assertThrows(SOPGPException.NoSignature.class, () -> {
×
281
            ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
1✔
282
                    .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
1✔
283
                    .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
1✔
284
                    .verifyNotAfter(beforeSignature)
1✔
285
                    .ciphertext(message)
1✔
286
                    .toByteArrayAndResult();
×
287

288
            // Some implementations do not throw NoSignature and instead return an empty list.
289
            if (bytesAndResult.getResult().getVerifications().isEmpty()) {
×
290
                throw new SOPGPException.NoSignature("No verifiable signature found.");
×
291
            }
292
        });
×
293
    }
×
294

295
    @ParameterizedTest
296
    @MethodSource("provideInstances")
297
    public void decryptVerifyNotBeforeTest(SOP sop) throws ParseException {
298
        byte[] message = ("-----BEGIN PGP MESSAGE-----\n" +
1✔
299
                "\n" +
300
                "wV4DR2b2udXyHrYSAQdAwlOwwyxFDJta5+H9abgSj8jum9v7etUc9usdrElESmow\n" +
301
                "2Hka48AFVfOezYh0OFn9R8+DMcpuE+e4nw3XnnX5nKs/j3AC2IW6zRHUkRcF3ZCq\n" +
302
                "0sBNAfjnTYCMjuBmqdcCLzaZT4Hadnpg6neP1UecT/jP14maGfv8nwt0IDGR0Bik\n" +
303
                "0WC/UJLpWyJ/6TgRrA5hNfANVnfiFBzIiThiVBRWPT2StHr2cOAvFxQK4Uk07rK9\n" +
304
                "9aTUak8FpML+QA83U8I3qOk4QbzGVBP+IDJ+AKmvDz+0V+9kUhKp+8vyXsBmo9c3\n" +
305
                "SAXjhFSiPQkU7ORsc6gQHL9+KPOU+W2poPK87H3cmaGiusnXMeLXLIUbkBUJTswd\n" +
306
                "JNrA2yAkTTFP9QabsdcdTGoeYamq1c29kHF3GOTTcEqXw4WWXngcF7Kbcf435kkL\n" +
307
                "4iSJnCaxTPftKUxmiGqMqLef7ICVnq/lz3HrH1VD54s=\n" +
308
                "=Ebi3\n" +
309
                "-----END PGP MESSAGE-----").getBytes(StandardCharsets.UTF_8);
1✔
310
        Date signatureDate = UTCUtil.parseUTCDate("2023-01-13T16:09:32Z");
1✔
311

312
        Date afterSignature = new Date(signatureDate.getTime() + 1000); // 1 sec after signing date
1✔
313

314
        assertThrows(SOPGPException.NoSignature.class, () -> {
×
315
            ByteArrayAndResult<DecryptionResult> bytesAndResult = assumeSupported(sop::decrypt)
1✔
316
                    .withKey(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
1✔
317
                    .verifyWithCert(TestData.ALICE_CERT.getBytes(StandardCharsets.UTF_8))
1✔
318
                    .verifyNotBefore(afterSignature)
1✔
319
                    .ciphertext(message)
1✔
320
                    .toByteArrayAndResult();
×
321

322
            // Some implementations do not throw NoSignature and instead return an empty list.
323
            if (bytesAndResult.getResult().getVerifications().isEmpty()) {
×
324
                throw new SOPGPException.NoSignature("No verifiable signature found.");
×
325
            }
326
        });
×
327
    }
×
328

329
    @ParameterizedTest
330
    @MethodSource("provideInstances")
331
    public void missingArgsTest(SOP sop) {
332
        byte[] message = TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8);
1✔
333

334
        assertThrows(SOPGPException.MissingArg.class, () -> assumeSupported(sop::encrypt)
1✔
335
                .plaintext(message)
1✔
336
                .toByteArrayAndResult()
×
337
                .getBytes());
×
338
    }
×
339

340
    @ParameterizedTest
341
    @MethodSource("provideInstances")
342
    public void passingSecretKeysForPublicKeysFails(SOP sop) {
343
        assertThrows(SOPGPException.BadData.class, () ->
×
344
                assumeSupported(sop::encrypt)
1✔
345
                        .withCert(TestData.ALICE_KEY.getBytes(StandardCharsets.UTF_8))
1✔
346
                        .plaintext(TestData.PLAINTEXT.getBytes(StandardCharsets.UTF_8))
1✔
347
                        .toByteArrayAndResult()
×
348
                        .getBytes());
×
349
    }
×
350

351
    @ParameterizedTest
352
    @MethodSource("provideInstances")
353
    public void encryptDecryptWithAllSupportedKeyGenerationProfiles(SOP sop) throws IOException {
354
        List<Profile> profiles = assumeSupported(sop::listProfiles).generateKey();
×
355

356
        List<byte[]> keys = new ArrayList<>();
×
357
        List<byte[]> certs = new ArrayList<>();
×
358
        for (Profile p : profiles) {
×
359
            byte[] k = assumeSupported(sop::generateKey)
×
360
                    .profile(p)
×
361
                    .userId(p.getName())
×
362
                    .generate()
×
363
                    .getBytes();
×
364
            keys.add(k);
×
365

366
            byte[] c = assumeSupported(sop::extractCert)
×
367
                    .key(k)
×
368
                    .getBytes();
×
369
            certs.add(c);
×
370
        }
×
371

372
        byte[] plaintext = "Hello, World!\n".getBytes();
×
373

374
        Encrypt encrypt = assumeSupported(sop::encrypt);
×
375
        for (byte[] c : certs) {
×
376
            encrypt.withCert(c);
×
377
        }
×
378
        for (byte[] k : keys) {
×
379
            encrypt.signWith(k);
×
380
        }
×
381

382
        ByteArrayAndResult<EncryptionResult> encRes = encrypt.plaintext(plaintext)
×
383
                .toByteArrayAndResult();
×
384
        EncryptionResult eResult = encRes.getResult();
×
385
        byte[] ciphertext = encRes.getBytes();
×
386

387
        for (byte[] k : keys) {
×
388
            Decrypt decrypt = assumeSupported(sop::decrypt)
×
389
                    .withKey(k);
×
390
            for (byte[] c : certs) {
×
391
                decrypt.verifyWithCert(c);
×
392
            }
×
393
            ByteArrayAndResult<DecryptionResult> decRes = decrypt.ciphertext(ciphertext)
×
394
                    .toByteArrayAndResult();
×
395
            DecryptionResult dResult = decRes.getResult();
×
396
            byte[] decPlaintext = decRes.getBytes();
×
397
            assertArrayEquals(plaintext, decPlaintext, "Decrypted plaintext does not match original plaintext.");
×
398
            assertEquals(certs.size(), dResult.getVerifications().size());
×
399
        }
×
400
    }
×
401
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc