1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-12 19:59:38 +02:00

Compare commits

..

1218 commits
1.0.0 ... main

Author SHA1 Message Date
0fe3a7abf6
Fix ArmorUtils spotless complaint 2025-08-30 10:36:40 +02:00
0d807cb6b8
Fix typo in error message 2025-07-23 11:26:40 +02:00
9b0a3cd4c7
Do not trim passphrases automatically 2025-07-23 11:26:28 +02:00
0ee31b232a
Allow UserIDs with trailing/leading whitespace and escape newlines in ASCII armor 2025-07-23 11:26:17 +02:00
f2cbde43be
Update codeql action to v3 2025-07-01 10:54:06 +02:00
4cf6c6b16a
Update CHANGELOG 2025-06-17 10:42:50 +02:00
0f54cc615c
Bump BC to 1.81, update native-image reflect-config, resource-config 2025-06-17 10:30:42 +02:00
a74db2d26d
Merge pull request #475 from felhag/fix/typo-readme
Fixed typo in sop readme
2025-06-04 16:37:00 +02:00
Felix Hagemans
5f30df6d16 Fixed typo in sop readme 2025-06-04 16:02:23 +02:00
7953ade136
Bump checkstyle to 10.25.0
Fixes https://github.com/pgpainless/pgpainless/security/dependabot/24
2025-06-03 12:37:04 +02:00
0649c041cd
gradle: migrate to new shadow plugin namespace 2025-04-21 19:12:10 +02:00
5a413f53a4
Specify license information for native-image metadata 2025-04-21 19:11:52 +02:00
3b92ccc59d
PGPainless 1.7.7-SNAPSHOT 2025-04-14 16:05:05 +02:00
83613250ef
PGPainless 1.7.6 2025-04-14 15:55:29 +02:00
05c84835e6
Bump SOP-Java to 10.1.1 2025-04-14 15:31:49 +02:00
d20a3b7556
Add config files for nativeimage
Those were generated by running the following commands in order:

gradle -Pagent test
gradle metadataCopy --task test --dir src/main/resources/META-INF/native-image

gradle nativeCompile

The resulting nativeimage can resolve method calls that use reflection. Yay
2025-04-14 15:31:49 +02:00
2d0608cf0f
Re-add shadow plugin 2025-04-13 19:45:12 +02:00
143c9777d6
Implement graal nativeimage compilation
Requires sop-java 10.1.1-SNAPSHOT for now, as that version includes picocli configurations files
2025-04-03 15:27:49 +02:00
9ac928fcf1
Update changelog 2025-03-26 15:04:22 +01:00
811f72ffef
Fix RevocationSignatureBuilder properly calculating 3rd-party delegation revocations 2025-03-26 15:02:52 +01:00
a6f8058fb4
PGPainless 1.7.6-SNAPSHOT 2025-03-25 12:09:43 +01:00
de5ef4de00
PGPainless 1.7.5 2025-03-25 12:05:33 +01:00
2b9c5ea272
Update CHANGELOG 2025-03-25 12:01:29 +01:00
f054c30460
Upgrade gradle-wrapper to 8.8 2025-03-25 11:58:55 +01:00
65113a6d82
Rework gradle, making use of toolchain feature 2025-03-25 11:22:16 +01:00
a0254f47fb
Simplify mavenCentralChecksums task 2025-03-25 11:03:22 +01:00
eebd02e309
Perform coveralls task after jacocoRootReport 2025-03-20 18:18:40 +01:00
7dc4329c52
PGPainless 1.7.5-SNAPSHOT 2025-03-20 18:05:37 +01:00
f22cada0ce
PGPainless 1.7.4 2025-03-20 18:02:07 +01:00
cb51bb64f3
Update changelog 2025-03-20 17:35:08 +01:00
c861a5eb73
Set Java release version to 8 to fix Kotlin desugaring
Fixes https://github.com/pgpainless/pgpainless/issues/471
2025-03-20 17:31:30 +01:00
62b0d0a560
Remove YourKit profiler usage 2025-03-14 13:44:24 +01:00
effc9e747a
Remove -werror flag from javadoc task 2025-03-11 22:55:43 +01:00
bb9393d948
PGPainless 1.7.4-SNAPSHOT 2025-03-11 22:43:46 +01:00
3b1dbf4102
PGPainless 1.7.3 2025-03-11 22:40:44 +01:00
d7e08186ac
Update CHANGELOG 2025-03-11 22:20:00 +01:00
883eb80a63
Bump bcpg, bcprov to 1.80, add bcutil dependency
Adding bcutil as a dependency is apparently required now.
See https://github.com/bcgit/bc-java/issues/1977
2025-03-11 22:04:03 +01:00
9a1a01fe05
pgpainless-cli version: Fix repository URL 2025-03-11 21:39:11 +01:00
13c3295e64
Bump sop-java to 10.1.0 2025-03-11 21:39:11 +01:00
8854429205
Revert "SOP: encrypt: Apply CRLF encoding if text encoding is used"
This reverts commit cb6dde4e39.
2025-03-11 20:53:24 +01:00
d5845d94a0
Merge pull request #470 from bjansen/bugfix/gh-469
Fix for #469
2025-03-11 19:51:08 +01:00
Bastien JANSEN
4185bf0326 Fix #469 2025-03-11 17:58:42 +01:00
74b28afd4a
SOP: inline-sign, detached-sign: Do not apply compression 2025-03-05 14:30:13 +01:00
bfdbac0f2d
Fix test by comparing to CRLF'd plaintext 2025-02-22 17:26:21 +01:00
cb6dde4e39
SOP: encrypt: Apply CRLF encoding if text encoding is used
Fixes #466
2025-02-22 15:46:36 +01:00
25c720b033
SOP inline-sign: Wrap data in LiteralData(text) packet if signing mode is text
Fixes #465
2025-02-22 13:37:51 +01:00
8d03810bf3
Fix typo in test 2025-02-18 14:26:01 +01:00
2d18d05bd7
Merge pull request #464 from elduffy/sig-bounds-typo
Fix a typo on signature creation bounds check message
2025-02-17 13:18:39 +01:00
Eric Duffy
c5bae9d50d Fix a typo on signature creation bounds check message 2025-02-14 10:35:15 -06:00
588b9d7469
Add REUSE.toml file to REUSE.toml file 2025-01-28 12:21:46 +01:00
3af506fedb
Migrate reuse from dep5 to REUSE.toml 2025-01-10 10:41:11 +01:00
1db59acf0d
Add reuse license header to properties file 2025-01-10 10:40:56 +01:00
02330a5aa1
Reproducible file order 2024-12-21 14:32:42 +01:00
4dbc633d7d
version --extended: Include sop-java version
Fixes #454
2024-12-21 13:41:37 +01:00
5018386318
Extract pgpainless-sop-version property via resource filtering 2024-12-21 13:24:05 +01:00
a43ae43722
Bump logback to 1.5.13
I hope I mitigated logback spam by modifying the logback.xml file and by
setting 'slf4j.internal.verbosity' to 'WARN'.
See https://github.com/pgpainless/pgpainless/issues/426 for reference
2024-12-21 12:43:05 +01:00
3e96af5450
Remove dependency on deprecated com.ginsberg:junit5-system-exit library 2024-12-21 11:14:37 +01:00
391549a7d6
Revert "Ensure proper compatibility with keys with missing direct-key or certification self-sigs"
This reverts commit 620c1fc96a.
2024-12-21 10:27:06 +01:00
fdf49cfafb
Improve error message when no acceptable certificate signature is found
Relates to #457
2024-11-19 13:59:41 +01:00
b99822f405
Ignore certificate signatures of unknown type 2024-11-19 13:58:54 +01:00
89790a0a94
Fix decryption example in README
Fixes #456
2024-11-11 11:52:56 +01:00
e46cbae6b8
PGPainless 1.7.3-SNAPSHOT 2024-10-31 14:54:34 +01:00
df5297a661
PGPainless 1.7.2 2024-10-31 14:52:45 +01:00
f6c4ddd288
Update changelog 2024-10-31 14:44:46 +01:00
34e9748d0f
Bump sop-java to 10.0.3 2024-10-31 14:39:56 +01:00
de4a113528
Update changelog 2024-10-24 19:06:56 +02:00
f1610f6425
Fix returning proper value for KeyRingInfo.lastModified
While porting to kotlin the code was accidentally changed to return the key ring creation
time instead of the latest self-sig creation time
2024-10-24 17:41:18 +02:00
d966349032
Add test to verify proper functionality of hash algorithm policy overrides for SOP 2024-10-15 14:45:28 +02:00
54569b3b02
Update changelog 2024-10-14 16:46:22 +02:00
dabafe538f
PGPainless 1.7.2-SNAPSHOT 2024-10-14 16:37:56 +02:00
fac23745b4
PGPainless 1.7.1 2024-10-14 16:35:52 +02:00
60c963fca5
Document logback spam 2024-10-14 16:33:47 +02:00
ab6cde3ec6
Bump sop-java to 10.0.2 2024-10-14 16:28:50 +02:00
e5a0617621
Fix CLI being spammed by logback by downgrading to logback-core 1.2.13 2024-10-14 15:47:00 +02:00
52b6d5c3f7
Fix some minor code issues 2024-10-14 15:18:49 +02:00
fb71ef2193
PGPainless 1.7.1-SNAPSHOT 2024-10-14 14:54:26 +02:00
8b40062288
PGPainless 1.7.0 2024-10-14 14:51:16 +02:00
3388d908a1
Update README 2024-10-14 12:27:04 +02:00
62c661e254
Update SECURITY.md 2024-10-14 12:23:31 +02:00
039595f5d6
Update changelog 2024-10-14 12:20:16 +02:00
19e389133a
Actually bump logback-core to 1.4.14 2024-10-14 12:12:03 +02:00
5dfebc5bde
Add support for LibrePGP OED packet 2024-10-14 11:58:04 +02:00
ad2976dbcc
SOP: KeyReader is silent 2024-10-14 11:42:02 +02:00
3c343dc45c
Prevent overreading when decompressing data 2024-10-14 11:40:57 +02:00
b719810575
Fix linking in KDoc documentation 2024-09-16 14:13:14 +02:00
67457bbe78
Replace use of addPassphrase()/addDecryptionPassphrase() in tests with addMessagePassphrase() 2024-08-22 13:42:30 +02:00
69a57ef3bc
Deprecate addPassphrase()/addDecryptionPassphrase in favor of addMessagePassphrase() 2024-08-22 13:41:51 +02:00
6f46f75602
Add PublicKeyAlgorithm entries for X25519, X448, Ed25519, Ed448 2024-06-21 14:12:54 +02:00
0378145b21
Merge branch 'eddsaLegacy' 2024-06-21 13:57:49 +02:00
b4d2a61459
Add support for padding packet 2024-06-21 13:48:00 +02:00
a9a07ff47d
Set java source compatibility 2024-06-18 11:33:49 +02:00
9bf41ca191
Update changelog 2024-06-12 22:14:15 +02:00
185150d70f
Bump BC to 1.78.1 2024-06-12 22:12:09 +02:00
0045f77551
Catch UnsupportedPacketVersionExceptions when parsing OnePassSignaturePackets 2024-06-12 22:11:47 +02:00
e9c57a9ed9
Remove support for generating EC keys over non-standard curve secp256k1 2024-06-12 22:11:08 +02:00
dd3ef89a5c
Add (failing) test for extracting certificate from key with unknown secret key encryption method 2024-04-10 10:47:13 +02:00
a6f3a223b1
Reject data signatures made by non-signing primary key 2024-04-10 10:38:50 +02:00
741d72eadc
Document nature of tests in pgpainless-sop 2024-03-30 19:20:12 +01:00
0b7511a223
Remove tests for armor --label 2024-03-30 19:07:12 +01:00
eeb5986890
Remove notice about armor's label() option 2024-03-30 19:06:42 +01:00
32d62c6610
Update pgpainless-cli usage documentation 2024-03-30 18:52:49 +01:00
1f9b65e3d2
Fix missing readthedocs theme 2024-03-30 00:37:51 +01:00
b96f22d0a9
Add EncryptionBuilder.discardOutput()
Also move NullOutputStream from pgpainless-sop to pgpainless-core
2024-03-29 20:37:24 +01:00
80cf1a7446
Merge branch 'sopKotlin' 2024-03-24 16:43:43 +01:00
fe80b1185e
Update man pages 2024-03-24 16:43:27 +01:00
b393a90da4
Port pgpainless-sop to Kotlin 2024-03-24 16:16:29 +01:00
8066650584
Add comments 2024-03-24 11:00:16 +01:00
bd1949871a
Update CHANGELOG 2024-03-24 10:52:16 +01:00
194e4e1458
Bump sop-java to 10.0.0 2024-03-24 10:52:15 +01:00
44be5aa981
Delegate verification operations to SOPVImpl 2024-03-24 10:52:15 +01:00
3ac273757a
Bump sop-java to 10.0.0-SNAPSHOT and implement sopv interface subset 2024-03-24 10:52:15 +01:00
fa5bdfcd82
Throw BadData if KEYS are passed where CERTS are expected 2024-03-24 10:52:14 +01:00
89038ebedf
Update CHANGELOG 2024-03-21 14:13:58 +01:00
337b5d68b6
Add Automatic-Module-Name to pgpainless-core and pgpainless-sop 2024-03-19 15:56:49 +01:00
265f72d99f
Fix OOM when detached signing large files
Fixes #432
2024-03-17 17:29:01 +01:00
a9cec16dc6
Fix badge showing SOP Spec revision to show 8 instead of 7 2024-03-17 15:50:40 +01:00
cbbd980554
Spotless apply 2024-03-05 21:30:28 +01:00
c2abc89d5e
Add tests for PGPKeyRingExtensions 2024-03-05 21:29:47 +01:00
c89c47c491
Add tests for PGPPublicKeyExtensions 2024-03-05 21:17:03 +01:00
e561d58562
Add tests for PGPSecretKeyExtensions 2024-03-05 21:05:34 +01:00
dfbc56fe24
Add tests for PGPSecretKeyRingExtensions 2024-03-05 20:54:15 +01:00
60ea98df00
Add Github Issue Templates to dep5 file 2024-03-05 20:38:12 +01:00
11cb7e2107
Update issue templates 2024-02-26 11:15:18 +01:00
b756de3082
Rename XDH to XDH_LEGACY 2024-02-21 15:19:33 +01:00
020d411417
Move CachingBcPublicKeyDataDecryptorFactory to org.pgpainless.decryption_verification package 2024-02-21 15:12:29 +01:00
a898323209
Rename KeyType.EDDSA to KeyType.EDDSA_LEGACY 2024-02-21 14:57:02 +01:00
e933af94c7
Rename PublicKeyAlgorithm.EDDSA to EDDSA_LEGACY 2024-02-21 14:52:48 +01:00
252c520ca2
Move org.bouncycastle classes to org.pgpainless.bouncycastle in order to avoid split package
See https://github.com/pgpainless/pgpainless/issues/428 for more background information
2024-02-21 14:43:38 +01:00
412f804eee
PGPainless 1.6.6 2024-02-03 11:56:53 +01:00
842c8980b9
Downgrade logback to 1.2.13 2024-02-02 22:11:38 +01:00
bd26268533
Add syntactic sugar for SignatureSubpacketCallback factory methods 2024-01-24 18:59:35 +01:00
b103f3ecc2
Update changelog 2024-01-24 11:33:29 +01:00
acd7f15744
Rename LibrePGP features 2024-01-24 11:30:20 +01:00
ce51f4b8cc
Add documentation to AEAD Algorithms 2024-01-24 11:28:35 +01:00
de9a161252
Accept certification signatures using SHA-1 before 2023-02-01
This commit introduces a dedicated SignatureHashAlgorithmPolicy for certification signatures.
The default configuration will accept SHA-1 on sigs created before 2023-02-01.
2024-01-04 18:20:09 +01:00
5053221e93
Bump logback-core to 1.4.14 2023-12-15 18:30:04 +01:00
fc45c9450a
Update SECURITY.md 2023-12-15 18:25:18 +01:00
69f1028fd9
Add method to change expiration time of subkeys
Port of e06f60f62c to kotlin
2023-12-15 18:20:51 +01:00
d7b6dfc8d4
Update changelog 2023-12-15 17:50:27 +01:00
7b37f206d6
Update CHANGELOG 2023-11-30 20:58:41 +01:00
a5a9153692
Bump logback to 1.4.13 2023-11-30 20:56:30 +01:00
b8b46a3ab2
Bump BC to 1.77 2023-11-30 20:56:18 +01:00
97455aa256
Add test for handling key with unknown signature subpacket 2023-11-30 19:36:44 +01:00
74c7b025a0
Do not choke on unknown signature subpackets
Fixes #418
2023-11-30 19:36:01 +01:00
f39d2c5566
Prevent subkey binding signature from predating subkey
Fixes #419
2023-11-30 17:58:10 +01:00
49de608785
Update changelog 2023-11-27 13:31:31 +01:00
b0caa95378
Properly feed an EOS token to the push down automata in OpenPgpMessageInputStream.read() 2023-11-27 13:27:23 +01:00
1e33408098
Please the checkstyle checker 2023-11-27 13:26:41 +01:00
Daniel Gultsch
9ab0c35b78 add unit test to read decryption stream beyond end 2023-11-26 10:55:47 +01:00
6999f4d241
Update changelog 2023-11-23 14:45:23 +01:00
417684f55d
Update changelog 2023-11-23 14:04:11 +01:00
f2448890e1
Bump sop-java to 8.0.1 2023-11-23 13:32:52 +01:00
b58826f907
Update changelog 2023-11-15 19:26:27 +01:00
481dfac455
Revert PassphraseProvider API change 2023-11-15 19:23:52 +01:00
cf638da130
Bump sop spec to 8 2023-11-15 19:09:15 +01:00
8203bd58c7
Bump sop-java to 8.0.0 2023-11-15 19:05:39 +01:00
1c10dd5bce
Adapt changes from sop-java 8.0.0 2023-11-15 13:40:22 +01:00
71431b7b0a
Bump sop-java to 8.0.0-SNAPSHOT 2023-11-15 13:40:00 +01:00
608ec0b7b0
Annotate methods with @Nonnull 2023-11-15 13:39:26 +01:00
f07063d55f
Kotlin conversion: SignatureBuilder classes 2023-11-13 16:21:08 +01:00
3bb25a62a2
Remove unused CRCingArmoredInputStreamWrapper class 2023-11-13 14:09:42 +01:00
620c1fc96a
Ensure proper compatibility with keys with missing direct-key or certification self-sigs 2023-11-08 15:16:41 +01:00
6fdf136024
Merge pull request #404 from pgpainless/pgpainless2
PGPainless 2.0 - Kotlin Conversion
2023-10-26 13:03:16 +02:00
f4bfb9dc04
Remove test with expired key 2023-10-26 12:52:21 +02:00
19b45644ae
Kotlin conversion: SignatureVerifier 2023-10-26 12:52:04 +02:00
dc05a492f5
Fix reuse 2023-10-25 19:08:03 +02:00
e9d8ddc57b
Kotlin conversion: SignatureValidator 2023-10-25 19:07:52 +02:00
733d5d6b82
Decrease continuation_indent from 8 to 4 2023-10-24 10:21:10 +02:00
4f64868914
Add dropbox-style editorconfig
Source: https://github.com/facebook/ktfmt/blob/main/docs/editorconfig/.editorconfig-dropbox
2023-10-24 10:15:16 +02:00
29fe4faeed
Add .git-blame-ignore-revs file 2023-10-23 14:27:54 +02:00
51e9bfc67f
Apply new formatting from 'gradle spotlessApply' 2023-10-23 14:24:31 +02:00
633048fd2a
Merge pull request #412 from msfjarvis/pgpainless2
build: add ktfmt via Spotless
2023-10-23 14:22:49 +02:00
Harsh Shandilya
384347ed1a build: add ktfmt via Spotless 2023-10-23 17:36:00 +05:30
b72f95b46c
Kotlin conversion: SignatureSubpacketsHelper 2023-10-20 14:55:51 +02:00
0effc84fac
Kotlin conversion: SignatureSubpackets + subclasses 2023-10-20 14:10:37 +02:00
4fc513fa25
Kotlin conversion: SignatureCreationDateComparator, SignatureValidityComparator 2023-10-17 18:58:37 +02:00
70e1b40cd2
Fix ArmorUtil header 2023-10-13 13:41:50 +02:00
efae652a66
Kotlin conversion: CertificateValidator 2023-10-13 13:38:45 +02:00
853e3de472
Clean up unused casts from EncryptionOptions 2023-10-10 13:00:01 +02:00
11c1c54111
Kotlin conversion: ProviderFactory 2023-10-09 12:49:17 +02:00
8351223614
Kotlin conversion: PublicKeyParameterValidationUtil 2023-10-09 12:49:17 +02:00
1cdce5c93a
Kotlin conversion: ImplementationFactory classes 2023-10-09 12:49:17 +02:00
d707dcf74a
Move now unused utility classes to test directory 2023-10-09 12:49:17 +02:00
8382da923d
Add TODO to CRCinArmoredInputStreamWrapper 2023-10-09 12:49:17 +02:00
aca884e936
Kotlin conversion: ArmoredOutputStreamFactory
Also allow configuration of CRC calculation for both input and output streams
2023-10-09 12:49:16 +02:00
e16376ca68
Kotlin conversion: ArmoredInputStreamFactory 2023-10-09 12:49:16 +02:00
6b397a0d56
Kotlin conversion: SignaturePicker 2023-10-09 12:49:16 +02:00
841b386226
Kotlin conversion: MultiMap
Warning: This commit changes the semantics of MultiMap.put()
put() now replaces values, while plus() adds them.
2023-10-09 12:49:16 +02:00
b324742a62
Kotlin conversion: ArmorUtils 2023-10-09 12:49:16 +02:00
9a917f7fdb
Kotlin conversion: DateUtil 2023-10-09 12:49:15 +02:00
4c237d55ed
Add note about deprecation to BaseSignatureSubpackets 2023-10-09 12:49:15 +02:00
c9f988b2d1
Kotlin conversion: SelectUserId 2023-10-09 12:49:15 +02:00
33037b9743
Kotlin conversion: Passphrase 2023-10-09 12:49:15 +02:00
53b1e3ff71
Kotlin conversion: HashContextSigning 2023-10-09 12:49:15 +02:00
a50be47fa4
Kotlin conversion: CRLFGeneratorStream 2023-10-09 12:49:14 +02:00
068aa0ec27
Kotlin conversion: SignatureGenerationStream 2023-10-09 12:49:14 +02:00
0fa09065cf
Kotlin conversion: TeeBCPGInputStream 2023-10-09 12:49:14 +02:00
befb1c8c0f
Kotlin conversion: MessageInspector 2023-10-09 12:49:14 +02:00
ea57c4aec0
Kotlin conversion: EncryptionStream 2023-10-09 12:49:14 +02:00
9ee29f7a53
Kotlin conversion: IntegrityProtectedInputStream 2023-10-09 12:49:13 +02:00
a6198aadb3
Kotlin conversion: RevocationAttributes 2023-10-09 12:49:13 +02:00
68ac5af255
Kotlin conversion: UserId 2023-10-09 12:49:13 +02:00
ec8ae3eff0
Kotlin conversion: SecretKeyRingEditor 2023-10-09 12:49:13 +02:00
4719d6ccea
Migrate further to extension methods 2023-10-09 12:49:13 +02:00
68af0a4f0e
Introduce more extension methods 2023-10-09 12:49:12 +02:00
bb796143ff
Improve public/secret key selection 2023-10-09 12:49:12 +02:00
76cf6173e8
Add test for OpenPgpFingerprint.getBytes() 2023-10-09 12:49:12 +02:00
de3ea580e3
Add extension methods to PGPKeyRing, PGPSecretKeyRing and PGPSignature 2023-10-09 12:49:12 +02:00
a0b01f121a
Remove KeyRingUtils.unlockSecretKey() 2023-10-09 12:49:12 +02:00
19063454cb
Add PGPSecretKey.unlock() methods 2023-10-09 12:49:11 +02:00
6f9e692474
Kotlin conversion: KeyRingInfo 2023-10-09 12:49:04 +02:00
85e2fe956a
Add test for SubkeyIdentifier.isPrimaryKey() 2023-10-09 12:45:47 +02:00
9ee0f09b8d
Fix bug caused by false field comparison in SubkeyIdentifier 2023-10-09 12:45:47 +02:00
8fe9d250a8
Kotlin conversion: KeyInfo 2023-10-09 12:45:47 +02:00
b6e47d7739
Kotlin conversion: KeyAccessor 2023-10-09 12:45:47 +02:00
ad734ca1b4
Kotlin conversion: XDH 2023-10-09 12:45:46 +02:00
521424c23a
Kotlin conversion: XDHSpec 2023-10-09 12:45:46 +02:00
ac245fb56b
Kotlin conversion: RSA 2023-10-09 12:45:46 +02:00
ca3ff6acce
Kotlin conversion: RsaLength 2023-10-09 12:45:46 +02:00
2d755be10e
Kotlin conversion: ElGamal 2023-10-09 12:45:46 +02:00
72147b685e
Kotlin conversion: ElGamalLength 2023-10-09 12:45:45 +02:00
f8abb28a81
Turn KeyLength method into val 2023-10-09 12:45:45 +02:00
4382c1f20e
Kotlin conversion: EdDSA 2023-10-09 12:45:45 +02:00
8f49b01d51
Kotlin conversion: EdDSACurve 2023-10-09 12:45:45 +02:00
89b73895f5
Kotlin conversion: ECDSA 2023-10-09 12:45:45 +02:00
9e7a25ffe1
Kotlin conversion: ECDH 2023-10-09 12:45:45 +02:00
7f96272152
Kotlin conversion: EllipticCurve 2023-10-09 12:45:44 +02:00
13082215d6
Fix property access 2023-10-09 12:45:37 +02:00
b3f4ba052a
Remove whitespace 2023-10-09 12:44:25 +02:00
472d5c4beb
Kotlin conversion: KeyType 2023-10-09 12:44:25 +02:00
1ebf8e1e6f
Kotlin conversion: KeyLength 2023-10-09 12:44:25 +02:00
0b071ff8e1
Kotlin conversion: CachingBcPublicKeyDataDecryptorFactory 2023-10-09 12:44:24 +02:00
e8fef1f1f3
Add PGPKeyRingExtensions class and make use of it 2023-10-09 12:44:24 +02:00
6a23016104
Kotlin conversion: EncryptionResult 2023-10-09 12:44:24 +02:00
a4cd965967
Kotlin conversion: ProducerOptions 2023-10-09 12:44:24 +02:00
5441993baf
Kotlin conversion: SigningOptions 2023-10-09 12:44:24 +02:00
6e653f3c92
Kotlin conversion: MissingKeyPassphraseStrategy 2023-10-09 12:44:23 +02:00
1234c8800a
Kotlin conversion: MissingPublicKeyCallback 2023-10-09 12:44:23 +02:00
a268cfd51a
Add missing license header 2023-10-09 12:44:23 +02:00
4cfdcca2e0
Kotlin conversion: MessageMetadata 2023-10-09 12:44:23 +02:00
b09979fa45
Kotlin conversion: HardwareSecurity 2023-10-09 12:44:23 +02:00
3115e13bc2
Kotlin conversion: CustomPublicKeyDataDecryptorFactory 2023-10-09 12:44:22 +02:00
d8df6c35d0
Rename heyKeyId -> openPgpKeyId 2023-10-09 12:44:22 +02:00
39e170064c
Kotlin conversion: NotationRegistry 2023-10-09 12:44:22 +02:00
dc064d1727
Kotlin conversion: KeyRingUtils 2023-10-09 12:44:22 +02:00
ab42a7503f
Replace usage of KeyIdUtil.formatKeyId() in Kotlin classes with Long.hexKeyId() 2023-10-09 12:44:22 +02:00
44c22f9044
Kotlin conversion: KeyIdUtil
This PR also introduces LongExtensions.kt which provides extension methods to
parse Long from Hex KeyIDs and to format Longs as Hex KeyIDs.
2023-10-09 12:44:22 +02:00
d075ed6637
Kotlin conversion: PGPKeyRingCollection 2023-10-09 12:44:21 +02:00
0e6a146594
Add missing license header 2023-10-09 12:44:21 +02:00
f3ea9f62e1
Kotlin conversion: EncryptionOptions 2023-10-09 12:44:21 +02:00
e9caa4af1f
Kotlin conversion: EncryptionBuilder + Interface 2023-10-09 12:44:21 +02:00
bbd956dbb7
Kotlin conversion: KeyRingReader 2023-10-09 12:44:21 +02:00
e3f51fbf56
Kotlin conversion: SecretKeyPassphraseProvider and subclasses
This commit also adds a workaround to build.gradle which enables proper Java interop for
Kotlin interfaces with default implementations
2023-10-09 12:44:20 +02:00
5cb6d6e41d
Kotlin conversion: S2KUsageFix 2023-10-09 12:44:20 +02:00
873db12125
Kotlin conversion: UnlockSecretKey 2023-10-09 12:44:20 +02:00
2547f1c687
Kotlin conversion: KeyRingProtectionSettings 2023-10-09 12:44:20 +02:00
ee32d9e446
Kotlin conversion: CachingSecretKeyRingProtector 2023-10-09 12:44:20 +02:00
b9c601b996
Kotlin conversion: PasswordBasedSecretKeyRingProtector 2023-10-09 12:44:19 +02:00
b125333c89
Kotlin conversion: UnprotectedKeysProtector 2023-10-09 12:44:19 +02:00
a8bc929f24
Kotlin conversion: BaseSecretKeyRingProtector 2023-10-09 12:44:19 +02:00
5fce443ad9
Kotlin conversion: SecretKeyRingProtector and subclasses 2023-10-09 12:44:19 +02:00
c40e2ba6c2
Move const to companion object 2023-10-09 12:44:19 +02:00
b343b4ad17
Kotlin conversion: KeyRingTemplates 2023-10-09 12:44:19 +02:00
41f56bdf99
Kotlin conversion: KeySpec 2023-10-09 12:44:15 +02:00
bb17c627ce
Kotlin conversion: KeySpecBuilder 2023-10-09 12:43:16 +02:00
eaef1fe44a
Kotlin conversion: KeyRingBuilder 2023-10-09 12:33:45 +02:00
43335cbcd3
Kotlin conversion: SessionKey 2023-10-09 12:29:34 +02:00
02511ac1ca
Kotlin conversion: SignatureVerification 2023-10-09 12:29:34 +02:00
48af91efbf
Kotlin conversion: Cleartext Signature Framework 2023-10-09 12:29:34 +02:00
8d67820f50
Kotlin conversion: OnePassSignatureCheck 2023-10-09 12:29:34 +02:00
145555997c
Kotlin conversion: SignatureCheck 2023-10-09 12:29:33 +02:00
1a701333e3
Remove deprecated OpenPgpMetadata class 2023-10-09 12:29:33 +02:00
8c25b59c8b
Add missing utility methods to MessageMetadata class 2023-10-09 12:29:33 +02:00
23f8777c34
Kotlin conversion: DecryptionStream 2023-10-09 12:29:33 +02:00
9988ba9940
Kotlin conversion: DecryptionBuilder 2023-10-09 12:29:33 +02:00
4a19e6ca20
WIP: Kotlin conversion: ConsumerOptions 2023-10-09 12:29:32 +02:00
cc63095ab0
Kotlin conversion: SignatureSubpacketsUtil 2023-10-09 12:29:32 +02:00
6dc08e7445
Improve SignatureUtils readability 2023-10-09 12:29:32 +02:00
fa765fdb0d
Add documentation to PGPSignatureExtensions 2023-10-09 12:29:32 +02:00
85b1ffe2e9
Add PGPSignatureExtensions file 2023-10-09 12:29:32 +02:00
b33ee90845
Kotlin conversion: SignatureUtils 2023-10-09 12:29:32 +02:00
fca5c88d09
Kotlin conversion: OpenPgpMessageInputStream 2023-10-09 12:29:31 +02:00
1ab5377f70
Rename syntax checker enums to screaming snake case 2023-10-09 12:29:31 +02:00
603f07d014
Kotlin conversion: Syntax checking 2023-10-09 12:29:31 +02:00
a1a090028d
Kotlin conversion: PDA 2023-10-09 12:29:31 +02:00
de73b3b00b
Kotlin conversion: CertifyCertificate 2023-10-09 12:29:27 +02:00
39c5d12096
Use IntRange for Trustworthiness range check 2023-10-09 12:24:49 +02:00
e57e74163c
Finish Policy conversion and move kotlin classes to src/kotlin/ 2023-10-09 12:24:49 +02:00
b68061373d
Wip: Kolin conversion of Policy 2023-10-09 12:24:49 +02:00
ca6e18d701
Kotlin conversion: SubkeyIdentifier 2023-10-09 12:24:49 +02:00
d36e878bf9
Kotlin conversion: OpenPgpFingerprints 2023-10-09 12:24:49 +02:00
344421a0f4
Kolin conversion: Some OpenPgpFingerprint classes 2023-10-09 12:24:49 +02:00
84e554fc0d
Kotlin conversion: CertificateAuthority 2023-10-09 12:24:48 +02:00
58951ce19a
Get rid of animalsniffer 2023-10-09 12:24:48 +02:00
075ca4baa3
Kotlin conversion: CertificateAuthenticity 2023-10-09 12:24:48 +02:00
b91e19fc39
Kotlin conversion: PGPainless 2023-10-09 12:24:48 +02:00
5a5b604411
Add missing license header 2023-10-09 12:24:48 +02:00
cf7a7f3aca
Kotlin conversion: SymmetricKeyAlgorithmNegotiator 2023-10-09 12:24:47 +02:00
fb235d7810
Kotlin conversion: HashAlgorithmNegotiator 2023-10-09 12:24:47 +02:00
b5d8126e48
Kotlin conversion: AlgorithmSuite 2023-10-09 12:24:47 +02:00
382817dc5f
Kotlin conversion: Trustworthiness 2023-10-09 12:24:47 +02:00
3a62b39197
Kotlin conversion: SymmetricKeyAlgorithm 2023-10-09 12:24:47 +02:00
5da58904b6
Kotlin conversion: StreamEncoding 2023-10-09 12:24:47 +02:00
595b9c7379
Kotlin conversion: SignatureType 2023-10-09 12:24:46 +02:00
f0082d3fb7
Kotlin conversion: SignatureSubpacket 2023-10-09 12:24:46 +02:00
2fb7e6c3a3
Kotlin conversion: RevocationStateType 2023-10-09 12:24:46 +02:00
c8bfcc807b
Kotlin conversion: RevocationState 2023-10-09 12:24:46 +02:00
608edc2378
Kotlin conversion: PublicKeyAlgorithm 2023-10-09 12:24:46 +02:00
eb07b94bcb
Kotlin conversion: OpenPgpPacket 2023-10-09 12:24:45 +02:00
294c469a29
Kotlin conversion: KeyFlag 2023-10-09 12:24:40 +02:00
2c38490742
Kotlin conversion: HashAlgorithm 2023-10-09 12:22:04 +02:00
98e9c0934d
Kotlin conversion: Feature 2023-10-09 12:22:04 +02:00
eb94aa6063
Kotlin conversion: EncryptionPurpose 2023-10-09 12:22:03 +02:00
e9dceb4553
Kotlin conversion: DocumentSignatureType 2023-10-09 12:22:03 +02:00
dfb33a5ecb
Kotlin conversion: CompressionAlgorithm 2023-10-09 12:22:03 +02:00
644700c8ee
Kotlin conversion: CertificationType 2023-10-09 12:22:03 +02:00
94ad4cfbe7
Kotlin conversion: AEADAlgorithm 2023-10-09 12:22:03 +02:00
c9dce319f8
Prepare for Kotlin conversion 2023-10-09 12:22:03 +02:00
64dfefc9e7
Remove usage of PublicKeyAlgorithm.EC 2023-10-09 12:22:02 +02:00
bf6c89af64
Test usability of keyflag-less key 2023-10-09 12:09:22 +02:00
1b96919d84
Allow generation of keys with empty key flags.
Forbid certification of thirdparty certificates if CERTIFY_OTHERS flag is missing
2023-10-09 12:02:10 +02:00
e7e269d7ce
Documentation: Add section on reading certificates 2023-10-06 12:46:17 +02:00
41dfe71994
Pad long KeyIDs with zeros to 16 chars 2023-09-04 14:18:13 +02:00
9ac681d88c
Update SECURITY.md 2023-08-30 13:18:39 +02:00
71ea2ce8c1
Remove sop-java and sop-java-picocli notices 2023-08-30 13:17:56 +02:00
682042f563
Update codeql action to v2 2023-08-30 12:23:11 +02:00
32b4a49102
PGPainless 1.6.3-SNAPSHOT 2023-08-30 12:19:13 +02:00
ea7e5e8022
PGPainless 1.6.2 2023-08-30 12:14:20 +02:00
3eb1c60991
Update changelog 2023-08-29 16:59:43 +02:00
29165f3df4
Switch bcpg and bcprov artifacts from -jdk15to18 variant to -jdk18on 2023-08-29 16:57:05 +02:00
1c3cc19ff7
Add documentation to OpenPgpInputStream 2023-08-03 15:20:24 +02:00
db7e1ce942
Allow overriding evluation date in SigningOptions 2023-08-03 14:57:31 +02:00
16a4836f8a
Override evaluation date in test with expiring key 2023-08-03 14:51:43 +02:00
f0e59ecef5
EncryptionOptions: Allow overriding evaluation date for recipient keys 2023-08-03 14:48:57 +02:00
d08bc6bd4b
Update changelog 2023-08-03 14:12:58 +02:00
975d59c5a9
Add method to allow for encryption for keys with missing keyflags.
There are legacy keys around, which do not carry any key flags.
This commit adds a method to EncryptionOptions that allow PGPainless to encrypt
for such keys.

Fixes #400
2023-08-03 14:04:40 +02:00
0d8db24b1a
Fix typo 2023-08-02 16:02:01 +02:00
1df6dcce13
Update sop quickstart document 2023-08-02 14:46:35 +02:00
e167fa37f3
Make use of new ArmoredOutputStream.Builder 2023-08-01 16:53:55 +02:00
8cdb7ee4e0
Add more tests for V6 fingerprints 2023-08-01 15:29:24 +02:00
15af265e3f
Update changelog 2023-08-01 14:31:09 +02:00
8bc2338463
Bump BC to 1.76 2023-08-01 14:27:49 +02:00
789fa507d1
PGPainless 1.6.2-SNAPSHOT 2023-07-22 15:21:10 +02:00
1768156899
PGPainless 1.6.1 2023-07-22 15:19:17 +02:00
b2ea55a67d
Update changelog 2023-07-22 15:13:01 +02:00
28e4bc44a1
Further integration of pgpainless-wot 2023-07-22 00:30:52 +02:00
616820fe0f
Update ecosystem diagram 2023-07-22 00:30:39 +02:00
6ac019a420
Add isAuthenticatablySignedBy() to MessageMetadata 2023-07-21 17:30:11 +02:00
44690d063c
Rename CertificateAuthority methods 2023-07-21 17:11:56 +02:00
c26ddc116e
Add identify API endpoint 2023-07-21 17:08:48 +02:00
ccbf4ab84d
Add documentation to CertificateAuthority 2023-07-21 16:55:21 +02:00
8926ff9dfb
s/identify/lookup 2023-07-21 16:50:49 +02:00
bf9bf94fb0
Integrate WoT by adding EncryptionOptions.addAuthenticatableRecipients() method 2023-07-21 16:38:34 +02:00
9d93c0f5ae
Add CertificateAuthority interface to enable integration with pgpainless-wot 2023-07-21 16:25:29 +02:00
22b4b93be8
Replace jetbrains annotations package with jsr305 2023-07-19 12:43:23 +02:00
59fa51bdf3
Expose SignatureValidator methods 2023-07-19 11:47:53 +02:00
f46790be00
Require UTF8 for KeyRingBuilder.addUserId(byte[]) 2023-07-12 16:49:38 +02:00
0fa62991ec
PGPainless 1.6.1-SNAPSHOT 2023-07-12 16:25:31 +02:00
d6dfcbf4b5
PGPainless 1.6.0 2023-07-12 16:23:42 +02:00
e4589bf147
Update CHANGELOG 2023-07-12 16:13:57 +02:00
8f53952c7c
Update man pages 2023-07-12 16:00:54 +02:00
06fd04ac76
Fix error message in rewriteManPages script 2023-07-12 15:59:38 +02:00
50787708f5
Bump SOP-Java dependency from 7.0.0-SNAPSHOT to 7.0.0 2023-07-12 15:45:56 +02:00
f3980304ed
SOP-Java now produces hard-revocations 2023-07-12 15:36:09 +02:00
c69af33588
revoke-key: Generate hard instead of soft revocation 2023-07-12 15:27:36 +02:00
6e9d276309
Add complex change-key-password test 2023-07-12 15:25:12 +02:00
e5539a810d
Use KeyReader class when reading public or secret keys 2023-07-12 15:25:03 +02:00
744c679e0c
Bump SOP_VERSION to 7 2023-07-12 01:37:19 +02:00
9c216e1ff4
Implement '--signing-only' option for 'generate-key' subcommand 2023-07-12 01:07:29 +02:00
d3fe850c95
Initial implementation of 'change-key-password' command of SOP-07 2023-07-12 00:40:59 +02:00
37bbe8bb39
Initial implementation of the new revoke-key command from SOP-07 2023-07-11 23:15:22 +02:00
556d1bee30
PGPainless 1.5.7-SNAPSHOT 2023-07-07 11:36:15 +02:00
4838cd8d62
PGPainless 1.5.6 2023-07-07 11:34:23 +02:00
b92365e268
Update changelog 2023-07-07 11:32:44 +02:00
e61d414a98
Manually checkout SignatureSubpacketsUtil methods for wot 2023-07-07 11:26:55 +02:00
6487cf2371
Remove unused Certification class 2023-07-07 11:25:26 +02:00
d52968514c
Add methods to access delegations and 3rd-party certifications from keys 2023-07-07 11:25:08 +02:00
172972fe29
Add utility methods to KeyRingInfo 2023-07-07 11:24:43 +02:00
30481cd510
Fix unreliable unit test
Fixes #389
2023-07-07 11:07:14 +02:00
cac500874a
Update changelog 2023-07-07 10:48:44 +02:00
50bbdddc73
Update comment of KeyRingReader.publicKeyRingCollection() 2023-07-07 10:47:02 +02:00
d707ecd20e
Disable now broken test caused by parsing certificates from secret keys 2023-07-07 10:44:16 +02:00
4cc8054cff
KeyRingReader.readPublicKeyRingCollection: Handle secret keys as public keys 2023-07-07 10:43:41 +02:00
bbdb300814
Add test to verify correct behavior dealing with non-utf8 userID 2023-07-07 10:42:32 +02:00
f37aa1097c
Prevent IAE when encountering non-UTF8 User-ID on a public key
Fixes #392
2023-07-07 10:42:18 +02:00
Heiko Schaefer
eb7bb1bebe
Bump jacoco dependency to 0.8.8
With jacoco 0.8.7 and JDK 17, tests show a
java.lang.instrument.IllegalClassFormatException
2023-07-06 16:51:24 +02:00
4be2956469
Remove unused methods from ImplementationFactory 2023-06-27 23:22:23 +02:00
9a4c5ec54f
PGPainless 1.5.6-SNAPSHOT 2023-06-27 15:13:21 +02:00
85b7114e10
PGPainless 1.5.5 2023-06-27 15:10:03 +02:00
36b015eb34
Update changelog 2023-06-27 15:06:27 +02:00
a41d361826
Merge branch 'checkstyle' 2023-06-27 15:01:51 +02:00
2ab820a864
Bump checkstyle plugin version to 10.12.1
Bumps checkstyles transitive dependency on vulnerable guava to 32.0.1
Fixes https://github.com/pgpainless/pgpainless/security/dependabot/6
Fixes https://github.com/pgpainless/pgpainless/security/dependabot/5
Fixes https://github.com/pgpainless/pgpainless/security/dependabot/4

F
2023-06-27 15:00:21 +02:00
0505b943de
Fix javadoc 2023-06-27 14:56:29 +02:00
ee7a45bbbc
Flex 2023-06-24 13:06:01 +02:00
1a38e2d31a
Add section about hidden recipients to documentation 2023-06-24 12:54:28 +02:00
55172898a6
Update CHANGELOG 2023-06-24 10:28:51 +02:00
fc3a477a6c
Bump BC to 1.75 2023-06-24 10:23:39 +02:00
f7576abd35
Minimal revocation certificate test: Test merging 2023-06-20 17:37:18 +02:00
53d6260210
Implement method to create minimal revocation certificate.
Fixes #386
2023-06-20 17:30:19 +02:00
2a7c6af022
Rename SecretKeyRingEditor.createRevocationCertificate() to createRevocation() 2023-06-20 16:41:46 +02:00
600d5f49bb
PGPainless 1.5.5-SNAPSHOT 2023-06-19 12:49:39 +02:00
2b0df01591
PGPainless 1.5.4 2023-06-19 12:46:34 +02:00
2ab1d7a42d
Readme: Fix usage of MessageMetada instead of OpenPgpMetadata 2023-06-19 12:36:59 +02:00
beccd6c5c7
Postpone removal of OpenPgpMetadata to 1.6.X 2023-06-19 12:36:59 +02:00
c962b7920b
Update workaround description in KeyInfo 2023-06-19 12:36:59 +02:00
eed1259cd2
Update changelog 2023-06-19 11:50:52 +02:00
90626a8a76
Add SignatureSubpacketsUtil.getRegularExpressions() 2023-06-19 11:27:49 +02:00
82cea93e7b
Replace JcaPGPObjectFactory with custom PGPObjectFactory.
Workaround for https://github.com/bcgit/bc-java/issues/1428
2023-06-15 15:20:08 +02:00
56cb9ba3c6
Update readme 2023-06-15 14:44:59 +02:00
2b119e8214
Basic V6 parsing test (only check non-crashing) 2023-06-15 14:41:41 +02:00
6b145475a8
Add test for anonymous recipients 2023-06-15 14:28:56 +02:00
e9cd6c55cf
Bump Bouncycastle 2023-06-15 14:28:45 +02:00
558e6693e6 Add javadoc 2023-06-15 14:10:52 +02:00
344f1fc67c Allow hidden recipients using wildcard keyIDs 2023-06-15 14:10:52 +02:00
383c9799c3
Add test for minimal revocation certificate 2023-06-13 19:46:56 +02:00
7f5bc91f6b
Add tests for ListProfilesCmd 2023-06-13 15:52:20 +02:00
d9ab91516d
Add encrypt-decrypt roundtrip test for rfc4880 profile to cli tests 2023-06-13 15:43:48 +02:00
8369333355
Clean unused methods from SignatureUtils 2023-06-12 15:04:57 +02:00
814421fe79
Test constructor of RevocationState.softRevoked() requires non-null date 2023-06-12 15:04:57 +02:00
1b7157a548
When testing support for RFC4880 profile, use RSA keys 2023-06-12 14:41:32 +02:00
ea9b0d68fb
SOP: Test profile support of encrypt subcommand 2023-06-12 14:37:33 +02:00
08ce7c099c
Test list-profiles of encrypt subcommand 2023-06-12 14:28:53 +02:00
2367674151
SOP: List profiles of null command throws 2023-06-12 14:27:08 +02:00
b9c2e8dfe9
Add test for weak public key behavior 2023-06-12 14:27:07 +02:00
6a6a9c7b5b
Update SECURITY.md 2023-06-10 14:32:02 +02:00
68a47e893e
Update changelog 2023-06-10 14:30:35 +02:00
4252f11d59
Update changelog 2023-06-10 14:13:58 +02:00
4d297880ff
Update SECURITY.md 2023-06-10 13:59:47 +02:00
fde506a7c2
PGPainless 1.5.4-SNAPSHOT 2023-06-10 13:33:56 +02:00
0d093c90dd
PGPainless 1.5.3 2023-06-10 13:10:43 +02:00
a1baeabd71
Update CHANGELOG 2023-06-08 14:13:25 +02:00
1fca51d771
SigningOptions: Add methods to sign with a single, chosen signing subkey 2023-06-08 14:04:06 +02:00
5aabd1ced4
Fix faulty bit-strength policy check for signing subkeys 2023-06-08 13:54:20 +02:00
490cbd58fd
Add reuse header to dependency-submission workflow 2023-06-08 03:25:26 +02:00
0d40b1bfb2
PGPainless 1.5.3-SNAPSHOT 2023-06-08 03:23:14 +02:00
00796a1151
PGPainless 1.5.2 2023-06-08 03:20:36 +02:00
25cde8225d
Remove outdated javadoc @throws annotations 2023-06-08 03:18:24 +02:00
40af7c3ce7
Update changelog 2023-06-08 03:13:04 +02:00
7769ff8173
Direct-Key signatures are calculated over the signee only, not the signer plus signee 2023-06-06 11:00:44 +02:00
0fdafdf956
Enable dependency-submission in submodules 2023-06-05 22:38:19 +02:00
d3ef513960
Fix checkstyle issues 2023-06-05 21:12:16 +02:00
1a9bb308ec
Bump checkstyle tool to 8.45.1 2023-06-05 21:12:04 +02:00
df4ec7ff59
Fix branches of codeql workflow 2023-06-05 20:38:09 +02:00
31a6a60c41
Add Gradle Dependency Submission workflow 2023-06-05 20:31:24 +02:00
41e663e25b
Allow setting custom version header when encrypting/signing message 2023-06-05 20:18:06 +02:00
add1b89019
Add test for MultiMap.putAll() 2023-06-05 19:44:58 +02:00
324302c536
Add MultiMap.flatten() 2023-06-05 19:44:47 +02:00
41d734f2db
ProviderFactory: Provide default implementation of _getProviderName() 2023-06-05 19:30:14 +02:00
96da3db2b8
Set AES-128 as default symmetric algorithm.
The crypto-refresh marks AES-128 as MUST implement.
2023-06-02 14:28:19 +02:00
e1038a8bb3
Replace more occurrences of new Date().getTime() with System.currentTimeMillis() 2023-06-02 00:03:55 +02:00
d25e7419c9
Replace new Date().getTime() with System.getCurrentTimeMillis() 2023-06-02 00:01:34 +02:00
528591f906
Key generation: Set default expiration periof of 5 years
Can be changed by calling 'keyRingBuilder.setExpirationDate(null);'
2023-06-01 23:43:41 +02:00
be5562d273
Fix typo in feature name 2023-05-30 14:51:32 +02:00
55058d6070
Rename GNUPG features 2023-05-22 14:37:02 +02:00
ec6fc810d4
Add missing accept rule to message format diagram 2023-05-16 16:54:03 +02:00
5cdd68c4cc
Add documentation to ListProfilesImpl 2023-05-16 16:49:50 +02:00
7d049d0ac4
Simplify KeyReader 2023-05-16 16:48:21 +02:00
5c11b8af08
Mark ArmoredInputStreamFactory methods as @Nonnull 2023-05-16 16:44:26 +02:00
772c0407b3
Mark ArmoredOutputStreamFactory methods as @Nullable 2023-05-16 16:43:53 +02:00
8b4dd0fc25
Annotate CollectionUtils methods with @Nullable, @Nonnull 2023-05-16 16:39:58 +02:00
126571a6cd
Document ProviderFactory 2023-05-16 16:34:57 +02:00
d3ae02f137
Mark KeyRingReader.read*KeyRing() as @Nullable/@Nonnull 2023-05-16 16:26:52 +02:00
1bf9abbdaf
Add link to EdDSA spec 2023-05-16 16:19:20 +02:00
0805076392
Deprecate ElGamal key type 2023-05-16 16:16:42 +02:00
7234b42144
compileJava: Add --release = 8 2023-05-09 20:09:49 +02:00
a369f1c103
Fix checkstyle 2023-05-09 19:07:07 +02:00
8fe3a7159d
Report PGPainless version in sop version --backend 2023-05-08 16:01:59 +02:00
1523497da4
Update changelog 2023-05-03 17:35:49 +02:00
92a5e559f8
Earlier catching of NPEs in tests 2023-05-03 17:26:21 +02:00
e08505e07d
CertificateValidator: Skip revocation signatures not made by primary key 2023-05-03 17:25:59 +02:00
495ff6aa5d
Fix javadoc reference 2023-05-03 17:25:19 +02:00
88de47490b
SignatureValidator: Prevent NPE when no EmbeddedSignature subpacket is found 2023-05-03 17:24:16 +02:00
005b9d477a
KeyRingReader: Remove unused @throws IOException 2023-05-03 17:23:13 +02:00
fb581f11c7
UserId.parse(): Prevent self-referencing javadoc 2023-05-03 17:20:02 +02:00
3cea985365
TeeBCPGInputStream: Annotate byte[] arg as @Nonnull 2023-05-03 17:19:18 +02:00
78cb2ec3d0
Do not catch and immediatelly rethrow exception 2023-05-03 17:16:56 +02:00
5c76f9046f
Turn empty catch block into test failure 2023-05-03 17:16:10 +02:00
7a194c517a
Remove KeyRingUtils.removeSecretKey() in favor of stripSecretKey() 2023-05-03 17:15:30 +02:00
09bacd40d1
SecretKeyRingEditor: referenceTime cannot be null anymore 2023-05-03 17:14:18 +02:00
21ae48d8c1
Use assert statements to flag impossible NPEs 2023-05-03 17:13:29 +02:00
d05ffd0451
Make DateUtil null-safe 2023-05-03 16:11:06 +02:00
953206b4ed
Make more of the API null-safe by using @Nonnull/@Nullable 2023-05-03 16:03:50 +02:00
3b8a1b47d7
Add javadoc p-tags 2023-05-03 16:03:12 +02:00
1d26751b45
Remove unused KeyRingEditorTest 2023-05-03 15:59:21 +02:00
64c6d7a904
Annotate EncryptionOptions methods with @Nonnull 2023-05-03 14:38:52 +02:00
304350fe5c
Add p-tags to EncryptionOptions javadoc 2023-05-03 14:38:38 +02:00
15f6cc70b1
Add MessageMetadata.getRecipientKeyIds()
Fixes #376
2023-05-03 14:30:08 +02:00
a8ab93a49a
SOP: GenerateKey with --profile=rfc4880 now generates RSA key with subkeys 2023-05-03 14:07:33 +02:00
8869d9bd78
Simplify key template methods by replacing String and UserID args with CharSequence 2023-05-03 13:51:59 +02:00
9c81137f48
Add template methods to generate RSA keys with primary and subkeys 2023-05-03 13:51:34 +02:00
671d45a911
PGPainless 1.5.2-rc2-SNAPSHOT 2023-05-01 10:15:24 +02:00
de5926fc47
PGPainless 1.5.2-rc1 2023-05-01 10:12:37 +02:00
2e730a2c48
Update changelog 2023-05-01 10:10:50 +02:00
52fa7e4d46
OpenPgpMessageInputStream: Return -1 instead of throwing MalformedOpenPgpMessageException when calling read() on drained stream 2023-05-01 09:35:28 +02:00
558036c485
Update man pages 2023-04-27 15:23:37 +02:00
eb45dee04f
rewriteManPages script: Remind to run
Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

See https://docs.gradle.org/7.5.1/userguide/command_line_interface.html#sec:command_line_warnings in sop repo
2023-04-27 15:22:43 +02:00
f51685c126
Update changelog 2023-04-27 15:16:37 +02:00
eb1ff27a90
Bump sop-java to 6.1.0 2023-04-27 15:15:42 +02:00
23fd630670
PGPainless 1.5.2-SNAPSHOT 2023-04-25 13:39:44 +02:00
bf2bb31b71
PGPainless 1.5.1 2023-04-25 13:36:48 +02:00
699381238c
Update changelog 2023-04-25 13:28:38 +02:00
6f3f529534
Merge branch 'relaxDecryptionKeyConstraints' 2023-04-25 13:28:26 +02:00
0cb0885251
Relax constraints on decryption keys to improve interop with faulty, broken legacy clients that have been very naughty and need punishment 2023-04-25 13:28:07 +02:00
d10841c57a
Add workflow for pull requests 2023-04-24 16:13:11 +02:00
d5f3dc80bc
Update changelog 2023-04-18 19:00:33 +02:00
e3bacdbe35
Introduce VerificationHelper class and export signature mode in decrypt operation 2023-04-18 18:53:50 +02:00
06c924d41d
Add tests for mode to DetachedSignTest 2023-04-18 18:43:56 +02:00
b0974c6ade
Add more tests for inline-sign-verify roundtrips 2023-04-18 18:40:25 +02:00
2ec176e938
DetachedVerifyImpl: Export signature mode in Verification result 2023-04-18 18:39:52 +02:00
05968533a5
InlineVerifyImpl: Export signature mode in Verification result 2023-04-18 18:35:47 +02:00
9a0b60ac7e
Update quickstart document 2023-04-18 17:41:02 +02:00
36a52a3e34
PGPainless 1.5.1-SNAPSHOT 2023-04-17 16:37:37 +02:00
94a609127e
PGPainless 1.5.0 2023-04-17 16:35:17 +02:00
d8f32b6689
Update changelog 2023-04-17 16:24:55 +02:00
3a3e193bb0
Bump bouncycastle to 1.73 2023-04-17 16:23:16 +02:00
e465ae60a7
VersionImpl: Fix outdated method names 2023-04-17 16:22:54 +02:00
003423a165
Update changelog 2023-04-17 16:09:51 +02:00
676e7d166a
EncryptImpl: Rename default profile, add documentation 2023-04-17 16:06:45 +02:00
926e540016
Test fine-grained SOP spec version 2023-04-17 16:01:17 +02:00
3b1edb076c
Basic support for sop encrypt --profile=XXX 2023-04-17 16:01:17 +02:00
5b363de6e4
Implement VersionImpl.getSopSpecVersion() 2023-04-17 16:01:17 +02:00
446d121777
Bump SOP version in VersionImpl to 06 2023-04-17 16:01:17 +02:00
66d8166005
Bump sop-java to 6.0.0-SNAPSHOT 2023-04-17 16:01:14 +02:00
772a98b4ae
Update changelog 2023-04-17 15:22:38 +02:00
11eda9be95
Bump sop-java to 5.0.0 2023-04-17 15:09:15 +02:00
6371485929
Add some clarifying comments to GenerateKeyImpl 2023-04-17 14:51:50 +02:00
702fdf085c
Thin out and rename profiles of generate-key 2023-04-17 12:49:23 +02:00
f3a4a01d19
Add basic tests for new functionality 2023-04-14 16:18:15 +02:00
b79e706d65
Bump SOP version in VersionImpl to 05 2023-04-14 16:18:15 +02:00
e35287a666
Add support for SOP05 features 2023-04-14 16:18:15 +02:00
44608744c2
Add missing license header 2023-04-14 16:17:58 +02:00
2587f19df3
BC173: Fix CRC error detection by improving error check 2023-04-09 18:49:20 +02:00
46f7cfdb1a
Introduce OpenPgpv6Fingerprint 2023-04-07 12:28:27 +02:00
e744668f5a
Deprecate OpenPgpFingerprint.parse() methods 2023-04-07 11:47:40 +02:00
acb5d3fd9e
getEncryptionSubkeys(): Compare expirations against reference date 2023-04-07 11:26:38 +02:00
ed2c53f5d6
Make getLastModified() @Nonnull 2023-02-25 11:26:58 +01:00
a25ea542d6
PGPainless 1.4.5-SNAPSHOT 2023-02-09 21:58:37 +01:00
afc7d49144
PGPainless 1.4.4 2023-02-09 21:55:10 +01:00
997a6c8c5d
Update changelog 2023-02-08 14:51:44 +01:00
d03f84f415
Add reuse header to VerifyVersion3SignaturePacketTest 2023-02-08 14:49:10 +01:00
Bastien JANSEN
30771f470a Support version 3 signature packets 2023-02-08 14:42:22 +01:00
6c2331d4e6
PGPainless 1.4.4-SNAPSHOT 2023-01-31 19:15:31 +01:00
1257c52ede
PGPainless 1.4.3 2023-01-31 19:12:45 +01:00
9509fff929
Update changelog 2023-01-31 19:10:14 +01:00
d53cd6d0bd
pgpainless-sop: reuse shared sop-java test suite 2023-01-31 19:03:11 +01:00
f4bd17ade8
Bump sop-java to 4.1.1 2023-01-31 19:02:49 +01:00
83ef9cfe80
SOP encrypt: Throw MissingArg if no encryption method was provided. 2023-01-31 18:20:03 +01:00
695e03f8b6
Add EncryptionOptions.hasEncryptionMethod() 2023-01-31 18:19:08 +01:00
DenBond7
9f98e4ce37 Fixed redundant dot an exception message. 2023-01-23 10:47:37 +01:00
67cc59efa2 Update changelog 2023-01-21 19:28:00 +01:00
4cf5a32cc0
Update SECURITY.md 2023-01-21 19:17:49 +01:00
a50c2d9714
More missing javadoc 2023-01-16 20:15:57 +01:00
b58861635d
Add some missing javadoc 2023-01-16 19:38:52 +01:00
6c0bb6c627
PGPainless 1.4.3-SNAPSHOT 2023-01-13 19:38:37 +01:00
2d33a7e5d3
PGPainless 1.4.2 2023-01-13 19:33:26 +01:00
75feb167f0
Update CHANGELOG 2023-01-13 19:30:41 +01:00
4bf2e07ddb
Bump sop-java to 4.1.0 2023-01-13 19:30:35 +01:00
8cb773841b
Revert certificate-store integration
Integration of certificate-store and pgpainless-cert-d makes packaging
complicated. Alternatively, users can simply integrate the certificate-store
with PGPainless themselves.
2023-01-13 19:18:02 +01:00
7a2c9d864c Add javadoc to DecryptionBuilder 2023-01-13 17:53:06 +01:00
ab6b6ca2e7 Add regression test for #351 2023-01-09 16:56:05 +01:00
3b2d0795f7 Fix NPE when sop generate-key --with-key-password is used with multiple uids
Fixes #351
2023-01-09 16:54:58 +01:00
980daeca31
Add missing javadoc to CustomPublicKeyDataDecryptorFactory 2023-01-04 18:55:57 +01:00
41cc71c274
Add missing javadoc to ConsumerOptions 2023-01-04 18:50:10 +01:00
abf723cc6c
Add note about UserId.parse().toString() not guaranteing identity 2023-01-04 18:27:14 +01:00
51c7bc932b
Bump gradlew to 7.5 2023-01-02 15:35:55 +01:00
1452af71fd
Add more docs for Policy related configuration 2023-01-02 15:23:29 +01:00
507b36468b
Update docs on UserId parsing 2023-01-02 14:06:19 +01:00
b36494ecd4
Update changelog 2023-01-02 13:53:12 +01:00
00b593823a
Modify SED test to test successful decryption of SED packet 2023-01-02 13:18:18 +01:00
94d9efa1e7
OpenPgpMessageInputStream: Ignore non-integrity-protected data if configured 2023-01-02 13:12:14 +01:00
7be12b0aaa
PGPainless 1.4.2-SNAPSHOT 2022-12-22 15:33:24 +01:00
77c476dff2
PGPainless 1.4.1 2022-12-22 15:31:32 +01:00
35c62663e9
Fix javadoc 2022-12-22 15:30:11 +01:00
b5128be6fb
Update changelog 2022-12-22 15:23:00 +01:00
44738766e5
Add comments to regexes 2022-12-22 15:19:42 +01:00
533b54a6b7
Add some more tests for valid email address formats 2022-12-22 15:01:10 +01:00
a376587680
Add tests for international user-ids 2022-12-22 14:43:09 +01:00
75f69c0473
Fix Android compatibility by using Matcher.group(int) instead of Matcher.group(String) 2022-12-20 17:27:32 +01:00
94851ccb8f Add javadoc for UserId.parse() 2022-12-20 17:20:44 +01:00
59217d2501 Implement UserId.parse(mailbox) 2022-12-20 17:20:32 +01:00
dbcca586d1
Add link to KOpenPGP to changelog 2022-12-16 17:34:53 +01:00
c90b56ba13
Add YourKit endorsement 2022-12-16 17:20:10 +01:00
01e6ef0013
PGPainless 1.4.1-SNAPSHOT 2022-12-15 18:22:04 +01:00
7a326ddef0
PGPainless 1.4.0 2022-12-15 18:19:13 +01:00
3f10efac7a
Update changelog 2022-12-15 18:06:50 +01:00
6a5c6c5509 Improve ElGamal validation by refraining from biginteger for loop variable 2022-12-15 18:05:55 +01:00
bfbaa30e4c Make KO-countermeasures configurable (off by default) 2022-12-15 18:05:46 +01:00
cfba77dea5 Update changelog 2022-12-15 18:05:35 +01:00
2d46fb18f7 SOP: Allow generation of keys without user-ids 2022-12-15 18:04:59 +01:00
66abd5f65f Cleartext-signatures MUST use TEXT mode 2022-12-15 18:04:53 +01:00
23130b6c8a PGPainless 1.3.14 2022-12-15 18:04:40 +01:00
4f435a0fa0 Fix parameter check for DSA keys
Fixes #345
2022-12-15 18:04:34 +01:00
f5414bcc19 Use proper method to unlock private key when detached-signing 2022-12-15 18:04:19 +01:00
e168ac6f55 Update documentation to use new MessageMetadata class 2022-12-15 18:04:06 +01:00
218da50da3 Create gradle mavenCentralChecksums task to quickly fetch checksums of published artifacts
gradle mavenCentralChecksums will fetch the checksums of the currently checked out release, while
gradle -Prelease=1.3.13 for example will fetch those of the 1.3.13 release
2022-12-15 18:03:58 +01:00
907d1c4d1c move V5OpenPgpKeyTest to org.pgpainless.key 2022-12-15 18:03:49 +01:00
bfcfaa04c4 Add UserId.compare(uid1, uid2, comparator) along with some default comparators 2022-12-15 18:03:37 +01:00
b07e0c2be5 Programmatically confirm that we do not yet support OpenPGP V5 keys :/ 2022-12-15 18:03:25 +01:00
e69c4a8cf7 More UserId tests 2022-12-15 18:03:15 +01:00
837fbd3635 Simplify UserIdTests 2022-12-15 18:03:04 +01:00
4c1d359971 Deprecate UserId.asString() 2022-12-15 18:02:53 +01:00
b0c283e143 Clean up UserId.toString() behavior 2022-12-15 18:02:44 +01:00
6913aa3d6d
Add more tests for RevocationState 2022-11-25 15:41:56 +01:00
ae6a427d90
Add test for UniversalSignatureBuilder 2022-11-25 15:34:54 +01:00
4426895814
Add tests for CollectionUtils 2022-11-25 14:55:46 +01:00
e1ab128c2e
Add annotations to GnuPGDummyKeyUtil 2022-11-25 14:40:57 +01:00
7cc2751527
Add @Nonnull annotations to OpenPgpMessageSyntax 2022-11-25 14:38:45 +01:00
3f70936ff1
Add documetation to PDA class 2022-11-25 14:26:55 +01:00
c8c9359485
PGPainless 1.4.0-rc3-SNAPSHOT 2022-11-24 22:28:15 +01:00
ce049bf9a4
PGPainless 1.4.0-rc2 2022-11-24 22:25:27 +01:00
e88a88a447
Add javadoc for OpenPgpMessageInputStream factory method return value 2022-11-24 22:24:12 +01:00
39d656d2dd
Add javadoc for HardwareDataDecryptorFactory constructor argument 2022-11-24 22:22:21 +01:00
9919bbf013
Enable test for reading broken keys in SOP 2022-11-24 22:20:02 +01:00
68886613a6
SOP KeyReader: wrap IOException in BadData 2022-11-24 22:14:06 +01:00
5bdd4f6ad0
Test rejection of messages with unacceptable skesk kek algorithm 2022-11-24 22:09:22 +01:00
a495f2275c
Precise error message for IntegrityProtectedInputStream 2022-11-24 21:34:25 +01:00
c72b3a4b8e Improve CachingBcPublicKeyDataDecryptorFactoryTest 2022-11-23 20:42:54 +01:00
be7349f0b5 Clean up CachingBcPublicKeyDataDecryptorFactory 2022-11-23 20:42:54 +01:00
b495e602e5 More precise error message for malformed message 2022-11-23 20:42:54 +01:00
25190fc5df SOP: Use new MessageMetadata class 2022-11-23 20:42:54 +01:00
b36b5413e2 Fix isEncryptedFor() 2022-11-23 20:42:54 +01:00
2c7801b759 Add MatchMakingSecretKeyRingProtectorTest 2022-11-23 20:42:54 +01:00
27fd15a012 Update examples with new MessageMetadata class 2022-11-23 20:42:54 +01:00
f005885318 Add MessageMetadata.isVerifiedSigned() and .getVerifiedSignatures() 2022-11-23 20:42:54 +01:00
c031ea9285 Remove empty newlines 2022-11-23 20:42:54 +01:00
6926cedf61 Fix compilation errors and simplify LayerIterator by introducing Packet interface 2022-11-23 20:42:54 +01:00
8f6227c14b Rework some tests to use MessageMetadata 2022-11-23 20:42:54 +01:00
39f8f89fe0 Add convenience methods to MessageMetadata 2022-11-23 20:42:54 +01:00
3ae2afcfa0 Update changelog 2022-11-23 20:42:54 +01:00
616e14d043 Enable tests for unsupported s2k identifiers 2022-11-23 20:42:54 +01:00
24ec665f76 Bump bcpg to 1.72.3 2022-11-23 20:42:53 +01:00
e4560ac5b5 Cleartext Signaure Framework: Support for multiple Hash: headers 2022-11-23 20:42:17 +01:00
a19fc9ebda Add tests for inline-detach 2022-11-23 20:42:17 +01:00
ab82a638cc Add tests for inline-sign 2022-11-23 20:42:17 +01:00
GregorGott
a34f46b6c6 Correct method name
Correct `verifyWith()` to `verifyWithCert()´
2022-11-23 20:42:17 +01:00
75c39c2fde Add tests for inline-verify 2022-11-23 20:42:17 +01:00
508864c4ff Add test for inline-sign --as=text 2022-11-23 20:42:17 +01:00
ce929fd055 Add inline-verify test for message without verifiable signatures 2022-11-23 20:42:17 +01:00
a9014f1985 Add disabled test for broken data during dearmor 2022-11-23 20:42:17 +01:00
b9f985a84c Add tests for SOP decrypt 2022-11-23 20:42:17 +01:00
de76f4b3a9 Fix indentation for CLI tests 2022-11-23 20:42:17 +01:00
b9152d5cde SOP: Add test to ensure that armoring already-armored data is idempotent 2022-11-23 20:42:17 +01:00
6ba7e91f2a Add documentation and removal-TODO to old OpenPgpMetadata class 2022-11-23 20:42:17 +01:00
4e4c095d8d Rename tests to end in Test 2022-11-23 20:42:17 +01:00
b95568f30a Rename IgnoreMarkerPacketsTest 2022-11-23 20:42:17 +01:00
8faec25ecf Enable previously disabled test for marker+seipd packet processing 2022-11-23 20:42:17 +01:00
fd2f6523ec More specific exception message for when nesting depth is exceeded 2022-11-23 20:42:17 +01:00
1437604836 Add documentation to DecryptionStream 2022-11-23 20:42:17 +01:00
70cca563d7 Add javadoc to getMetadata() and getResult() 2022-11-23 20:42:17 +01:00
33d9a784bb Add javadoc to MEssageMetadata class 2022-11-23 20:42:17 +01:00
3023d532e3 Make DecryptionStream.getMetadata() first-class, deprecate getResult() 2022-11-23 20:42:17 +01:00
e976cc6dd2 Move getResult() method around 2022-11-23 20:42:17 +01:00
03d04fb324 Tests: Replace usages of default algorithm policies with specific policies 2022-11-23 20:42:17 +01:00
847d4b5e33 Update SECURITY.md 2022-11-23 20:42:17 +01:00
936a3f654f Docs: Add usage examples for pgpainless-cli 2022-11-23 20:42:17 +01:00
38a2162d6a Docs: Document generation/updating of man pages 2022-11-23 20:42:17 +01:00
bfeed54ede Docs: Update output of pgpainless-cli help command 2022-11-23 20:42:17 +01:00
b7478729b1 Update man pages
Since commit 0e777de14f927ad4b265adfc5ba201be29b1bde1 in pgpainless/sop-java,
man page generation is reproducible.
This very commit adopts reproducible man pages for the first time
2022-11-23 20:42:17 +01:00
093d786329 Doc: Add section about indirect data types 2022-11-23 20:42:17 +01:00
90c3a01577 Add test to verify proper functionality of MatchMakingSecretKeyRingProtector 2022-11-23 20:42:17 +01:00
3877410a65 Update CHANGELOG 2022-11-23 20:42:17 +01:00
d7e4fcaec6 OpenPgpMessageInputStream: Source verification certs from ConsumerOptions.getCertificateSource() 2022-11-23 20:42:17 +01:00
a792952845 Remove code to manually throw NSEE for missing certs
This is now done further down in the store itself
2022-11-23 20:42:17 +01:00
43e0f43bd9 Bump cert-d-java to 0.2.1 and cert-d-pgpainless to 0.2.0 2022-11-23 20:42:17 +01:00
c19b8297a3 Add TODO for when bumping cert-d-java 2022-11-23 20:42:17 +01:00
4594b494a9 Implement signature verification with certificate stores as cert source 2022-11-23 20:42:17 +01:00
22abb62443 Add test for encryption to cert from certificate store 2022-11-23 20:42:17 +01:00
d0277fbbec Bump cert-d-java to 0.2.0 2022-11-23 20:42:17 +01:00
6dc5b84d66 Depend on pgp-certificate-store again 2022-11-23 20:42:17 +01:00
d486a17cf1 Implement EncryptionOptions.addRecipient(store, fingerprint) 2022-11-23 20:42:17 +01:00
b287d28a28 Depend on pgp-certificate-store 2022-11-23 20:42:17 +01:00
86c7229172 Do not reject bnacksig signatures when they predate subkey binding date
Fixes #334

SOP verify: force data to be non-openpgp data

Update changelog

SOP: Unify key/certificate reading code

Fix key/password matching in SOPs detached sign command

Rework CLI tests

update changelog

PGPainless 1.3.11

PGPainless 1.3.12-SNAPSHOT

Merge branch 'release/1.3'
2022-11-23 20:42:17 +01:00
5524596082 Add more tests for sop code 2022-11-23 20:42:17 +01:00
2dc72d7690 Update CHANGELOG 2022-11-23 20:42:17 +01:00
6dcc1e68cd Fix expected exception in roundtrip test 2022-11-23 20:42:17 +01:00
963b678a9e Enable test for decryption of messages without ESKs 2022-11-23 20:42:17 +01:00
59e81dc514 Use BCs PGPEncryptedDataList.extractSessionKeyEncryptedData() for decryption with session key 2022-11-23 20:42:17 +01:00
f80b3e0cdb Use BCs PGPEncryptedDataList.isIntegrityProtected() 2022-11-23 20:42:17 +01:00
6243d69061 PGPainless 1.4.0-rc2-SNAPSHOT 2022-11-23 20:42:17 +01:00
256920bfae PGPainless 1.4.0-rc1 2022-11-23 20:42:16 +01:00
313dbcfaa8 Update changelog 2022-11-23 20:41:25 +01:00
b1f9a1398a Add comment for ArmorUtils method 2022-11-23 20:40:40 +01:00
f86aae4997 Implement efficient read(buf,off,len) for DelayedInputStream 2022-11-23 20:40:40 +01:00
ca49ed087b Small clean-ups in OpenPgpMessageInputStream 2022-11-23 20:40:40 +01:00
58195c19b1 Properly handle failed decryption caused by removed private keys 2022-11-23 20:40:40 +01:00
58aa9f5712 Move classes related to GNU dummy keys to gnupg package 2022-11-23 20:40:40 +01:00
df4fc94ce7 Add test for decryption with removed private key 2022-11-23 20:40:40 +01:00
3af6ab1b85 Rename GnuPGDummyExtension + GnuPGDummyKeyUtil 2022-11-23 20:40:40 +01:00
033beaa8f2 Use S2K usage SHA1 in GnuDummyKeyUtil 2022-11-23 20:40:40 +01:00
a8d2319d63 Add documentation to GnuDummyKeyUtil 2022-11-23 20:40:40 +01:00
2487e3300a Add and test GnuDummyKeyUtil 2022-11-23 20:40:40 +01:00
7467170bcc Move CachingBcPublicKeyDataDecryptorFactoryTest to correct package 2022-11-23 20:40:40 +01:00
07320ed3cf Fix HardwareSecurity.getIdsOfHardwareBackedKeys() 2022-11-23 20:40:40 +01:00
8c0d096fc6 Fix CachingBcPublicKeyDataDecryptorFactory 2022-11-23 20:40:40 +01:00
705e36080c Implement caching PublicKeyDataDecryptorFactory 2022-11-23 20:40:40 +01:00
8fafb6aa56 Add comments 2022-11-23 20:40:40 +01:00
208612ab56 Add (commented-out) read(buf, off, len) implementation for DelayedTeeInputStream 2022-11-23 20:40:40 +01:00
8cb7d19487 Allow injection of different syntax into PDA 2022-11-23 20:40:40 +01:00
161ce57711 Clean up old unused code 2022-11-23 20:40:40 +01:00
ec793c66ff More cleanup and better error reporting 2022-11-23 20:40:40 +01:00
8ca0cfd3ae Rename *Alphabet to *Symbol and add javadoc 2022-11-23 20:40:40 +01:00
b3d61b0494 Separate out syntax logic 2022-11-23 20:40:40 +01:00
798e68e87f Improve syntax error reporting 2022-11-23 20:40:40 +01:00
a2a5c9223e Remove debugging fields 2022-11-23 20:40:40 +01:00
a0ba6828c9 Remove superfluous states 2022-11-23 20:40:40 +01:00
7e8841abf3 Handle unknown packet versions gracefully 2022-11-23 20:40:40 +01:00
192aa98326 Add missing REUSE license headers 2022-11-23 20:40:40 +01:00
a013ab4ebb Wrap MalformedOpenPgpMessageException in BadData 2022-11-23 20:40:40 +01:00
8097c87b7f Fix last two broken tests 2022-11-23 20:40:40 +01:00
e0b2145793 Fix more tests 2022-11-23 20:40:40 +01:00
aa398f9963 Only check message integrity once 2022-11-23 20:40:40 +01:00
e281143d48 Delete old DecryptionStreamFactory 2022-11-23 20:40:40 +01:00
3f8653cf2e Fix CRCing test and fully depend on new stream for decryption 2022-11-23 20:40:40 +01:00
54cb9dad71 Further increase coverage of PDA class 2022-11-23 20:40:40 +01:00
3977d1f407 Add more direct PDA tests 2022-11-23 20:40:40 +01:00
977f8c4101 Rename automaton package to syntax_check 2022-11-23 20:40:40 +01:00
a27c0ff36e Add detailled logging to OpenPgpMessageInputStream 2022-11-23 20:40:40 +01:00
b7acb2a59c Enable logging in tests 2022-11-23 20:40:40 +01:00
a9993fd866 Throw UnacceptableAlgEx for unencrypted encData 2022-11-23 20:40:40 +01:00
3d5916c545 Implement custom decryptor factories in pda 2022-11-23 20:40:40 +01:00
a39c6bc881 Identify custom decryptor factories by subkey id 2022-11-23 20:40:40 +01:00
cfd3f77491 Make map final 2022-11-23 20:40:40 +01:00
228918f96b Change HardwareSecurity DecryptionCallback to emit key-id 2022-11-23 20:40:40 +01:00
529c64cf43 Implement exploratory support for custom decryption factories
This may enable decryption of messages with hardware-backed keys
2022-11-23 20:40:40 +01:00
d39d062a0d WIP: Explore Hardware Decryption 2022-11-23 20:40:40 +01:00
7da34c8329 Work on postponed keys 2022-11-23 20:40:40 +01:00
d3f07a2250 Reuse *SignatureCheck class 2022-11-23 20:40:40 +01:00
dfbb01d61c Enfore max recursion depth and fix CRC test 2022-11-23 20:40:40 +01:00
7097d44916 Fix NPEs and expose decryption keys 2022-11-23 20:40:40 +01:00
6fd705b1dc Fix checkstyle issues 2022-11-23 20:40:40 +01:00
fbcde13df3 Reinstate integrity-protection and fix tests
Integrity Protection is now checked when reading from the stream,
not only when closing.
2022-11-23 20:40:40 +01:00
654493dfcc Properly expose signatures 2022-11-23 20:40:40 +01:00
a9f77ea100 Cleaning up and collect signature verifications 2022-11-23 20:40:40 +01:00
43c369f1f9 It was the buffering. 2022-11-23 20:40:40 +01:00
bdc968dd43 Create TeeBCPGInputStream to move teeing logic out of OpenPgpMessageInputStream 2022-11-23 20:40:40 +01:00
e420678076 2/3 the way to working sig verification 2022-11-23 20:40:40 +01:00
5e37d8038a WIP: So close to working notarizations 2022-11-23 20:40:39 +01:00
5288fb81c3 Reformat KeyRingReader 2022-11-23 20:40:03 +01:00
18b1fadeb6 Suppress DefaultCharset warning 2022-11-23 20:40:03 +01:00
2ce4486e89 Convert links in javadoc to html 2022-11-23 20:40:03 +01:00
babd1542e3 DO NOT MERGE: Disable broken test 2022-11-23 20:40:03 +01:00
09f94944b3 Remove unnecessary throws declarations 2022-11-23 20:40:03 +01:00
81bb8cba54 Use BCs Arrays.constantTimeAreEqual(char[], char[]) 2022-11-23 20:40:03 +01:00
527aab922e Fix ModificationDetectionException by not calling PGPUtil.getDecoderStream() 2022-11-23 20:40:03 +01:00
ec28ba2924 SIGNATURE VERIFICATION IN OPENPGP SUCKS BIG TIME 2022-11-23 20:40:03 +01:00
4e44691ef6 Wip 2022-11-23 20:40:03 +01:00
45555bf82d Wip: Work on OPS verification 2022-11-23 20:40:03 +01:00
e25f6e1712 Fix checkstyle issues 2022-11-23 20:40:03 +01:00
5c93eb3705 Wip: Introduce MessageMetadata class 2022-11-23 20:40:03 +01:00
efdf2bca0d WIP: Play around with TeeInputStreams 2022-11-23 20:40:03 +01:00
7537c9520c WIP: Add LayerMetadata class 2022-11-23 20:40:03 +01:00
54d7d0c7ae Implement experimental signature verification (correctness only) 2022-11-23 20:40:03 +01:00
9366700895 Add read(b,off,len) 2022-11-23 20:40:03 +01:00
7b9db97212 Clean close() method 2022-11-23 20:40:03 +01:00
0753f4d38a Work on getting signature verification to function again 2022-11-23 20:40:03 +01:00
d81c0d4400 Fix tests 2022-11-23 20:40:02 +01:00
e86062c427 WIP: Replace nesting with independent instancing 2022-11-23 20:40:02 +01:00
bf8949d7f4 WIP: Implement custom PGPDecryptionStream 2022-11-23 20:40:02 +01:00
bc73d26118 Add Pushdown Automaton for checking OpenPGP message syntax
The automaton implements what is described in
https://github.com/pgpainless/pgpainless/blob/main/misc/OpenPGPMessageFormat.md

However, some differences exist to adopt it to BouncyCastle

Part of #237
2022-11-23 20:40:02 +01:00
ec7237390a
PGPainless 1.3.14-SNAPSHOT 2022-11-23 20:36:54 +01:00
aacfc0f995
PGPainless 1.3.13 2022-11-23 20:33:04 +01:00
29009cf224
Update changelog 2022-11-23 20:27:45 +01:00
d7c567649d
Improve documentation of bouncyPgVersion 2022-11-23 20:26:43 +01:00
40715c3e04
Bump sop-java to 4.0.7 2022-11-23 20:26:26 +01:00
95ba4e46ca
PGPainless 1.3.13-SNAPSHOT 2022-11-11 14:00:36 +01:00
678dac902f
PGPainless 1.3.12 2022-11-11 13:57:28 +01:00
3fc19f56af
Update changelog 2022-11-11 13:52:53 +01:00
ae88fdf4ab
Document ArmoredOutputStreamFactory.setVersionInfo(null) 2022-11-11 13:49:28 +01:00
243d64fcb4 Bump sop-java to 4.0.5 and adopt changes (--as=clearsigned) 2022-11-11 13:46:43 +01:00
86b06ee5e3 SOP: Hide armor version header by default 2022-11-11 13:46:43 +01:00
48005da7f3 SOP : Do not armor already-armored data. 2022-11-11 13:46:40 +01:00
2d6f9738ec
PGPainless 1.3.12-SNAPSHOT 2022-11-09 22:14:36 +01:00
c35deaed16
PGPainless 1.3.11 2022-11-09 22:12:23 +01:00
52c0ec1208
update changelog 2022-11-09 22:08:38 +01:00
858f8e00f3
Rework CLI tests 2022-11-09 22:02:16 +01:00
fd55ce3657
Fix key/password matching in SOPs detached sign command 2022-11-09 22:01:52 +01:00
e15dd70b85
SOP: Unify key/certificate reading code 2022-11-09 22:01:20 +01:00
1c127933bd
Update changelog 2022-11-09 15:50:01 +01:00
c77c96f849 SOP verify: force data to be non-openpgp data 2022-11-09 15:44:34 +01:00
c253732ad9 Do not reject bnacksig signatures when they predate subkey binding date
Fixes #334
2022-11-09 15:44:20 +01:00
faae5c64b1
PGPainless 1.3.11-SNAPSHOT 2022-11-07 16:57:22 +01:00
4143ad3165
PGPainless 1.3.10 2022-11-07 16:55:00 +01:00
3cd621b436
Update CHANGELOG 2022-11-07 16:51:19 +01:00
5ebeeed3e8
Bump sop-java to 4.0.3 2022-11-07 16:49:06 +01:00
50d18a4581 Fix NPE when validating signature made by key without keyflags on direct key sigature
(Presumably) fixes #332
2022-11-07 15:34:18 +01:00
b02ae86ff6 Annotate SignatureSubpacketsUtil methods with @Nullable and @Nonnull 2022-11-07 15:34:18 +01:00
b4420772a0
PGPainless 1.3.10-SNAPSHOT 2022-11-06 15:18:26 +01:00
78de682a14
PGPainless 1.3.9 2022-11-06 15:16:28 +01:00
095e58eddc
Update man page documentation 2022-11-06 15:08:07 +01:00
db9745d7a2
Update changelog 2022-11-06 15:06:50 +01:00
e67c43a6f7
Bump sop-java to 4.0.2 and improve exception handling 2022-11-06 15:03:35 +01:00
3000e496bc
PGPainless 1.3.9-SNAPSHOT 2022-11-03 11:48:58 +01:00
df258b46c1
PGPainless 1.3.8 2022-11-03 11:46:07 +01:00
b0258f8c5b
Update CHANGELOG 2022-11-02 10:57:53 +01:00
f5e4c7571c
Bump BC to 1.72, BCPG to 1.72.1 2022-11-02 10:53:53 +01:00
754fcf72a1
Implement ProducerOptions.setHideArmorHeaders()
Fixes #328
2022-10-31 11:43:24 +01:00
8834d8ad10
Increase timeframe for some tests which check expiration dates 2022-10-18 15:13:49 +02:00
00eafc3957
PGPainless 1.3.8-SNAPSHOT 2022-10-05 12:31:20 +02:00
19ee2ebacf
PGPainless 1.3.7 2022-10-05 12:27:38 +02:00
2c76f66987
Update changelog 2022-10-04 19:13:01 +02:00
f94917d01f
Fix checkstyle issue 2022-09-28 13:18:34 +02:00
6a2a604ba4
Update TODO for BC 173 2022-09-27 16:47:23 +02:00
d74a8d0408
Add PGPainless.asciiArmor(PGPSignature) 2022-09-27 16:28:31 +02:00
dac059c702
Add test for PGPainless.asciiArmor(key, stream) 2022-09-27 16:17:22 +02:00
5bccc1960e Add PGPainless.asciiArmor(key, outputStream) 2022-09-27 16:12:26 +02:00
639d2a19f8
Remove unused provideSessionKeyDataDecryptorFactory() methods 2022-09-13 20:27:16 +02:00
609bb4556a
Use ImplementationFactory.getSessionKeyDataDecryptorFactory() method 2022-09-13 20:26:13 +02:00
0e45de9b4a Formatting 2022-09-13 20:23:06 +02:00
9e403c1124 Add ImplementationFactory.getSessionKeyDataDecryptorFactory() and impls 2022-09-13 20:22:53 +02:00
8dfabf1842 Test decryption of messages using Session Key 2022-09-12 15:26:06 +02:00
7480c47fa7
Add behavior test to ensure that ArmoredInputStream cuts away any data outside of the armor 2022-09-08 18:15:52 +02:00
bf0370fdc1
Add grammar from RFC to diagram 2022-09-07 20:32:47 +02:00
14941e77b3
More latex, less markdown 2022-09-07 19:54:50 +02:00
acb845d280
Change transition to latex 2022-09-07 19:37:36 +02:00
c01f2db5ef
Add formal definition of PDA 2022-09-07 19:35:41 +02:00
21cadcb8eb
Introduce dedicated state for Signed Message 2022-09-07 18:33:36 +02:00
ba5eea8b9c
Add alphabet description 2022-09-07 18:27:54 +02:00
36cb6918a4
Refine PDA diagram 2022-09-07 18:16:11 +02:00
27476831d9
Add hint about dotted line in message format diagram 2022-09-07 17:56:46 +02:00
e78880602a
Add diagram of pushdown automaton for the OpenPGP Message Format 2022-09-07 17:29:04 +02:00
31c4570d10
Move finalization of signatures into own method 2022-09-07 13:48:59 +02:00
bed18dc0ad
Update changelog 2022-09-05 15:51:19 +02:00
0dd54f27b7
Add test for processing message byte by byte 2022-09-05 15:43:32 +02:00
0bafc410a0
Add missing parseAndCombineSignatures call
For some reason this was missing from the single-byte read() method
of the SignatureInputStream, causing issues if draining the stream
byte by byte
2022-09-05 15:41:58 +02:00
9106d98449
Add tests for Certificate merging 2022-09-05 15:25:29 +02:00
cd0b9603e7
Add KeyRingUtils.injectCertification(keys, certification) 2022-09-05 15:15:58 +02:00
5be42b22bd
Add test for KeyRingUtils.keysPlusPublicKey 2022-09-05 14:45:22 +02:00
4ec38bb63b
Add tests for ArmoredInputStreamFactory 2022-09-05 14:37:23 +02:00
70ce4d45f4
Remove unused CRCinArmoredInputStreamWrapper.possiblyWrap() 2022-09-05 14:20:11 +02:00
c80e0dd0d5
Add further information to docs index 2022-09-05 13:56:29 +02:00
fb0908ffd1
Add explanation for secret key protector hint to documentation 2022-09-05 13:46:12 +02:00
0d23809524
Update README of docs directory 2022-09-05 13:28:00 +02:00
7bff3128cb
Update READMEs 2022-09-03 18:07:32 +02:00
a188b62170
PGPainless 1.3.7-SNAPSHOT 2022-09-03 18:06:28 +02:00
f6f9df6e7f
PGPainless 1.3.6 2022-09-03 18:04:38 +02:00
f2903515e7
Update changelog 2022-09-03 18:04:04 +02:00
3a33bb126a
Add RNGPerformanceTest to help diagnose performance bottlenecks
Related to https://github.com/pgpainless/pgpainless/issues/309
2022-09-03 14:24:37 +02:00
3cd5a95d89
Rename inspectionDate to referenceTime 2022-09-03 13:48:02 +02:00
7189516dd4
Add documentation for modifyKeyRing(keys, date) 2022-09-03 13:46:32 +02:00
c3dc3c9d87 Allow modification of keys with custom reference date
Also, bind subkeys using SubkeyBindingSignatureBuilder
2022-09-03 13:42:58 +02:00
3030de7f3f
Add further information about key protectors to documentation 2022-08-31 21:59:40 +02:00
328b8ccf8a
Add information about KeyRingProtectionSettings to documentation 2022-08-31 21:38:09 +02:00
15046cdc32
Switch default S2K for secret key protection over to use SHA256 and add documentation 2022-08-31 21:37:31 +02:00
251bbaeaa7
Remove branches section from README 2022-08-30 22:35:50 +02:00
cf43c5f83b
Update changelog 2022-08-29 14:43:41 +02:00
c6676d3c91
Add support for generating keys without user-ids
Fixes #296
2022-08-29 14:12:02 +02:00
76905cc1e8
Add installation hint to cli usage guide 2022-08-29 13:28:08 +02:00
a2bfb55d87
Use *Options.get() factory methods in SOP module 2022-08-29 13:22:44 +02:00
4efe8fb468
Quickstart guide: Add sections on encrypting, signing, decryption, verification 2022-08-29 13:18:02 +02:00
bc24c4626a
Add ConsumerOptions.get() factory method 2022-08-29 13:00:50 +02:00
5746985bb7
Add EncryptionOptions.get() factory method 2022-08-29 12:46:36 +02:00
d1001412a1
Add SigningOptions.addDetachedSignature(protector, key) shortcut method 2022-08-29 12:36:16 +02:00
39ff2bca73
Fix javadoc of SigningOptions methods 2022-08-29 12:35:51 +02:00
3f82bd3114
Quickstart guide: Add section on ASCII armor 2022-08-29 12:03:01 +02:00
1b04d67e1a
Remove unused SignatureSubpacketGeneratorUtil class and tests 2022-08-29 11:30:26 +02:00
0cc884523c
Integrate RevocationState into KeyRingInfo class 2022-08-29 11:30:10 +02:00
c73905d179
Import RevocationStateTest from wot branch 2022-08-29 11:12:42 +02:00
d019c0d5db
Add RevocationState implementation from wot branch 2022-08-29 11:09:32 +02:00
405e67c0cb
Add documentation to AlgorithmNegotiator classes 2022-08-29 11:06:17 +02:00
6f161c8336
Update changelog 2022-08-29 10:42:18 +02:00
7faa6c580a
Remove deprecated ArmorUtils.createArmoredOutputStream() 2022-08-29 10:38:44 +02:00
054828ef8c
Remove deprecated EncryptionResult.getSymmetricKeyAlgorithm()
Use getEncryptionAlgorithm() instead
2022-08-29 10:37:55 +02:00
6b3d676531
Move maven packaging badge next to repology badge 2022-08-12 13:06:09 +02:00
aeffcdd8ee
Add repology packaging status badge 2022-08-12 13:05:17 +02:00
bbcbba021d
PGPainless 1.3.6-SNAPSHOT 2022-08-11 15:58:57 +02:00
c361fecdd9
PGPainless 1.3.5 2022-08-11 15:56:32 +02:00
b3c6a33afe
Update readme 2022-08-11 10:36:32 +02:00
bc5dc50b78
Add KeyRingInfo.isSigningCapable()
Fixes #307
2022-08-09 15:08:59 +02:00
b9845912ee
Add tests for readKeyRing() 2022-08-08 13:20:28 +02:00
e6b89e2c3b Add KeyRingReader.keyRing(*) mnethods to read either a public or secret key ring 2022-08-08 13:15:03 +02:00
a1eceaf8e1
Add manpages, script to generate manpages 2022-08-08 00:23:58 +02:00
fb5f039991
Tweaks to layout 2022-08-04 15:48:35 +02:00
1bb015d10f
Fix pages widths 2022-08-04 15:45:03 +02:00
0257c4d99e
Add pages logo 2022-08-04 15:38:44 +02:00
06b66dcc17
Exclude irrelevant md files from gh-pages 2022-08-04 15:33:09 +02:00
5b186fc387
Fix licenses for gh pages layout stuff 2022-08-04 15:21:16 +02:00
38d04dca35
Merge gh-pages stuff into main 2022-08-04 15:15:58 +02:00
ff750d32fb
PGPainless 1.3.5-SNAPSHOT 2022-08-04 14:58:04 +02:00
6c936a2570
PGPainless 1.3.4 2022-08-04 14:55:08 +02:00
e36c1a00b1
Update changelog 2022-08-04 14:12:33 +02:00
b23a1d77a3
Bump sop-java version to 4.0.1 and override executable name 2022-08-04 14:05:12 +02:00
3ceb4c1a36
Acknowledge donors 2022-08-03 20:02:58 +02:00
a48ca4ede7
Update changelog 2022-08-03 19:57:59 +02:00
ca09ac62ca
KeyRingInfo.isUsableFor*(): Check if primary key is revoked 2022-08-03 13:37:18 +02:00
c1de66e1d7
Fix javadoc lying about only encrypting to single subkeys
Fixes #305
2022-08-02 16:53:01 +02:00
8bbb3aa8ba
PGPainless 1.3.4-SNAPSHOT 2022-07-24 12:45:16 +02:00
bd10630fb4
PGPainless 1.3.3 2022-07-24 12:42:16 +02:00
cc9af93e75
Fix command usage strings not being picked up due to renamed parent command 2022-07-23 01:16:28 +02:00
e622a2256e
Rename build job and point build badge to github action 2022-07-22 20:53:42 +02:00
324244ae13
Update badges 2022-07-22 20:50:01 +02:00
55c9b6ed9e
Add reuse headers to files in .github 2022-07-22 20:48:53 +02:00
895fcced9a
Add gradle CI action 2022-07-22 20:21:02 +02:00
cb23cad625
Fix checkstyle issues and java API compatibility 2022-07-22 13:59:15 +02:00
Jérôme Charaoui
5a86d9db62 Fix tests that read from jar-embedded resources
It seems that none of the functions used here actually require a File
object as arguments, and will happily work on InputStream objects.
This also changes readFromResource() to use InputStream.readAllBytes()
instead of File.readAllBytes(), which is available from Java 9.
2022-07-22 13:55:05 +02:00
c4bffad478
Abort (skip) tests reading from resources 2022-07-21 21:34:44 +02:00
cb8e0d7951
Create FUNDING.yml 2022-07-21 13:17:26 +02:00
914f2bf5b6
Add IRC channel to readme 2022-07-20 18:15:38 +02:00
f966c1ed07
Explicitly cast Long to long to fix ambiguity in debian tests 2022-07-20 18:07:42 +02:00
e67d5b405c
Add javadoc to ProducerOptions.noEncryptionNoSigning() 2022-07-18 14:50:53 +02:00
9b6d08f3c5
Add MODIFICATION_DETECTION_2 feature constant 2022-07-18 12:03:16 +02:00
cd5982cd47
Add AEADAlgorithm class and test 2022-07-18 11:30:37 +02:00
59adbe1d0a
Add SHA3 hash algorithms to HashAlgorithm class 2022-07-18 11:30:25 +02:00
d4953bd8f6
PGPainless 1.3.3-SNAPSHOT 2022-07-16 13:05:08 +02:00
93e50b75fe
PGPainless 1.3.2 2022-07-16 13:02:55 +02:00
51552d4a8c
Update changelog 2022-07-16 13:00:15 +02:00
fe913172d5
Add missing javadoc 2022-07-16 12:58:22 +02:00
6fc1d25db8 Merge branch 'fix298' 2022-07-16 12:37:20 +02:00
ba191a1d0f
Prevent adding NULL to symmetric algorithm preference when generating key
Fixes #301
2022-07-15 14:19:45 +02:00
dec3c8be60
Add SecretKeyRingEditor.replaceUserId(old,new,protector) 2022-07-15 14:00:41 +02:00
32e1f1234b
Add KeyRingUtils.publicKeyRingCollectionFrom(PGPSecretKeyRingCollection) 2022-07-15 13:21:59 +02:00
2ad67a85fb
Add test to make sure we do not allow unencrypted as sym alg preference 2022-07-15 13:20:23 +02:00
4730ac427b
Add test for #298 2022-07-13 14:54:16 +02:00
223cf009fc
Fix User-ID format in documentation and note invalid user-id formats in tests 2022-07-12 10:33:43 +02:00
56abb51757
Small formatting changes of doc index 2022-07-12 08:49:30 +02:00
df7505eadb
Add more documentation 2022-07-11 16:11:40 +02:00
50d31eb463
KeyRingTemplates: Add methods taking Passphrase as argument 2022-07-11 14:15:54 +02:00
52c8439da5
Prevent third-party assigned user-ids from being accidentally returned as primary user-id
Fixes #293
2022-07-10 23:02:00 +02:00
520fcd7cbf
Fix help text in pgpainless-cli documentation 2022-07-10 22:44:48 +02:00
1bd2d68a11
Start pgpainless-cli usage guide 2022-07-08 18:57:03 +02:00
b217b8b218
cli: Use dedicated shadow plugin for building fat jar
'gradle shadowJar' can be used to build a fat jar
'gradle jar' now only builds slim jar
2022-07-08 18:26:45 +02:00
7169b369b3
Add documentation about documentation to documentation readme
heh
2022-07-08 17:45:58 +02:00
a131fe32aa
Extend sphinx documentation 2022-07-08 15:57:43 +02:00
3842aa9ced
Add test to explore behavior when dealing with V3 keys 2022-07-08 15:08:45 +02:00
ac52c4bbc5
Fix documentation link to verifications section 2022-07-08 00:32:18 +02:00
6169a37086
Add readthedocs badge 2022-07-08 00:29:43 +02:00
6f2b5ed1ca
Fix mermaid-cli cmd 2022-07-08 00:21:40 +02:00
556496dc87
Add .readthedocs.yaml 2022-07-07 00:44:28 +02:00
c916cec042
URL footnotes in pdf and conf.py documentation 2022-07-07 00:13:50 +02:00
16c44e670e
Initial sphinx-based documentation 2022-07-06 23:56:41 +02:00
762391659e
Add node_modules to .gitignore 2022-07-06 23:56:08 +02:00
170aaaa0c5
Document KO protection utility class 2022-07-04 11:05:16 +02:00
8b66b3527e
Add tests for pet name certification and scoped delegation 2022-06-30 13:16:15 +02:00
a99ce15969
Forward userIdOnCertificate() method call 2022-06-30 13:11:27 +02:00
b8f4cc3935 Merge branch 'certification' 2022-06-29 16:01:03 +02:00
b2a5351cc3
Delete unused KeyRingValidator class 2022-06-29 16:00:21 +02:00
7e0b1b344c
s/{validation|evaluation}Date/referenceTime/g 2022-06-24 12:47:35 +02:00
0c0f82ce2e Add KeyRingInfo constructor that takes Policy instance 2022-06-24 12:29:03 +02:00
3f40fb99ef Add RevocationState enum 2022-06-24 12:28:56 +02:00
d0ad0ac3e4
Fix heading of changelog 2022-06-23 12:12:42 +02:00
62ef8b8d5f
PGPainless 1.3.2-SNAPSHOT 2022-06-23 12:10:58 +02:00
9654af15e6
PGPainless 1.3.1 2022-06-23 12:09:01 +02:00
e0be145541
Update changelog 2022-06-23 11:57:28 +02:00
14531f0050
Make sop decrypt throw for unencrypted data 2022-06-23 11:47:48 +02:00
efc0cb357b
EncryptDecryptRoundTripTest: make passphrase constant 2022-06-23 11:47:20 +02:00
0c28c7a389
symmetrically encrypted messages are still encrypted 2022-06-23 11:46:19 +02:00
07f9c3ceef
Fix decrypt no signatures test 2022-06-23 10:30:16 +02:00
bca359805b
SOP decrypt: Do not throw if no signatures found 2022-06-22 22:14:22 +02:00
e5ba4f9933
Add buffer to improve encryption performance 2022-06-21 19:48:49 +02:00
8d1794544a
Fix indentation 2022-06-21 19:48:38 +02:00
b6975b38f1
Add tests for KeyFlag bitmask methods 2022-06-20 19:03:52 +02:00
0690a21360
Increase coverage of Policy class 2022-06-20 18:48:27 +02:00
37441a81e8
Add OpenPgpV5Fingerprint constructor tests using mocked v5 keys 2022-06-20 18:35:48 +02:00
2873de0d05
Include mockito as test dependency 2022-06-20 18:35:31 +02:00
fed3080ae8
Add tests to increase coverage of v5 fingerprint class 2022-06-20 18:19:24 +02:00
ca39efda99
Add test for CleartextSignedMessageUtil 2022-06-20 18:10:44 +02:00
82ff62b4e6
Remove unused NotYetImplementedException 2022-06-20 17:58:27 +02:00
a944d2a6b9
Fix build errors 2022-06-20 15:09:02 +02:00
7223b40b23 Add javadoc and indentation 2022-06-20 12:44:40 +02:00
8d2afdf3b6 Make certify() methods public 2022-06-20 12:44:40 +02:00
bbd94c6c9a More documentation 2022-06-20 12:44:40 +02:00
1483ff9e24 Add another test for Trustworthiness 2022-06-20 12:44:40 +02:00
870af0e005 Add javadoc documentation to Trustworthiness class 2022-06-20 12:44:40 +02:00
d2b48e83d9 Implement certifying of certifications 2022-06-20 12:44:40 +02:00
fa5ddfd112 WIP: Implement delegations
THERE ARE THINGS BROKEN NOW. DO NOT MERGE!
2022-06-20 12:44:40 +02:00
c1170773bc Implement certification of third party keys 2022-06-20 12:44:40 +02:00
73da2cc889
Fix reproducible builds by specifying file and directory modes for archive tasks 2022-06-19 22:14:11 +02:00
57743383e5
PGPainless 1.3.1-SNAPSHOT 2022-06-19 18:51:44 +02:00
c078c320c3
PGPainless 1.3.0 2022-06-19 18:49:30 +02:00
fd3e574d0d
Update CHANGELOG 2022-06-19 18:45:13 +02:00
40f662edbc
Bump sop-java to 4.0.0 2022-06-19 18:44:38 +02:00
749a623d88
Bump SOP version 2022-06-19 17:56:26 +02:00
d64e749f22
Fix sop encrypt --sign-with allowing for protected keys 2022-06-19 17:50:31 +02:00
75455f1a3c
Add OpenPgpMetadata.isCleartextSigned and use it in sop to determine if message was cleartext signed 2022-06-19 17:31:48 +02:00
5375cd454f Implement inline-detach for 3 different types of input 2022-06-19 16:59:42 +02:00
2d60650cc6 Progress on SOP04 support 2022-06-19 16:59:42 +02:00
3f16c54867 Create test util to write data to temp file 2022-06-19 16:59:42 +02:00
7074ff5f2f Rename command tests and add generate-key test for encrypted keys 2022-06-19 16:59:42 +02:00
a3b2070e76 Rename test and reference exit codes directly 2022-06-19 16:59:42 +02:00
53df487e59 Adopt changes from SOP-Java and add test for using incapable keys 2022-06-19 16:59:42 +02:00
0b69e18715 Experimental support for inline-sign, inline-verify 2022-06-19 16:59:42 +02:00
dd26b5230d Use newly introduced modernKeyRing(userId) method 2022-06-19 16:59:42 +02:00
9a545a2936 Wip: SOP 4 2022-06-19 16:59:42 +02:00
9cdea63ec4 Fix performance issues of sop armor and dearmor operations 2022-06-16 11:22:35 +02:00
57fbb469ea Fix performance issue of encrypt and sign operations by buffering 2022-06-16 11:22:35 +02:00
c967cbb9f0 SOP: Properly throw CannotDecrypt 2022-06-16 11:22:21 +02:00
03be9b8bae
Update README 2022-06-04 18:39:56 +02:00
444ec6d593
Add documentation to enforceBounds() 2022-06-01 13:40:07 +02:00
44c32d0620
When setting expiration dates: Prevent integer overflow 2022-06-01 13:36:00 +02:00
70a861611c
Improve SignatureUtils.wasIssuedBy() by adding support for v5 fingerprints 2022-05-18 14:21:22 +02:00
9921fc6ff6
Add and test OpenPgpFingerprint.parseFromBinary(bytes) 2022-05-18 14:19:08 +02:00
3a9bfd57ac Add test for SignatureUtils.getSignaturesForUserIdBy() 2022-05-17 18:38:48 +02:00
1a37058c66 Add SignatureUtils.getSignaturesForUserIdBy(key, userId, keyId) 2022-05-17 18:38:48 +02:00
77d010ec94 Add CollectionUtils.addAll(iterator, collection) 2022-05-17 18:38:48 +02:00
51baa0e5cb Add modernKeyRing(userId) shortcut method 2022-05-17 18:38:48 +02:00
c510551f16
Move Flowcrypt and NGI logos to external host 2022-05-12 18:16:44 +02:00
8fd67da973
Add comment about readSignatures skipping compressed data packets 2022-05-08 11:34:56 +02:00
12e62d381c
Make readSignatures skip over compressed data packets without decompression. 2022-05-08 11:24:34 +02:00
08ec140b63
Add Logos for FlowCrypt and NGI and add NGI info 2022-05-07 22:49:22 +02:00
ba767cc7ed
Add NGI and Flowcrypt logo svgs 2022-05-07 22:29:17 +02:00
be6c16079e
Switch to version agnostic SOP spec URL 2022-05-07 22:15:13 +02:00
49d65788b4 Remove support for processing compressed detached signatures
Signatures are indistinguishable from randomness, so there is no point in
compressing them, apart from attempting to exploit flaws in compression
algorithms.
Thanks to @DemiMarie for pointing this out

Fixes #286
2022-05-07 21:46:12 +02:00
d3f412873b
Fix checkstyle issues 2022-05-07 21:44:52 +02:00
374e6452f0
Add RevokedKeyException 2022-05-07 14:12:18 +02:00
3e7e6df3f9
Disallow stripping of primary secret keys 2022-05-07 14:11:39 +02:00
64a50266f1
Test for detection of uncompressed, signed messages, and improve decryption of seip messages 2022-05-05 12:43:44 +02:00
7b7707b3a9
PGPainless 1.2.3-SNAPSHOT 2022-05-05 11:25:32 +02:00
ae3004a221
PGPainless 1.2.2 2022-05-05 11:22:59 +02:00
4c72bc2a8e
Update changelog 2022-05-05 11:19:23 +02:00
826331917f
Add comments to unexhaustive parsing method 2022-05-05 11:15:19 +02:00
69f84f24b6
Implement heavy duty packet inspection to figure out nature of data 2022-05-04 20:55:29 +02:00
288f1b414b
Fix javadoc links 2022-05-03 11:31:19 +02:00
2b37c4c9cb
Deprecate Policy.*.default*Policy() methods in favor of methods with more expressive names
You cannot tell, what defaultHashAlgorithmPolicy() really means.
Therefore the default methods were deprecated in favor for more expressive methods
2022-05-03 11:23:40 +02:00
b980fcd7b1
EncryptionOptions.addRecipients(collection): Disallow empty collections
Fixes #281
2022-04-29 22:49:45 +02:00
51cd75533b
PGPainless 1.2.2-SNAPSHOT 2022-04-29 17:05:59 +02:00
c3f6ca2ab8
PGPainless 1.2.1 2022-04-29 17:02:11 +02:00
3b00eb3334
Update changelog 2022-04-29 17:01:22 +02:00
a983f99644
Bump sop-java to 1.2.3 2022-04-29 17:01:11 +02:00
71d5007edc
Add dependency diagram 2022-04-26 02:11:53 +02:00
009ef61699
Update changelog 2022-04-26 00:41:39 +02:00
249cab6eab
Bump logback to 1.2.11 2022-04-26 00:39:40 +02:00
6bf1649cb7
Bump slf4j to 1.7.36 2022-04-26 00:39:25 +02:00
4698b68015
Fix javadoc generation 2022-04-23 01:47:44 +02:00
9b11b94354
Update CHANGELOG 2022-04-22 23:06:46 +02:00
9b8cf37dd1
Use smart hash algorithm policy as default revocation hash policy 2022-04-22 23:06:40 +02:00
6c983d66e0
Take hash algorithm usage date into account when checking algorithm acceptance 2022-04-22 22:45:39 +02:00
4764202ac9
Change visibility of BcPGPHashContextContentSignerBuilder constructor 2022-04-22 22:43:19 +02:00
6c442e9568 Merge remote-tracking branch 'origin/hashContextSigner' 2022-04-22 21:36:47 +02:00
230725f6ff
Add option to force handling of data as non-openpgp 2022-04-22 21:33:13 +02:00
8172aa1083
Update documentation of #96 workaround 2022-04-22 20:56:02 +02:00
46f69b9fa5
Introduce OpenPgpInputStream to distinguish between armored, binary and non-OpenPGP data 2022-04-22 20:53:44 +02:00
3309781b11 Merge branch 'bumpSop' 2022-04-22 18:38:02 +02:00
73b7f1b9bb
Refactoring 2022-04-19 21:07:46 +02:00
c3dfb254b1
Experimental implementation of signing of existing hash contexts (MessageDigest instances) 2022-04-16 00:23:20 +02:00
b64d6e8e55
Stabilize HashAlgorithm.fromName() 2022-04-16 00:22:41 +02:00
218d7becae Bump sop-java to 1.2.2 2022-04-11 18:19:36 +02:00
5307402edb
Bump sop-java to 1.2.2 2022-04-11 14:15:29 +02:00
9558deab74
Set mainClass name in application section 2022-04-11 12:11:26 +02:00
5f9ad3396a
PGPainless 1.2.1-SNAPSHOT 2022-04-07 21:22:53 +02:00
9f50946dd7
PGPainless 1.2.0 2022-04-07 21:20:46 +02:00
05022fcbb5
Fix whitespace error 2022-04-07 21:17:00 +02:00
9a012b5bab
Update changelog 2022-04-07 21:15:43 +02:00
e4bccaf58d
Add support for RegularExpression subpackets (fixes #246) 2022-04-07 20:47:47 +02:00
7710845454
Simplify setPolicyUrl implementation 2022-04-07 20:46:21 +02:00
d4c56f655f
Add support for PolicyURI subpackets (fixes #248) 2022-04-07 20:41:21 +02:00
5f65ca4437
Remove workaround for BC not properly parsing RevocationKey subpacket 2022-04-07 20:28:45 +02:00
d0544e690e
Fix KeyRingUtils.keysPlusPublicKey() 2022-04-07 20:24:36 +02:00
361d2376f5
Update documentation on curve oid workaround 2022-04-07 20:21:07 +02:00
73fa46895e
Implement merging of certificates
Fixes #211
2022-04-07 19:51:42 +02:00
864bfad80c Add test for encryption / decryption, signing with missing secret subkey 2022-04-07 19:42:58 +02:00
bb8ecaa1c1 PGPainless-1.2.0-SNAPSHOT 2022-04-07 19:42:58 +02:00
a22336a795 Create dedicated KeyException class for key-related exceptions. 2022-04-07 19:42:58 +02:00
6b3f37796c
Restructure dependencies and version.gradle 2022-04-07 19:40:56 +02:00
53017d2d38
Bump BC to 1.71 2022-04-07 19:40:39 +02:00
02d6d19aac
Update ECOSYSTEM 2022-04-06 11:37:31 +02:00
6e67895428
Add ECOSYSTEM.md 2022-04-05 17:01:15 +02:00
636fc63bc1 Add local.properties to .gitignore 2022-04-05 14:58:09 +02:00
8e45a2a7f6
PGPainless-1.1.6-SNAPSHOT 2022-04-05 14:51:26 +02:00
a7d56e3461
PGPainless 1.1.5 2022-04-05 14:48:44 +02:00
3245dff731
Update changelog 2022-04-05 14:43:14 +02:00
Péter Barabás
30c9ea254a Fix XML comment 2022-04-05 14:36:58 +02:00
Péter Barabás
8c6813ce56 #266 Handle ClassCastException in signature.init calls 2022-04-05 14:36:58 +02:00
f6c6b9aded
Do not attempt to verify signatures made by external keys using primary key.
This aims at fixing #266 in combination with #267.
2022-04-05 14:10:04 +02:00
0bce68d6ee
Add shortcut SigningOptions.addSignature() method 2022-04-04 20:18:15 +02:00
d0b070f0f3
Fix javadoc 2022-04-04 20:17:57 +02:00
e601f8dbda
In Encrypt example: Read keys from string 2022-04-04 19:49:28 +02:00
2065b4e4ed
Document planned removal of BCUtil.constantTimeAreEquals(char[], char[]) 2022-04-04 13:08:24 +02:00
c8a1ca5b29
Make use of DateUtil.now() in test 2022-04-04 12:53:47 +02:00
2c86d8dfe4
Document various KeyRingSelectionStrategies 2022-04-04 12:49:00 +02:00
7ca9934cbe
Document KeyRingSelectionStrategy 2022-04-04 12:32:37 +02:00
bfbe03f9e0
Document SelectUserIds 2022-04-04 12:19:07 +02:00
4aaa242d64
Add javadoc to SignatureSubpacketsUtil 2022-04-04 10:40:57 +02:00
58dee0d970
Fix javadoc warnings 2022-04-02 18:56:05 +02:00
4bd01578fb
Fix javadoc generation 2022-04-02 18:14:17 +02:00
7eb2f5fb4d
Document how PGPainlessCLI works 2022-04-02 17:16:37 +02:00
6869c66937
Add TODOs to remove deprecated methods in 1.2.X 2022-04-02 17:12:12 +02:00
8ec86e6464
Rename KeyRingUtil.removeSecretKey() to stripSecretKey() 2022-04-02 17:03:38 +02:00
50bcb6a135 Fix changelog and change method signature 2022-04-02 16:18:12 +02:00
39382c7de6 Add annotations to SignatureGenerationStream constructor 2022-04-02 16:18:12 +02:00
131c0c6d03 Add javadoc header to SignatureGenerationStream 2022-04-02 16:18:12 +02:00
f8e66f4d61 Add ProducerOptions.applyCRLFEncoding()
Enabling it will automatically apply CRLF encoding to input data.
Further, disentangle signing from the encryption stream
2022-04-02 16:18:12 +02:00
ade07bde85
Update changelog 2022-03-30 16:43:23 +02:00
6bef376992
Fix signature generation with all format and signature type combinations
This comes at the cost of that we no longer CR/LF encode literal data before encryption/signing.
That means that applications that rely on PGPainless to do the CR/LF encoding must manually
do the encoding before feeding the message to PGPainless.
The newly introduced CRLFGeneratorStream has documentation on how to do that.
Fixes #264
2022-03-30 16:13:08 +02:00
c31fd7d5e0
SOP: Fix mapping of encryption format 2022-03-30 13:14:36 +02:00
9497cbaeb1
Update changelog 2022-03-30 12:50:29 +02:00
4782868bc1
SOP encrypt: match signature type when using --as= option 2022-03-30 12:49:26 +02:00
30a62daec9
PGPainless-1.1.5-SNAPSHOT 2022-03-30 12:28:37 +02:00
ccee24dd93
PGPainless 1.1.4 2022-03-30 12:26:50 +02:00
b0eb32d550
Fix checkstyle 2022-03-30 12:21:53 +02:00
87e6b044d9
Add EncryptionStream class description 2022-03-30 12:18:03 +02:00
a8fa501a7a
Update CHANGELOG 2022-03-30 12:08:29 +02:00
620deaa1f9
Deprecate ProducerOptions.setEncoding()
The reason is that values other than BINARY oftentimes cause issues
(see https://github.com/pgpainless/pgpainless/issues/264), and further
experts recommended to ignore the metadata of the LiteralData packet
and only produce with ('b'/0/) as metadata values.
2022-03-27 17:34:24 +02:00
1cb3e559b5
Eliminate removed 'm' StreamEncoding 2022-03-27 17:29:42 +02:00
82936c5499 Add investigative test for broken messages when using different data/sig encodings 2022-03-27 17:01:31 +02:00
80d97b1bc0 Fix malformed signature packets 2022-03-27 17:01:31 +02:00
8ff405d6ad
Add toString() to SessionKey 2022-03-24 14:16:13 +01:00
405c7225f6
Deprecate ProducerOptions.setForYourEyesOnly()
Use of this special file name is deprecated since at least crypto-refresh-05
2022-03-23 15:17:29 +01:00
aeb321b576
Add short project description to README 2022-03-23 13:40:14 +01:00
e8b03834cb
Annotate fromId(code) methods with Nullable and add Nonnull requireFromId(code) methods 2022-03-22 15:09:09 +01:00
16b0d0730e
Annotate and document ArmorUtils class 2022-03-22 14:17:35 +01:00
e89e0f216c
Annotate KeyRingUtils methods with Nullable and Nonnull 2022-03-22 13:20:36 +01:00
4bae2e74c4
Add documentation for further KeyRingUtils methods 2022-03-22 13:05:27 +01:00
b5ccb23a62
Add documentation for KeyRingUtils.removeSecretKey() 2022-03-22 12:49:30 +01:00
b1eb33eb2c
Update CHANGELOG 2022-03-22 12:44:36 +01:00
3585203557
Prettify user-id info on armor 2022-03-21 16:44:59 +01:00
Simon Frankenberger
e569c2c991
ArmorUtils now prints out the primary user-id and brief information about other user-ids 2022-03-21 16:09:45 +01:00
Simon Frankenberger
d6cf1c6609
fix "Easily Generate Keys" example code missing passphrase wrapper class 2022-03-21 16:05:56 +01:00
9e0aa95a5a
Add documentation for the DecryptOrVerify examples 2022-03-16 21:29:34 +01:00
2dba981e07
Update README 2022-03-15 17:20:55 +01:00
29dc20d0bc Add EncryptionResult.isEncryptedFor(certificate) 2022-03-15 17:02:02 +01:00
ecfa3823fb Add utility method to remove secret subkey from key ring
This might be useful for offline primary keys
2022-03-15 17:01:50 +01:00
f155768539
PGPainless-1.1.4-SNAPSHOT 2022-03-15 15:43:50 +01:00
655f4ae09a
PGPainless 1.1.3 2022-03-15 15:41:30 +01:00
bfe140294c
Update changelog 2022-03-15 15:38:07 +01:00
d4d29553ec
Add decryption example 2022-03-15 15:10:23 +01:00
0819592b3a
Update changelog 2022-03-14 11:12:21 +01:00
ffdbd21491 Implement configuration option for SignerUserId subpacket verification level.
By default we ignore SignerUserId subpackets on signatures.
This behavior can be changed by calling Policy.setSignerUserIdValidationLevel().
Right now, STRICT and DISABLED are available as options, but it may make sense to implement
another option PARTIALLY, which will accept signatures made by key with user-id 'A <foo@bar>'
but where the sig contains a signer user id of value 'foo@bar' for example.
2022-03-14 11:10:12 +01:00
661c043cdc
DFix KeyRingInfo.getValidAndExpiredUserIds considering unbound user-ids as valid 2022-03-13 16:52:57 +01:00
26c804b2dd
Add comment about hash algorithm header 2022-03-13 15:12:38 +01:00
6b9b956c2c
Add OpenPgpFingerprint.parse(String) 2022-03-10 12:22:02 +01:00
8f473b513f
Add support for OpenPGP v5 fingerprints.
Obviously we need support for key.getFingerprint() in BC, but once
that is there, this should magically start working.
2022-03-10 12:01:12 +01:00
0824bbd37c
Add investigative test for signers user-ids 2022-03-09 21:05:17 +01:00
26d79679f0
Fix crash when validating unmatched signer's user-id subpacket
TODO: We might want to deprecate Signer's UserID subpackets completely and ignore them.
See results of sequoias test suite once PR below gets merged.
https://gitlab.com/sequoia-pgp/openpgp-interoperability-test-suite/-/merge_requests/28
2022-03-09 21:05:00 +01:00
b34866b012
Make SigningOptions.getSigningMethods package visible 2022-03-09 21:03:31 +01:00
5b43cfaf8c
PGPainless-1.1.3-SNAPSHOT 2022-03-07 15:05:02 +01:00
95aed9bf22
PGPainless 1.1.2 2022-03-07 15:02:24 +01:00
8563cf0969
Update changelog 2022-03-07 15:00:33 +01:00
db02106518
Fix typo 2022-03-07 14:57:00 +01:00
3fe78ab12a
Fix NPE when validating broken signature 2022-03-07 14:56:56 +01:00
f1f7dec8b6
Fix accidental verification of thirdparty user-id revocations using primary key 2022-03-07 14:56:37 +01:00
fc65bb4496
Raise readable error message when trying to encrypt for key without acceptable self-sigs 2022-03-07 14:55:45 +01:00
9d160ef047
Reject subkeys with predating binding signatures 2022-03-07 12:17:45 +01:00
5d3646cd36
Add missing @throws documentation 2022-03-07 11:27:21 +01:00
c3f5b997ab
Update changelog 2022-03-07 11:11:04 +01:00
10e72f6773
Allow custom key creation dates during generation 2022-03-07 11:08:59 +01:00
a6dcf027c0
Add and document PGPainless.inspectKeyRing(key, date) 2022-03-07 10:36:20 +01:00
a7d1f09b5c
Document SimpleDateFormat not thread-safe 2022-03-07 10:26:24 +01:00
126cc9df70
Make toSecondsPrecision() more readable and improv performance 2022-03-07 10:24:08 +01:00
5b9e72d42c
Add KeyRingInfo.isUsableForEncryption() 2022-03-06 14:58:36 +01:00
afad3fc747
Fix changelog 2022-03-06 14:35:52 +01:00
54b443f183
Document generics fix in CHANGELOG 2022-03-02 11:36:55 +01:00
2e6ae5c117 Update README 2022-03-02 11:17:07 +01:00
1949cc5eea
Fix generics of CertificationSubpackets callback 2022-03-02 11:15:07 +01:00
35dd4f9a67
Fix unused import 2022-03-01 17:37:24 +01:00
63b39c56bd
Fix README 2022-03-01 17:18:20 +01:00
d55d6a1686
Improve RegExs for extracting email addresses from keys
Based on https://github.com/pgpainless/pgpainless/pull/257/
Thanks @bratkartoffel for the initial proposed changes
2022-03-01 12:14:09 +01:00
1088b6c8ae
Add dep5 license info for pgpainless.svg 2022-02-25 16:23:27 +01:00
d876f770a6
Bump version in sop readme 2022-02-25 16:12:56 +01:00
37be70e0f3
PGPainless-1.1.2-SNAPSHOT 2022-02-25 16:11:00 +01:00
69c0a1bfa4
PGPainless 1.1.1 2022-02-25 16:06:01 +01:00
a681a27bb7
Add logo svg 2022-02-25 15:56:56 +01:00
feri
a1deb531a4 trim comment lines. 2022-02-24 17:46:45 +01:00
feri
7a77d0847a Support multiline comments in ProducerOption.setComment(). 2022-02-24 17:46:45 +01:00
53f7815778 солідарність 2022-02-24 13:05:21 +01:00
fc33e56ad8
Some clarifications in javadoc 2022-02-24 01:08:23 +01:00
367a07411d
Update CHANGELOG 2022-02-24 01:01:13 +01:00
feri
928fa12b51
Add new ProducerOption setComment() for Ascii armored EncryptionStreams. (#254)
* Add new ProducerOption setComment() for Ascii armored EncryptionStreams.
2022-02-24 00:51:16 +01:00
1753cef10e Simplify handling of cleartext-signed data 2022-02-23 18:45:29 +01:00
db58280db6
Change default criticality of signature subpackets to mirror those of sequoia 2022-02-19 17:07:56 +01:00
41ed056165
By default emit IssuerFingerprint signature subpackets as non-critical 2022-02-19 16:05:02 +01:00
a3f9311d9a
Add some comments to messy DecryptionStreamFactory code 2022-02-19 14:48:17 +01:00
08a3f3e8b0
s/Bouncycastle/Bouncy Castle 2022-02-16 13:50:45 +01:00
5e48a5a786
Update SECURITY.md 2022-02-15 18:44:58 +01:00
8edd0c6a14
PGPainless-1.1.1-SNAPSHOT 2022-02-15 15:03:50 +01:00
b31742f215
PGPainless 1.1.0 2022-02-15 15:00:42 +01:00
66d81067b5 Update README javadoc link 2022-02-15 14:29:07 +01:00
36c5ec8a28 Host javadoc on javadoc.io 2022-02-15 14:29:07 +01:00
2b9d92ed89 Update changelog 2022-02-15 14:29:06 +01:00
c405a99994 Bump sop-java to 1.2.0 2022-02-15 14:28:33 +01:00
5b4018d2f0
PGPainless-1.0.5-SNAPSHOT 2022-02-15 14:27:38 +01:00
bb9e3e89b1
PGPainless 1.0.4 2022-02-15 14:25:45 +01:00
e8da3b30d8 Yet another patch for ASCII armor detection -.- 2022-02-15 14:23:03 +01:00
f3cf3456ab ConsumerOptions.setIsCleartextSigned -> return this 2022-02-15 14:22:57 +01:00
b33885c268 Remove accidental marking of buffered stream in PGPUtilWrapper 2022-02-15 14:22:49 +01:00
e276507b65
PGPainless-1.0.4-SNAPSHOT 2022-02-11 14:11:11 +01:00
09c091bfea
PGPainless 1.0.3 2022-02-11 14:09:12 +01:00
458b4f1f78 Fix detection of unarmored data in detached signature verification 2022-02-11 14:07:29 +01:00
9b5cc2da5a
PGPainless-1.0.3-SNAPSHOT 2022-02-05 15:05:07 +01:00
132981abfa
PGPainless 1.0.2 2022-02-05 14:55:38 +01:00
de73166849 Merge branch 'pre_1.1.0__sop-readme' 2022-02-05 14:48:49 +01:00
94a9c84434
Update changelog 2022-02-05 14:48:40 +01:00
059a38a0a2
sop-java, sop-java-picocli placeholder readme: add links to codeberg 2022-02-05 14:41:43 +01:00
593a6cdb65
Document how to include pgpainless-sop in build scripts 2022-01-15 14:38:36 +01:00
7aa48f458b Fix pgpainless-cli:jar resolution of pgpainless-sop.jar 2022-01-15 02:46:42 +01:00
e55fad6078 Switch pgpainless-sop over to java-library plugin and api-depend on sop-java 2022-01-15 02:46:42 +01:00
9b270197c2 Add MIME StreamEncoding enum val 2022-01-15 02:46:42 +01:00
020c0be8fb Remove further traces of sop-java from the build script 2022-01-15 02:46:42 +01:00
ef02163d97 Remove dependency on picocli 2022-01-15 02:46:42 +01:00
f4e574011b Add info about sop modules to root readme 2022-01-15 02:46:42 +01:00
a7208e6c42 Update CHANGELOG 2022-01-15 02:46:42 +01:00
09c2a84ec1 Add reuse header to sop-java*/readme files 2022-01-15 02:46:42 +01:00
c6bc8f9774 Moved sop-java and sop-java-picocli to its own repositories
See https://github.com/pgpainless/sop-java
See https://codeberg.org/PGPainless/sop-java
2022-01-15 02:46:42 +01:00
9800ca8bd4 Update CHANGELOG 2022-01-15 02:46:42 +01:00
19b6c8b1e3 SOP-CLI: Implement additional version flags 2022-01-15 02:46:42 +01:00
fc432901ed Add information to SOP READMEs 2022-01-15 02:46:42 +01:00
2e7a21b5b7 Add javadoc description to DetachInbandSignatureAndMessage 2022-01-15 02:46:42 +01:00
bbc42fd8e4 Document workaround for BCs ECUtil.getCurveName() returning null for ed25519 keys
See https://github.com/bcgit/bc-java/issues/1087
2022-01-15 02:46:42 +01:00
01839728f0 Remove workaround for publicKey.getBitStrength() == -1 in BC
see https://github.com/bcgit/bc-java/issues/972
2022-01-15 02:46:41 +01:00
e374951ed0 Remove ProofUtil.
This does not belong here.
2022-01-15 02:46:41 +01:00
1aca7112d2 SOP commands: Throw UnsupportedSubcommand error when sop.command() returns null 2022-01-15 02:46:41 +01:00
1c2cbf0e75 Add SigningResultTest 2022-01-15 02:46:41 +01:00
824b8de404 Add sessionKey fromString test 2022-01-15 02:46:41 +01:00
1b1a13e7d0 Fix spelling of Bouncy Castle 2022-01-15 02:46:41 +01:00
f2d88d8a86 Introduce SigningResult class to allow for additional signing information to be returned 2022-01-15 02:46:41 +01:00
987c328ad8 Add MicAlgTest 2022-01-15 02:46:41 +01:00
1cb49f4b12 Update SOP implementation to the latest spec version
See https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-03
2022-01-15 02:46:41 +01:00
5e0ca369bf Document workaround for https://github.com/bcgit/bc-java/pull/1085 2022-01-15 02:46:41 +01:00
fa0e208c98 Workaround for BC not correctly parsing RevocationKey packets 2022-01-15 02:46:41 +01:00
88e3c61b20 RevocationSignatureBuilder: Allow for generation of external revocation signatures 2022-01-15 02:46:41 +01:00
5884c4afcd ArmorUtils: Add method to print single public keys 2022-01-15 02:46:41 +01:00
1447dfc642 Add SignatureUtils.wasIssuedBy 2022-01-15 02:46:41 +01:00
99496f80f5 Hex decode data in OpenPgpV4Fingerprint constructor 2022-01-15 02:46:41 +01:00
d9e3c6ed91 Remove investigative test with expired key 2022-01-15 02:46:41 +01:00
e7f583c1af Fix KeyRingInfo.get*Algorithm(keyId) 2022-01-15 02:45:21 +01:00
9de196d6c5 Fix test for algorithm preference extraction 2022-01-15 02:45:10 +01:00
1cbeafb3ad
PGPainless-1.0.2-SNAPSHOT 2022-01-15 01:06:18 +01:00
d264982388
PGPainless 1.0.1 2022-01-15 01:02:59 +01:00
b58bdf8ff1 Fix KeyAccessor.ViaKeyId sourcing primary user-id signature 2022-01-15 00:59:54 +01:00
e177dafd60
PGPainless-1.0.1-SNAPSHOT 2021-12-28 14:07:27 +01:00
742 changed files with 43833 additions and 29474 deletions

View file

@ -1,513 +1,51 @@
# SPDX-FileCopyrightText: 2021 Paul Schaub <info@pgpainless.org>
# This .editorconfig section approximates ktfmt's formatting rules. You can include it in an
# existing .editorconfig file or use it standalone by copying it to <project root>/.editorconfig
# and making sure your editor is set to read settings from .editorconfig files.
#
# SPDX-License-Identifier: CC0-1.0
# It includes editor-specific config options for IntelliJ IDEA.
#
# If any option is wrong, PR are welcome
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
[{*.kt,*.kts}]
indent_style = space
insert_final_newline = false
max_line_length = 120
tab_width = 4
ij_continuation_indent_size = 8
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = false
ij_smart_tabs = false
ij_visual_guides = none
ij_wrap_on_typing = false
[*.java]
ij_java_align_consecutive_assignments = false
ij_java_align_consecutive_variable_declarations = false
ij_java_align_group_field_declarations = false
ij_java_align_multiline_annotation_parameters = false
ij_java_align_multiline_array_initializer_expression = false
ij_java_align_multiline_assignment = false
ij_java_align_multiline_binary_operation = false
ij_java_align_multiline_chained_methods = false
ij_java_align_multiline_extends_list = false
ij_java_align_multiline_for = true
ij_java_align_multiline_method_parentheses = false
ij_java_align_multiline_parameters = true
ij_java_align_multiline_parameters_in_calls = false
ij_java_align_multiline_parenthesized_expression = false
ij_java_align_multiline_records = true
ij_java_align_multiline_resources = true
ij_java_align_multiline_ternary_operation = false
ij_java_align_multiline_text_blocks = false
ij_java_align_multiline_throws_list = false
ij_java_align_subsequent_simple_methods = false
ij_java_align_throws_keyword = false
ij_java_annotation_parameter_wrap = off
ij_java_array_initializer_new_line_after_left_brace = false
ij_java_array_initializer_right_brace_on_new_line = false
ij_java_array_initializer_wrap = off
ij_java_assert_statement_colon_on_next_line = false
ij_java_assert_statement_wrap = off
ij_java_assignment_wrap = off
ij_java_binary_operation_sign_on_next_line = false
ij_java_binary_operation_wrap = off
ij_java_blank_lines_after_anonymous_class_header = 0
ij_java_blank_lines_after_class_header = 0
ij_java_blank_lines_after_imports = 1
ij_java_blank_lines_after_package = 1
ij_java_blank_lines_around_class = 1
ij_java_blank_lines_around_field = 0
ij_java_blank_lines_around_field_in_interface = 0
ij_java_blank_lines_around_initializer = 1
ij_java_blank_lines_around_method = 1
ij_java_blank_lines_around_method_in_interface = 1
ij_java_blank_lines_before_class_end = 0
ij_java_blank_lines_before_imports = 1
ij_java_blank_lines_before_method_body = 0
ij_java_blank_lines_before_package = 0
ij_java_block_brace_style = end_of_line
ij_java_block_comment_at_first_column = true
ij_java_builder_methods = none
ij_java_call_parameters_new_line_after_left_paren = false
ij_java_call_parameters_right_paren_on_new_line = false
ij_java_call_parameters_wrap = off
ij_java_case_statement_on_separate_line = true
ij_java_catch_on_new_line = false
ij_java_class_annotation_wrap = split_into_lines
ij_java_class_brace_style = end_of_line
ij_java_class_count_to_use_import_on_demand = 10000
ij_java_class_names_in_javadoc = 1
ij_java_do_not_indent_top_level_class_members = false
ij_java_do_not_wrap_after_single_annotation = false
ij_java_do_while_brace_force = never
ij_java_doc_add_blank_line_after_description = true
ij_java_doc_add_blank_line_after_param_comments = false
ij_java_doc_add_blank_line_after_return = false
ij_java_doc_add_p_tag_on_empty_lines = true
ij_java_doc_align_exception_comments = true
ij_java_doc_align_param_comments = true
ij_java_doc_do_not_wrap_if_one_line = false
ij_java_doc_enable_formatting = true
ij_java_doc_enable_leading_asterisks = true
ij_java_doc_indent_on_continuation = false
ij_java_doc_keep_empty_lines = true
ij_java_doc_keep_empty_parameter_tag = true
ij_java_doc_keep_empty_return_tag = true
ij_java_doc_keep_empty_throws_tag = true
ij_java_doc_keep_invalid_tags = true
ij_java_doc_param_description_on_new_line = false
ij_java_doc_preserve_line_breaks = false
ij_java_doc_use_throws_not_exception_tag = true
ij_java_else_on_new_line = false
ij_java_enum_constants_wrap = off
ij_java_extends_keyword_wrap = off
ij_java_extends_list_wrap = off
ij_java_field_annotation_wrap = split_into_lines
ij_java_finally_on_new_line = false
ij_java_for_brace_force = never
ij_java_for_statement_new_line_after_left_paren = false
ij_java_for_statement_right_paren_on_new_line = false
ij_java_for_statement_wrap = off
ij_java_generate_final_locals = false
ij_java_generate_final_parameters = false
ij_java_if_brace_force = never
ij_java_imports_layout = $*,|,java.**,javax.**,|,*
ij_java_indent_case_from_switch = true
ij_java_insert_inner_class_imports = false
ij_java_insert_override_annotation = true
ij_java_keep_blank_lines_before_right_brace = 2
ij_java_keep_blank_lines_between_package_declaration_and_header = 2
ij_java_keep_blank_lines_in_code = 2
ij_java_keep_blank_lines_in_declarations = 2
ij_java_keep_builder_methods_indents = false
ij_java_keep_control_statement_in_one_line = true
ij_java_keep_first_column_comment = true
ij_java_keep_indents_on_empty_lines = false
ij_java_keep_line_breaks = true
ij_java_keep_multiple_expressions_in_one_line = false
ij_java_keep_simple_blocks_in_one_line = false
ij_java_keep_simple_classes_in_one_line = false
ij_java_keep_simple_lambdas_in_one_line = false
ij_java_keep_simple_methods_in_one_line = false
ij_java_label_indent_absolute = false
ij_java_label_indent_size = 0
ij_java_lambda_brace_style = end_of_line
ij_java_layout_static_imports_separately = true
ij_java_line_comment_add_space = false
ij_java_line_comment_at_first_column = true
ij_java_method_annotation_wrap = split_into_lines
ij_java_method_brace_style = end_of_line
ij_java_method_call_chain_wrap = off
ij_java_method_parameters_new_line_after_left_paren = false
ij_java_method_parameters_right_paren_on_new_line = false
ij_java_method_parameters_wrap = off
ij_java_modifier_list_wrap = false
ij_java_names_count_to_use_import_on_demand = 1000
ij_java_new_line_after_lparen_in_record_header = false
ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.*
ij_java_parameter_annotation_wrap = off
ij_java_parentheses_expression_new_line_after_left_paren = false
ij_java_parentheses_expression_right_paren_on_new_line = false
ij_java_place_assignment_sign_on_next_line = false
ij_java_prefer_longer_names = true
ij_java_prefer_parameters_wrap = false
ij_java_record_components_wrap = normal
ij_java_repeat_synchronized = true
ij_java_replace_instanceof_and_cast = false
ij_java_replace_null_check = true
ij_java_replace_sum_lambda_with_method_ref = true
ij_java_resource_list_new_line_after_left_paren = false
ij_java_resource_list_right_paren_on_new_line = false
ij_java_resource_list_wrap = off
ij_java_rparen_on_new_line_in_record_header = false
ij_java_space_after_closing_angle_bracket_in_type_argument = false
ij_java_space_after_colon = true
ij_java_space_after_comma = true
ij_java_space_after_comma_in_type_arguments = true
ij_java_space_after_for_semicolon = true
ij_java_space_after_quest = true
ij_java_space_after_type_cast = true
ij_java_space_before_annotation_array_initializer_left_brace = false
ij_java_space_before_annotation_parameter_list = false
ij_java_space_before_array_initializer_left_brace = false
ij_java_space_before_catch_keyword = true
ij_java_space_before_catch_left_brace = true
ij_java_space_before_catch_parentheses = true
ij_java_space_before_class_left_brace = true
ij_java_space_before_colon = true
ij_java_space_before_colon_in_foreach = true
ij_java_space_before_comma = false
ij_java_space_before_do_left_brace = true
ij_java_space_before_else_keyword = true
ij_java_space_before_else_left_brace = true
ij_java_space_before_finally_keyword = true
ij_java_space_before_finally_left_brace = true
ij_java_space_before_for_left_brace = true
ij_java_space_before_for_parentheses = true
ij_java_space_before_for_semicolon = false
ij_java_space_before_if_left_brace = true
ij_java_space_before_if_parentheses = true
ij_java_space_before_method_call_parentheses = false
ij_java_space_before_method_left_brace = true
ij_java_space_before_method_parentheses = false
ij_java_space_before_opening_angle_bracket_in_type_parameter = false
ij_java_space_before_quest = true
ij_java_space_before_switch_left_brace = true
ij_java_space_before_switch_parentheses = true
ij_java_space_before_synchronized_left_brace = true
ij_java_space_before_synchronized_parentheses = true
ij_java_space_before_try_left_brace = true
ij_java_space_before_try_parentheses = true
ij_java_space_before_type_parameter_list = false
ij_java_space_before_while_keyword = true
ij_java_space_before_while_left_brace = true
ij_java_space_before_while_parentheses = true
ij_java_space_inside_one_line_enum_braces = false
ij_java_space_within_empty_array_initializer_braces = false
ij_java_space_within_empty_method_call_parentheses = false
ij_java_space_within_empty_method_parentheses = false
ij_java_spaces_around_additive_operators = true
ij_java_spaces_around_assignment_operators = true
ij_java_spaces_around_bitwise_operators = true
ij_java_spaces_around_equality_operators = true
ij_java_spaces_around_lambda_arrow = true
ij_java_spaces_around_logical_operators = true
ij_java_spaces_around_method_ref_dbl_colon = false
ij_java_spaces_around_multiplicative_operators = true
ij_java_spaces_around_relational_operators = true
ij_java_spaces_around_shift_operators = true
ij_java_spaces_around_type_bounds_in_type_parameters = true
ij_java_spaces_around_unary_operator = false
ij_java_spaces_within_angle_brackets = false
ij_java_spaces_within_annotation_parentheses = false
ij_java_spaces_within_array_initializer_braces = false
ij_java_spaces_within_braces = false
ij_java_spaces_within_brackets = false
ij_java_spaces_within_cast_parentheses = false
ij_java_spaces_within_catch_parentheses = false
ij_java_spaces_within_for_parentheses = false
ij_java_spaces_within_if_parentheses = false
ij_java_spaces_within_method_call_parentheses = false
ij_java_spaces_within_method_parentheses = false
ij_java_spaces_within_parentheses = false
ij_java_spaces_within_record_header = false
ij_java_spaces_within_switch_parentheses = false
ij_java_spaces_within_synchronized_parentheses = false
ij_java_spaces_within_try_parentheses = false
ij_java_spaces_within_while_parentheses = false
ij_java_special_else_if_treatment = true
ij_java_subclass_name_suffix = Impl
ij_java_ternary_operation_signs_on_next_line = false
ij_java_ternary_operation_wrap = off
ij_java_test_name_suffix = Test
ij_java_throws_keyword_wrap = off
ij_java_throws_list_wrap = off
ij_java_use_external_annotations = false
ij_java_use_fq_class_names = false
ij_java_use_relative_indents = false
ij_java_use_single_class_imports = true
ij_java_variable_annotation_wrap = off
ij_java_visibility = public
ij_java_while_brace_force = never
ij_java_while_on_new_line = false
ij_java_wrap_comments = false
ij_java_wrap_first_method_in_call_chain = false
ij_java_wrap_long_lines = false
[*.properties]
ij_properties_align_group_field_declarations = false
ij_properties_keep_blank_lines = false
ij_properties_key_value_delimiter = equals
ij_properties_spaces_around_key_value_delimiter = false
[.editorconfig]
ij_editorconfig_align_group_field_declarations = false
ij_editorconfig_space_after_colon = false
ij_editorconfig_space_after_comma = true
ij_editorconfig_space_before_colon = false
ij_editorconfig_space_before_comma = false
ij_editorconfig_spaces_around_assignment_operators = true
[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.jspx,*.pom,*.rng,*.tagx,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}]
ij_xml_align_attributes = true
ij_xml_align_text = false
ij_xml_attribute_wrap = normal
ij_xml_block_comment_at_first_column = true
ij_xml_keep_blank_lines = 2
ij_xml_keep_indents_on_empty_lines = false
ij_xml_keep_line_breaks = true
ij_xml_keep_line_breaks_in_text = true
ij_xml_keep_whitespaces = false
ij_xml_keep_whitespaces_around_cdata = preserve
ij_xml_keep_whitespaces_inside_cdata = false
ij_xml_line_comment_at_first_column = true
ij_xml_space_after_tag_name = false
ij_xml_space_around_equals_in_attribute = false
ij_xml_space_inside_empty_tag = false
ij_xml_text_wrap = normal
ij_xml_use_custom_settings = false
[{*.bash,*.sh,*.zsh}]
indent_size = 2
tab_width = 2
ij_shell_binary_ops_start_line = false
ij_shell_keep_column_alignment_padding = false
ij_shell_minify_program = false
ij_shell_redirect_followed_by_space = false
ij_shell_switch_cases_indented = false
ij_shell_use_unix_line_separator = true
[{*.gant,*.gradle,*.groovy,*.gy}]
ij_groovy_align_group_field_declarations = false
ij_groovy_align_multiline_array_initializer_expression = false
ij_groovy_align_multiline_assignment = false
ij_groovy_align_multiline_binary_operation = false
ij_groovy_align_multiline_chained_methods = false
ij_groovy_align_multiline_extends_list = false
ij_groovy_align_multiline_for = true
ij_groovy_align_multiline_list_or_map = true
ij_groovy_align_multiline_method_parentheses = false
ij_groovy_align_multiline_parameters = true
ij_groovy_align_multiline_parameters_in_calls = false
ij_groovy_align_multiline_resources = true
ij_groovy_align_multiline_ternary_operation = false
ij_groovy_align_multiline_throws_list = false
ij_groovy_align_named_args_in_map = true
ij_groovy_align_throws_keyword = false
ij_groovy_array_initializer_new_line_after_left_brace = false
ij_groovy_array_initializer_right_brace_on_new_line = false
ij_groovy_array_initializer_wrap = off
ij_groovy_assert_statement_wrap = off
ij_groovy_assignment_wrap = off
ij_groovy_binary_operation_wrap = off
ij_groovy_blank_lines_after_class_header = 0
ij_groovy_blank_lines_after_imports = 1
ij_groovy_blank_lines_after_package = 1
ij_groovy_blank_lines_around_class = 1
ij_groovy_blank_lines_around_field = 0
ij_groovy_blank_lines_around_field_in_interface = 0
ij_groovy_blank_lines_around_method = 1
ij_groovy_blank_lines_around_method_in_interface = 1
ij_groovy_blank_lines_before_imports = 1
ij_groovy_blank_lines_before_method_body = 0
ij_groovy_blank_lines_before_package = 0
ij_groovy_block_brace_style = end_of_line
ij_groovy_block_comment_at_first_column = true
ij_groovy_call_parameters_new_line_after_left_paren = false
ij_groovy_call_parameters_right_paren_on_new_line = false
ij_groovy_call_parameters_wrap = off
ij_groovy_catch_on_new_line = false
ij_groovy_class_annotation_wrap = split_into_lines
ij_groovy_class_brace_style = end_of_line
ij_groovy_class_count_to_use_import_on_demand = 5
ij_groovy_do_while_brace_force = never
ij_groovy_else_on_new_line = false
ij_groovy_enum_constants_wrap = off
ij_groovy_extends_keyword_wrap = off
ij_groovy_extends_list_wrap = off
ij_groovy_field_annotation_wrap = split_into_lines
ij_groovy_finally_on_new_line = false
ij_groovy_for_brace_force = never
ij_groovy_for_statement_new_line_after_left_paren = false
ij_groovy_for_statement_right_paren_on_new_line = false
ij_groovy_for_statement_wrap = off
ij_groovy_if_brace_force = never
ij_groovy_import_annotation_wrap = 2
ij_groovy_imports_layout = *,|,javax.**,java.**,|,$*
ij_groovy_indent_case_from_switch = true
ij_groovy_indent_label_blocks = true
ij_groovy_insert_inner_class_imports = false
ij_groovy_keep_blank_lines_before_right_brace = 2
ij_groovy_keep_blank_lines_in_code = 2
ij_groovy_keep_blank_lines_in_declarations = 2
ij_groovy_keep_control_statement_in_one_line = true
ij_groovy_keep_first_column_comment = true
ij_groovy_keep_indents_on_empty_lines = false
ij_groovy_keep_line_breaks = true
ij_groovy_keep_multiple_expressions_in_one_line = false
ij_groovy_keep_simple_blocks_in_one_line = false
ij_groovy_keep_simple_classes_in_one_line = true
ij_groovy_keep_simple_lambdas_in_one_line = true
ij_groovy_keep_simple_methods_in_one_line = true
ij_groovy_label_indent_absolute = false
ij_groovy_label_indent_size = 0
ij_groovy_lambda_brace_style = end_of_line
ij_groovy_layout_static_imports_separately = true
ij_groovy_line_comment_add_space = false
ij_groovy_line_comment_at_first_column = true
ij_groovy_method_annotation_wrap = split_into_lines
ij_groovy_method_brace_style = end_of_line
ij_groovy_method_call_chain_wrap = off
ij_groovy_method_parameters_new_line_after_left_paren = false
ij_groovy_method_parameters_right_paren_on_new_line = false
ij_groovy_method_parameters_wrap = off
ij_groovy_modifier_list_wrap = false
ij_groovy_names_count_to_use_import_on_demand = 3
ij_groovy_parameter_annotation_wrap = off
ij_groovy_parentheses_expression_new_line_after_left_paren = false
ij_groovy_parentheses_expression_right_paren_on_new_line = false
ij_groovy_prefer_parameters_wrap = false
ij_groovy_resource_list_new_line_after_left_paren = false
ij_groovy_resource_list_right_paren_on_new_line = false
ij_groovy_resource_list_wrap = off
ij_groovy_space_after_assert_separator = true
ij_groovy_space_after_colon = true
ij_groovy_space_after_comma = true
ij_groovy_space_after_comma_in_type_arguments = true
ij_groovy_space_after_for_semicolon = true
ij_groovy_space_after_quest = true
ij_groovy_space_after_type_cast = true
ij_groovy_space_before_annotation_parameter_list = false
ij_groovy_space_before_array_initializer_left_brace = false
ij_groovy_space_before_assert_separator = false
ij_groovy_space_before_catch_keyword = true
ij_groovy_space_before_catch_left_brace = true
ij_groovy_space_before_catch_parentheses = true
ij_groovy_space_before_class_left_brace = true
ij_groovy_space_before_closure_left_brace = true
ij_groovy_space_before_colon = true
ij_groovy_space_before_comma = false
ij_groovy_space_before_do_left_brace = true
ij_groovy_space_before_else_keyword = true
ij_groovy_space_before_else_left_brace = true
ij_groovy_space_before_finally_keyword = true
ij_groovy_space_before_finally_left_brace = true
ij_groovy_space_before_for_left_brace = true
ij_groovy_space_before_for_parentheses = true
ij_groovy_space_before_for_semicolon = false
ij_groovy_space_before_if_left_brace = true
ij_groovy_space_before_if_parentheses = true
ij_groovy_space_before_method_call_parentheses = false
ij_groovy_space_before_method_left_brace = true
ij_groovy_space_before_method_parentheses = false
ij_groovy_space_before_quest = true
ij_groovy_space_before_switch_left_brace = true
ij_groovy_space_before_switch_parentheses = true
ij_groovy_space_before_synchronized_left_brace = true
ij_groovy_space_before_synchronized_parentheses = true
ij_groovy_space_before_try_left_brace = true
ij_groovy_space_before_try_parentheses = true
ij_groovy_space_before_while_keyword = true
ij_groovy_space_before_while_left_brace = true
ij_groovy_space_before_while_parentheses = true
ij_groovy_space_in_named_argument = true
ij_groovy_space_in_named_argument_before_colon = false
ij_groovy_space_within_empty_array_initializer_braces = false
ij_groovy_space_within_empty_method_call_parentheses = false
ij_groovy_spaces_around_additive_operators = true
ij_groovy_spaces_around_assignment_operators = true
ij_groovy_spaces_around_bitwise_operators = true
ij_groovy_spaces_around_equality_operators = true
ij_groovy_spaces_around_lambda_arrow = true
ij_groovy_spaces_around_logical_operators = true
ij_groovy_spaces_around_multiplicative_operators = true
ij_groovy_spaces_around_regex_operators = true
ij_groovy_spaces_around_relational_operators = true
ij_groovy_spaces_around_shift_operators = true
ij_groovy_spaces_within_annotation_parentheses = false
ij_groovy_spaces_within_array_initializer_braces = false
ij_groovy_spaces_within_braces = true
ij_groovy_spaces_within_brackets = false
ij_groovy_spaces_within_cast_parentheses = false
ij_groovy_spaces_within_catch_parentheses = false
ij_groovy_spaces_within_for_parentheses = false
ij_groovy_spaces_within_gstring_injection_braces = false
ij_groovy_spaces_within_if_parentheses = false
ij_groovy_spaces_within_list_or_map = false
ij_groovy_spaces_within_method_call_parentheses = false
ij_groovy_spaces_within_method_parentheses = false
ij_groovy_spaces_within_parentheses = false
ij_groovy_spaces_within_switch_parentheses = false
ij_groovy_spaces_within_synchronized_parentheses = false
ij_groovy_spaces_within_try_parentheses = false
ij_groovy_spaces_within_tuple_expression = false
ij_groovy_spaces_within_while_parentheses = false
ij_groovy_special_else_if_treatment = true
ij_groovy_ternary_operation_wrap = off
ij_groovy_throws_keyword_wrap = off
ij_groovy_throws_list_wrap = off
ij_groovy_use_flying_geese_braces = false
ij_groovy_use_fq_class_names = false
ij_groovy_use_fq_class_names_in_javadoc = true
ij_groovy_use_relative_indents = false
ij_groovy_use_single_class_imports = true
ij_groovy_variable_annotation_wrap = off
ij_groovy_while_brace_force = never
ij_groovy_while_on_new_line = false
ij_groovy_wrap_long_lines = false
[{*.gradle.kts,*.kt,*.kts,*.main.kts,*.space.kts}]
insert_final_newline = true
max_line_length = 100
indent_size = 4
ij_continuation_indent_size = 4 # was 8
ij_java_names_count_to_use_import_on_demand = 9999
ij_kotlin_align_in_columns_case_branch = false
ij_kotlin_align_multiline_binary_operation = false
ij_kotlin_align_multiline_extends_list = false
ij_kotlin_align_multiline_method_parentheses = false
ij_kotlin_align_multiline_parameters = true
ij_kotlin_align_multiline_parameters_in_calls = false
ij_kotlin_allow_trailing_comma = false
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_assignment_wrap = off
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true
ij_kotlin_assignment_wrap = normal
ij_kotlin_blank_lines_after_class_header = 0
ij_kotlin_blank_lines_around_block_when_branches = 0
ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1
ij_kotlin_block_comment_at_first_column = true
ij_kotlin_call_parameters_new_line_after_left_paren = false
ij_kotlin_call_parameters_new_line_after_left_paren = true
ij_kotlin_call_parameters_right_paren_on_new_line = false
ij_kotlin_call_parameters_wrap = off
ij_kotlin_call_parameters_wrap = on_every_item
ij_kotlin_catch_on_new_line = false
ij_kotlin_class_annotation_wrap = split_into_lines
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
ij_kotlin_continuation_indent_for_chained_calls = true
ij_kotlin_continuation_indent_for_expression_bodies = true
ij_kotlin_continuation_indent_in_argument_lists = true
ij_kotlin_continuation_indent_in_elvis = true
ij_kotlin_continuation_indent_in_if_conditions = true
ij_kotlin_continuation_indent_in_parameter_lists = true
ij_kotlin_continuation_indent_in_supertype_lists = true
ij_kotlin_continuation_indent_in_elvis = false
ij_kotlin_continuation_indent_in_if_conditions = false
ij_kotlin_continuation_indent_in_parameter_lists = false
ij_kotlin_continuation_indent_in_supertype_lists = false
ij_kotlin_else_on_new_line = false
ij_kotlin_enum_constants_wrap = off
ij_kotlin_extends_list_wrap = off
ij_kotlin_extends_list_wrap = normal
ij_kotlin_field_annotation_wrap = split_into_lines
ij_kotlin_finally_on_new_line = false
ij_kotlin_if_rparen_on_new_line = false
ij_kotlin_import_nested_classes = false
ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^
ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
ij_kotlin_keep_blank_lines_before_right_brace = 2
ij_kotlin_keep_blank_lines_in_code = 2
@ -519,13 +57,13 @@ ij_kotlin_lbrace_on_next_line = false
ij_kotlin_line_comment_add_space = false
ij_kotlin_line_comment_at_first_column = true
ij_kotlin_method_annotation_wrap = split_into_lines
ij_kotlin_method_call_chain_wrap = off
ij_kotlin_method_parameters_new_line_after_left_paren = false
ij_kotlin_method_parameters_right_paren_on_new_line = false
ij_kotlin_method_parameters_wrap = off
ij_kotlin_name_count_to_use_star_import = 5
ij_kotlin_name_count_to_use_star_import_for_members = 3
ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.**
ij_kotlin_method_call_chain_wrap = normal
ij_kotlin_method_parameters_new_line_after_left_paren = true
ij_kotlin_method_parameters_right_paren_on_new_line = true
ij_kotlin_method_parameters_wrap = on_every_item
ij_kotlin_name_count_to_use_star_import = 9999
ij_kotlin_name_count_to_use_star_import_for_members = 9999
ij_java_names_count_to_use_import_on_demand = 9999
ij_kotlin_parameter_annotation_wrap = off
ij_kotlin_space_after_comma = true
ij_kotlin_space_after_extend_colon = true
@ -552,72 +90,5 @@ ij_kotlin_spaces_around_when_arrow = true
ij_kotlin_variable_annotation_wrap = off
ij_kotlin_while_on_new_line = false
ij_kotlin_wrap_elvis_expressions = 1
ij_kotlin_wrap_expression_body_functions = 0
ij_kotlin_wrap_expression_body_functions = 1
ij_kotlin_wrap_first_method_in_call_chain = false
[{*.har,*.json}]
indent_size = 2
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
ij_json_keep_line_breaks = true
ij_json_space_after_colon = true
ij_json_space_after_comma = true
ij_json_space_before_colon = true
ij_json_space_before_comma = false
ij_json_spaces_within_braces = false
ij_json_spaces_within_brackets = false
ij_json_wrap_long_lines = false
[{*.htm,*.html,*.sht,*.shtm,*.shtml}]
ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3
ij_html_align_attributes = true
ij_html_align_text = false
ij_html_attribute_wrap = normal
ij_html_block_comment_at_first_column = true
ij_html_do_not_align_children_of_min_lines = 0
ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p
ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot
ij_html_enforce_quotes = false
ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var
ij_html_keep_blank_lines = 2
ij_html_keep_indents_on_empty_lines = false
ij_html_keep_line_breaks = true
ij_html_keep_line_breaks_in_text = true
ij_html_keep_whitespaces = false
ij_html_keep_whitespaces_inside = span,pre,textarea
ij_html_line_comment_at_first_column = true
ij_html_new_line_after_last_attribute = never
ij_html_new_line_before_first_attribute = never
ij_html_quote_style = double
ij_html_remove_new_line_before_tags = br
ij_html_space_after_tag_name = false
ij_html_space_around_equality_in_attribute = false
ij_html_space_inside_empty_tag = false
ij_html_text_wrap = normal
ij_html_uniform_ident = false
[{*.markdown,*.md}]
ij_markdown_force_one_space_after_blockquote_symbol = true
ij_markdown_force_one_space_after_header_symbol = true
ij_markdown_force_one_space_after_list_bullet = true
ij_markdown_force_one_space_between_words = true
ij_markdown_keep_indents_on_empty_lines = false
ij_markdown_max_lines_around_block_elements = 1
ij_markdown_max_lines_around_header = 1
ij_markdown_max_lines_between_paragraphs = 1
ij_markdown_min_lines_around_block_elements = 1
ij_markdown_min_lines_around_header = 1
ij_markdown_min_lines_between_paragraphs = 1
[{*.yaml,*.yml}]
indent_size = 2
ij_yaml_align_values_properties = do_not_align
ij_yaml_autoinsert_sequence_marker = true
ij_yaml_block_mapping_on_new_line = false
ij_yaml_indent_sequence_value = true
ij_yaml_keep_indents_on_empty_lines = false
ij_yaml_keep_line_breaks = true
ij_yaml_sequence_on_new_line = false
ij_yaml_space_before_colon = false
ij_yaml_spaces_within_braces = true
ij_yaml_spaces_within_brackets = true

2
.git-blame-ignore-revs Normal file
View file

@ -0,0 +1,2 @@
# Ignore initial spotlessApply using ktfmt
51e9bfc67f19e16a69790a8d92bd6b1c86a76a5f

17
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: 2021 Paul Schaub <info@pgpainless.org>
#
# SPDX-License-Identifier: Apache-2.0
# These are supported funding model platforms
github: vanitasvitae # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#patreon: # Replace with a single Patreon username
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
#liberapay: # Replace with a single Liberapay username
#issuehunt: # Replace with a single IssueHunt username
#otechie: # Replace with a single Otechie username
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
#custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View file

@ -0,0 +1,35 @@
---
name: CLI Application
about: Report an issue with the pgpainless-cli utility
title: ''
labels: 'module: cli'
assignees: ''
---
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
**Version**
<!-- What version of the software are you using? -->
- `pgpainless-cli`:
**Installation Source**
<!-- Where did you install / build pgpainless-cli from? -->
- Debian Repository
- Built locally (`gradle build...`)
**To Reproduce**
<!-- Steps to reproduce the behavior: -->
1. `pgpainless-cli foo bar [...]`'
2. ...
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Additional context**
<!-- Add any other context (test keys, test messages) about the problem here. -->
```
-----BEGIN PGP FOO BAR-----
...
```

28
.github/ISSUE_TEMPLATE/library.md vendored Normal file
View file

@ -0,0 +1,28 @@
---
name: Library
about: Report an issue with the libraries pgpainless-core or pgpainless-sop
title: ''
labels: 'module: core'
assignees: ''
---
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
**Version**
<!-- What version of the software are you using? Delete lines which are not applicable. -->
- `pgpainless-core`:
- `pgpainless-sop`:
**To Reproduce**
<!-- Steps to reproduce the behavior: -->
```
Example Code Block with your Code
```
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Additional context**
<!-- Add any other context about the problem here. -->

View file

@ -17,10 +17,10 @@ name: "CodeQL"
on:
push:
branches: [ master, release/* ]
branches: [ main, release/* ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
branches: [ main ]
schedule:
- cron: '16 10 * * 0'
@ -36,7 +36,7 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
language: [ 'java-kotlin' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
@ -46,7 +46,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -57,7 +57,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -71,4 +71,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v3

View file

@ -0,0 +1,27 @@
# SPDX-FileCopyrightText: 2023 Paul Schaub <info@pgpainless.org>
#
# SPDX-License-Identifier: Apache-2.0
name: Dependencies
on:
push:
jobs:
build:
name: Dependencies
runs-on: ubuntu-latest
permissions: # The Dependency Submission API requires write permission
contents: write
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
- name: Run snapshot action
uses: mikepenz/gradle-dependency-submission@v0.8.6
with:
gradle-build-module: |-
:pgpainless-core
:pgpainless-sop
:pgpainless-cli
sub-module-mode: |-
INDIVIDUAL_DEEP

40
.github/workflows/gradle_push.yml vendored Normal file
View file

@ -0,0 +1,40 @@
# SPDX-FileCopyrightText: 2021 Paul Schaub <info@pgpainless.org>
#
# SPDX-License-Identifier: Apache-2.0
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Push
on:
push:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Build and Check
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: check jacocoRootReport
- name: Coveralls
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
with:
arguments: coveralls

33
.github/workflows/pr.yml vendored Normal file
View file

@ -0,0 +1,33 @@
# SPDX-FileCopyrightText: 2021 Paul Schaub <info@pgpainless.org>
#
# SPDX-License-Identifier: Apache-2.0
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: PR
on:
pull_request:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
- name: Build, Check and Coverage
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: check jacocoRootReport

4
.gitignore vendored
View file

@ -17,6 +17,8 @@ libs/
*.log
*.jar
local.properties
gradle.properties
!gradle-wrapper.jar
@ -29,3 +31,5 @@ pgpainless-core/.project
pgpainless-core/.settings/
push_html.sh
node_modules

40
.readthedocs.yaml Normal file
View file

@ -0,0 +1,40 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
# apt_packages:
# - libgtk-3-0
# - libasound2
# - libnss3
# - libxss1
# - libgbm1
# - libxshmfence1
tools:
python: "3.9"
# You can also specify other tool versions:
# nodejs: "16"
# rust: "1.55"
# golang: "1.17"
# jobs:
# post_install:
# - npm install -g @mermaid-js/mermaid-cli
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# If using Sphinx, optionally build your docs in additional formats such as PDF
formats:
- pdf
- epub
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt

View file

@ -1,32 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: PGPainless
Upstream-Contact: Paul Schaub <info@pgpainless.org>
Source: https://pgpainless.org
# Sample paragraph, commented out:
#
# Files: src/*
# Copyright: $YEAR $NAME <$CONTACT>
# License: ...
# Gradle build tool
Files: gradle*
Copyright: 2015 the original author or authors.
License: Apache-2.0
# PGPainless Logo
Files: assets/repository-open-graph.png
Copyright: 2021 Paul Schaub <info@pgpainless.org>
License: CC-BY-3.0
Files: assets/test_vectors/*
Copyright: 2018 Paul Schaub <info@pgpainless.org>
License: CC0-1.0
Files: pgpainless-core/src/test/resources/*
Copyright: 2020 Paul Schaub <info@pgpainless.org>
License: CC0-1.0
Files: audit/*
Copyright: 2021 Paul Schaub <info@pgpainless.org>
License: CC0-1.0

View file

@ -5,6 +5,534 @@ SPDX-License-Identifier: CC0-1.0
# PGPainless Changelog
## 1.7.7-SNAPSHOT
- Bump `bcpg-jdk8on` to `1.81`
- Bump `bcprov-jdk18on` to `1.81`
## 1.7.6
- Fix `RevocationSignatureBuilder` properly calculating third-party signatures of type `KeyRevocation` (delegation revocations)
- Enable support for native images
- Re-enable shadow plugin and build fat-jar
## 1.7.5
- Actually attempt to fix Kotlin desugaring.
- Bump javaSourceCompatibility and javaTargetCompatibility to 11
- Bump gradle-wrapper to 8.8
## 1.7.4
- Fix proper Kotlin desugaring for Java 8
## 1.7.3
- Bump `bcpg-jdk8on` to `1.80`
- Bump `bcprov-jdk18on` to `1.80`
- Add dependency on `bcutil-jdk18on` as a workaround
- Ignore unknown type signatures on certificates
- Fix typo on signature creation bounds check (thanks @elduffy)
- Fix superfluous newline added in CRLF encoding (thanks @bjansen)
- Bump `sop-java` to `1.10.0`
- SOP inline-sign: Do not apply compression
## 1.7.2
- Fix bug in `KeyRingInfo.lastModified` (thanks to @Jerbell, @sosnovsky for reporting)
- Bump `sop-java` to `10.0.3`
- allow multiple arguments `--with-key-password` in `revoke-key` command
- Properly pass `--old-key-password` and `--new-key-password` options as indirect arguments in `change-key-password` command
## 1.7.1
- Bump `sop-java` to `10.0.2`
- Downgrade `logback-core` and `logback-classic` to `1.2.13` (fix CLI spam)
## 1.7.0
- Bump `bcpg-jdk8on` to `1.78.1`
- Bump `bcprov-jdk18on` to `1.78.1`
- Bump `logback-core` and `logback-classic` to `1.4.14`
- `pgpainless-core`
- Rewrote most of the codebase in Kotlin
- Removed `OpenPgpMetadata` (`decryptionStream.getResult()`) in favor of `MessageMetadata` (`decryptionStream.getMetadata()`)
- Removed support for generating EC keys over non-standard curve `secp256k1`
- Properly feed EOS tokens to the pushdown automaton when reaching the end of stream (thanks @iNPUTmice)
- Do not choke on unknown signature subpackets (thanks @Jerbell)
- Prevent timing issues resulting in subkey binding signatures predating the subkey (@thanks Jerbell)
- Rename LibrePGP-related `Feature` enums:
- `GNUPG_AEAD_ENCRYPTED_DATA` -> `LIBREPGP_OCB_ENCRYPTED_DATA`
- `GNUPG_VERSION_5_PUBLIC_KEY` -> `LIBREPGP_VERSION_5_PUBLIC_KEY`
- Properly reject signatures by non-signing primary keys
- Add `EncryptionBuilder.discardOutput()` (useful for detached signing)
- Remove support for generation of keys over non-standard `secp256k1` curve
- Add base support for padding packets
- Do not choke on LibrePGP OED packets
- Supersede `addPassphrase()`/`addDecryptionPassphrase()` methods with more clear `addMessagePassphrase()`
- `pgpainless-sop`, `pgpainless-cli`
- Bump `sop-java` to `10.0.1`, implementing [SOP Spec Revision 10](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-10.html)
- Change API of `sop.encrypt` to return a `ReadyWithResult<EncryptionResult>` to expose the session key
- `decrypt --verify-with`: Fix to not throw `NoSignature` exception (exit code 3) if `VERIFICATIONS` is empty
- Separate signature verification operations into `SOPV` interface
- Add `version --sopv` option
- Throw `BadData` error when passing KEYS where CERTS are expected.
- `armor`: Remove `--label` option
## 1.6.8
- Bump `sop-java` to `7.0.2`
- SOP `change-key-password`: Fix reading password from indirect parameter instead of erroneously passing filename (fixes #453)
- SOP `revoke-key`: Allow for multiple `--with-key-password` options
## 1.6.7
- SOP: Fix OOM error when detached-signing large amounts of data (fix #432)
- Move `CachingBcPublicKeyDataDecryptorFactory` from `org.bouncycastle` packet to `org.pgpainless.decryption_verification` to avoid package split (partially addresses #428)
- Basic support for Java Modules for `pgpainless-core` and `pgpainless-sop`
- Added `Automatic-Module-Name` directive to gradle build files
## 1.6.6
- Downgrade `logback-core` and `logback-classic` to `1.2.13` to fix #426
## 1.6.5
- Add `SecretKeyRingEditor.setExpirationDateOfSubkey()`
## 1.6.4
- Bump `bcpg-jdk8on` to `1.77`
- Bump `bcprov-jdk18on` to `1.77`
- Bump `logback-core` and `logback-classic` to `1.4.13`
- Properly feed EOS tokens to the pushdown automaton when reaching the end of stream (thanks @iNPUTmice)
- Do not choke on unknown signature subpackets (thanks @Jerbell)
- Prevent timing issues resuting in subkey binding signatures predating the subkey (@thanks Jerbell)
## 1.6.3
- Bump `sop-java` to `7.0.1`
- `decrypt --verify-with`: Fix to not throw `NoSignature` exception (exit code 3) if `VERIFICATIONS` is empty
## 1.6.2
- Switch `bcpg` and `bcprov` artifacts from `-jdk15to18`variant to `-jdk18on`
- Bump `bcpg-jdk8on` to `1.76`
- Bump `bcprov-jdk18on` to `1.76`
- Add `EncryptionOptions.setAllowEncryptionWithMissingKeyFlags()` to properly allow
encrypting to legacy keys which do not carry any key flags.
- Allow overriding of reference time in `EncryptionOptions` and `SigningOptions`.
## 1.6.1
- `KeyRingBuilder`: Require UTF8 when adding user-ID via `addUserId(byte[])`
- `pgpainless-sop`: Remove dependency on jetbrains annotations
- Add `CertificateAuthority` interface to allow integration with [`pgpainless-wot`](https://github.com/pgpainless/pgpainless-wot)
- Add `EncryptionOptions.addAuthenticatableRecipients()` method
- Add `MessageMetadata.isAuthenticatablySignedBy()` method
## 1.6.0
- Bump `sop-java` to `7.0.0`, implementing [SOP Spec Revision 07](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-07.html)
- Implement `revoke-key` subcommand and API
- Implement `change-key-password` subcommand and API
- `generate-key`: Add support for new `--signing-only` option
- Move some methods related to password changing from `SecretKeyRingEditor` to `KeyRingUtils`
## 1.5.7
- Bump `sop-java` to `6.1.1`
- `decrypt --verify-with`: Fix to not throw `NoSignature` exception (exit code 3) if `VERIFICATIONS` is empty
## 1.5.6
- Bump `jacoco` to `0.8.8` (thanks @hkos)
- Ignore malformed, non-UTF8 user-IDs on certificates
- `KeyRingReader.readPublicKeyRingCollection()`: Extract and return public keys from encountered secret keys
- Add some utility methods to `KeyRingInfo`:
- `getValidSubkeys()` only returns validly bound sub-keys
- Add some utility methods to `SignatureUtils`:
- `getDelegations()` returns all third-party signatures made over the primary key
- `get3rdPartyCertificationsFor(userId)` returns all third-party certification signatures made over the given user-id
- Add some utility methods to `SignatureSubpacketsUtil`:
- `isExportable()` will return true if the signature is *not* marked as non-exportable
- `getTrustDepthOr()` returns the signatures trust-depth, or a default value if there is no trust-signature subpacket
- `getTrustAmountOr()` returns the signatures trust-amount, or a default value if there is no trust-signature subpacket
## 1.5.5
- Bump `bcpg-jdk15to18` to `1.75`
- Bump `bcprov-jdk15to18` to `1.75`
- Bump `checkstyle` to `10.12.1` to fix build dependency on [vulnerable guava](https://github.com/pgpainless/pgpainless/security/dependabot/6).
- `SecretKeyRingEditor`:
- Rename `createRevocationCertificate()` to `createRevocation()`
- Add `createMinimalRevocationCertificate()` method to generate OpenPGP v6-style self-certifying revocation certificates
## 1.5.4
- Bump `bcpg-jdk15to18` to `1.74`
- Bump `bcprov-jdk15to18` to `1.74`
- Remove unused methods from `SignatureUtils`
- Encryption: Allow anonymous recipients using wildcard key-IDs
- Add `SignatureSubpacketsUtil.getRegularExpressions()`
- Tests, tests, tests
## 1.5.3
- Fix minimal bit-strength check for signing-subkeys accidentally comparing the bit-strength of the primary key
- `SigningOptions`: Add new methods to add signatures using a single, chosen signing subkey
## 1.5.2
- Bugfix: Create proper direct-key signatures
- `KeyRingTemplates`:
- Add `rsaKeyRing()` for generating RSA keys with primary key and dedicated signing, encryption subkeys
- Reduce number of template methods by replacing `UserId`, `String` arguments with `CharSequence`
- Add `MessageMetadata.getRecipientKeyIds()`
- Work towards more null-safe API by annotating methods in `EncryptionOptions`, `SigningOptions`, `KeyRingInfo`, `PGPainless` with `@Nonnull`, `@Nullable`
- `KeyRingUtils`: Removed `removeSecretKey()` in favour of `stripSecretKey()`
- General code cleanup
- SOP: generating keys with `rfc4880` profile now generates key with primary key and subkeys
- Deprecate ElGamal key type
- Key generation: Set expiration period of 5 years by default
- Set AES-128 as default fallback symmetric algorithm
- `ProducerOptions`: Allow setting custom version header when encrypting/signing message
## 1.5.2-rc1
- Bump `sop-java` to `6.1.0`
- Normalize `OpenPgpMessageInputStream.read()` behaviour when reading past the stream
- Instead of throwing a `MalformedOpenPgpMessageException` which could throw off unsuspecting parsers,
we now simply return `-1` like every other `InputStream`.
## 1.5.1
- SOP: Emit signature `mode:{binary|text}` in `Verification` results
- core: Relax constraints on decryption subkeys to improve interoperability with broken clients
- Allow decryption with revoked keys
- Allow decryption with expired keys
- Allow decryption with erroneously addressed keys without encryption key flags
## 1.5.0
- Bump `bcpg-jdk15to18` to `1.73`
- Bump `bcprov-jdk15to18` to `1.73`
- Introduce `OpenPgpv6Fingerprint` class
- Bump `sop-java` to `5.0.0`, implementing [SOP Spec Revision 05](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-05.html)
- Add support for `list-profiles` subcommand (`generate-key` only for now)
- `generate-key`: Add support for `--profile=` option
- Add profile `draft-koch-eddsa-for-openpgp-00` which represents status quo.
- Add profile `rfc4880` which generates keys based on 4096-bit RSA.
- Bump `sop-java` to `6.0.0`, implementing [SOP Spec Revision 06](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-06.html)
- `encrypt`: Add support for `--profile=` option
- Add profile `rfc4880` to reflect status quo
- `version`: Add support for `--sop-spec` option
## 1.4.6
- Bump `sop-java` to `4.1.2`
- Fix `decrypt --verify-with` to not throw `NoSignature` exception (exit code 3) if `VERIFICAIONS` is empty.
## 1.4.5
- Bugfix: Direct-Key signatures are calculated over the signee key only, not the signer key + signee key
- Security: Fix faulty bit-strength policy check for signing subkeys
## 1.4.4
- Fix expectations on subpackets of v3 signatures (thanks @bjansen)
- Properly verify v3 signatures, which do not yet have signature subpackets, yet we required them to have
a hashed creation date subpacket.
## 1.4.3
- Bump `sop-java` to `4.1.1`
- Reuse shared test suite of `sop-java`
- Add `EncryptionOptions.hasEncryptionMethod()`
- SOP `encrypt`: Throw `MissingArg` exception if no encryption method was provided
- Fix redundant dot in exception message (thanks @DenBond7)
## 1.4.2
- Properly decrypt messages without MDC packets when `ConsumerOptions.setIgnoreMDCErrors(true)` is set
- Fix crash in `sop generate-key --with-key-password` when more than one user-id is given
- Revert integration with `pgp-certificate-store`
- Bump `sop-java` to `4.1.0`
## 1.4.1
- Add `UserId.parse()` method to parse user-ids into their components
## 1.4.0
- `sop generate-key`: Add support for keys without user-ids
- `sop inline-sign --as=clearsigned`: Make signature in TEXT mode
- Make countermeasures against [KOpenPGP](https://kopenpgp.com/) attacks configurable
- Countermeasures are now disabled by default since they are costly and have a specific threat model
- Can be enabled by calling `Policy.setEnableKeyParameterValidation(true)`
## 1.4.0-rc2
- Bump `bcpg-jdk15to18` to `1.72.3`
- Use BCs `PGPEncryptedDataList.extractSessionKeyEncryptedData()` method
to do decryption using session keys. This enables decryption of messages
without encrypted session key packets.
- Use BCs `PGPEncryptedDataList.isIntegrityProtected()` to check for integrity protection
- Depend on `pgp-certificate-store`
- Add `ConsumerOptions.addVerificationCerts(PGPCertificateStore)` to allow sourcing certificates from
e.g. a [certificate store implementation](https://github.com/pgpainless/cert-d-java).
- Make `DecryptionStream.getMetadata()` first class
- Deprecate `DecryptionStream.getResult()`
## 1.4.0-rc1
- Reimplement message consumption via new `OpenPgpMessageInputStream`
- Fix validation of prepended signatures (#314)
- Fix validation of nested signatures (#319)
- Reject malformed messages (#237)
- Utilize new `PDA` syntax verifier class
- Allow for custom message syntax via `Syntax` class
- Gracefully handle `UnsupportedPacketVersionException` for signatures
- Allow plugin decryption code (e.g. to add support for hardware-backed keys (see #318))
- Add `HardwareSecurity` utility class
- Add `GnuPGDummyKeyUtil` which can be used to mimic GnuPGs proprietary S2K extensions
for keys which were placed on hardware tokens
- Add `OpenPgpPacket` enum class to enumerate available packet tags
- Remove old decryption classes in favor of new implementation
- Removed `DecryptionStream` class and replaced with new abstract class
- Removed `DecryptionStreamFactory`
- Removed `FinalIOException`
- Removed `MissingLiteralDataException` (replaced by `MalformedOpenPgpMessageException`)
- Introduce `MessageMetadata` class as potential future replacement for `OpenPgpMetadata`.
- can be obtained via `((OpenPgpMessageInputStream) decryptionStream).getMetadata();`
- Add `CachingBcPublicKeyDataDecryptorFactory` which can be extended to prevent costly decryption
of session keys
- Fix: Only verify message integrity once
- Remove unnecessary `@throws` declarations on `KeyRingReader` methods
- Remove unnecessary `@throws` declarations on `KeyRingUtils` methods
- Add `KeyIdUtil.formatKeyId(long id)` to format hexadecimal key-ids.
- Add `KeyRingUtils.publicKeys(PGPKeyRing keys)`
- Remove `BCUtil` class
## 1.3.18
- Bump `sop-java` to `4.1.2`
- Fix `decrypt --verify-with XYZ` not throwing `NoSignature` exception (exit code 3) if `VERIFICATIONS` is empty (#415)
## 1.3.17
- Bugfix: Direct-Key signatures are calculated over the signee key only, not the signer key + signee key
- Security: Fix faulty bit-strength policy check for signing subkeys
## 1.3.16
- Bump `sop-java` to `4.1.0`
- Bump `gradlew` to `7.5`
## 1.3.15
- Fix crash in `sop generate-key --with-key-password` when more than one user-id is given
- `sop generate-key`: Allow key generation without user-ids
- `sop inline-sign --as=clearsigned`: Make signatures of type 'text' instead of 'binary'
## 1.3.14
- Bump `bcpg` to `1.72.3`
- Fix DSA key parameter check
- Use proper method to unlock private signing keys when creating detached signatures
## 1.3.13
- Bump `sop-java` to `4.0.7`
## 1.3.12
- Bump `sop-java` to `4.0.5`
- Fix: `sop inline-sign`: Adopt `--as=clearsigned` instead of `--as=cleartextsigned`
- SOP: Hide `Version: PGPainless` armor header in all armored outputs
- Fix: `sop armor`: Do not re-armor already armored data
## 1.3.11
- Fix: When verifying subkey binding signatures with embedded recycled primary
key binding signatures, do not reject signature if primary key binding
predates subkey binding
- SOP `verify`: Forcefully expect `data()` to be non-OpenPGP data
- SOP `sign`: Fix matching of keys and passphrases
- CLI: Added tons of tests \o/
## 1.3.10
- Bump `sop-java` to `4.0.3`
- Fix: Fix NPE when verifying signature made by key without key flags on direct-key signature
## 1.3.9
- Bump `sop-java` to `4.0.2`
- SOP: Improve exception handling
## 1.3.8
- Bump `bcprov` to `1.72`
- Bump `bcpg` to `1.72.1`
- Add `ProducerOptions.setHideArmorHeaders(boolean)` to hide automatically added armor headers
in encrypted messages
## 1.3.7
- Bugfix: Fix signature verification when `DecryptionStream` is drained byte-by-byte using `read()` call
- Add `KeyRingUtils.injectCertification(keys, certification)`
- Add `PGPainless.asciiArmor(key, outputStream)`
- Add `PGPainless.asciiArmor(signature)`
## 1.3.6
- Remove deprecated methods
- `ArmorUtils.createArmoredOutputStreamFor()` -> use `ArmorUtils.toAsciiArmoredStream()` instead
- `EncryptionResult.getSymmetricKeyAlgorithm()` -> use `EncryptionResult.getEncryptionAlgorithm()` instead
- Add `KeyRingInfo.getRevocationState()`
- Better way to determine whether a key is revoked
- Add `SigningOptions.addDetachedSignature(protector, key)` shortcut method
- Add `EncryptionOptions.get()`, `ConsumerOptions.get()` factory methods
- Add support for generating keys without user-id (only using `PGPainless.buildKeyRing()` for now)
- Switch to `SHA256` as default `S2K` hash algorithm for secret key protection
- Allow to set custom reference time when modifying secret keys
- Add diagnostic test to explore system PRNG performance
## 1.3.5
- Add `KeyRingInfo.isCapableOfSigning()`
- Add `KeyRingReader.readKeyRing(*)` methods that can take both secret- and public keys
- Add manpages
- Add script to generate manpages from sop-java-picocli
- Build website from main branch
## 1.3.4
- Fix `KeyRingInfo.isUsableForEncryption()`, `KeyRingInfo.isUsableForSigning()` not detecting revoked primary keys
- Bump `sop-java` and `sop-java-picocli` to `4.0.1`
- Fixes help text strings being resolved properly while allowing to override executable name
## 1.3.3
- Improve test compatibility against older JUnit versions
- Fix tests that read from jar-embedded resources (thanks @jcharaoui)
- `pgpainless-cli help`: Fix i18n strings
## 1.3.2
- Add `KeyRingInfo(Policy)` constructor
- Delete unused `KeyRingValidator` class
- Add `PGPainless.certify()` API
- `certify().userIdOnCertificate()` can be used to certify other users User-IDs
- `certify().certificate()` can be used to create direct-key signatures on other users keys
- We now have a [User Guide!](https://pgpainless.rtfd.io/)
- Fixed build script
- `pgpainless-cli`s `gradle build` task no longer builds fat jar
- Fat jars are now built by dedicated shadow plugin
- Fix third-party assigned user-ids on keys to accidentally get picked up as primary user-id
- Add `KeyRingUtils.publicKeyRingCollectionFrom(PGPSecretKeyRingCollection)`
- Add `SecretKeyRingEditor.replaceUserId(oldUid, newUid, protector)`
- Prevent adding `SymmetricKeyAlgorithm.NULL` (unencrypted) as encryption algo preference when generating keys
## 1.3.1
- Fix reproducibility of builds by setting fixed file permissions in archive task
- Improve encryption performance by buffering streams
- Fix `OpenPgpMetadata.isEncrypted()` to also return true for symmetrically encrypted messages
- SOP changes
- decrypt: Do not throw `NoSignatures` if no signatures found
- decrypt: Throw `BadData` when ciphertext is not encrypted
## 1.3.0
- Add `RevokedKeyException`
- `KeyRingUtils.stripSecretKey()`: Disallow stripping of primary secret key
- Remove support for reading compressed detached signatures
- Add `PGPainless.generateKeyRing().modernKeyRing(userId)` shortcut method without passphrase
- Add `CollectionUtils.addAll(Iterator, Collection)`
- Add `SignatureUtils.getSignaturesForUserIdBy(key, userId, keyId)`
- Add `OpenPgpFingerprint.parseFromBinary(bytes)`
- `SignatureUtils.wasIssuedBy()`: Add support for V5 fingerprints
- Prevent integer overflows when setting expiration dates
- SOP: Properly throw `KeyCannotDecrypt` exception
- Fix performance issues of encrypt and sign operations by using buffering
- Fix performance issues of armor and dearmor operations
- Bump dependency `sop-java` to `4.0.0`
- Add support for SOP specification version 04
- Implement `inline-sign`
- Implement `inline-verify`
- Rename `DetachInbandSignatureAndMessageImpl` to `InlineDetachImpl`
- Rename `SignImpl` to `DetachedSignImpl`
- Rename `VerifyImpl` to `DetachedVerifyImpl`
- Add support for `--with-key-password` option in `GenerateKeyImpl`, `DetachedSignImpl`, `DecryptImpl`, `EncryptImpl`.
- `InlineDetachImpl` now supports 3 different message types:
- Messages using Cleartext Signature Framework
- OpenPGP messages using OnePassSignatures
- OpenPGP messages without OnePassSignatures
- Introduce `OpenPgpMetadata.isCleartextSigned()`
## 1.2.2
- `EncryptionOptions.addRecipients(collection)`: Disallow empty collections to prevent misuse from resulting in unencrypted messages
- Deprecate default policy factory methods in favor of policy factory methods with expressive names
- Another fix for OpenPGP data detection
- We now inspect the first packet of the data stream to figure out, whether it is plausible OpenPGP data, without exhausting the stream
## 1.2.1
- Bump `sop-java` dependency to `1.2.3`
- Bump `slf4j` dependency to `1.7.36`
- Bump `logback` dependency to `1.2.11`
- Add experimental support for creating signatures over pre-calculated `MessageDigest` objects.
- `BcHashContextSigner.signHashContext()` can be used to create OpenPGP signatures over manually hashed data.
This allows applications to do the hashing themselves.
- Harden detection of binary/ascii armored/non-OpenPGP data
- Add `ConsumerOptions.forceNonOpenPgpData()` to force PGPainless to handle data as non-OpenPGP data
- This is a workaround for when PGPainless accidentally mistakes non-OpenPGP data for binary OpenPGP data
- Implement "smart" hash algorithm policies, which take the 'usage-date' for algorithms into account
- This allows for fine-grained signature hash algorithm policing with usage termination dates
- Switch to smart signature hash algorithm policies by default
- PGPainless now accepts SHA-1 signatures if they were made before 2013-02-01
- We also now accept RIPEMD160 signatures if they were made before 2013-02-01
- We further accept MD5 signatures made prior to 1997-02-01
## 1.2.0
- Improve exception hierarchy for key-related exceptions
- See [PR](https://github.com/pgpainless/pgpainless/pull/261) for more information on how to migrate.
- Bump Bouncy Castle to `1.71`
- Switch from `bcpg-jdk15on:1.70` to `bcpg-jdk15to18:1.71`
- Switch from `bcprov-jdk15on:1.70` to `bcprov-jdk15to18:1.71`
- Implement merging of certificate copies
- can be used to implement updating certificates from key servers
- Fix `KeyRingUtils.keysPlusPublicKey()`
- Add support for adding `PolicyURI` and `RegularExpression` signature subpackets on signatures
## 1.1.5
- SOP encrypt: match signature type when using `encrypt --as=` option
- `ProducerOptions.setEncoding()`: The encoding is henceforth only considered metadata and will no longer trigger CRLF encoding.
- This fixes broken signature generation for mismatching (`StreamEncoding`,`DocumentSignatureType`) tuples.
- Applications that rely on CRLF-encoding can request PGPainless to apply this encoding by calling `ProducerOptions.applyCRLFEncoding(true)`.
- Rename `KeyRingUtils.removeSecretKey()` to `stripSecretKey()`.
- Add handy `SignatureOptions.addSignature()` method.
- Fix `ClassCastException` when evaluating a certificate with third party signatures. Thanks @p-barabas for the initial report and bug fix!
## 1.1.4
- Add utility method `KeyRingUtils.removeSecretKey()` to remove secret key part from key ring
- This can come in handy when using primary keys stored offline
- Add `EncryptionResult.isEncryptedFor(certificate)`
- `ArmorUtils.toAsciiArmoredString()` methods now print out primary user-id and brief information about further user-ids (thanks @bratkartoffel for the patch)
- Methods of `KeyRingUtils` and `ArmorUtils` classes are now annotated with `@Nonnull/@Nullable`
- Enums `fromId(code)` methods are now annotated with `@Nullable` and there are now `requireFromId(code)` counterparts which are `@Nonnull`.
- `ProducerOptions.setForYourEyesOnly()` is now deprecated (reason is deprecation in the
- [crypto-refresh-05](https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-05.html#name-special-filename-_console-d) document)
- Add `SessionKey.toString()`
- Partially fix generation of malformed signature packets when using different combinations of `StreamEncoding` and `DocumentSignatureType` values
- Unfortunately PGPainless still produces broken signatures when using either `StreamEncoding.TEXT` or `StreamEncoding.UTF8` in combination with `DocumentSignatureType.BINARY_DOCUMENT`.
- Deprecate `ProducerOptions.setEncoding(StreamEncoding)`
- Will be removed in a future release
- Remove `StreamEncoding.MIME` (was removed from the standard)
## 1.1.3
- Make `SigningOptions.getSigningMethods()` part of internal API
- Fix crash when trying to do verification of unmatched `SignersUserId` signature subpacket
- For now, verification of `SignersUserId` is disabled but can be enabled via `Policy.setSignerUserIdValidationLevel()`
- Initial support for `OpenPgpV5Fingerprint`
- Add `OpenPgpFingerprint.parse(string)`
- Security: Fix `KeyRingInfo.getValidAndExpiredUserIds()` accidentally including unbound user-ids
## 1.0.5
- Security: Fix `KeyRingInfo.getValidAndExpiredUserIds()` accidentally including unbound user-ids
## 1.1.2
- Fix `keyRingInfo.getEmailAddresses()` incorrectly matching some mail addresses (thanks @bratkartoffel for reporting and initial patch proposal)
- Fix generic type of `CertificationSubpackets.Callback`
- Add `KeyRingInfo.isUsableForEncryption()`
- Add `PGPainless.inspectKeyRing(key, date)`
- Allow custom key creation dates during key generation
- Reject subkeys with bindings that predate key generation
- `EncryptionOptions.addRecipient()`: Transform `NoSuchElementException` into `IllegalArgumentException` with proper error message
- Fix `ClassCastException` by preventing accidental verification of 3rd-party-issued user-id revocation with primary key.
- Fix `NullPointerException` when trying to verify malformed signature
## 1.1.1
- Add `producerOptions.setComment(string)` to allow adding ASCII armor comments when creating OpenPGP messages (thanks @ferenc-hechler)
- Simplify consumption of cleartext-signed data
- Change default criticality of signature subpackets
- Issuer Fingerprint: critical -> non-critical
- Revocable: non-critical -> critical
- Issuer KeyID: critical -> non-critical
- Preferred Algorithms: critical -> non-critical
- Revocation Reason: critical -> non-critical
## 1.1.0
- `pgpainless-sop`: Update `sop-java` to version 1.2.0
- Treat passwords and session keys as indirect parameters
This means they are no longer treated as string input, but pointers to files or env variables
## 1.0.4
- Yet another patch for faulty ASCII armor detection 😒
## 1.0.3
- Fix detection of unarmored data in signature verification
## 1.0.2
- Update SOP implementation to specification revision 03
- Move `sop-java` and `sop-java-picocli` modules to [its own repository](https://github.com/pgpainless/sop-java)
- `OpenPGPV4Fingerprint`: Hex decode bytes in constructor
- Add `ArmorUtils.toAsciiArmoredString()` for single key
- Fix `ClassCastException` when retrieving `RevocationKey` subpackets from signatures
- Fix `pgpainless-sop` gradle script
- it now automatically pulls in transitive dependencies
## 1.0.1
- Fix sourcing of preferred algorithms by primary user-id when key is located via key-id
## 1.0.0
- Introduce `DateUtil.toSecondsPrecision()`
- Clean JUnit tests, fix code style issues and fix typos in documentation

1
CNAME Normal file
View file

@ -0,0 +1 @@
gh.pgpainless.org

99
LICENSES/CC-BY-SA-3.0.txt Normal file
View file

@ -0,0 +1,99 @@
Creative Commons Attribution-ShareAlike 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License.
b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License.
c. "Creative Commons Compatible License" means a license that is listed at http://creativecommons.org/compatiblelicenses that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License.
d. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership.
e. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike.
f. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License.
g. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast.
h. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work.
i. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
j. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images.
k. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections;
b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified.";
c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and,
d. to Distribute and Publicly Perform Adaptations.
e. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and,
iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License.
The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved.
4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested.
b. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License.
c. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties.
d. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
e. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License.
Creative Commons may be contacted at http://creativecommons.org/.

View file

@ -6,28 +6,33 @@ SPDX-License-Identifier: Apache-2.0
# PGPainless - Use OpenPGP Painlessly!
[![Travis (.com)](https://travis-ci.com/pgpainless/pgpainless.svg?branch=master)](https://travis-ci.com/pgpainless/pgpainless)
[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/pgpainless-core)](https://search.maven.org/artifact/org.pgpainless/pgpainless-core)
[![Coverage Status](https://coveralls.io/repos/github/pgpainless/pgpainless/badge.svg?branch=master)](https://coveralls.io/github/pgpainless/pgpainless?branch=master)
[![JavaDoc](https://badgen.net/badge/javadoc/yes/green)](https://pgpainless.org/releases/latest/javadoc/)
[![Interoperability Test-Suite](https://badgen.net/badge/Sequoia%20Test%20Suite/%232/green)](https://tests.sequoia-pgp.org/)
[![Build Status](https://github.com/pgpainless/pgpainless/actions/workflows/gradle_push.yml/badge.svg)](https://github.com/pgpainless/pgpainless/actions/workflows/gradle_push.yml)
[![Coverage Status](https://coveralls.io/repos/github/pgpainless/pgpainless/badge.svg?branch=main)](https://coveralls.io/github/pgpainless/pgpainless?branch=main)
[![Interoperability Test-Suite](https://badgen.net/badge/Sequoia%20Test%20Suite/results/green)](https://tests.sequoia-pgp.org/)
[![PGP](https://img.shields.io/badge/pgp-A027%20DB2F%203E1E%20118A-blue)](https://keyoxide.org/7F9116FEA90A5983936C7CFAA027DB2F3E1E118A)
[![REUSE status](https://api.reuse.software/badge/github.com/pgpainless/pgpainless)](https://api.reuse.software/info/github.com/pgpainless/pgpainless)
[![Documentation Status](https://readthedocs.org/projects/pgpainless/badge/?version=latest)](https://pgpainless.readthedocs.io/en/latest/?badge=latest)
**PGPainless is an easy-to-use OpenPGP library for Java and Android applications**
[![Packaging status](https://repology.org/badge/vertical-allrepos/pgpainless.svg)](https://repology.org/project/pgpainless/versions)
[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/pgpainless-core)](https://search.maven.org/artifact/org.pgpainless/pgpainless-core)
## About
PGPainless aims to make using OpenPGP in Java projects as simple as possible.
It does so by introducing an intuitive Builder structure, which allows easy
setup of encryption/decryption operations, as well as straight forward key generation.
PGPainless is based around the Bouncycastle java library and can be used on Android down to API level 10.
It can be configured to either use the Java Cryptographic Engine (JCE), or Bouncycastles lightweight reimplementation.
PGPainless is based around the Bouncy Castle java library and can be used on Android down to API level 10.
It can be configured to either use the Java Cryptographic Engine (JCE), or Bouncy Castles lightweight reimplementation.
While signature verification in Bouncycastle is limited to signature correctness, PGPainless goes much further.
While signature verification in Bouncy Castle is limited to signature correctness, PGPainless goes much further.
It also checks if signing subkeys are properly bound to their primary key, if keys are expired or revoked, as well as
if keys are allowed to create signatures in the first place.
These rigorous checks make PGPainless stand out from other Java-based OpenPGP libraries and are the reason why
PGPainless currently [*scores second place* on Sequoia-PGPs Interoperability Test-Suite](https://tests.sequoia-pgp.org).
PGPainless currently [*scores first place* on Sequoia-PGPs Interoperability Test-Suite](https://tests.sequoia-pgp.org).
> At FlowCrypt we are using PGPainless in our Kotlin code bases on Android and on server side.
> The ergonomics of legacy PGP tooling on Java is not very good, and PGPainless improves it greatly.
@ -40,12 +45,20 @@ PGPainless currently [*scores second place* on Sequoia-PGPs Interoperability Tes
>
> -Mario @ Cure53.de
## Get Started
The very easiest way to start using OpenPGP on Java/Kotlin based systems is to use an implementation of [sop-java](https://github.com/pgpainless/sop-java).
`sop-java` defines a very stripped down API and is super easy to get started with.
Luckily PGPainless provides an implementation for the `sop-java` interface definitions in the form of [pgpainless-sop](pgpainless-sop/README.md).
If you need more flexibility, directly using `pgpainless-core` is the way to go.
## Features
Most of PGPainless' features can be accessed directly from the `PGPainless` class.
If you want to get started, this class is your friend :)
For further details you should check out the [javadoc](https://pgpainless.org/releases/latest/javadoc/)!
For further details you should check out the [javadoc](https://javadoc.io/doc/org.pgpainless/pgpainless-core)!
### Handle Keys
Reading keys from ASCII armored strings or from binary files is easy:
@ -99,7 +112,7 @@ There are some predefined key archetypes, but it is possible to fully customize
KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)
).addUserId("Juliet <juliet@montague.lit>")
.addUserId("xmpp:juliet@capulet.lit")
.setPassphrase("romeo_oh_Romeo<3")
.setPassphrase(Passphrase.fromPassword("romeo_oh_Romeo<3"))
.build();
```
@ -119,7 +132,7 @@ Still it allows you to manually specify which algorithms to use of course.
.addRecipient(aliceKey)
.addRecipient(bobsKey)
// optionally encrypt to a passphrase
.addPassphrase(Passphrase.fromPassword("password123"))
.addMessagePassphrase(Passphrase.fromPassword("password123"))
// optionally override symmetric encryption algorithm
.overrideEncryptionAlgorithm(SymmetricKeyAlgorithm.AES_192),
new SigningOptions()
@ -159,10 +172,10 @@ This behaviour can be modified though using the `Policy` class.
decryptionStream.close();
// Result contains information like signature status etc.
OpenPgpMetadata metadata = decryptionStream.getResult();
MessageMetadata metadata = decryptionStream.getMetadata();
```
*After* the `DecryptionStream` was closed, you can get metadata about the processed data by retrieving the `OpenPgpMetadata`.
*After* the `DecryptionStream` was closed, you can get metadata about the processed data by retrieving the `MessageMetadata`.
Again, this object will contain information about how the message was encrypted, who signed it and so on.
#### Many more examples can be found in the [examples package](pgpainless-core/src/test/java/org/pgpainless/example)!!!
@ -178,31 +191,38 @@ repositories {
}
dependencies {
implementation 'org.pgpainless:pgpainless-core:1.0.0'
implementation 'org.pgpainless:pgpainless-core:1.7.6'
}
```
## About
PGPainless is a by-product of my [Summer of Code 2018 project](https://blog.jabberhead.tk/summer-of-code-2018/)
implementing OpenPGP support for the XMPP client library [Smack](https://github.com/igniterealtime/Smack).
For that project I was in need of a simple-to-use OpenPGP library.
Originally I was going to use [Bouncy-GPG](https://github.com/neuhalje/bouncy-gpg) for my project,
but ultimately I decided to create my own OpenPGP library which better fits my needs.
However, PGPainless was heavily influenced by Bouncy-GPG.
To reach out to the development team, feel free to send a mail: info@pgpainless.org
## Professional Support
Do you need a custom feature? Are you unsure of what's the best way to integrate PGPainless into your product?
We offer paid professional services. Don't hesitate to send an inquiry to [info@pgpainless.org](mailto:info@pgpainless.org).
## Development
Join the projects IRC channel [**#pgpainless**](ircs://irc.oftc.net:6697/#pgpainless) on OFTC if you have any questions!
PGPainless is developed in - and accepts contributions from - the following places:
* [Github](https://github.com/pgpainless/pgpainless)
* [Codeberg](https://codeberg.org/PGPainless/pgpainless)
We are using SemVer (MAJOR.MINOR.PATCH) versioning, although MINOR releases could contain breaking changes from time to time.
If you want to contribute a bug fix, please check the `release/X.Y` branches first to see, what the oldest release is
which contains the bug you are fixing. That way we can update older revisions of the library easily.
Please follow the [code of conduct](CODE_OF_CONDUCT.md) if you want to be part of the project.
## Acknowledgements
Development on PGPainless is generously sponsored by [FlowCrypt.com](https://flowcrypt.com). Thank you very very very much!
[![FlowCrypt Logo](https://blog.jabberhead.tk/wp-content/uploads/2022/05/flowcrypt-logo.svg)](https://flowcrypt.com)
Continuous Integration is kindly provided by [Travis-CI.com](https://travis-ci.com/).
Parts of PGPainless development ([project page](https://nlnet.nl/project/PGPainless/)) will be funded by [NGI Assure](https://nlnet.nl/assure/) through [NLNet](https://nlnet.nl).
NGI Assure is made possible with financial support from the [European Commission](https://ec.europa.eu/)'s [Next Generation Internet](https://ngi.eu/) programme, under the aegis of [DG Communications Networks, Content and Technology](https://ec.europa.eu/info/departments/communications-networks-content-and-technology_en).
[![NGI Assure Logo](https://blog.jabberhead.tk/wp-content/uploads/2022/05/NGIAssure_tag.svg)](https://nlnet.nl/assure/)
Big thank you also to those who decided to support the work by donating!
Notably @msfjarvis
You make my day!

118
REUSE.toml Normal file
View file

@ -0,0 +1,118 @@
version = 1
SPDX-PackageName = "PGPainless"
SPDX-PackageSupplier = "Paul Schaub <info@pgpainless.org>"
SPDX-PackageDownloadLocation = "https://pgpainless.org"
[[annotations]]
path = "REUSE.toml"
precedence = "aggregate"
SPDX-FileCopyrightText = "2025 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = ".git-blame-ignore-revs"
precedence = "aggregate"
SPDX-FileCopyrightText = "2023 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "docs/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC-BY-3.0"
[[annotations]]
path = ".readthedocs.yaml"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "gradle**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2015 the original author or authors."
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = ".editorconfig"
precedence = "aggregate"
SPDX-FileCopyrightText = "Facebook"
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = "assets/repository-open-graph.png"
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC-BY-3.0"
[[annotations]]
path = "assets/pgpainless.svg"
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC-BY-3.0"
[[annotations]]
path = "assets/logo.png"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC-BY-3.0"
[[annotations]]
path = "assets/test_vectors/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2018 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "pgpainless-core/src/test/resources/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "audit/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "CNAME"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "_config.yml"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "_layouts/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>, 2017 Steve Smith"
SPDX-License-Identifier = "CC-BY-SA-3.0"
[[annotations]]
path = "pgpainless-cli/src/main/resources/META-INF/native-image/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2025 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = "pgpainless-cli/rewriteManPages.sh"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = "pgpainless-cli/packaging/man/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2022 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = ".github/ISSUE_TEMPLATE/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2024 Paul Schaub <info@pgpainless.org>"
SPDX-License-Identifier = "CC0-1.0"

View file

@ -12,10 +12,14 @@ SPDX-License-Identifier: Apache-2.0
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| 0.2.x | :white_check_mark: |
| < 0.2.0 | :x: |
| Version | Supported | Note |
|---------|--------------------|------------|
| 1.7.X | :white_check_mark: | |
| 1.6.X | :white_check_mark: | LTS branch |
| 1.5.X | :white_check_mark: | |
| 1.4.X | :white_check_mark: | |
| 1.3.X | :white_check_mark: | LTS branch |
| < 1.3.X | :x: | |
## Reporting a Vulnerability
@ -23,3 +27,11 @@ If you find a security relevant vulnerability inside PGPainless, please let me k
[Here](https://keyoxide.org/7F9116FEA90A5983936C7CFAA027DB2F3E1E118A) you can find my OpenPGP key to email me confidentially.
Valid security issues will be fixed ASAP.
## Audits
### Cure53 - FLO-04
PGPainless has received a security audit by [cure53.de](https://cure53.de) in late 2021.
The [penetrationj test and audit](https://cure53.de/pentest-report_pgpainless.pdf) covered PGPainless
release candidate 1.0.0-rc6.
Security fixes for discovered flaws were deployed before the final 1.0.0 release.

8
_config.yml Normal file
View file

@ -0,0 +1,8 @@
logo: /assets/logo.png
theme: jekyll-theme-minimal
exclude:
- CHANGELOG.md
- CODE_OF_CONDUCT.md
- SECURITY.md
- docs

76
_layouts/default.html Normal file
View file

@ -0,0 +1,76 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>{{ site.title | default: site.github.repository_name }} by {{
site.github.owner_name }}</title>
<link rel="stylesheet" href="{{ '/assets/css/style.css?v=' | append:
site.github.build_revision | relative_url }}">
<meta name="viewport" content="width=device-width">
<!--[if lt IE 9]>
<script
src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<div class="wrapper" style="width: 1060px">
<header>
{% if site.logo %}
<img src="{{site.logo | relative_url}}" alt="Logo" />
{% endif %}
<p>{{ site.description | default: site.github.project_tagline
}}</p>
<a href="https://pgpainless.org">Home</a>
<br>
<a href="https://search.maven.org/search?q=g:org.pgpainless%20AND%20a:pgpainless-core&core=gav">Releases</a>
<br>
<a href="https://pgpainless.rtfd.io">Documentation</a>
<br>
<a href=" https://javadoc.io/doc/org.pgpainless ">Javadoc</a>
<br>
<a href="https://coveralls.io/github/pgpainless/pgpainless">Coverage</a>
<br>
{% if site.github.is_project_page %}
<p class="view"><a href="{{ site.github.repository_url
}}">View the Project on GitHub <small>{{ github_name }}</small></a></p>
{% endif %}
{% if site.github.is_user_page %}
<p class="view"><a href="{{ site.github.owner_url }}">View My
GitHub Profile</a></p>
{% endif %}
{% if site.show_downloads %}
<ul>
<li><a href="{{ site.github.zip_url }}">Download <strong>ZIP
File</strong></a></li>
<li><a href="{{ site.github.tar_url }}">Download <strong>TAR
Ball</strong></a></li>
<li><a href="{{ site.github.repository_url }}">View On
<strong>GitHub</strong></a></li>
</ul>
{% endif %}
</header>
<section style="width: 780px">
{{ content }}
</section>
<footer>
{% if site.github.is_project_page %}
<p>This project is maintained by <a href="{{
site.github.owner_url }}">{{ site.github.owner_name }}</a></p>
{% endif %}
<p><small>Hosted on GitHub Pages &mdash; Theme by <a
href="https://github.com/orderedlist">orderedlist</a></small></p>
</footer>
</div>
<script src="{{ '/assets/js/scale.fix.js' | relative_url
}}"></script>
</body>
</html>

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

110
assets/pgpainless.svg Normal file
View file

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="48px"
viewBox="0 0 24 24"
width="48px"
fill="#000000"
version="1.1"
id="svg893"
sodipodi:docname="pgpainless.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
<metadata
id="metadata899">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs897" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1056"
id="namedview895"
showgrid="true"
inkscape:zoom="17.895833"
inkscape:cx="24"
inkscape:cy="21.764843"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg893">
<inkscape:grid
type="xygrid"
id="grid1629" />
</sodipodi:namedview>
<g
fill="none"
id="g889">
<path
d="M0 0h24v24H0V0z"
id="path885" />
<path
d="M0 0h24v24H0V0z"
opacity=".87"
id="path887" />
</g>
<path
d="M 18,8 H 17 V 6 C 17,3.24 14.76,1 12,1 9.24,1 7,3.24 7,6 V 8 H 6 C 4.9,8 4,8.9 4,10 v 10 c 0,1.1 0.9,2 2,2 h 12 c 1.1,0 2,-0.9 2,-2 V 10 C 20,8.9 19.1,8 18,8 Z M 9,6 c 0,-1.66 1.34,-3 3,-3 1.66,0 3,1.34 3,3 V 8 H 9 Z"
id="path891"
sodipodi:nodetypes="scssscsssssssssssccs" />
<g
style="fill:#ffffff"
id="g1627"
transform="matrix(0.93317811,0,0,0.93317811,2.6775507,5.7004657)">
<g
id="g1594">
<rect
fill="none"
height="20"
width="20"
x="0"
id="rect1592"
y="0" />
</g>
<g
id="g1608">
<g
id="g1596" />
<g
id="g1606">
<path
d="m 10,14 c 1.86,0 3.41,-1.28 3.86,-3 H 6.14 c 0.45,1.72 2,3 3.86,3 z"
id="path1598" />
<path
d="M 9.99,3 C 6.13,3 3,6.14 3,10 3,13.86 6.13,17 9.99,17 13.86,17 17,13.86 17,10 17,6.14 13.86,3 9.99,3 Z m 0,13 C 6.69,16 4,13.31 4,10 4,6.69 6.69,4 9.99,4 13.31,4 16,6.69 16,10 c 0,3.31 -2.69,6 -6.01,6 z"
id="path1600" />
<circle
cx="13"
cy="8"
r="1"
id="circle1602" />
<circle
cx="7"
cy="8"
r="1"
id="circle1604" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Before After
Before After

View file

@ -18,7 +18,8 @@ buildscript {
}
plugins {
id 'ru.vyarus.animalsniffer' version '1.5.3'
id 'org.jetbrains.kotlin.jvm' version "1.8.10"
id 'com.diffplug.spotless' version '6.22.0' apply false
}
apply from: 'version.gradle'
@ -29,45 +30,58 @@ allprojects {
apply plugin: 'eclipse'
apply plugin: 'jacoco'
apply plugin: 'checkstyle'
apply plugin: 'kotlin'
apply plugin: 'com.diffplug.spotless'
// For non-sop modules, enable android api compatibility check
if (it.name.equals('pgpainless-core') || it.name.equals('sop-java') || it.name.equals('pgpainless-sop')) {
// animalsniffer
apply plugin: 'ru.vyarus.animalsniffer'
dependencies {
signature "net.sf.androidscents.signature:android-api-level-${pgpainlessMinAndroidSdk}:2.3.3_r2@signature"
}
animalsniffer {
sourceSets = [sourceSets.main]
}
// Only generate jar for submodules
// without this we would generate an empty pgpainless.jar for the project root
// https://stackoverflow.com/a/25445035
jar {
reproducibleFileOrder = true
onlyIf { !sourceSets.main.allSource.files.isEmpty() }
}
// checkstyle
checkstyle {
toolVersion = '8.18'
toolVersion = '10.25.0'
}
spotless {
kotlin {
ktfmt().dropboxStyle()
}
}
group 'org.pgpainless'
description = "Simple to use OpenPGP API for Java based on Bouncycastle"
version = shortVersion
sourceCompatibility = javaSourceCompatibility
repositories {
mavenCentral()
mavenLocal()
}
// Reproducible Builds
tasks.withType(AbstractArchiveTask) {
preserveFileTimestamps = false
reproducibleFileOrder = true
dirMode = 0755
fileMode = 0644
}
kotlin {
jvmToolchain(javaSourceCompatibility)
}
// Compatibility of default implementations in kotlin interfaces with Java implementations.
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
kotlinOptions {
freeCompilerArgs += ["-Xjvm-default=all-compatibility"]
}
}
project.ext {
slf4jVersion = '1.7.32'
logbackVersion = '1.2.9'
junitVersion = '5.8.2'
picocliVersion = '4.6.2'
rootConfigDir = new File(rootDir, 'config')
gitCommit = getGitCommit()
isContinuousIntegrationEnvironment = Boolean.parseBoolean(System.getenv('CI'))
@ -92,7 +106,7 @@ allprojects {
}
jacoco {
toolVersion = "0.8.7"
toolVersion = "0.8.8"
}
jacocoTestReport {
@ -100,7 +114,7 @@ allprojects {
sourceDirectories.setFrom(project.files(sourceSets.main.allSource.srcDirs))
classDirectories.setFrom(project.files(sourceSets.main.output))
reports {
xml.enabled true
xml.required = true
}
}
@ -118,15 +132,15 @@ subprojects {
apply plugin: 'signing'
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
archiveClassifier = 'sources'
from sourceSets.main.allSource
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
archiveClassifier = 'javadoc'
from javadoc.destinationDir
}
task testsJar(type: Jar, dependsOn: testClasses) {
classifier = 'tests'
archiveClassifier = 'tests'
from sourceSets.test.output
}
@ -223,7 +237,7 @@ task jacocoRootReport(type: JacocoReport) {
classDirectories.setFrom(files(subprojects.sourceSets.main.output))
executionData.setFrom(files(subprojects.jacocoTestReport.executionData))
reports {
xml.enabled true
xml.required = true
xml.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml")
}
// We could remove the following setOnlyIf line, but then
@ -234,10 +248,6 @@ task jacocoRootReport(type: JacocoReport) {
}
task javadocAll(type: Javadoc) {
def currentJavaVersion = JavaVersion.current()
if (currentJavaVersion.compareTo(JavaVersion.VERSION_1_9) >= 0) {
options.addStringOption("-release", "8");
}
source subprojects.collect {project ->
project.sourceSets.main.allJava }
destinationDir = new File(buildDir, 'javadoc')
@ -250,3 +260,23 @@ task javadocAll(type: Javadoc) {
"https://docs.oracle.com/javase/${sourceCompatibility.getMajorVersion()}/docs/api/",
] as String[]
}
/**
* Fetch sha256 checksums of artifacts published to maven central.
*
* Example: gradle -Prelease=1.3.13 mavenCentralChecksums
*/
task mavenCentralChecksums() {
description 'Fetch and display checksums for artifacts published to Maven Central'
String ver = project.hasProperty('release') ? release : shortVersion
doLast {
for (Project p : rootProject.subprojects) {
String url = "https://repo1.maven.org/maven2/org/pgpainless/${p.name}/${ver}/${p.name}-${ver}.jar.sha256"
Process fetch = "curl -f $url".execute()
if (fetch.waitFor() == 0) {
print fetch.text.trim()
println " ${p.name}/build/libs/${p.name}-${ver}.jar"
}
}
}
}

View file

@ -114,18 +114,12 @@ SPDX-License-Identifier: CC0-1.0
</module>
<module name="JavadocMethod">
<!-- TODO stricten those checks -->
<property name="scope" value="public"/>
<!--<property name="allowUndeclaredRTE" value="true"/>-->
<property name="accessModifiers" value="public"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowMissingJavadoc" value="true"/>
<property name="suppressLoadErrors" value="true"/>
</module>
<module name="JavadocStyle">
<property name="scope" value="public"/>
<property name="checkEmptyJavadoc" value="true"/>
<property name="checkHtml" value="false"/>
</module>

20
docs/Makefile Normal file
View file

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

31
docs/README.md Normal file
View file

@ -0,0 +1,31 @@
# User Guide for PGPainless
Documentation for PGPainless is built from Markdown using Sphinx and MyST.
A built version of the documentation is available on http://pgpainless.rtfd.io/
## Useful resources
* [Sphix Documentation Generator](https://www.sphinx-doc.org/en/master/)
* [MyST Markdown Syntax](https://myst-parser.readthedocs.io/en/latest/index.html)
## Build the Guide
To build:
```shell
$ make {html|epub|latexpdf}
```
Note: Diagrams are currently not built from source.
Instead, pre-built image files are used directly, because there are issues with mermaid in CLI systems.
If you want to build the diagrams from source, you need `mermaid-cli` to be installed on your system.
```shell
$ npm install -g @mermaid-js/mermaid-cli
```
You can then use `mmdc` to build/update single diagram files like this:
```shell
mmdc --theme default --width 1600 --backgroundColor transparent -i ecosystem_dia.md -o ecosystem_dia.svg
```

35
docs/make.bat Normal file
View file

@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

3
docs/requirements.txt Normal file
View file

@ -0,0 +1,3 @@
myst-parser>=0.17
sphinxcontrib-mermaid>=0.7.1
sphinx_rtd_theme>=2.0.0

56
docs/source/conf.py Normal file
View file

@ -0,0 +1,56 @@
import os
# Configuration file for the Sphinx documentation builder.
# -- Project information
project = 'PGPainless'
copyright = '2022, Paul Schaub'
author = 'Paul Schaub'
master_doc = 'index'
# https://protips.readthedocs.io/git-tag-version.html
latest_tag = os.popen('git describe --abbrev=0').read().strip()
release = latest_tag
version = release
myst_substitutions = {
"repo_host" : "codeberg.org",
# "repo_host" : "github.com",
"repo_pgpainless_src" : "codeberg.org/pgpainless/pgpainless/src/branch",
# "repo_pgpainless_src" : "github.com/pgpainless/pgpainless/tree",
}
# -- General configuration
extensions = [
'myst_parser',
'sphinxcontrib.mermaid',
'sphinx.ext.duration',
'sphinx.ext.doctest',
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
]
source_suffix = ['.rst', '.md']
myst_enable_extensions = [
'colon_fence',
'substitution',
]
myst_heading_anchors = 3
templates_path = ['_templates']
# -- Options for HTML output
html_theme = 'sphinx_rtd_theme'
# Show URLs as footnotes
#epub_show_urls = 'footnote'
latex_show_urls = 'footnote'
# 'raw' does not work for epub and pdf, neither does 'svg'
mermaid_output_format = 'png'
mermaid_params = ['--theme', 'default', '--width', '1600', '--backgroundColor', 'transparent']

59
docs/source/ecosystem.md Normal file
View file

@ -0,0 +1,59 @@
# The PGPainless Ecosystem
PGPainless consists of an ecosystem of different libraries and projects.
The diagram below shows, how the different projects relate to one another.
![Ecosystem](ecosystem_dia.*)
<!--
```{include} ecosystem_dia.md
```
-->
## Libraries and Tools
* {{ '[PGPainless](https://{}/pgpainless/pgpainless)'.format(repo_host) }}
The main repository contains the following components:
* `pgpainless-core` - core implementation - powerful, yet easy to use OpenPGP API
* `pgpainless-sop` - super simple OpenPGP implementation. Drop-in for `sop-java`
* `pgpainless-cli` - SOP CLI implementation using PGPainless
* {{ '[SOP-Java](https://{}/pgpainless/sop-java)'.format(repo_host) }}
An API definition and CLI implementation of the [Stateless OpenPGP Protocol](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/) (SOP).
Consumers of the SOP API can simply depend on `sop-java` and then switch out the backend as they wish.
Read more about the [SOP](sop) protocol here.
* `sop-java` - generic OpenPGP API definition
* `sop-java-picocli` - CLI frontend for `sop-java`
* {{ '[WKD-Java](https://{}/pgpainless/wkd-java)'.format(repo_host) }}
Implementation of the [Web Key Directory](https://www.ietf.org/archive/id/draft-koch-openpgp-webkey-service-13.html).
* `wkd-java` - generic WKD discovery implementation
* `wkd-java-cli` - CLI frontend for WKD discovery using PGPainless
* `wkd-test-suite` - Generator for test vectors for testing WKD implementations
* {{ '[VKS-Java](https://{}/pgpainless/vks-java)'.format(repo_host) }}
Client-side API for communicating with Verifying Key Servers, such as https://keys.openpgp.org/.
* `vks-java` - VKS client implementation
* `vks-java-cli` - CLI frontend for `vks-java`
* {{ '[Cert-D-Java](https://{}/pgpainless/cert-d-java)'.format(repo_host) }}
Implementations of the [Shared OpenPGP Certificate Directory specification](https://sequoia-pgp.gitlab.io/pgp-cert-d/).
* `pgp-certificate-store` - abstract definitions of OpenPGP certificate stores
* `pgp-cert-d-java` - implementation of `pgp-certificate-store` following the PGP-CERT-D spec
* `pgp-cert-d-java-jdbc-sqlite-lookup` - subkey lookup using sqlite database
* {{ '[Cert-D-PGPainless](https://{}/pgpainless/cert-d-pgpainless)'.format(repo_host) }}
Implementation of the [Shared OpenPGP Certificate Directory specification](https://sequoia-pgp.gitlab.io/pgp-cert-d/) using PGPainless.
* `pgpainless-cert-d` - PGPainless-based implementation of `pgp-cert-d-java`
* `pgpainless-cert-d-cli` - CLI frontend for `pgpainless-cert-d`
* {{ '[PGPainless-WOT](https://{}/pgpainless/pgpainless-wot)'.format(repo_host) }}
Implementation of the [OpenPGP Web of Trust specification](https://sequoia-pgp.gitlab.io/sequoia-wot/) using PGPainless.
* `pgpainless-wot` - Parse OpenPGP keyrings into a generic `Network` object
* `wot-dijkstra` - Perform queries to find paths inside a `Network` object
* `pgpainless-wot-cli` - CLI frontend for `pgpainless-wot` and `wot-dijkstra`
* `wot-test-suite` - Test vectors ported from [Sequoia-PGPs WoT implementation](https://gitlab.com/sequoia-pgp/sequoia-wot/-/tree/main/tests/data)
* {{ '[PGPeasy](https://{}/pgpainless/pgpeasy)'.format(repo_host) }}
Prototypical, comprehensive OpenPGP CLI application
* `pgpeasy` - CLI application

View file

@ -0,0 +1,45 @@
```mermaid
flowchart LR
subgraph SOP-JAVA
sop-java-picocli-->sop-java
end
subgraph PGPAINLESS
pgpainless-sop-->pgpainless-core
pgpainless-sop-->sop-java
pgpainless-cli-->pgpainless-sop
pgpainless-cli-->sop-java-picocli
end
subgraph WKD-JAVA
wkd-java-cli-->wkd-java
wkd-test-suite-->wkd-java
wkd-test-suite-->pgpainless-core
end
subgraph CERT-D-JAVA
pgp-cert-d-java-->pgp-certificate-store
pgp-cert-d-java-jdbc-sqlite-lookup-->pgp-cert-d-java
end
subgraph CERT-D-PGPAINLESS
pgpainless-cert-d-->pgpainless-core
pgpainless-cert-d-->pgp-cert-d-java
pgpainless-cert-d-cli-->pgpainless-cert-d
pgpainless-cert-d-cli-->pgp-cert-d-java-jdbc-sqlite-lookup
end
subgraph VKS-JAVA
vks-java-cli-->vks-java
end
subgraph PGPAINLESS-WOT
wot-test-suite-->pgpainless-wot
pgpainless-wot-->wot-dijkstra
pgpainless-wot-cli-->pgpainless-wot
pgpainless-wot-->pgpainless-core
pgpainless-wot-cli-->pgpainless-cert-d
end
subgraph PGPEASY
pgpeasy-->pgpainless-cli
pgpeasy-->wkd-java-cli
pgpeasy-->vks-java-cli
pgpeasy-->pgpainless-cert-d-cli
end
wkd-java-cli-->pgpainless-cert-d
wkd-java-->pgp-certificate-store
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 41 KiB

38
docs/source/index.rst Normal file
View file

@ -0,0 +1,38 @@
PGPainless - Painless OpenPGP
=============================
**OpenPGP** (`RFC 4480 <https://datatracker.ietf.org/doc/rfc4880/>`_) is an Internet Standard mostly used for email
encryption.
It provides mechanisms to ensure *confidentiality*, *integrity* and *authenticity* of messages.
However, OpenPGP can also be used for other purposes, such as secure messaging or as a signature mechanism for
software distribution.
**PGPainless** strives to improve the (currently pretty dire) state of the ecosystem of Java libraries and tooling
for OpenPGP.
The library focuses on being easy and intuitive to use without getting into your way.
Common functions such as creating keys, encrypting data, and so on are implemented using a builder structure that
guides you through the necessary steps.
Internally, it is based on `Bouncy Castles <https://www.bouncycastle.org/java.html>`_ mighty, but low-level ``bcpg``
OpenPGP API.
PGPainless' goal is to empower you to use OpenPGP without needing to write all the boilerplate code required by
Bouncy Castle.
It aims to be secure by default while allowing customization if required.
From its inception in 2018 as part of a `Google Summer of Code project <https://summerofcode.withgoogle.com/archive/2018/projects/6037508810866688>`_,
the library was steadily advanced.
Since 2020, FlowCrypt is the primary sponsor of its development.
In 2022, PGPainless received a `grant from NLnet for creating a Web-of-Trust implementation <https://nlnet.nl/project/PGPainless/>`_ as part of NGI Assure.
Contents
--------
.. toctree::
ecosystem.md
quickstart.md
pgpainless-cli/usage.md
sop.md
pgpainless-core/indepth.rst

View file

@ -0,0 +1,163 @@
# User Guide PGPainless-CLI
The module `pgpainless-cli` contains a command line application which conforms to the
[Stateless OpenPGP Command Line Interface](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/).
You can use it to generate keys, encrypt, sign and decrypt messages, as well as verify signatures.
## Implementation
Essentially, `pgpainless-cli` is just a very small composing module, which injects `pgpainless-sop` as a
concrete implementation of `sop-java` into `sop-java-picocli`.
## Install
The `pgpainless-cli` command line application is available in Debian unstable / Ubuntu 22.10 and can be installed via APT:
```shell
$ sudo apt install pgpainless-cli
```
This method comes with man-pages:
```shell
$ man pgpainless-cli
```
## Build
To build a standalone *fat*-jar:
```shell
$ cd pgpainless-cli/
$ gradle shadowJar
```
The fat-jar can afterwards be found in `build/libs/`.
To build a [distributable](https://docs.gradle.org/current/userguide/distribution_plugin.html):
```shell
$ cd pgpainless-cli/
$ gradle installDist
```
Afterwards, an uncompressed distributable is installed in `build/install/`.
To execute the application, you can call `build/install/bin/pgpainless-cli{.bat}`
Building / updating man pages is a two-step process.
The contents of the man pages is largely defined by the `sop-java-picocli` source code.
In order to generate a fresh set of man pages from the `sop-java-picocli` source, you need to clone that repository
next to the `pgpainless` repository:
```shell
$ ls
pgpainless
$ git clone https://github.com/pgpainless/sop-java.git
$ ls
pgpainless sop-java
```
Next, you need to execute the `asciiDoctor` gradle task inside the sop-java repository:
```shell
$ cd sop-java
$ gradle asciiDoctor
```
This will generate generic sop manpages in `sop-java-picocli/build/docs/manpage/`.
Next, you need to execute a script for converting the `sop` manpages to fit the `pgpainless-cli` command with the help
of a script in the `pgpainless` repository:
```shell
$ cd ../pgpainless/pgpainless-cli
$ ./rewriteManPages.sh
```
The resulting updated man pages are placed in `packaging/man/`.
## Usage
Hereafter, the program will be referred to as `pgpainless-cli`.
```
$ pgpainless-cli help
Stateless OpenPGP Protocol
Usage: pgpainless-cli [--stacktrace] [COMMAND]
Options:
--stacktrace Print stacktrace
Commands:
version Display version information about the tool
list-profiles Emit a list of profiles supported by the identified
subcommand
generate-key Generate a secret key
change-key-password Update the password of a key
revoke-key Generate revocation certificates
extract-cert Extract a public key certificate from a secret key
sign Create a detached message signature
verify Verify a detached signature
encrypt Encrypt a message from standard input
decrypt Decrypt a message
inline-detach Split signatures from a clearsigned message
inline-sign Create an inline-signed message
inline-verify Verify an inline-signed message
armor Add ASCII Armor to standard input
dearmor Remove ASCII Armor from standard input
help Display usage information for the specified subcommand
Exit Codes:
0 Successful program execution
1 Generic program error
3 Verification requested but no verifiable signature found
13 Unsupported asymmetric algorithm
17 Certificate is not encryption capable
19 Usage error: Missing argument
23 Incomplete verification instructions
29 Unable to decrypt
31 Password is not human-readable
37 Unsupported Option
41 Invalid data or data of wrong type encountered
53 Non-text input received where text was expected
59 Output file already exists
61 Input file does not exist
67 Cannot unlock password protected secret key
69 Unsupported subcommand
71 Unsupported special prefix (e.g. "@ENV/@FD") of indirect parameter
73 Ambiguous input (a filename matching the designator already exists)
79 Key is not signing capable
83 Options were supplied that are incompatible with each other
89 The requested profile is unsupported, or the indicated subcommand does
not accept profiles
```
To get help on a subcommand, e.g. `encrypt`, just call the help subcommand followed by the subcommand you
are interested in (e.g. `pgpainless-cli help encrypt`).
## Examples
```shell
$ # Generate a key
$ pgpainless-cli generate-key "Alice <alice@pgpainless.org>" > key.asc
$ # Extract a certificate from a key
$ cat key.asc | pgpainless-cli extract-cert > cert.asc
$ # Create an encrypted signed message
$ echo "Hello, World!" | pgpainless-cli encrypt cert.asc --sign-with key.asc > msg.asc
$ # Decrypt an encrypted message and verify the signature
$ cat msg.asc | pgpainless-cli decrypt key.asc --verify-with cert.asc --verifications-out verifications.txt
Hello, World!
$ cat verifications.txt
2022-11-15T21:25:48Z 4FF67C69150209ED8139DE22578CB2FABD5D7897 9000235358B8CEA6A368EC86DE56DC2D942ACAA4
```
## Indirect Data Types
Some commands take options whose arguments are indirect data types. Those are arguments which are not used directly,
but instead they point to a place where the argument value can be sourced from, such as a file, an environment variable
or a file descriptor.
It is important to keep in mind, that options like `--with-password` or `--with-key-password` are examples for such
indirect data types. If you want to unlock a key whose password is `sw0rdf1sh`, you *cannot* provide the password
like `--with-key-password sw0rdf1sh`, but instead you have to either write out the password into a file and provide
the file's path (e.g. `--with-key-password /path/to/file`), store the password in an environment variable and pass that
(e.g. `--with-key-password @ENV:myvar`), or provide a numbered file descriptor from which the password can be read
(e.g. `--with-key-password @FD:4`).
Note, that environment variables and file descriptors can only be used to pass input data to the program.
For output parameters (e.g. `--verifications-out`) only file paths are allowed.

View file

@ -0,0 +1 @@
# Edit Keys

View file

@ -0,0 +1,101 @@
# PGPainless In-Depth: Generate Keys
There are two API endpoints for generating OpenPGP keys using `pgpainless-core`:
`PGPainless.generateKeyRing()` presents a selection of pre-configured OpenPGP key archetypes:
```java
// Modern, EC-based OpenPGP key with dedicated primary certification key
// This method is recommended by the authors
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
.modernKeyRing(
"Alice <alice@pgpainless.org>",
Passphrase.fromPassword("sw0rdf1sh"));
// Simple, EC-based OpenPGP key with combined certification and signing key
// plus encryption subkey
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
.simpleEcKeyRing(
"Alice <alice@pgpainless.org>",
Passphrase.fromPassword("0r4ng3"));
// Simple, RSA OpenPGP key made of a single RSA key used for all operations
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
.simpleRsaKeyRing(
"Alice <alice@pgpainless.org>",
RsaLength._4096, Passphrase.fromPassword("m0nk3y")):
```
If you have special requirements on algorithms you can use `PGPainless.buildKeyRing()` instead, which offers more
control over parameters:
```java
// Customized key
// Specification for primary key
KeySpecBuilder primaryKeySpec = KeySpec.getBuilder(
KeyType.RSA(RsaLength._8192), // 8192 bits RSA key
KeyFlag.CERTIFY_OTHER) // used for certification
// optionally override algorithm preferences
.overridePreferredCompressionAlgorithms(CompressionAlgorithm.ZLIB)
.overridePreferredHashAlgorithms(HashAlgorithm.SHA512, HashAlgorithm.SHA384)
.overridePreferredSymmetricKeyAlgorithms(SymmetricKeyAlgorithm.AES256);
// Specification for a signing subkey
KeySpecBuilder signingSubKeySpec = KeySpec.getBuilder(
KeyType.ECDSA(EllipticCurve._P256), // P-256 ECDSA key
KeyFlag.SIGN_DATA); // Used for signing
// Specification for an encryption subkey
KeySpecBuilder encryptionSubKeySpec = KeySpec.getBuilder(
KeyType.ECDH(EllipticCurve._P256),
KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE);
// Build the key itself
PGPSecretKeyRing secretKey = PGPainless.buildKeyRing()
.setPrimaryKey(primaryKeySpec)
.addSubkey(signingSubKeySpec)
.addSubkey(encryptionSubKeySpec)
.addUserId("Juliet <juliet@montague.lit>") // Primary User-ID
.addUserId("xmpp:juliet@capulet.lit") // Additional User-ID
.setPassphrase(Passphrase.fromPassword("romeo_oh_Romeo<3")) // passphrase protection
.build();
```
To specify, which algorithm to use for a single (sub) key, `KeySpec.getBuilder(_)` can be used, passing a `KeyType`,
as well as some `KeyFlag`s as argument.
`KeyType` defines an algorithm and its parameters, e.g. RSA with a certain key size, or ECDH over a certain
elliptic curve.
Currently, PGPainless supports the following `KeyType`s:
* `KeyType.RSA(_)`: Signing, Certification, Encryption
* `KeyType.ECDH(_)`: Encryption
* `KeyType.ECDSA(_)`: Signing, Certification
* `KeyType.EDDSA(_)`: Signing, Certification
* `KeyType.XDH(_)`: Encryption
The `KeyFlag`s are used to specify, how the key will be used later on. A signing key can only be used for signing,
if it carries the `KeyFlag.SIGN_DATA`.
A key can carry multiple key flags.
It is possible to override the default algorithm preferences used by PGPainless with custom preferences.
An algorithm preference list contains algorithms from most to least preferred.
Every OpenPGP key MUST have a primary key. The primary key MUST be capable of certification, so you MUST use an
algorithm that can be used to generate signatures.
The primary key can be set by calling `setPrimaryKey(primaryKeySpec)`.
Furthermore, an OpenPGP key can contain zero or more subkeys.
Those can be set by repeatedly calling `addSubkey(subkeySpec)`.
OpenPGP keys are usually bound to User-IDs like names and/or email addresses.
There can be multiple user-ids bound to a key, in which case the very first User-ID will be marked as primary.
To add a User-ID to the key, call `addUserId(userId)`.
By default, keys do not have an expiration date. This can be changed by setting an expiration date using
`setExpirationDate(date)`.
To enable password protection for the OpenPGP key, you can call `setPassphrase(passphrase)`.
If this method is not called, or if the passed in `Passphrase` is empty, the key will be unprotected.
Finally, calling `build()` will generate a fresh OpenPGP key according to the specifications given.

View file

@ -0,0 +1,14 @@
In-Depth Guide to pgpainless-core
=================================
This is an in-depth introduction to OpenPGP using PGPainless.
If you are looking for a quickstart introduction instead, check out [](quickstart.md).
Contents
--------
.. toctree::
generate_keys.md
edit_keys.md
userids.md
passphrase.md

View file

@ -0,0 +1,89 @@
# Passwords
In Java based applications, passing passwords as `String` objects has the
[disadvantage](https://stackoverflow.com/a/8881376/11150851) that you have to rely on garbage collection to clean up
once they are no longer used.
For that reason, `char[]` is the preferred method for dealing with passwords.
Once a password is no longer used, the character array can simply be overwritten to remove the sensitive data from
memory.
## Passphrase
PGPainless uses a wrapper class `Passphrase`, which takes care for the wiping of unused passwords:
```java
Passphrase passphrase = new Passphrase(new char[] {'h', 'e', 'l', 'l', 'o'});
assertTrue(passphrase.isValid());
assertArrayEquals(new char[] {'h', 'e', 'l', 'l', 'o'}, passphrase.getChars()):
// Once we are done, we can clean the data
passphrase.clear();
assertFalse(passphrase.isValid());
assertNull(passphrase.getChars());
```
Furthermore, `Passphrase` can also wrap empty passphrases, which increases null-safety of the API:
```java
Passphrase empty = Passphrase.emptyPassphrase();
assertTrue(empty.isValid());
assertTrue(empty.isEmpty());
assertNull(empty.getChars());
empty.clear();
assertFalse(empty.isValid());
```
## SecretKeyRingProtector
There are certain operations that require you to provide the passphrase for a key.
Examples are decryption of messages, or creating signatures / certifications.
The primary way of telling PGPainless, which password to use for a certain key is the `SecretKeyRingProtector`
interface which maps `Passphrases` to (sub-)keys.
There are multiple implementations of this interface, which may or may not suite your needs:
```java
// If your key is not password protected, this implementation is for you:
SecretKeyRingProtector unprotected = SecretKeyRingProtector
.unprotectedKeys();
// If you use a single passphrase for all (sub-) keys, take this:
SecretKeyRingProtector singlePassphrase = SecretKeyRingProtector
.unlockAnyKeyWith(passphrase);
// If you want to be flexible, use this:
CachingSecretKeyRingProtector flexible = SecretKeyRingProtector
.defaultSecretKeyRingProtector(passphraseCallback);
```
`SecretKeyRingProtector.unprotectedKeys()` will return an empty passphrase for any key.
It is best used when dealing with unencrypted secret keys.
`SecretKeyRingProtector.unlockAnyKeyWith(passphrase)` will return the same exact passphrase for any given key.
You should use this if you have a single key with a static passphrase.
The last example shows how to instantiate the `CachingSecretKeyRingProtector` with a `SecretKeyPassphraseProvider`
as argument.
As the name suggests, the `CachingSecretKeyRingProtector` caches passphrases it knows about in a map.
That way, you only have to provide the passphrase for a certain key only once, after which it will be remembered.
If you try to unlock a protected secret key for which no passphrase is cached, the `getPassphraseFor()` method of
the `SecretKeyPassphraseProvider` callback will be called to interactively ask for the missing passphrase.
Afterwards, the acquired passphrase will be cached for future use.
:::{note}
While especially the `CachingSecretKeyRingProtector` can handle multiple keys without problems, it is advised
to use individual `SecretKeyRingProtector` objects per key.
The reason for this is, that internally the 64bit key-id is used to resolve `Passphrase` objects and collisions are not
unlikely in this key-space.
Furthermore, multiple OpenPGP keys could contain the same subkey, but with different passphrases set.
If the same `SecretKeyRingProtector` is used for two OpenPGP keys with the same subkey, but different passwords,
the key-id collision will cause the password to be overwritten for one of the keys, which might result in issues.
See `FLO-04-004 WP2` of the [2021 security audit](https://cure53.de/pentest-report_pgpainless.pdf) for more details.
:::
Most `SecretKeyRingProtector` implementations can be instantiated with custom `KeyRingProtectionSettings`.
By default, most implementations use `KeyRingProtectionSettings.secureDefaultSettings()` which corresponds to iterated
and salted S2K using AES256 and SHA256 with an iteration count of 65536.

View file

@ -0,0 +1,505 @@
## PGPainless API with pgpainless-core
The `pgpainless-core` module contains the bulk of the actual OpenPGP implementation.
This is a quickstart guide. For more in-depth exploration of the API, checkout [](indepth.md).
:::{note}
This chapter is work in progress.
:::
### Setup
PGPainless' releases are published to and can be fetched from Maven Central.
To get started, you first need to include `pgpainless-core` in your projects build script:
```
// If you use Gradle
...
dependencies {
...
implementation "org.pgpainless:pgpainless-core:XYZ"
...
}
// If you use Maven
...
<dependencies>
...
<dependency>
<groupId>org.pgpainless</groupId>
<artifactId>pgpainless-core</artifactId>
<version>XYZ</version>
</dependency>
...
</dependencies>
```
This will automatically pull in PGPainless' dependencies, such as Bouncy Castle.
:::{important}
Replace `XYZ` with the current version, in this case {{ env.config.version }}!
:::
The entry point to the API is the `PGPainless` class.
For many common use-cases, examples can be found in the
{{ '[examples package](https://{}/main/pgpainless-core/src/test/java/org/pgpainless/example)'.format(repo_pgpainless_src) }}.
There is a very good chance that you can find code examples there that fit your needs.
### Read and Write Keys
Reading keys from ASCII armored strings or from binary files is easy:
```java
// Secret Keys
String key = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n"...;
PGPSecretKeyRing secretKey = PGPainless.readKeyRing()
.secretKeyRing(key);
// Certificates (Public Keys)
String cert = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...";
PGPPublicKeyRing certificate = PGPainless.readKeyRing()
.publicKeyRing(cert);
```
Similarly, keys or certificates can quickly be exported:
```java
// ASCII armored key
PGPSecretKeyRing secretKey = ...;
String armored = PGPainless.asciiArmor(secretKey);
// binary (unarmored) key
byte[] binary = secretKey.getEncoded();
```
### Generate a Key
PGPainless comes with a method to quickly generate modern OpenPGP keys.
There are some predefined key archetypes, but it is possible to fully customize the key generation to fit your needs.
```java
// EdDSA primary key with EdDSA signing- and XDH encryption subkeys
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
.modernKeyRing("Romeo <romeo@montague.lit>", "thisIsAPassword");
// RSA key without additional subkeys
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
.simpleRsaKeyRing("Juliet <juliet@montague.lit>", RsaLength._4096);
```
As you can see, it is possible to generate all kinds of different keys.
### Extract a Certificate
If you have a secret key, you might want to extract a public key certificate from it:
```java
PGPSecretKeyRing secretKey = ...;
PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKey);
```
### Apply / Remove ASCII Armor
ASCII armor is a layer of radix64 encoding that can be used to wrap binary OpenPGP data in order to make it save to
transport via text-based channels (e.g. email bodies).
The way in which ASCII armor can be applied depends on the type of data that you want to protect.
The easies way to ASCII armor an OpenPGP key or certificate is by using PGPainless' `asciiArmor()` method:
```java
PGPPublicKey certificate = ...;
String asciiArmored = PGPainless.asciiArmor(certificate);
```
If you want to ASCII armor ciphertext, you can enable ASCII armoring during encrypting/signing by requesting
PGPainless to armor the result:
```java
ProducerOptions producerOptions = ...; // prepare as usual (see next section)
producerOptions.setAsciiArmor(true); // enable armoring
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
.onOutputStream(out)
.withOptions(producerOptions);
...
```
If you have an already encrypted / signed binary message and want to add ASCII armoring retrospectively, you need
to make use of BouncyCastle's `ArmoredOutputStream` as follows:
```java
InputStream binaryOpenPgpIn = ...; // e.g. new ByteArrayInputStream(binaryMessage);
OutputStream output = ...; // e.g. new ByteArrayOutputStream();
ArmoredOutputStream armorOut = ArmoredOutputStreamFactory.get(output);
Streams.pipeAll(binaryOpenPgpIn, armorOut);
armorOut.close(); // important!
```
The output stream will now contain the ASCII armored representation of the binary data.
If the data you want to wrap in ASCII armor is non-OpenPGP data (e.g. the String "Hello World!"),
you need to use the following code:
```java
InputStream inputStream = ...;
OutputStream output = ...;
EncryptionStream armorStream = PGPainless.encryptAndOrSign()
.onOutputStream(output)
.withOptions(ProducerOptions.noEncryptionNoSigning()
.setAsciiArmor(true));
Streams.pipeAll(inputStream, armorStream);
armorStream.close();
```
To remove ASCII armor, you can make use of BouncyCastle's `ArmoredInputStream` as follows:
```java
InputStream input = ...; // e.g. new ByteArrayInputStream(armoredString.getBytes(StandardCharsets.UTF8));
OutputStream output = ...;
ArmoredInputStream armorIn = new ArmoredInputStream(input);
Streams.pipeAll(armorIn, output);
armorIn.close();
```
The output stream will now contain the binary OpenPGP data.
### Encrypt and/or Sign a Message
Encrypting and signing messages is done using the same API in PGPainless.
The type of action depends on the configuration of the `ProducerOptions` class, which in term accepts
`SigningOptions` and `EncryptionOptions` objects:
```java
// Encrypt only
ProducerOptions options = ProducerOptions.encrypt(encryptionOptions);
// Sign only
ProducerOptions options = ProducerOptions.sign(signingOptions);
// Sign and encrypt
ProducerOptions options = ProducerOptions.signAndEncrypt(signingOptions, encryptionOptions);
```
The `ProducerOptions` object can then be passed into the `encryptAndOrSign()` API:
```java
InputStream plaintext = ...; // The data that shall be encrypted and/or signed
OutputStream ciphertext = ...; // Destination for the ciphertext
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
.onOutputStream(ciphertext)
.withOptions(options); // pass in the options object
Streams.pipeAll(plaintext, encryptionStream); // pipe the data through
encryptionStream.close(); // important! Close the stream to finish encryption/signing
EncryptionResult result = encryptionStream.getResult(); // metadata
```
The `ciphertext` output stream now contains the encrypted and/or signed data.
Now lets take a look at the configuration of the `SigningOptions` object and how to instruct PGPainless to add a simple
signature to the message:
```java
PGPSecretKeyRing signingKey = ...; // Key used for signing
SecretKeyRingProtector protector = ...; // Protector to unlock the signing key
SigningOptions signOptions = SigningOptions.get()
.addSignature(protector, signingKey);
```
This will add an inline signature to the message.
It is possible to add multiple signatures from different keys by repeating the `addSignature()` method call.
If instead of an inline signature, you want to create a detached signature instead (e.g. because you do not want
to alter the data you are signing), you can add the signature as follows:
```java
signOptions.addDetachedSignature(protector, signingKey);
```
Passing in the `SigningOptions` object like this will result in the signature not being added to the message itself.
Instead, the signature can later be acquired from the `EncryptionResult` object via `EncryptionResult.getDetachedSignatures()`.
That way, it can be distributed independent of the message.
The `EncryptionOptions` object can be configured in a similar way:
```java
PGPPublicKey certificate = ...;
EncryptionOptions encOptions = EncryptionOptions.get()
.addRecipient(certificate);
```
Once again, it is possible to add multiple recipients by repeating the `addRecipient()` method call.
In order to prevent metadata leaks, you might want to add recipients anonymously.
Anonymous recipients have their key-id hidden by replacing it with a wildcard.
That way, it is not easily possible for an attacker to deduce the recipients of a message without further
analysis of additional metadata.
Anonymous recipients can be added like follows:
```java
encOptions.addHiddenRecipient(certificate);
```
You can also encrypt a message to a password like this:
```java
encOptions.addPassphrase(Passphrase.fromPassword("sw0rdf1sh"));
```
Both methods can be used in combination to create a message which can be decrypted with either a recipients secret key
or the passphrase.
### Decrypt and/or Verify a Message
Decryption and verification of a message is both done using the same API.
Whether a message was actually signed / encrypted can be determined after the message has been processed by checking
the `MessageMetadata` object which can be obtained from the `DecryptionStream`.
To configure the decryption / verification process, the `ConsumerOptions` object is used:
```java
PGPPublicKeyRing verificationCert = ...; // optional, signers certificate for signature verification
PGPSecretKeyRing decryptionKey = ...; // optional, decryption key
ConsumerOptions options = ConsumerOptions.get()
.addVerificationCert(verificationCert) // add a verification cert for signature verification
.addDecryptionKey(decryptionKey); // add a secret key for message decryption
```
Both verification certificates and decryption keys are optional.
If you know the message is signed, but not encrypted you can omit providing a decryption key.
Same goes for if you know that the message is encrypted, but not signed.
In this case you can omit the verification certificate.
On the other hand, providing these parameters does not hurt.
PGPainless will ignore unused keys / certificates, so if you provide a decryption key and the message is not encrypted,
nothing bad will happen.
It is possible to provide multiple verification certs and decryption keys. PGPainless will pick suitable ones on the fly.
If the message is signed with key `0xAAAA` and you provide certificates `0xAAAA` and `0xBBBB`, it will verify
with cert `0xAAAA` and ignore `0xBBBB`.
To do the actual decryption / verification of the message, do the following:
```java
InputStream ciphertext = ...; // encrypted and/or signed message
OutputStream plaintext = ...; // destination for the plaintext
ConsumerOptions options = ...; // see above
DecryptionStream consumerStream = PGPainless.decryptAndOrVerify()
.onInputStream(ciphertext)
.withOptions(options);
Streams.pipeAll(consumerStream, plaintext);
consumerStream.close(); // important!
// The result will contain metadata of the message
MessageMetadata result = consumerStream.getMetadata();
```
After the message has been processed, you can consult the `MessageMetadata` object to determine the nature of the message:
```java
boolean wasEncrypted = result.isEncrypted();
SubkeyIdentifier decryptionKey = result.getDecryptionKey();
List<SignatureVerification> validSignatures = result.getVerifiedSignatures();
boolean wasSignedByCert = result.isVerifiedSignedBy(certificate);
// For files:
String fileName = result.getFileName();
Date modificationData = result.getModificationDate();
```
### Verify a Signature
In some cases, detached signatures are distributed alongside the message.
This is the case for example with Debians `Release` and `Release.gpg` files.
Here, `Release` is the plaintext message, which is unaltered by the signing process while `Release.gpg` contains
the detached OpenPGP signature.
To verify a detached signature, you need to call the PGPainless API like this:
```java
InputStream plaintext = ...; // e.g. new FileInputStream(releaseFile);
InputStream detachedSignature = ...; // e.g. new FileInputStream(releaseGpgFile);
PGPPublicKeyRing certificate = ...; // e.g. debians public signing key
ConsumerOptions options = ConsumerOptions.get()
.addVerificationCert(certificate) // provide certificate for verification
.addVerificationOfDetachedSignatures(detachedSignature) // provide detached signature
DecryptionStream verificationStream = PGPainless.decryptAndOrVerify()
.onInputStream(plaintext)
.withOptions(options);
Streams.drain(verificationStream); // push all the data through the stream
verificationStream.close(); // finish verification
MessageMetadata result = verificationStream.getMetadata(); // get metadata of signed message
assertTrue(result.isVerifiedSignedBy(certificate)); // check if message was in fact signed
```
### Legacy Compatibility
Out of the box, PGPainless is configured to use secure defaults and perform checks for recommended
security features. This means that for example messages generated using older OpenPGP
implementations which do not follow those best practices might fail to decrypt/verify.
It is however possible to circumvent certain security checks to allow processing of such messages.
:::{note}
It is not recommended to disable security checks, as that might enable certain attacks on the OpenPGP protocol.
:::
#### Missing / broken MDC (modification detection code)
RFC4880 has two different types of encrypted data packets. The *Symmetrically Encrypted Data* packet (SED) and the *Symmetrically Encrypted Integrity-Protected Data* packet.
The latter has an added MDC packet which prevents modifications to the ciphertext.
While implementations are highly encouraged to only use the latter package type, some older implementations still generate
encrypted data packets which are not integrity protected.
To allow PGPainless to decrypt such messages, you need to set a flag in the `ConsumerOptions` object:
```java
ConsumerOptions options = ConsumerOptions.get()
.setIgnoreMDCErrors(true) // <-
.setDecryptionKey(secretKey)
...
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(ciphertextIn)
.withOptions(options);
...
```
:::{note}
It is highly advised to only set this flag if you know what you are doing.
It might also be a good idea to try decrypting a message without the flag set first and only re-try
decryption with the flag set in case of a `MessageNotIntegrityProtectedException` (don't forget to rewind the ciphertextInputStream).
:::
#### Weak keys and broken algorithms
Some users might cling on to older keys using weak algorithms / small key sizes.
PGPainless refuses to encrypt to weak certificates and sign with weak keys.
By default, PGPainless follows the recommendations for acceptable key sizes of [the German BSI in 2021](https://www.bsi.bund.de/SharedDocs/Downloads/EN/BSI/Publications/TechGuidelines/TG02102/BSI-TR-02102-1.pdf).
It can however be configured to accept older key material / algorithms too.
Minimal key lengths can be configured by changing PGPainless' policy:
```java
Map<PublicKeyAlgorithm, Integer> algorithms = new HashMap<>();
// put all acceptable algorithms and their minimal key length
algorithms.put(PublicKeyAlgorithm.RSA_GENERAL, 1024);
algorithms.put(PublicKeyAlgorithm.ECDSA, 100);
...
Policy.PublicKeyAlgorithmPolicy pkPolicy =
new Policy.PublicKeyAlgorithmPolicy(algorithms);
// set the custom algorithm policy
PGPainless.getPolicy().setPublicKeyAlgorithmPolicy();
```
Since OpenPGP uses a hybrid encryption scheme of asymmetric and symmetric encryption algorithms,
it also comes with a policy for symmetric encryption algorithms.
This list can be modified to allow for weaker algorithms like follows:
```java
// default fallback algorithm for message encryption
SymmetricKeyAlgorithm fallbackAlgorithm = SymmetricKeyAlgorithm.AES_256;
// acceptable algorithms
List<SymmetricKeyAlgorithm> algorithms = new ArrayList<>();
algorithms.add(SymmetricKeyAlgorithm.AES_256);
algorithms.add(SymmetricKeyAlgorithm.AES_192);
algorithms.add(SymmetricKeyAlgorithm.AES_128);
algorithms.add(SymmetricKeyAlgorithm.TWOFISH);
algorithms.add(SymmetricKeyAlgorithm.BLOWFISH);
...
Policy.SymmetricKeyAlgorithmPolicy skPolicy =
new SymmtricKeyAlgorithmPolicy(fallbackAlgorithm, algorithms);
// set the custom algorithm policy
// algorithm policy applicable when decrypting messages created by legacy senders:
PGPainless.getPolicy()
.setSymmetricKeyDecryptionAlgorithmPolicy(skPolicy);
// algorithm policy applicable when generating messages for legacy recipients:
PGPainless.getPolicy()
.setSymmetricKeyEncryptionAlgorithmPolicy(skPolicy);
```
Hash algorithms are used in OpenPGP to create signatures.
Since signature verification is an integral part of the OpenPGP protocol, PGPainless comes
with multiple policies for acceptable hash algorithms, depending on the use-case.
Revocation signatures are critical, so you might want to handle revocation signatures differently from normal signatures.
By default, PGPainless uses a smart hash algorithm policy for both use-cases, which takes into consideration
not only the hash algorithm itself, but also the creation date of the signature.
That way, signatures using SHA-1 are acceptable if they were created before February 2013, but are rejected if their
creation date is after that point in time.
A custom hash algorithm policy can be set like this:
```java
HashAlgorithm fallbackAlgorithm = HashAlgorithm.SHA512;
Map<HashAlgorithm, Date> algorithms = new HashMap<>();
// Accept MD5 on signatures made before 1997-02-01
algorithms.put(HashAlgorithm.MD5,
DateUtil.parseUTCDate("1997-02-01 00:00:00 UTC"));
// Accept SHA-1, regardless of signature creation time
algorithms.put(HashAlgorithm.SHA1, null);
...
Policy.HashAlgorithmPolicy hPolicy =
new Policy.HashAlgorithmPolicy(fallbackAlgorithm, algorithms);
// set policy for revocation signatures
PGPainless.getPolicy()
.setRevocationSignatureHashAlgorithmPolicy(hPolicy);
// set policy for normal signatures (certifications and document signatures)
PGPainless.getPolicy()
.setSignatureHashAlgorithmPolicy(hPolicy);
```
Lastly, PGPainless comes with a policy on acceptable compression algorithms, which currently accepts any
compression algorithm.
A custom compression algorithm policy can be set in a similar way:
```java
CompressionAlgorithm fallback = CompressionAlgorithm.ZIP;
List<CompressionAlgorithm> algorithms = new ArrayList<>();
algorithms.add(CompressionAlgorith.ZIP);
algorithms.add(CompressionAlgorithm.BZIP2);
...
Policy.CompressionAlgorithmPolicy cPolicy =
new Policy.CompressionAlgorithmPolicy(fallback, algorithms);
PGPainless.getPolicy()
.setCompressionAlgorithmPolicy(cPolicy);
```
To prevent a class of attacks described in the [paper](https://www.kopenpgp.com/#paper)
"Victory by KO: Attacking OpenPGP Using Key Overwriting",
PGPainless offers the option to validate private key material each time before using it,
to make sure that an attacker didn't tamper with the corresponding public key parameters.
These checks are disabled by default, but they can be enabled as follows:
```java
PGPainless.getPolicy()
.setEnableKeyParameterValidation(true);
```
:::{note}
Validation checks against KOpenPGP attacks are disabled by default, since they are very costly
and only make sense in certain scenarios.
Please read and understand the paper to decide, if enabling the checks makes sense for your use-case.
:::
### Known Notations
In OpenPGP, signatures can contain [notation subpackets](https://www.rfc-editor.org/rfc/rfc4880#section-5.2.3.16).
A notation can give meaning to a signature, or add additional contextual information.
Signature subpackets can be marked as critical, meaning an implementation that does not know about
a certain subpacket MUST reject the signature.
The same is true for critical notations.
For that reason, PGPainless comes with a `NotationRegistry` class which can be used to register known notations,
such that a signature containing a critical notation of a certain value is not rejected.
To register a known notation, you can do the following:
```java
NotationRegistry registry = PGPainless.getPolicy()
.getNotationRegistry();
registry.addKnownNotation("sample@example.com");
```

View file

@ -0,0 +1,47 @@
# User-IDs
User-IDs are identities that users go by. A User-ID might be a name, an email address or both.
User-IDs can also contain both and even have a comment.
In general, the format of a User-ID is not fixed, so it can contain arbitrary strings.
However, it is agreed upon to use the
Below is a selection of possible User-IDs:
```
Firstname Lastname (Comment) <email@address.tld>
Firstname Lastname
Firstname Lastname (Comment)
<email@address.tld>
```
PGPainless comes with a builder class `UserId`, which can be used to safely construct User-IDs:
```java
UserId nameAndEMail = UserId.nameAndEmail("Jane Doe", "jane@pgpainless.org");
assertEquals("Jane Doe <jane@pgpainless.org>", nameAndEmail.toString()):
UserId onlyEmail = UserId.onlyEmail("john@pgpainless.org");
assertEquals("<john@pgpainless.org>", onlyEmail.toString());
UserId full = UserId.newBuilder()
.withName("Peter Pattern")
.withEmail("peter@pgpainless.org")
.withComment("Work Address")
.build();
assertEquals("Peter Pattern (Work Address) <peter@pgpainless.org>", full.toString());
```
If you have a User-ID in form of a string (e.g. because a user provided it via a text field),
you can parse it into its components like this:
```java
String string = "John Doe <john@doe.corp>";
UserId userId = UserId.parse(string);
// Now you can access the different components
assertEquals("John Doe", userId.getName());
assertEquals("john@doe.corp", userId.getEmail());
assertNull(userId.getComment());
```
The method `UserId.parse(String string)` will throw an `IllegalArgumentException` if the User-ID is malformed.

View file

@ -0,0 +1,546 @@
## SOP API with pgpainless-sop
The Stateless OpenPGP Protocol (SOP) defines a simplistic interface for the most important OpenPGP operations.
It allows you to encrypt, decrypt, sign and verify messages, generate keys and add/remove ASCII armor from data.
However, it does not yet provide tools for key management.
Furthermore, the implementation is deciding for you, which (secure) algorithms to use, and it doesn't let you
change those.
If you want to read more about the background of the SOP protocol, there is a [whole chapter](../sop) dedicated to it.
### Setup
PGPainless' releases are published to and can be fetched from Maven Central.
To get started, you first need to include `pgpainless-sop` in your projects build script.
```
// If you use Gradle
...
dependencies {
...
implementation "org.pgpainless:pgpainless-sop:XYZ"
...
}
// If you use Maven
...
<dependencies>
...
<dependency>
<groupId>org.pgpainless</groupId>
<artifactId>pgpainless-sop</artifactId>
<version>XYZ</version>
</dependency>
...
</dependencies>
```
:::{important}
Replace `XYZ` with the current version, in this case {{ env.config.version }}!
:::
The entry point to the API is the `SOP` interface, for which `pgpainless-sop` provides a concrete implementation
`SOPImpl`.
```java
// Instantiate the API
SOP sop = new SOPImpl();
```
Now you are ready to go!
### Generate a Key
To generate a new OpenPGP key, the method `SOP.generateKey()` is your friend:
```java
// generate key
byte[] keyBytes = sop.generateKey()
.userId("John Doe <john.doe@pgpainless.org>")
.withKeyPassword("f00b4r")
.generate()
.getBytes();
```
The call `userId(String userId)` can be called multiple times to add multiple user-ids to the key, but it MUST
be called at least once.
The argument given in the first invocation will become the keys primary user-id.
Optionally, the key can be protected with a password by calling `withKeyPassword(String password)`.
If this method is not called, the key will be unprotected.
The `generate()` method call generates the key and returns a `Ready` object.
This in turn can be used to write the result to a stream via `writeTo(OutputStream out)`, or to get the result
as bytes via `getBytes()`.
In both cases, the resulting output will be the UTF8 encoded, ASCII armored OpenPGP secret key.
To disable ASCII armoring, call `noArmor()` before calling `generate()`.
Revision `05` of the Stateless OpenPGP Protocol specification introduced the concept of profiles for
certain operations.
The key generation feature is the first operation to make use of profiles to specify different key algorithms.
To set a profile, simply call `profile(String profileName)` and pass in one of the available profile identifiers.
To explore, which profiles are available, refer to the dedicated [section](#explore-profiles).
The default profile used by `pgpainless-sop` is called `draft-koch-eddsa-for-openpgp-00`.
If this profile is used, the resulting OpenPGP secret key will consist of a certification-capable 256-bits
ed25519 EdDSA primary key, a 256-bits ed25519 EdDSA subkey used for signing, as well as a 256-bits X25519
ECDH subkey for encryption.
Another profile defined by `pgpainless-sop` is `rfc4880`, which changes the key generation behaviour such that
the resulting key is a single 4096-bit RSA key capable of certifying, signing and encrypting.
The whole key does not have an expiration date set.
### Extract a Certificate
Now that you generated your secret key, you probably want to share the public key with your contacts.
To extract the OpenPGP public key (which we will call *certificate* from now on) from the secret key,
use the `SOP.extractCert()` method call:
```java
// extract certificate
byte[] certificateBytes = sop.extractCert()
.key(keyBytes)
.getBytes();
```
The `key(_)` method either takes a byte array (like in the example), or an `InputStream`.
In both cases it returns another `Ready` object from which the certificate can be accessed, either via
`writeTo(OutputStream out)` or `getBytes()`.
By default, the resulting certificate will be ASCII armored, regardless of whether the input key was armored or not.
To disable ASCII armoring, call `noArmor()` before calling `key(_)`.
In our example, `certificateBytes` can now safely be shared with anyone.
### Change Key Password
OpenPGP keys can (but don't need to) be password protected.
The `changeKeyPassword()` API can be used to add, change or remove password protection from OpenPGP keys.
While the input to this operation can be keys with different per-subkey passwords, the output will use at most one password.
Via `oldKeyPassphrase()`, multiple decryption passphrase candidates can be provided.
These are tried one after another to unlock protected subkeys.
In order to successfully change the passphrase of an OpenPGP key, all of its subkeys needs to be successfully decrypted.
If one or more subkeys cannot be decrypted, the operation fails with a `KeyIsProtected` exception.
The result is either fully encrypted for a single passphrase (passed via `newKeyPassphrase()`),
or unprotected if the new key passphrase is omitted.
```java
byte[] keyBefore = ...
byte[] keyAfter = sop.changeKeyPassword()
// Provide old passphrases - all subkeys need to be decryptable,
// otherwise KeyIsProtected exception will be thrown
.oldKeyPassphrase("4d4m5m1th")
.oldKeyPassphrase("d4v1dR1c4rd0")
// Provide the new passphrase - if omitted, key will be unprotected
.newKeyPassphrase("fr1edr1ch3n93l5")
.keys(keyBefore)
.getBytes();
```
### Generate Revocation Certificates
You might want to generate a revocation certificate for your OpenPGP key.
This certificate can be published to a key server to let your contacts known that your key is no longer
trustworthy.
The `revokeKey()` API can be used to generate a "hard-revocation", which retroactively invalidates all
signatures previously issued by the key.
If the input secret key is an OpenPGP v6 key, the result will be a minimal revocation certificate,
consisting of only the bare primary public key and a revocation signature. For v4 keys, the result
will consist of the whole public certificate plus a revocation signature.
```java
byte[] keys = ...
byte[] revoked = sop.revokeKey()
// primary key password(s) if the key(s) are protected
.withKeyPassword("5w0rdf1sh")
// one or more secret keys
.keys(keys)
.getBytes();
```
### Apply / Remove ASCII Armor
Perhaps you want to print your secret key onto a piece of paper for backup purposes,
but you accidentally called `noArmor()` when generating the key.
To add ASCII armor to some binary OpenPGP data, the `armor()` API can be used:
```java
// wrap data in ASCII armor
byte[] armoredData = sop.armor()
.data(binaryData)
.getBytes();
```
The `data(_)` method can either be called by providing a byte array, or an `InputStream`.
To remove ASCII armor from armored data, simply use the `dearmor()` API:
```java
// remove ASCII armor
byte[] binaryData = sop.unarmor()
.data(armoredData)
.getBytes();
```
Once again, the `data(_)` method can be called either with a byte array or an `InputStream` as argument.
If the input data is not validly armored OpenPGP data, the `data(_)` method call will throw a `BadData` exception.
### Encrypt a Message
Now lets get to the juicy part and finally encrypt a message!
In this example, we will assume that Alice is the sender that wants to send a message to Bob.
Beforehand, Alice acquired Bobs certificate, e.g. by fetching it from a key server.
To encrypt a message, you can make use of the `encrypt()` API:
```java
// encrypt and sign a message
byte[] aliceKey = ...; // Alice' secret key
byte[] aliceCert = ...; // Alice' certificate (e.g. via extractCert())
byte[] bobCert = ...; // Bobs certificate
byte[] plaintext = "Hello, World!\n".getBytes(); // plaintext
byte[] ciphertext = sop.encrypt()
// encrypt for each recipient
.withCert(bobCert)
.withCert(aliceCert)
// Optionally: Sign the message
.signWith(aliceKey)
.withKeyPassword("sw0rdf1sh") // if signing key is protected
// provide the plaintext
.plaintext(plaintext)
.getBytes();
```
Here you encrypt the message for each recipient (Alice probably wants to be able to decrypt the message too!)
by calling `withCert(_)` with the recipients certificate as argument. It does not matter, if the certificate
is ASCII armored or not, and the method can either be called with a byte array or an `InputStream` as argument.
The API not only supports asymmetric encryption via OpenPGP certificates, but it can also encrypt messages
symmetrically using one or more passwords. Both mechanisms can even be used together in the same message!
To (additionally or exclusively) encrypt the message for a password, simply call `withPassword(String password)`
before the `plaintext(_)` method call.
It is recommended (but not required) to sign encrypted messages.
In order to sign the message before encryption is applied, call `signWith(_)` with the signing key as argument.
This method call can be repeated multiple times to sign the message with multiple signing keys.
If any keys used for signing are password protected, you need to provide the signing key password via
`withKeyPassword(_)`.
It does not matter in which order signing keys and key passwords are provided, the implementation will figure out
matches on its own. If different key passwords are used, the `withKeyPassword(_)` method can be called multiple times.
You can modify the behaviour of the encrypt operation by switching between different profiles via the
`profile(String profileName)` method.
At the time of writing, the only available profile for this operation is `rfc4880` which applies encryption
as defined in [rfc4880](https://datatracker.ietf.org/doc/html/rfc4880).
To explore, which profiles are available, refer to the dedicated [section](#explore-profiles).
By default, the encrypted message will be ASCII armored. To disable ASCII armor, call `noArmor()` before the
`plaintext(_)` method call.
Lastly, you need to provide the plaintext by calling `plaintext(_)` with either a byte array or an `InputStream`
as argument.
The ciphertext can then be accessed from the resulting `Ready` object as usual.
### Decrypt a Message
Now let's switch perspective and help Bob decrypt the message from Alice.
Decrypting encrypted messages is done in a similar fashion using the `decrypt()` API:
```java
// decrypt a message and verify its signature(s)
byte[] aliceCert = ...; // Alice' certificate
byte[] bobKey = ...; // Bobs secret key
byte[] bobCert = ...; // Bobs certificate
byte[] ciphertext = ...; // the encrypted message
ReadyWithResult<DecryptionResult> readyWithResult = sop.decrypt()
.withKey(bobKey)
.verifyWithCert(aliceCert)
.withKeyPassword("password123") // if decryption key is protected
.ciphertext(ciphertext);
```
The `ReadyWithResult<DecryptionResult>` can now be processed in two different ways, depending on whether you want the
plaintext as bytes or simply write it out to an `OutputStream`.
To get the plaintext bytes directly, you shall proceed as follows:
```java
ByteArrayAndResult<DecryptionResult> bytesAndResult = readyWithResult.toByteArrayAndResult();
DecryptionResult result = bytesAndResult.getResult();
byte[] plaintext = bytesAndResult.getBytes();
```
If you instead want to write the plaintext out to an `OutputStream`, the following code can be used:
```java
OutputStream out = ...;
DecryptionResult result = readyWithResult.writeTo(out);
```
Note, that in both cases you acquire a `DecryptionResult` object. This contains information about the message,
such as which signatures could successfully be verified.
If you provided the senders certificate for the purpose of signature verification via `verifyWith(_)`, you now
probably want to check, if the message was actually signed by the sender by checking `result.getVerifications()`.
:::{note}
Signature verification will be discussed in more detail in section "Verifications".
:::
If the message was encrypted symmetrically using a password, you can also decrypt is symmetrically by calling
`withPassword(String password)` before the `ciphertext(_)` method call. This method call can be repeated multiple
times. The implementation will try different passwords until it finds a matching one.
### Sign a Message
There are three different main ways of signing a message:
* Inline Signatures
* Cleartext Signatures
* Detached Signatures
An inline-signature will be part of the message itself (e.g. like with messages that are encrypted *and* signed).
Inline-signed messages are not human-readable without prior processing.
A cleartext signature makes use of the [cleartext signature framework](https://datatracker.ietf.org/doc/html/rfc4880#section-7).
Messages signed in this way do have an ASCII armor header and footer, yet the content of the message is still
human-readable without special software.
Lastly, a detached signature can be distributed as an extra file alongside the message without altering it.
This is useful if the plaintext itself cannot be modified (e.g. if a binary file is signed).
The SOP API can generate all of those signature types.
#### Inline-Signatures
Let's start with an inline signature:
```java
byte[] signingKey = ...;
byte[] message = ...;
byte[] inlineSignedMessage = sop.inlineSign()
.mode(InlineSignAs.Text) // or 'Binary'
.key(signingKey)
.withKeyPassword("fnord")
.data(message)
.getBytes();
```
You can choose between two different signature formats which can be set using `mode(InlineSignAs mode)`.
The default value is `Binary`. You can also set it to `Text` which signals to the receiver that the data is
UTF8 text.
:::{note}
For inline signatures, do NOT set the `mode()` to `CleartextSigned`, as that will create message which uses the
cleartext signature framework (see further below).
:::
You must provide at least one signing key using `key(_)` in order to be able to sign the message.
If any key is password protected, you need to provide its password using `withKeyPassword(_)` which
can be called multiple times to provide multiple passwords.
Once you provide the plaintext using `data(_)` with either a byte array or an `InputStream` as argument,
you will get a `Ready` object back, from which the signed message can be retrieved as usual.
By default, the signed message will be ASCII armored. This can be disabled by calling `noArmor()`
before the `data(_)` method call.
#### Cleartext Signatures
A cleartext-signed message can be generated in a similar way to an inline-signed message, however,
there are is one subtle difference:
```java
byte[] signingKey = ...;
byte[] message = ...;
byte[] cleartextSignedMessage = sop.inlineSign()
.mode(InlineSignAs.CleartextSigned) // This MUST be set
.key(signingKey)
.withKeyPassword("fnord")
.data(message)
.getBytes();
```
:::{important}
In order to produce a cleartext-signed message, the signature mode MUST be set to `CleartextSigned`
by calling `mode(InlineSignAs.CleartextSigned)`.
:::
:::{note}
Calling `noArmor()` will have no effect for cleartext-signed messages, so such method call will be ignored.
:::
#### Detached Signatures
As the name suggests, detached signatures are detached from the message itself and can be distributed separately.
To produce a detached signature, the `detachedSign()` API is used:
```java
byte[] signingKey = ...;
byte[] message = ...;
ReadyWithResult<SigningResult> readyWithResult = sop.detachedSign()
.key(signingKey)
.withKeyPassword("fnord")
.data(message);
```
Here you have the choice, how you want to write out the signature.
If you want to write the signature to an `OutputStream`, you can do the following:
```java
OutputStream out = ...;
SigningResult result = readyWithResult.writeTo(out);
```
If instead you want to get the signature as a byte array, do this instead:
```java
ByteArrayAndResult<SigningResult> bytesAndResult = readyWithResult.toByteArrayAndResult();
SigningResult result = bytesAndResult.getResult();
byte[] detachedSignature = bytesAndResult.getBytes();
```
In any case, the detached signature can now be distributed alongside the original message.
By default, the resulting detached signature will be ASCII armored. This can be disabled by calling `noArmor()`
prior to calling `data(_)`.
The `SigningResult` object you got back in both cases contains information about the signature.
### Verify a Signature
In order to verify signed messages, there are two API endpoints available.
#### Inline and Cleartext Signatures
To verify inline-signed messages, or messages that make use of the cleartext signature framework,
use the `inlineVerify()` API:
```java
byte[] signingCert = ...;
byte[] signedMessage = ...;
ReadyWithResult<List<Verification>> readyWithResult = sop.inlineVerify()
.cert(signingCert)
.data(signedMessage);
```
The `cert(_)` method MUST be called at least once. It takes either a byte array or an `InputStream` containing
an OpenPGP certificate.
If you are not sure, which certificate was used to sign the message, you can provide multiple certificates.
It is also possible to reject signatures that were not made within a certain time window by calling
`notBefore(Date timestamp)` and/or `notAfter(Date timestamp)`.
Signatures made before the `notBefore(_)` or after the `notAfter(_)` constraints will be rejected.
You can now either write out the plaintext message to an `OutputStream`...
```java
OutputStream out = ...;
List<Verifications> verifications = readyWithResult.writeTo(out);
```
... or you can acquire the plaintext message as a byte array directly:
```java
ByteArrayAndResult<List<Verifications>> bytesAndResult = readyWithResult.toByteArrayAndResult();
byte[] plaintextMessage = bytesAndResult.getBytes();
List<Verifications> verifications = bytesAndResult.getResult();
```
In both cases, the plaintext message will have the signatures stripped.
#### Detached Signatures
To verify detached signatures (signatures that come separate from the message itself), you can use the
`detachedVerify()` API:
```java
byte[] signingCert = ...;
byte[] message = ...;
byte[] detachedSignature = ...;
List<Verification> verifications = sop.detachedVerify()
.cert(signingCert)
.signatures(detachedSignature)
.data(signedMessage);
```
You can provide one or more OpenPGP certificates using `cert(_)`, providing either a byte array or an `InputStream`.
The detached signatures need to be provided separately using the `signatures(_)` method call.
You can provide as many detached signatures as you like, and those can be binary or ASCII armored.
Like with Inline Signatures, you can constrain the time window for signature validity using
`notAfter(_)` and `notBefore(_)`.
#### Verifications
In all above cases, the `verifications` list will contain `Verification` objects for each verifiable, valid signature.
Those objects contain information about the signatures:
`verification.getSigningCertFingerprint()` will return the fingerprint of the certificate that created the signature.
`verification.getSigningKeyFingerprint()` will return the fingerprint of the used signing subkey within that certificate.
### Detach Signatures from Messages
It is also possible, to detach inline or cleartext signatures from signed messages to transform them into
detached signatures.
The same way you can turn inline or cleartext signed messages into plaintext messages.
To detach signatures from messages, use the `inlineDetach()` API:
```java
byte[] signedMessage = ...;
ReadyWithResult<Signatures> readyWithResult = sop.inlineDetach()
.message(signedMessage);
ByteArrayAndResult<Signatures> bytesAndResult = readyWithResult.toByteArrayAndResult();
byte[] plaintext = bytesAndResult.getBytes();
Signatures signatures = bytesAndResult.getResult();
byte[] encodedSignatures = signatures.getBytes();
```
By default, the signatures output will be ASCII armored. This can be disabled by calling `noArmor()`
prior to `message(_)`.
The detached signatures can now be verified like in the section above.
### Explore Profiles
Certain operations allow modification of their behaviour by selecting between different profiles.
An example for this is the `generateKey()` operation, where different profiles result in different algorithms used
during key generation.
To explore, which profiles are supported by a certain operation, you can use the `listProfiles()` operation.
For example, this is how you can get a list of profiles supported by the `generateKey()` operation:
```java
List<Profile> profiles = sop.listProfiles().subcommand("generate-key");
```
:::{note}
As you can see, the argument passed into the `subcommand()` method must match the operation name as defined in the
[Stateless OpenPGP Protocol specification](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/).
:::
At the time of writing (the latest revision of the SOP spec is 06), only `generate-key` and `encrypt` accept profiles.

20
docs/source/quickstart.md Normal file
View file

@ -0,0 +1,20 @@
# Quickstart Guide
In this guide, we will get you started with OpenPGP using PGPainless as quickly as possible.
At first though, you need to decide which API you want to use;
* PGPainless' core API is powerful and heavily customizable
* The SOP API is a bit less powerful, but *dead* simple to use
The SOP API is the recommended way to go if you just want to get started already.
In case you need more technical documentation, Javadoc can be found in the following places:
* For the core API: {{ '[pgpainless-core](https://javadoc.io/doc/org.pgpainless/pgpainless-core/{}/index.html)'.format(env.config.version) }}
* For the SOP API: {{ '[pgpainless-sop](https://javadoc.io/doc/org.pgpainless/pgpainless-sop/{}/index.html)'.format(env.config.version) }}
```{include} pgpainless-sop/quickstart.md
```
```{include} pgpainless-core/quickstart.md
```

10
docs/source/sop.md Normal file
View file

@ -0,0 +1,10 @@
# Stateless OpenPGP Protocol (SOP)
The [Stateless OpenPGP Protocol](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/)
(short *SOP*) is a specification of a standardized command line interface for a limited set of OpenPGP operations.
By standardizing the interface, users are able to choose between different, compatible implementations.
:::{note}
This chapter is work in progress.
:::

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-rc-1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

File diff suppressed because one or more lines are too long

View file

@ -6,19 +6,21 @@ SPDX-License-Identifier: Apache-2.0
# PGPainless-CLI
PGPainless-CLI is an implementation of the [Stateless OpenPGP Command Line Interface](https://tools.ietf.org/html/draft-dkg-openpgp-stateless-cli-01) specification based on PGPainless.
[![javadoc](https://javadoc.io/badge2/org.pgpainless/pgpainless-cli/javadoc.svg)](https://javadoc.io/doc/org.pgpainless/pgpainless-cli)
PGPainless-CLI is an implementation of the [Stateless OpenPGP Command Line Interface](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/) specification based on PGPainless.
It plugs `pgpainless-sop` into `sop-java-picocli`.
## Build
To build an executable, `gradle jar` should be sufficient. The resulting jar file can be found in `pgpainless-sop/build/libs/`.
To build an executable, `gradle shadowJar` should be sufficient. The resulting jar file can be found in `pgpainless-cli/build/libs/`.
## Execute
The jar file produced in the step above is executable as is.
```
java -jar pgpainless-cli-XXX.jar help
java -jar pgpainless-cli-XXX-all.jar help
```
Alternatively you can use the provided `./pgpainless-cli` script to directly build and execute PGPainless' Stateless Command Line Interface from within Gradle.

View file

@ -4,26 +4,13 @@
plugins {
id 'application'
}
def generatedVersionDir = "${buildDir}/generated-version"
sourceSets {
main {
output.dir(generatedVersionDir, builtBy: 'generateVersionProperties')
}
id 'org.graalvm.buildtools.native' version '0.10.6'
id 'com.gradleup.shadow' version '8.3.6'
}
task generateVersionProperties {
doLast {
def propertiesFile = file "$generatedVersionDir/version.properties"
propertiesFile.parentFile.mkdirs()
propertiesFile.createNewFile()
// Instead of using a Properties object here, we directly write to the file
// since Properties adds a timestamp, ruining reproducibility
propertiesFile.write("version="+rootProject.version.toString())
}
graalvmNative {
toolchainDetection = true
}
processResources.dependsOn generateVersionProperties
dependencies {
@ -31,18 +18,15 @@ dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// https://todd.ginsberg.com/post/testing-system-exit/
testImplementation 'com.ginsberg:junit5-system-exit:1.1.2'
// implementation "ch.qos.logback:logback-core:1.2.6"
// We want logback logging in tests and in the app
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
implementation "ch.qos.logback:logback-classic:$logbackVersion"
// implementation "ch.qos.logback:logback-classic:$logbackVersion"
implementation "org.slf4j:slf4j-nop:$slf4jVersion"
implementation(project(":pgpainless-sop"))
implementation(project(":sop-java-picocli"))
implementation "info.picocli:picocli:$picocliVersion"
implementation "org.pgpainless:sop-java-picocli:$sopJavaVersion"
// https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
@ -50,19 +34,8 @@ dependencies {
mainClassName = 'org.pgpainless.cli.PGPainlessCLI'
jar {
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
manifest {
attributes 'Main-Class': "$mainClassName"
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
} {
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
}
application {
mainClass = mainClassName
}
run {
@ -73,5 +46,3 @@ run {
args Eval.me(appArgs)
}
}
tasks."jar".dependsOn(":pgpainless-core:assemble")

View file

@ -0,0 +1,41 @@
'\" t
.\" Title: pgpainless-cli-armor
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-ARMOR" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-armor \- Add ASCII Armor to standard input
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli armor\fP [\fB\-\-stacktrace\fP]
.SH "DESCRIPTION"
.SH "OPTIONS"
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE

View file

@ -0,0 +1,67 @@
'\" t
.\" Title: pgpainless-cli-change-key-password
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-CHANGE\-KEY\-PASSWORD" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-change\-key\-password \- Update the password of a key
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli change\-key\-password\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-new\-key\-password\fP
[=\fIPASSWORD\fP]] [\fB\-\-old\-key\-password\fP=\fIPASSWORD\fP]...
.SH "DESCRIPTION"
.sp
Unlock all secret keys from STDIN using the given old passwords and emit them re\-locked using the new password to STDOUT.
If any (sub\-) key cannot be unlocked, this operation will exit with error code 67.
.SH "OPTIONS"
.sp
\fB\-\-new\-key\-password\fP[=\fIPASSWORD\fP]
.RS 4
New password to lock the keys with.
.sp
If no new password is passed in, the keys will be emitted unlocked.
.sp
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
.RE
.sp
\fB\-\-[no\-]armor\fP
.RS 4
ASCII armor the output
.RE
.sp
\fB\-\-old\-key\-password\fP=\fIPASSWORD\fP
.RS 4
Old passwords to unlock the keys with.
.sp
Multiple passwords can be passed in, which are tested sequentially to unlock locked subkeys.
.sp
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
.RE
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE

View file

@ -0,0 +1,41 @@
'\" t
.\" Title: pgpainless-cli-dearmor
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-DEARMOR" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-dearmor \- Remove ASCII Armor from standard input
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli dearmor\fP [\fB\-\-stacktrace\fP]
.SH "DESCRIPTION"
.SH "OPTIONS"
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE

View file

@ -0,0 +1,106 @@
'\" t
.\" Title: pgpainless-cli-decrypt
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-DECRYPT" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-decrypt \- Decrypt a message
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli decrypt\fP [\fB\-\-stacktrace\fP] [\fB\-\-session\-key\-out\fP=\fISESSIONKEY\fP]
[\fB\-\-verifications\-out\fP=\fIVERIFICATIONS\fP] [\fB\-\-verify\-not\-after\fP=\fIDATE\fP]
[\fB\-\-verify\-not\-before\fP=\fIDATE\fP] [\fB\-\-verify\-with\fP=\fICERT\fP]...
[\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]... [\fB\-\-with\-password\fP=\fIPASSWORD\fP]...
[\fB\-\-with\-session\-key\fP=\fISESSIONKEY\fP]... [\fIKEY\fP...]
.SH "DESCRIPTION"
.SH "OPTIONS"
.sp
\fB\-\-session\-key\-out\fP=\fISESSIONKEY\fP
.RS 4
Can be used to learn the session key on successful decryption
.RE
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE
.sp
\fB\-\-verifications\-out\fP=\fIVERIFICATIONS\fP, \fB\-\-verify\-not\-after\fP=\fIDATE\fP
.RS 4
ISO\-8601 formatted UTC date (e.g. \(aq2020\-11\-23T16:35Z)
.sp
Reject signatures with a creation date not in range.
.sp
Defaults to current system time (\(aqnow\(aq).
.sp
Accepts special value \(aq\-\(aq for end of time.
.RE
.sp
\fB\-\-verify\-not\-before\fP=\fIDATE\fP
.RS 4
ISO\-8601 formatted UTC date (e.g. \(aq2020\-11\-23T16:35Z)
.sp
Reject signatures with a creation date not in range.
.sp
Defaults to beginning of time (\(aq\-\(aq).
.RE
.sp
\fB\-\-verify\-with\fP=\fICERT\fP
.RS 4
Certificates for signature verification
.RE
.sp
\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP
.RS 4
Passphrase to unlock the secret key(s).
.sp
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
.RE
.sp
\fB\-\-with\-password\fP=\fIPASSWORD\fP
.RS 4
Symmetric passphrase to decrypt the message with.
.sp
Enables decryption based on any "SKESK" packets in the "CIPHERTEXT".
.sp
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
.RE
.sp
\fB\-\-with\-session\-key\fP=\fISESSIONKEY\fP
.RS 4
Symmetric message key (session key).
.sp
Enables decryption of the "CIPHERTEXT" using the session key directly against the "SEIPD" packet.
.sp
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
.RE
.SH "ARGUMENTS"
.sp
[\fIKEY\fP...]
.RS 4
Secret keys to attempt decryption with
.RE

View file

@ -0,0 +1,84 @@
'\" t
.\" Title: pgpainless-cli-encrypt
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-ENCRYPT" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-encrypt \- Encrypt a message from standard input
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli encrypt\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-as\fP=\fI{binary|text}\fP]
[\fB\-\-profile\fP=\fIPROFILE\fP] [\fB\-\-session\-key\-out\fP=\fISESSIONKEY\fP]
[\fB\-\-sign\-with\fP=\fIKEY\fP]... [\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]...
[\fB\-\-with\-password\fP=\fIPASSWORD\fP]... [\fICERTS\fP...]
.SH "DESCRIPTION"
.SH "OPTIONS"
.sp
\fB\-\-as\fP=\fI{binary|text}\fP
.RS 4
Type of the input data. Defaults to \(aqbinary\(aq
.RE
.sp
\fB\-\-[no\-]armor\fP
.RS 4
ASCII armor the output
.RE
.sp
\fB\-\-profile\fP=\fIPROFILE\fP
.RS 4
Profile identifier to switch between profiles
.RE
.sp
\fB\-\-session\-key\-out\fP=\fISESSIONKEY\fP, \fB\-\-sign\-with\fP=\fIKEY\fP
.RS 4
Sign the output with a private key
.RE
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE
.sp
\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP
.RS 4
Passphrase to unlock the secret key(s).
.sp
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
.RE
.sp
\fB\-\-with\-password\fP=\fIPASSWORD\fP
.RS 4
Encrypt the message with a password.
.sp
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
.RE
.SH "ARGUMENTS"
.sp
[\fICERTS\fP...]
.RS 4
Certificates the message gets encrypted to
.RE

View file

@ -0,0 +1,47 @@
'\" t
.\" Title: pgpainless-cli-extract-cert
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-EXTRACT\-CERT" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-extract\-cert \- Extract a public key certificate from a secret key
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli extract\-cert\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP]
.SH "DESCRIPTION"
.sp
Read a secret key from STDIN and emit the public key certificate to STDOUT.
.SH "OPTIONS"
.sp
\fB\-\-[no\-]armor\fP
.RS 4
ASCII armor the output
.RE
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE

View file

@ -0,0 +1,165 @@
'\" t
.\" Title: pgpainless-cli-generate-completion
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source: generate-completion 4.6.3
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-GENERATE\-COMPLETION" "1" "" "generate\-completion 4.6.3" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-generate\-completion \- Stateless OpenPGP Protocol
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli generate\-completion\fP [\fB\-hV\fP] [\fB\-\-stacktrace\fP]
.SH "DESCRIPTION"
.sp
Generate bash/zsh completion script for pgpainless\-cli.
Run the following command to give \f(CRpgpainless\-cli\fP TAB completion in the current shell:
.sp
.if n .RS 4
.nf
source <(pgpainless\-cli generate\-completion)
.fi
.if n .RE
.SH "OPTIONS"
.sp
\fB\-h\fP, \fB\-\-help\fP
.RS 4
Show this help message and exit.
.RE
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE
.sp
\fB\-V\fP, \fB\-\-version\fP
.RS 4
Print version information and exit.
.RE
.SH "EXIT CODES:"
.sp
\fB0\fP
.RS 4
Successful program execution
.RE
.sp
\fB1\fP
.RS 4
Generic program error
.RE
.sp
\fB3\fP
.RS 4
Verification requested but no verifiable signature found
.RE
.sp
\fB13\fP
.RS 4
Unsupported asymmetric algorithm
.RE
.sp
\fB17\fP
.RS 4
Certificate is not encryption capable
.RE
.sp
\fB19\fP
.RS 4
Usage error: Missing argument
.RE
.sp
\fB23\fP
.RS 4
Incomplete verification instructions
.RE
.sp
\fB29\fP
.RS 4
Unable to decrypt
.RE
.sp
\fB31\fP
.RS 4
Password is not human\-readable
.RE
.sp
\fB37\fP
.RS 4
Unsupported Option
.RE
.sp
\fB41\fP
.RS 4
Invalid data or data of wrong type encountered
.RE
.sp
\fB53\fP
.RS 4
Non\-text input received where text was expected
.RE
.sp
\fB59\fP
.RS 4
Output file already exists
.RE
.sp
\fB61\fP
.RS 4
Input file does not exist
.RE
.sp
\fB67\fP
.RS 4
Cannot unlock password protected secret key
.RE
.sp
\fB69\fP
.RS 4
Unsupported subcommand
.RE
.sp
\fB71\fP
.RS 4
Unsupported special prefix (e.g. "@ENV/@FD") of indirect parameter
.RE
.sp
\fB73\fP
.RS 4
Ambiguous input (a filename matching the designator already exists)
.RE
.sp
\fB79\fP
.RS 4
Key is not signing capable
.RE
.sp
\fB83\fP
.RS 4
Options were supplied that are incompatible with each other
.RE
.sp
\fB89\fP
.RS 4
The requested profile is unsupported, or the indicated subcommand does not accept profiles
.RE

View file

@ -0,0 +1,71 @@
'\" t
.\" Title: pgpainless-cli-generate-key
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-GENERATE\-KEY" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-generate\-key \- Generate a secret key
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli generate\-key\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-signing\-only\fP] [\fB\-\-stacktrace\fP]
[\fB\-\-profile\fP=\fIPROFILE\fP] [\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP] [\fIUSERID\fP...]
.SH "DESCRIPTION"
.SH "OPTIONS"
.sp
\fB\-\-[no\-]armor\fP
.RS 4
ASCII armor the output
.RE
.sp
\fB\-\-profile\fP=\fIPROFILE\fP
.RS 4
Profile identifier to switch between profiles
.RE
.sp
\fB\-\-signing\-only\fP
.RS 4
Generate a key that can only be used for signing
.RE
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE
.sp
\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP
.RS 4
Password to protect the private key with
.sp
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
.RE
.SH "ARGUMENTS"
.sp
[\fIUSERID\fP...]
.RS 4
User\-ID, e.g. "Alice <\c
.MTO "alice\(atexample.com" "" ">""
.RE

View file

@ -0,0 +1,160 @@
'\" t
.\" Title: pgpainless-cli-help
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-HELP" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-help \- Stateless OpenPGP Protocol
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli help\fP [\fB\-h\fP] [\fB\-\-stacktrace\fP] [\fICOMMAND\fP]
.SH "DESCRIPTION"
.sp
When no COMMAND is given, the usage help for the main command is displayed.
If a COMMAND is specified, the help for that command is shown.
.SH "OPTIONS"
.sp
\fB\-h\fP, \fB\-\-help\fP
.RS 4
Show usage help for the help command and exit.
.RE
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE
.SH "ARGUMENTS"
.sp
[\fICOMMAND\fP]
.RS 4
The COMMAND to display the usage help message for.
.RE
.SH "EXIT CODES:"
.sp
\fB0\fP
.RS 4
Successful program execution
.RE
.sp
\fB1\fP
.RS 4
Generic program error
.RE
.sp
\fB3\fP
.RS 4
Verification requested but no verifiable signature found
.RE
.sp
\fB13\fP
.RS 4
Unsupported asymmetric algorithm
.RE
.sp
\fB17\fP
.RS 4
Certificate is not encryption capable
.RE
.sp
\fB19\fP
.RS 4
Usage error: Missing argument
.RE
.sp
\fB23\fP
.RS 4
Incomplete verification instructions
.RE
.sp
\fB29\fP
.RS 4
Unable to decrypt
.RE
.sp
\fB31\fP
.RS 4
Password is not human\-readable
.RE
.sp
\fB37\fP
.RS 4
Unsupported Option
.RE
.sp
\fB41\fP
.RS 4
Invalid data or data of wrong type encountered
.RE
.sp
\fB53\fP
.RS 4
Non\-text input received where text was expected
.RE
.sp
\fB59\fP
.RS 4
Output file already exists
.RE
.sp
\fB61\fP
.RS 4
Input file does not exist
.RE
.sp
\fB67\fP
.RS 4
Cannot unlock password protected secret key
.RE
.sp
\fB69\fP
.RS 4
Unsupported subcommand
.RE
.sp
\fB71\fP
.RS 4
Unsupported special prefix (e.g. "@ENV/@FD") of indirect parameter
.RE
.sp
\fB73\fP
.RS 4
Ambiguous input (a filename matching the designator already exists)
.RE
.sp
\fB79\fP
.RS 4
Key is not signing capable
.RE
.sp
\fB83\fP
.RS 4
Options were supplied that are incompatible with each other
.RE
.sp
\fB89\fP
.RS 4
The requested profile is unsupported, or the indicated subcommand does not accept profiles
.RE

View file

@ -0,0 +1,51 @@
'\" t
.\" Title: pgpainless-cli-inline-detach
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-INLINE\-DETACH" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-inline\-detach \- Split signatures from a clearsigned message
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli inline\-detach\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-signatures\-out\fP=\fISIGNATURES\fP]
.SH "DESCRIPTION"
.SH "OPTIONS"
.sp
\fB\-\-[no\-]armor\fP
.RS 4
ASCII armor the output
.RE
.sp
\fB\-\-signatures\-out\fP=\fISIGNATURES\fP
.RS 4
Destination to which a detached signatures block will be written
.RE
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE

View file

@ -0,0 +1,73 @@
'\" t
.\" Title: pgpainless-cli-inline-sign
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-INLINE\-SIGN" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-inline\-sign \- Create an inline\-signed message
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli inline\-sign\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-as\fP=\fI{binary|text|clearsigned}\fP]
[\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]... [\fIKEYS\fP...]
.SH "DESCRIPTION"
.SH "OPTIONS"
.sp
\fB\-\-as\fP=\fI{binary|text|clearsigned}\fP
.RS 4
Specify the signature format of the signed message.
.sp
\(aqtext\(aq and \(aqbinary\(aq will produce inline\-signed messages.
.sp
\(aqclearsigned\(aq will make use of the cleartext signature framework.
.sp
Defaults to \(aqbinary\(aq.
.sp
If \(aq\-\-as=text\(aq and the input data is not valid UTF\-8, inline\-sign fails with return code 53.
.RE
.sp
\fB\-\-[no\-]armor\fP
.RS 4
ASCII armor the output
.RE
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE
.sp
\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP
.RS 4
Passphrase to unlock the secret key(s).
.sp
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
.RE
.SH "ARGUMENTS"
.sp
[\fIKEYS\fP...]
.RS 4
Secret keys used for signing
.RE

View file

@ -0,0 +1,73 @@
'\" t
.\" Title: pgpainless-cli-inline-verify
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-INLINE\-VERIFY" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-inline\-verify \- Verify an inline\-signed message
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli inline\-verify\fP [\fB\-\-stacktrace\fP] [\fB\-\-not\-after\fP=\fIDATE\fP] [\fB\-\-not\-before\fP=\fIDATE\fP]
[\fB\-\-verifications\-out\fP=\fIVERIFICATIONS\fP] [\fICERT\fP...]
.SH "DESCRIPTION"
.SH "OPTIONS"
.sp
\fB\-\-not\-after\fP=\fIDATE\fP
.RS 4
ISO\-8601 formatted UTC date (e.g. \(aq2020\-11\-23T16:35Z)
.sp
Reject signatures with a creation date not in range.
.sp
Defaults to current system time ("now").
.sp
Accepts special value "\-" for end of time.
.RE
.sp
\fB\-\-not\-before\fP=\fIDATE\fP
.RS 4
ISO\-8601 formatted UTC date (e.g. \(aq2020\-11\-23T16:35Z)
.sp
Reject signatures with a creation date not in range.
.sp
Defaults to beginning of time ("\-").
.RE
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE
.sp
\fB\-\-verifications\-out\fP=\fIVERIFICATIONS\fP
.RS 4
File to write details over successful verifications to
.RE
.SH "ARGUMENTS"
.sp
[\fICERT\fP...]
.RS 4
Public key certificates for signature verification
.RE

View file

@ -0,0 +1,47 @@
'\" t
.\" Title: pgpainless-cli-list-profiles
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-LIST\-PROFILES" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-list\-profiles \- Emit a list of profiles supported by the identified subcommand
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli list\-profiles\fP [\fB\-\-stacktrace\fP] \fICOMMAND\fP
.SH "DESCRIPTION"
.SH "OPTIONS"
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE
.SH "ARGUMENTS"
.sp
\fICOMMAND\fP
.RS 4
Subcommand for which to list profiles
.RE

View file

@ -0,0 +1,54 @@
'\" t
.\" Title: pgpainless-cli-revoke-key
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-REVOKE\-KEY" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-revoke\-key \- Generate revocation certificates
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli revoke\-key\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]
.SH "DESCRIPTION"
.sp
Emit revocation certificates for secret keys from STDIN to STDOUT.
.SH "OPTIONS"
.sp
\fB\-\-[no\-]armor\fP
.RS 4
ASCII armor the output
.RE
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE
.sp
\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP
.RS 4
Passphrase to unlock the secret key(s).
.sp
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
.RE

View file

@ -0,0 +1,74 @@
'\" t
.\" Title: pgpainless-cli-sign
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-SIGN" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-sign \- Create a detached message signature
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli sign\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-as\fP=\fI{binary|text}\fP]
[\fB\-\-micalg\-out\fP=\fIMICALG\fP] [\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]... [\fIKEYS\fP...]
.SH "DESCRIPTION"
.SH "OPTIONS"
.sp
\fB\-\-as\fP=\fI{binary|text}\fP
.RS 4
Specify the output format of the signed message.
.sp
Defaults to \(aqbinary\(aq.
.sp
If \(aq\-\-as=text\(aq and the input data is not valid UTF\-8, sign fails with return code 53.
.RE
.sp
\fB\-\-micalg\-out\fP=\fIMICALG\fP
.RS 4
Emits the digest algorithm used to the specified file in a way that can be used to populate the micalg parameter for the PGP/MIME Content\-Type (RFC3156).
.RE
.sp
\fB\-\-[no\-]armor\fP
.RS 4
ASCII armor the output
.RE
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE
.sp
\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP
.RS 4
Passphrase to unlock the secret key(s).
.sp
Is an INDIRECT data type (e.g. file, environment variable, file descriptor...).
.RE
.SH "ARGUMENTS"
.sp
[\fIKEYS\fP...]
.RS 4
Secret keys used for signing
.RE

View file

@ -0,0 +1,74 @@
'\" t
.\" Title: pgpainless-cli-verify
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-VERIFY" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-verify \- Verify a detached signature
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli verify\fP [\fB\-\-stacktrace\fP] [\fB\-\-not\-after\fP=\fIDATE\fP] [\fB\-\-not\-before\fP=\fIDATE\fP] \fISIGNATURE\fP
\fICERT\fP...
.SH "DESCRIPTION"
.sp
Verify a detached signature over some data from STDIN.
.SH "OPTIONS"
.sp
\fB\-\-not\-after\fP=\fIDATE\fP
.RS 4
ISO\-8601 formatted UTC date (e.g. \(aq2020\-11\-23T16:35Z)
.sp
Reject signatures with a creation date not in range.
.sp
Defaults to current system time ("now").
.sp
Accepts special value "\-" for end of time.
.RE
.sp
\fB\-\-not\-before\fP=\fIDATE\fP
.RS 4
ISO\-8601 formatted UTC date (e.g. \(aq2020\-11\-23T16:35Z)
.sp
Reject signatures with a creation date not in range.
.sp
Defaults to beginning of time ("\-").
.RE
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE
.SH "ARGUMENTS"
.sp
\fISIGNATURE\fP
.RS 4
Detached signature
.RE
.sp
\fICERT\fP...
.RS 4
Public key certificates for signature verification
.RE

View file

@ -0,0 +1,56 @@
'\" t
.\" Title: pgpainless-cli-version
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI\-VERSION" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli\-version \- Display version information about the tool
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli version\fP [\fB\-\-stacktrace\fP] [\fB\-\-extended\fP | \fB\-\-backend\fP | \fB\-\-pgpainless\-cli\-spec\fP | \fB\-\-sopv\fP]
.SH "DESCRIPTION"
.SH "OPTIONS"
.sp
\fB\-\-backend\fP
.RS 4
Print information about the cryptographic backend
.RE
.sp
\fB\-\-extended\fP
.RS 4
Print an extended version string
.RE
.sp
\fB\-\-pgpainless\-cli\-spec\fP
.RS 4
Print the latest revision of the SOP specification targeted by the implementation
.RE
.sp
\fB\-\-sopv\fP, \fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE

View file

@ -0,0 +1,233 @@
'\" t
.\" Title: pgpainless-cli
.\" Author: [see the "AUTHOR(S)" section]
.\" Generator: Asciidoctor 2.0.10
.\" Manual: PGPainless-CLI Manual
.\" Source:
.\" Language: English
.\"
.TH "PGPAINLESS\-CLI" "1" "" "" "PGPainless\-CLI Manual"
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.ss \n[.ss] 0
.nh
.ad l
.de URL
\fI\\$2\fP <\\$1>\\$3
..
.als MTO URL
.if \n[.g] \{\
. mso www.tmac
. am URL
. ad l
. .
. am MTO
. ad l
. .
. LINKSTYLE blue R < >
.\}
.SH "NAME"
pgpainless\-cli \- Stateless OpenPGP Protocol
.SH "SYNOPSIS"
.sp
\fBpgpainless\-cli\fP [\fB\-\-stacktrace\fP] [COMMAND]
.SH "DESCRIPTION"
.SH "OPTIONS"
.sp
\fB\-\-stacktrace\fP
.RS 4
Print stacktrace
.RE
.SH "COMMANDS"
.sp
\fBversion\fP
.RS 4
Display version information about the tool
.RE
.sp
\fBlist\-profiles\fP
.RS 4
Emit a list of profiles supported by the identified subcommand
.RE
.sp
\fBgenerate\-key\fP
.RS 4
Generate a secret key
.RE
.sp
\fBchange\-key\-password\fP
.RS 4
Update the password of a key
.RE
.sp
\fBrevoke\-key\fP
.RS 4
Generate revocation certificates
.RE
.sp
\fBextract\-cert\fP
.RS 4
Extract a public key certificate from a secret key
.RE
.sp
\fBsign\fP
.RS 4
Create a detached message signature
.RE
.sp
\fBverify\fP
.RS 4
Verify a detached signature
.RE
.sp
\fBencrypt\fP
.RS 4
Encrypt a message from standard input
.RE
.sp
\fBdecrypt\fP
.RS 4
Decrypt a message
.RE
.sp
\fBinline\-detach\fP
.RS 4
Split signatures from a clearsigned message
.RE
.sp
\fBinline\-sign\fP
.RS 4
Create an inline\-signed message
.RE
.sp
\fBinline\-verify\fP
.RS 4
Verify an inline\-signed message
.RE
.sp
\fBarmor\fP
.RS 4
Add ASCII Armor to standard input
.RE
.sp
\fBdearmor\fP
.RS 4
Remove ASCII Armor from standard input
.RE
.sp
\fBhelp\fP
.RS 4
Stateless OpenPGP Protocol
.RE
.sp
\fBgenerate\-completion\fP
.RS 4
Stateless OpenPGP Protocol
.RE
.SH "EXIT CODES:"
.sp
\fB0\fP
.RS 4
Successful program execution
.RE
.sp
\fB1\fP
.RS 4
Generic program error
.RE
.sp
\fB3\fP
.RS 4
Verification requested but no verifiable signature found
.RE
.sp
\fB13\fP
.RS 4
Unsupported asymmetric algorithm
.RE
.sp
\fB17\fP
.RS 4
Certificate is not encryption capable
.RE
.sp
\fB19\fP
.RS 4
Usage error: Missing argument
.RE
.sp
\fB23\fP
.RS 4
Incomplete verification instructions
.RE
.sp
\fB29\fP
.RS 4
Unable to decrypt
.RE
.sp
\fB31\fP
.RS 4
Password is not human\-readable
.RE
.sp
\fB37\fP
.RS 4
Unsupported Option
.RE
.sp
\fB41\fP
.RS 4
Invalid data or data of wrong type encountered
.RE
.sp
\fB53\fP
.RS 4
Non\-text input received where text was expected
.RE
.sp
\fB59\fP
.RS 4
Output file already exists
.RE
.sp
\fB61\fP
.RS 4
Input file does not exist
.RE
.sp
\fB67\fP
.RS 4
Cannot unlock password protected secret key
.RE
.sp
\fB69\fP
.RS 4
Unsupported subcommand
.RE
.sp
\fB71\fP
.RS 4
Unsupported special prefix (e.g. "@ENV/@FD") of indirect parameter
.RE
.sp
\fB73\fP
.RS 4
Ambiguous input (a filename matching the designator already exists)
.RE
.sp
\fB79\fP
.RS 4
Key is not signing capable
.RE
.sp
\fB83\fP
.RS 4
Options were supplied that are incompatible with each other
.RE
.sp
\fB89\fP
.RS 4
The requested profile is unsupported, or the indicated subcommand does not accept profiles
.RE

View file

@ -0,0 +1,26 @@
#!/usr/bin/env bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
SOP_DIR=$(realpath $SCRIPT_DIR/../../sop-java)
[ ! -d "$SOP_DIR" ] && echo "sop-java repository MUST be cloned next to pgpainless repo" && exit 1;
SRC_DIR=$SOP_DIR/sop-java-picocli/build/docs/manpage
[ ! -d "$SRC_DIR" ] && echo "No sop manpages found. Please run 'gradle asciidoctor' in the sop-java repo." && exit 1;
DEST_DIR=$SCRIPT_DIR/packaging/man
mkdir -p $DEST_DIR
for page in $SRC_DIR/*
do
SRC="${page##*/}"
DEST="${SRC/sop/pgpainless-cli}"
sed \
-e 's/sopv/PLACEHOLDERV/g' \
-e 's#.\\" Title: sop#.\\" Title: pgpainless-cli#g' \
-e 's/Manual: Sop Manual/Manual: PGPainless-CLI Manual/g' \
-e 's/.TH "SOP/.TH "PGPAINLESS\\-CLI/g' \
-e 's/"Sop Manual"/"PGPainless\\-CLI Manual"/g' \
-e 's/\\fBsop/\\fBpgpainless\\-cli/g' \
-e 's/sop/pgpainless\\-cli/g' \
-e 's/PLACEHOLDERV/sopv/g' \
$page > $DEST_DIR/$DEST
done

View file

@ -7,13 +7,25 @@ package org.pgpainless.cli;
import org.pgpainless.sop.SOPImpl;
import sop.cli.picocli.SopCLI;
/**
* This class merely binds PGPainless to {@link SopCLI} by injecting a {@link SOPImpl} instance.
* CLI command calls are then simply forwarded to {@link SopCLI#execute(String[])}.
*/
public class PGPainlessCLI {
static {
// Prevent slf4j initialization logging
// https://github.com/qos-ch/slf4j/issues/422#issuecomment-2277280185
System.setProperty("slf4j.internal.verbosity", "WARN");
SopCLI.EXECUTABLE_NAME = "pgpainless-cli";
SopCLI.setSopInstance(new SOPImpl());
}
/**
* Main method of the CLI application.
* @param args arguments
*/
public static void main(String[] args) {
int result = execute(args);
if (result != 0) {
@ -21,6 +33,12 @@ public class PGPainlessCLI {
}
}
/**
* Execute the given command and return the exit code of the program.
*
* @param args command string array (e.g. ["pgpainless-cli", "generate-key", "Alice"])
* @return exit code
*/
public static int execute(String... args) {
return SopCLI.execute(args);
}

View file

@ -0,0 +1,7 @@
[
{
"type":"agent-extracted",
"classes":[
]
}
]

View file

@ -0,0 +1,891 @@
[
{
"name":"[Ljava.lang.Object;"
},
{
"name":"ch.qos.logback.classic.encoder.PatternLayoutEncoder",
"queryAllPublicMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"ch.qos.logback.classic.joran.SerializedModelConfigurator",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"ch.qos.logback.classic.util.DefaultJoranConfigurator",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"ch.qos.logback.core.ConsoleAppender",
"queryAllPublicMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setTarget","parameterTypes":["java.lang.String"] }]
},
{
"name":"ch.qos.logback.core.OutputStreamAppender",
"methods":[{"name":"setEncoder","parameterTypes":["ch.qos.logback.core.encoder.Encoder"] }]
},
{
"name":"ch.qos.logback.core.encoder.Encoder",
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
},
{
"name":"ch.qos.logback.core.encoder.LayoutWrappingEncoder",
"methods":[{"name":"setParent","parameterTypes":["ch.qos.logback.core.spi.ContextAware"] }]
},
{
"name":"ch.qos.logback.core.pattern.PatternLayoutEncoderBase",
"methods":[{"name":"setPattern","parameterTypes":["java.lang.String"] }]
},
{
"name":"ch.qos.logback.core.spi.ContextAware",
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
},
{
"name":"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"groovy.lang.Closure"
},
{
"name":"java.io.FilePermission"
},
{
"name":"java.lang.Enum"
},
{
"name":"java.lang.Object",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"java.lang.RuntimePermission"
},
{
"name":"java.lang.System",
"methods":[{"name":"console","parameterTypes":[] }]
},
{
"name":"java.lang.invoke.MethodHandle"
},
{
"name":"java.net.NetPermission"
},
{
"name":"java.net.SocketPermission"
},
{
"name":"java.net.URLPermission",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.String"] }]
},
{
"name":"java.nio.channels.SelectionKey",
"fields":[{"name":"attachment"}]
},
{
"name":"java.nio.file.Path"
},
{
"name":"java.nio.file.Paths",
"methods":[{"name":"get","parameterTypes":["java.lang.String","java.lang.String[]"] }]
},
{
"name":"java.security.AllPermission"
},
{
"name":"java.security.MessageDigestSpi"
},
{
"name":"java.security.SecureRandomParameters"
},
{
"name":"java.security.SecurityPermission"
},
{
"name":"java.security.cert.PKIXRevocationChecker"
},
{
"name":"java.sql.Connection"
},
{
"name":"java.sql.Driver"
},
{
"name":"java.sql.DriverManager",
"methods":[{"name":"getConnection","parameterTypes":["java.lang.String"] }, {"name":"getDriver","parameterTypes":["java.lang.String"] }]
},
{
"name":"java.sql.Time",
"methods":[{"name":"<init>","parameterTypes":["long"] }]
},
{
"name":"java.sql.Timestamp",
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
},
{
"name":"java.time.Duration",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.Instant",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.LocalDate",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.LocalDateTime",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.LocalTime",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.MonthDay",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.OffsetDateTime",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.OffsetTime",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.Period",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.Year",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.YearMonth",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.time.ZoneId",
"methods":[{"name":"of","parameterTypes":["java.lang.String"] }]
},
{
"name":"java.time.ZoneOffset",
"methods":[{"name":"of","parameterTypes":["java.lang.String"] }]
},
{
"name":"java.time.ZonedDateTime",
"methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }]
},
{
"name":"java.util.HashSet"
},
{
"name":"java.util.LinkedHashSet"
},
{
"name":"java.util.PropertyPermission"
},
{
"name":"java.util.concurrent.ArrayBlockingQueue"
},
{
"name":"java.util.concurrent.atomic.AtomicReference",
"fields":[{"name":"value"}]
},
{
"name":"java.util.concurrent.locks.AbstractOwnableSynchronizer"
},
{
"name":"java.util.concurrent.locks.AbstractQueuedSynchronizer"
},
{
"name":"java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject"
},
{
"name":"java.util.concurrent.locks.ReentrantLock"
},
{
"name":"java.util.concurrent.locks.ReentrantLock$NonfairSync"
},
{
"name":"java.util.concurrent.locks.ReentrantLock$Sync"
},
{
"name":"javax.smartcardio.CardPermission"
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.COMPOSITE$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.CONTEXT$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.CompositeSignatures$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.DH$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.DSA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.DSTU4145$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.Dilithium$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.EC$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.ECGOST$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.EXTERNAL$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.EdEC$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.ElGamal$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.Falcon$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.GM$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.GOST$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.IES$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.LMS$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.MLDSA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.MLKEM$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.NTRU$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.NoSig$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.RSA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.SLHDSA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.SPHINCSPlus$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.X509$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.edec.KeyPairGeneratorSpi$EdDSA",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.edec.KeyPairGeneratorSpi$XDH",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.asymmetric.rsa.KeyPairGeneratorSpi",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Blake2b$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Blake2s$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Blake3$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.DSTU7564$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.GOST3411$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Haraka$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Keccak$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.MD2$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.MD4$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.MD5$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD128$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD160$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD256$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.RIPEMD320$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA1$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA224$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA256$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA3$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA384$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SHA512$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.SM3$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Skein$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Tiger$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.digest.Whirlpool$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.drbg.DRBG$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.keystore.BC$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.keystore.BCFKS$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.keystore.PKCS12$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.AES$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.ARC4$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.ARIA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Blowfish$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.CAST5$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.CAST6$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Camellia$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.ChaCha$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.DES$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.DESede$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.DSTU7624$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.GOST28147$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.GOST3412_2015$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Grain128$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Grainv1$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.HC128$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.HC256$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.IDEA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Noekeon$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.OpenSSLPBKDF$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF1$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.PBEPBKDF2$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.PBEPKCS12$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Poly1305$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.RC2$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.RC5$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.RC6$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Rijndael$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SCRYPT$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SEED$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SM4$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Salsa20$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Serpent$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Shacal2$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SipHash$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.SipHash128$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Skipjack$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.TEA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.TLSKDF$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Threefish$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Twofish$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.VMPC$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.VMPCKSA3$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.XSalsa20$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.XTEA$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.bouncycastle.jcajce.provider.symmetric.Zuc$Mappings",
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.ExitCodeTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"successfulExecutionDoesNotTerminateJVM","parameterTypes":[] }, {"name":"testCommandWithUnknownOption_37","parameterTypes":[] }, {"name":"testUnknownCommand_69","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.TestUtils",
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true
},
{
"name":"org.pgpainless.cli.commands.ArmorCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"armorAlreadyArmoredDataIsIdempotent","parameterTypes":[] }, {"name":"armorMessage","parameterTypes":[] }, {"name":"armorPublicKey","parameterTypes":[] }, {"name":"armorSecretKey","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.CLITest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"methods":[{"name":"cleanup","parameterTypes":[] }, {"name":"setup","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.DearmorCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"dearmorBrokenArmoredKeyFails","parameterTypes":[] }, {"name":"dearmorCertificate","parameterTypes":[] }, {"name":"dearmorGarbageEmitsEmpty","parameterTypes":[] }, {"name":"dearmorMessage","parameterTypes":[] }, {"name":"dearmorSecretKey","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.ExtractCertCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"extractCertFromGarbageFails","parameterTypes":[] }, {"name":"testExtractCert","parameterTypes":[] }, {"name":"testExtractCertFromCertFails","parameterTypes":[] }, {"name":"testExtractCertUnarmored","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.GenerateKeyCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"testGenerateBinaryKey","parameterTypes":[] }, {"name":"testGenerateKey","parameterTypes":[] }, {"name":"testGenerateKeyWithMultipleUserIds","parameterTypes":[] }, {"name":"testGeneratePasswordProtectedKey_missingPasswordFile","parameterTypes":[] }, {"name":"testPasswordProtectedKey","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.InlineDetachCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"detachInbandSignatureAndMessage","parameterTypes":[] }, {"name":"detachInbandSignatureAndMessageNoArmor","parameterTypes":[] }, {"name":"detachMissingSignaturesFromCleartextSignedMessageFails","parameterTypes":[] }, {"name":"detachNonOpenPgpDataFails","parameterTypes":[] }, {"name":"existingSignatureOutCausesException","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.ListProfilesCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"listProfileOfGenerateKey","parameterTypes":[] }, {"name":"listProfilesOfEncrypt","parameterTypes":[] }, {"name":"listProfilesWithoutCommand","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.RoundTripEncryptDecryptCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"decryptGarbageFails","parameterTypes":[] }, {"name":"decryptMalformedMessageYieldsBadData","parameterTypes":[] }, {"name":"decryptMessageWithSessionKey","parameterTypes":[] }, {"name":"decryptMessageWithWrongKeyFails","parameterTypes":[] }, {"name":"decryptWithPasswordWithPendingWhitespaceWorks","parameterTypes":[] }, {"name":"decryptWithWhitespacePasswordWorks","parameterTypes":[] }, {"name":"decrypt_verifyWithGarbageCertFails","parameterTypes":[] }, {"name":"decrypt_withGarbageKeyFails","parameterTypes":[] }, {"name":"encryptAndDecryptAMessage","parameterTypes":[] }, {"name":"encryptAndDecryptMessageWithPassphrase","parameterTypes":[] }, {"name":"encryptWithGarbageCertFails","parameterTypes":[] }, {"name":"encryptWithPasswordADecryptWithPasswordBFails","parameterTypes":[] }, {"name":"encryptWithProtectedKey_wrongPassphraseFails","parameterTypes":[] }, {"name":"encryptWithTrailingWhitespaceDecryptWithoutWorks","parameterTypes":[] }, {"name":"encrypt_signWithGarbageKeyFails","parameterTypes":[] }, {"name":"testDecryptVerifyOut_withoutVerifyWithFails","parameterTypes":[] }, {"name":"testDecryptWithSessionKeyVerifyWithYieldsExpectedVerifications","parameterTypes":[] }, {"name":"testDecryptWithoutDecryptionOptionFails","parameterTypes":[] }, {"name":"testEncryptDecryptRoundTripWithPasswordProtectedKey","parameterTypes":[] }, {"name":"testEncryptDecryptWithFreshRSAKey","parameterTypes":[] }, {"name":"testEncryptWithIncapableCert","parameterTypes":[] }, {"name":"testEncrypt_SignWithCertFails","parameterTypes":[] }, {"name":"testMissingArgumentsIfNoArgsSupplied","parameterTypes":[] }, {"name":"testSessionKeyOutWritesSessionKeyOut","parameterTypes":[] }, {"name":"testSignWithIncapableKey","parameterTypes":[] }, {"name":"testVerificationsOutAlreadyExistFails","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.RoundTripInlineSignInlineVerifyCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"cannotVerifyEncryptedMessage","parameterTypes":[] }, {"name":"cannotVerifyMalformedMessage","parameterTypes":[] }, {"name":"createAndVerifyCleartextSignedMessage","parameterTypes":[] }, {"name":"createAndVerifyMultiKeyBinarySignedMessage","parameterTypes":[] }, {"name":"createAndVerifyTextSignedMessage","parameterTypes":[] }, {"name":"createCleartextSignedMessage","parameterTypes":[] }, {"name":"createMalformedMessage","parameterTypes":[] }, {"name":"createSignedMessageWithKeyAAndVerifyWithKeyBFails","parameterTypes":[] }, {"name":"createTextSignedMessageInlineDetachAndDetachedVerify","parameterTypes":[] }, {"name":"signWithProtectedKeyWithWrongPassphraseFails","parameterTypes":[] }, {"name":"testInlineSignWithMissingSecretKeysFails","parameterTypes":[] }, {"name":"testUnlockKeyWithOneOfMultiplePasswords","parameterTypes":[] }, {"name":"verifyPrependedSignedMessage","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.RoundTripInlineSignVerifyCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"encryptAndDecryptAMessage","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.RoundTripSignVerifyCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"createArmoredSignature","parameterTypes":[] }, {"name":"createUnarmoredSignature","parameterTypes":[] }, {"name":"signWithProtectedKey","parameterTypes":[] }, {"name":"signWithProtectedKey_missingPassphraseFails","parameterTypes":[] }, {"name":"signWithProtectedKey_wrongPassphraseFails","parameterTypes":[] }, {"name":"testNotAfter","parameterTypes":[] }, {"name":"testNotBefore","parameterTypes":[] }, {"name":"testSignWithIncapableKey","parameterTypes":[] }, {"name":"testSignatureCreationAndVerification","parameterTypes":[] }, {"name":"unarmorArmoredSigAndVerify","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.commands.VersionCmdTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"testExtendedVersion","parameterTypes":[] }, {"name":"testGetBackendVersion","parameterTypes":[] }, {"name":"testSopSpecVersion","parameterTypes":[] }, {"name":"testVersion","parameterTypes":[] }]
},
{
"name":"org.pgpainless.cli.misc.SignUsingPublicKeyBehaviorTest",
"allDeclaredFields":true,
"allDeclaredClasses":true,
"queryAllDeclaredMethods":true,
"queryAllPublicMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"testSignatureCreationAndVerification","parameterTypes":[] }]
},
{
"name":"picocli.AutoComplete$GenerateCompletion",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"picocli.CommandLine$AutoHelpMixin",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"picocli.CommandLine$HelpCommand",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"sop.cli.picocli.SopCLI",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.SopCLI$InitLocale",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"sop.cli.picocli.commands.AbstractSopCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"sop.cli.picocli.commands.ArmorCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.ChangeKeyPasswordCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"sop.cli.picocli.commands.DearmorCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.DecryptCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.EncryptCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.ExtractCertCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.GenerateKeyCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.InlineDetachCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.InlineSignCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.InlineVerifyCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.ListProfilesCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.RevokeKeyCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"sop.cli.picocli.commands.SignCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.VerifyCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.VersionCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sop.cli.picocli.commands.VersionCmd$Exclusive",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"sun.security.provider.NativePRNG",
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }]
},
{
"name":"sun.security.provider.SHA",
"methods":[{"name":"<init>","parameterTypes":[] }]
}
]

View file

@ -0,0 +1,93 @@
{
"resources":{
"includes":[{
"pattern":"\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E"
}, {
"pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E"
}, {
"pattern":"\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/java.util.spi.ResourceBundleControlProvider\\E"
}, {
"pattern":"\\QMETA-INF/services/javax.xml.parsers.SAXParserFactory\\E"
}, {
"pattern":"\\QMETA-INF/services/org.junit.platform.engine.TestEngine\\E"
}, {
"pattern":"\\QMETA-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener\\E"
}, {
"pattern":"\\QMETA-INF/services/org.junit.platform.launcher.LauncherSessionListener\\E"
}, {
"pattern":"\\QMETA-INF/services/org.junit.platform.launcher.PostDiscoveryFilter\\E"
}, {
"pattern":"\\QMETA-INF/services/org.junit.platform.launcher.TestExecutionListener\\E"
}, {
"pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E"
}, {
"pattern":"\\Qjunit-platform.properties\\E"
}, {
"pattern":"\\Qlogback-test.scmo\\E"
}, {
"pattern":"\\Qlogback-test.xml\\E"
}, {
"pattern":"\\Qlogback.scmo\\E"
}, {
"pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E"
}, {
"pattern":"\\Qpgpainless-sop.properties\\E"
}, {
"pattern":"\\Qsop-java-version.properties\\E"
}, {
"pattern":"java.base:\\Qsun/text/resources/LineBreakIteratorData\\E"
}]},
"bundles":[{
"name":"msg_armor",
"locales":["de", "und"]
}, {
"name":"msg_change-key-password",
"locales":["de", "und"]
}, {
"name":"msg_dearmor",
"locales":["de", "und"]
}, {
"name":"msg_decrypt",
"locales":["de", "und"]
}, {
"name":"msg_detached-sign",
"locales":["de", "und"]
}, {
"name":"msg_detached-verify",
"locales":["de", "und"]
}, {
"name":"msg_encrypt",
"locales":["de", "und"]
}, {
"name":"msg_extract-cert",
"locales":["de", "und"]
}, {
"name":"msg_generate-key",
"locales":["de", "und"]
}, {
"name":"msg_inline-detach",
"locales":["de", "und"]
}, {
"name":"msg_inline-sign",
"locales":["de", "und"]
}, {
"name":"msg_inline-verify",
"locales":["de", "und"]
}, {
"name":"msg_list-profiles",
"locales":["de", "und"]
}, {
"name":"msg_revoke-key",
"locales":["de", "und"]
}, {
"name":"msg_sop",
"locales":["de", "und"]
}, {
"name":"msg_version",
"locales":["de", "und"]
}]
}

View file

@ -0,0 +1,41 @@
{
"types":[
{
"name":"java.lang.Enum"
},
{
"name":"java.lang.Object[]"
},
{
"name":"java.util.HashSet"
},
{
"name":"java.util.LinkedHashSet"
},
{
"name":"java.util.concurrent.ArrayBlockingQueue"
},
{
"name":"java.util.concurrent.locks.AbstractOwnableSynchronizer"
},
{
"name":"java.util.concurrent.locks.AbstractQueuedSynchronizer"
},
{
"name":"java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject"
},
{
"name":"java.util.concurrent.locks.ReentrantLock"
},
{
"name":"java.util.concurrent.locks.ReentrantLock$NonfairSync"
},
{
"name":"java.util.concurrent.locks.ReentrantLock$Sync"
}
],
"lambdaCapturingTypes":[
],
"proxies":[
]
}

View file

@ -5,22 +5,5 @@ SPDX-License-Identifier: Apache-2.0
-->
<configuration debug="false">
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
<target>System.err</target>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="error">
<appender-ref ref="STDERR" />
</root>
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
</configuration>

View file

@ -4,27 +4,35 @@
package org.pgpainless.cli;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import com.ginsberg.junit.exit.FailOnSystemExit;
import org.junit.jupiter.api.Test;
import org.pgpainless.cli.commands.CLITest;
import org.slf4j.LoggerFactory;
import sop.exception.SOPGPException;
public class ExitCodeTest {
import java.io.IOException;
@Test
@ExpectSystemExitWithStatus(69)
public void testUnknownCommand_69() {
PGPainlessCLI.main(new String[] {"generate-kex"});
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ExitCodeTest extends CLITest {
public ExitCodeTest() {
super(LoggerFactory.getLogger(ExitCodeTest.class));
}
@Test
@ExpectSystemExitWithStatus(37)
public void testCommandWithUnknownOption_37() {
PGPainlessCLI.main(new String[] {"generate-key", "-k", "\"k is unknown\""});
public void testUnknownCommand_69() throws IOException {
assertEquals(SOPGPException.UnsupportedSubcommand.EXIT_CODE,
executeCommand("unsupported-subcommand"));
}
@Test
@FailOnSystemExit
public void successfulExecutionDoesNotTerminateJVM() {
PGPainlessCLI.main(new String[] {"version"});
public void testCommandWithUnknownOption_37() throws IOException {
assertEquals(SOPGPException.UnsupportedOption.EXIT_CODE,
executeCommand("generate-key", "-k", "\"k is unknown\""));
}
@Test
public void successfulExecutionDoesNotTerminateJVM() throws IOException {
assertSuccess(executeCommand("version"));
}
}

View file

@ -11,6 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@ -40,6 +41,17 @@ public class TestUtils {
return dir;
}
public static File writeTempFile(File tempDir, byte[] value) throws IOException {
File tempFile = new File(tempDir, randomString(10));
tempFile.createNewFile();
tempFile.deleteOnExit();
FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
fileOutputStream.write(value);
fileOutputStream.flush();
fileOutputStream.close();
return tempFile;
}
private static String randomString(int length) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {

View file

@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.slf4j.LoggerFactory;
public class ArmorCmdTest extends CLITest {
public ArmorCmdTest() {
super(LoggerFactory.getLogger(ArmorCmdTest.class));
}
private static final String key = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
"Version: PGPainless\n" +
"Comment: 62E9 DDA4 F20F 8341 D2BC 4B4C 8B07 5177 01F9 534C\n" +
"Comment: alice@pgpainless.org\n" +
"\n" +
"lFgEY2vOkhYJKwYBBAHaRw8BAQdAqGOtLd1tKnuwaYYcdr2/7C0cPiCCggRMKG+W\n" +
"t32QQdEAAP9VaBzjk/AaAqyykZnQHmS1HByEvRLv5/4yJMSr22451BFjtBRhbGlj\n" +
"ZUBwZ3BhaW5sZXNzLm9yZ4iOBBMWCgBBBQJja86SCRCLB1F3AflTTBYhBGLp3aTy\n" +
"D4NB0rxLTIsHUXcB+VNMAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAACZhAP4s\n" +
"8hn/RBDvyLvGROOd15EYATnWlgyi+b5WXP6cELalJwD1FZy3RROhfNtZWcJPS43f\n" +
"G03pYNyb0NXoitIMAaXEB5xdBGNrzpISCisGAQQBl1UBBQEBB0CqCcYethOynfni\n" +
"8uRO+r/cZWp9hCLy8pRIExKqzcyEFAMBCAcAAP9sRRLoZkLpDaTNNrtIBovXu2AN\n" +
"hL8keUMWtVcuEHnkQA6iiHUEGBYKAB0FAmNrzpICngECmwwFFgIDAQAECwkIBwUV\n" +
"CgkICwAKCRCLB1F3AflTTBVpAP491etrjqCMWx2bBaw3K1vP0Mix6U0vF3J4kP9U\n" +
"eZm6owEA4kX9VAGESvLgIc7CEiswmxdWjxnLQyCRtWXfjgFmYQucWARja86SFgkr\n" +
"BgEEAdpHDwEBB0DBslhDpWC6CV3xJUSo071NSO5Cf4fgOwOj+QHs8mpFbwABAPkQ\n" +
"ioSydYiMi04LyfPohyrhhcdJDHallQg+jYHHUb2pEJCI1QQYFgoAfQUCY2vOkgKe\n" +
"AQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNrzpIACgkQiHlkvEXh+f1e\n" +
"ywEA9A2GLU9LxCJxZf2X4qcZY//YJDChIZHPnY0Vaek1DsMBAN1YILrH2rxQeCXj\n" +
"m4bUKfJIRrGt6ZJscwORgNI1dFQFAAoJEIsHUXcB+VNMK3gA/3vvPm57JsHA860w\n" +
"lB4D1II71oFNL8TFnJqTAvpSKe1AAP49S4mKB4PE0ElcDo7n+nEYt6ba8IMRDlMo\n" +
"rsH85mUgCw==\n" +
"=EMKf\n" +
"-----END PGP PRIVATE KEY BLOCK-----\n";
@Test
public void armorSecretKey() throws IOException {
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(key);
byte[] binary = secretKeys.getEncoded();
pipeBytesToStdin(binary);
ByteArrayOutputStream armorOut = pipeStdoutToStream();
assertSuccess(executeCommand("armor"));
PGPSecretKeyRing armored = PGPainless.readKeyRing().secretKeyRing(armorOut.toString());
assertArrayEquals(secretKeys.getEncoded(), armored.getEncoded());
}
@Test
public void armorPublicKey() throws IOException {
PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(key);
PGPPublicKeyRing publicKey = PGPainless.extractCertificate(secretKey);
byte[] bytes = publicKey.getEncoded();
pipeBytesToStdin(bytes);
ByteArrayOutputStream armorOut = pipeStdoutToStream();
assertSuccess(executeCommand("armor"));
PGPPublicKeyRing armored = PGPainless.readKeyRing().publicKeyRing(armorOut.toString());
assertArrayEquals(publicKey.getEncoded(), armored.getEncoded());
}
@Test
public void armorMessage() throws IOException {
String message = "Hello, World!\n";
pipeStringToStdin(message);
ByteArrayOutputStream armorOut = pipeStdoutToStream();
assertSuccess(executeCommand("armor"));
String armored = armorOut.toString();
assertTrue(armored.startsWith("-----BEGIN PGP MESSAGE-----\n"));
assertTrue(armored.contains("SGVsbG8sIFdvcmxkIQo="));
}
@Test
public void armorAlreadyArmoredDataIsIdempotent() throws IOException {
pipeStringToStdin(key);
ByteArrayOutputStream armorOut = pipeStdoutToStream();
assertSuccess(executeCommand("armor"));
String armored = armorOut.toString();
assertEquals(key, armored);
}
}

View file

@ -1,91 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import com.ginsberg.junit.exit.FailOnSystemExit;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.cli.PGPainlessCLI;
public class ArmorTest {
private static PrintStream originalSout;
@BeforeEach
public void saveSout() {
originalSout = System.out;
}
@AfterEach
public void restoreSout() {
System.setOut(originalSout);
}
@Test
@FailOnSystemExit
public void armorSecretKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
.modernKeyRing("alice@pgpainless.org", null);
byte[] bytes = secretKey.getEncoded();
System.setIn(new ByteArrayInputStream(bytes));
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(armorOut));
PGPainlessCLI.execute("armor");
PGPSecretKeyRing armored = PGPainless.readKeyRing().secretKeyRing(armorOut.toString());
assertArrayEquals(secretKey.getEncoded(), armored.getEncoded());
}
@Test
@FailOnSystemExit
public void armorPublicKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
.modernKeyRing("alice@pgpainless.org", null);
PGPPublicKeyRing publicKey = PGPainless.extractCertificate(secretKey);
byte[] bytes = publicKey.getEncoded();
System.setIn(new ByteArrayInputStream(bytes));
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(armorOut));
PGPainlessCLI.execute("armor");
PGPPublicKeyRing armored = PGPainless.readKeyRing().publicKeyRing(armorOut.toString());
assertArrayEquals(publicKey.getEncoded(), armored.getEncoded());
}
@Test
@FailOnSystemExit
public void armorMessage() {
String message = "Hello, World!\n";
System.setIn(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)));
ByteArrayOutputStream armorOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(armorOut));
PGPainlessCLI.execute("armor");
String armored = armorOut.toString();
assertTrue(armored.startsWith("-----BEGIN PGP MESSAGE-----\n"));
assertTrue(armored.contains("SGVsbG8sIFdvcmxkIQo="));
}
}

View file

@ -0,0 +1,167 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import javax.annotation.Nonnull;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.opentest4j.TestAbortedException;
import org.pgpainless.cli.TestUtils;
import org.pgpainless.sop.SOPImpl;
import org.slf4j.Logger;
import sop.cli.picocli.SopCLI;
public abstract class CLITest {
protected File testDirectory;
protected InputStream stdin;
protected PrintStream stdout;
protected final Logger LOGGER;
public CLITest(@Nonnull Logger logger) {
LOGGER = logger;
SopCLI.setSopInstance(new SOPImpl());
}
@BeforeEach
public void setup() throws IOException {
testDirectory = TestUtils.createTempDirectory();
testDirectory.deleteOnExit();
LOGGER.debug(testDirectory.getAbsolutePath());
stdin = System.in;
stdout = System.out;
}
@AfterEach
public void cleanup() throws IOException {
resetStreams();
}
public File nonExistentFile(String name) {
File file = new File(testDirectory, name);
if (file.exists()) {
throw new TestAbortedException("File " + file.getAbsolutePath() + " already exists.");
}
return file;
}
public File pipeStdoutToFile(String name) throws IOException {
File file = new File(testDirectory, name);
file.deleteOnExit();
if (!file.createNewFile()) {
throw new TestAbortedException("Cannot create new file " + file.getAbsolutePath());
}
System.setOut(new PrintStream(Files.newOutputStream(file.toPath())));
return file;
}
public ByteArrayOutputStream pipeStdoutToStream() {
ByteArrayOutputStream out = new ByteArrayOutputStream();
pipeStdoutToStream(out);
return out;
}
public void pipeStdoutToStream(OutputStream stream) {
System.setOut(new PrintStream(stream));
}
public void pipeFileToStdin(File file) throws IOException {
System.setIn(Files.newInputStream(file.toPath()));
}
public void pipeBytesToStdin(byte[] bytes) {
System.setIn(new ByteArrayInputStream(bytes));
}
public void pipeStringToStdin(String string) {
System.setIn(new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8)));
}
public void resetStdout() {
if (System.out != stdout) {
System.out.flush();
System.out.close();
}
System.setOut(stdout);
}
public void resetStdin() throws IOException {
if (System.in != stdin) {
System.in.close();
}
System.setIn(stdin);
}
public void resetStreams() throws IOException {
resetStdout();
resetStdin();
}
public File writeFile(String name, String data) throws IOException {
return writeFile(name, data.getBytes(StandardCharsets.UTF_8));
}
public File writeFile(String name, byte[] bytes) throws IOException {
return writeFile(name, new ByteArrayInputStream(bytes));
}
public File writeFile(String name, InputStream data) throws IOException {
File file = new File(testDirectory, name);
if (!file.createNewFile()) {
throw new TestAbortedException("Cannot create new file " + file.getAbsolutePath());
}
file.deleteOnExit();
try (FileOutputStream fileOut = new FileOutputStream(file)) {
Streams.pipeAll(data, fileOut);
fileOut.flush();
}
return file;
}
public byte[] readBytesFromFile(File file) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (FileInputStream fileIn = new FileInputStream(file)) {
Streams.pipeAll(fileIn, buffer);
} catch (FileNotFoundException e) {
throw new TestAbortedException("File " + file.getAbsolutePath() + " does not exist!", e);
} catch (IOException e) {
throw new TestAbortedException("Cannot read from file " + file.getAbsolutePath(), e);
}
return buffer.toByteArray();
}
public String readStringFromFile(File file) {
return new String(readBytesFromFile(file), StandardCharsets.UTF_8);
}
public int executeCommand(String... command) throws IOException {
int exitCode = SopCLI.execute(command);
resetStreams();
return exitCode;
}
public void assertSuccess(int exitCode) {
assertEquals(0, exitCode,
"Expected successful program execution");
}
}

View file

@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.slf4j.LoggerFactory;
import sop.exception.SOPGPException;
public class DearmorCmdTest extends CLITest {
public DearmorCmdTest() {
super(LoggerFactory.getLogger(DearmorCmdTest.class));
}
private static final String key = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
"Version: PGPainless\n" +
"Comment: 62E9 DDA4 F20F 8341 D2BC 4B4C 8B07 5177 01F9 534C\n" +
"Comment: alice@pgpainless.org\n" +
"\n" +
"lFgEY2vOkhYJKwYBBAHaRw8BAQdAqGOtLd1tKnuwaYYcdr2/7C0cPiCCggRMKG+W\n" +
"t32QQdEAAP9VaBzjk/AaAqyykZnQHmS1HByEvRLv5/4yJMSr22451BFjtBRhbGlj\n" +
"ZUBwZ3BhaW5sZXNzLm9yZ4iOBBMWCgBBBQJja86SCRCLB1F3AflTTBYhBGLp3aTy\n" +
"D4NB0rxLTIsHUXcB+VNMAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAACZhAP4s\n" +
"8hn/RBDvyLvGROOd15EYATnWlgyi+b5WXP6cELalJwD1FZy3RROhfNtZWcJPS43f\n" +
"G03pYNyb0NXoitIMAaXEB5xdBGNrzpISCisGAQQBl1UBBQEBB0CqCcYethOynfni\n" +
"8uRO+r/cZWp9hCLy8pRIExKqzcyEFAMBCAcAAP9sRRLoZkLpDaTNNrtIBovXu2AN\n" +
"hL8keUMWtVcuEHnkQA6iiHUEGBYKAB0FAmNrzpICngECmwwFFgIDAQAECwkIBwUV\n" +
"CgkICwAKCRCLB1F3AflTTBVpAP491etrjqCMWx2bBaw3K1vP0Mix6U0vF3J4kP9U\n" +
"eZm6owEA4kX9VAGESvLgIc7CEiswmxdWjxnLQyCRtWXfjgFmYQucWARja86SFgkr\n" +
"BgEEAdpHDwEBB0DBslhDpWC6CV3xJUSo071NSO5Cf4fgOwOj+QHs8mpFbwABAPkQ\n" +
"ioSydYiMi04LyfPohyrhhcdJDHallQg+jYHHUb2pEJCI1QQYFgoAfQUCY2vOkgKe\n" +
"AQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNrzpIACgkQiHlkvEXh+f1e\n" +
"ywEA9A2GLU9LxCJxZf2X4qcZY//YJDChIZHPnY0Vaek1DsMBAN1YILrH2rxQeCXj\n" +
"m4bUKfJIRrGt6ZJscwORgNI1dFQFAAoJEIsHUXcB+VNMK3gA/3vvPm57JsHA860w\n" +
"lB4D1II71oFNL8TFnJqTAvpSKe1AAP49S4mKB4PE0ElcDo7n+nEYt6ba8IMRDlMo\n" +
"rsH85mUgCw==\n" +
"=EMKf\n" +
"-----END PGP PRIVATE KEY BLOCK-----\n";
@Test
public void dearmorSecretKey() throws IOException {
PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(key);
pipeStringToStdin(key);
ByteArrayOutputStream dearmored = pipeStdoutToStream();
assertSuccess(executeCommand("dearmor"));
assertArrayEquals(secretKey.getEncoded(), dearmored.toByteArray());
}
@Test
public void dearmorBrokenArmoredKeyFails() throws IOException {
// contains a "-"
String invalidBase64 = "lFgEY2vOkhYJKwYBBAHaRw8BAQdAqGOtLd1tKnuwaYYcdr2/7C0cPiCCggRMKG+Wt32QQdEAAP9VaBzjk/AaAqyykZnQHmS1HByEvRLv5/4yJMSr22451BFjtBRhbGljZUBwZ3BhaW5sZXNzLm9yZ4iOBBMWCgBBBQJja86SCRCLB1F3AflTTBYhBGLp3aTyD4NB0rxLT-IsHUXcB+VNMAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAACZhAP4s8hn/RBDvyLvGROOd15EYATnWlgyi+b5WXP6cELalJwD1FZy3RROhfNtZWcJPS43fG03pYNyb0NXoitIMAaXEB5xdBGNrzpISCisGAQQBl1UBBQEBB0CqCcYethOynfni8uRO+r/cZWp9hCLy8pRIExKqzcyEFAMBCAcAAP9sRRLoZkLpDaTNNrtIBovXu2ANhL8keUMWtVcuEHnkQA6iiHUEGBYKAB0FAmNrzpICngECmwwFFgIDAQAECwkIBwUVCgkICwAKCRCLB1F3AflTTBVpAP491etrjqCMWx2bBaw3K1vP0Mix6U0vF3J4kP9UeZm6owEA4kX9VAGESvLgIc7CEiswmxdWjxnLQyCRtWXfjgFmYQucWARja86SFgkrBgEEAdpHDwEBB0DBslhDpWC6CV3xJUSo071NSO5Cf4fgOwOj+QHs8mpFbwABAPkQioSydYiMi04LyfPohyrhhcdJDHallQg+jYHHUb2pEJCI1QQYFgoAfQUCY2vOkgKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNrzpIACgkQiHlkvEXh+f1eywEA9A2GLU9LxCJxZf2X4qcZY//YJDChIZHPnY0Vaek1DsMBAN1YILrH2rxQeCXjm4bUKfJIRrGt6ZJscwORgNI1dFQFAAoJEIsHUXcB+VNMK3gA/3vvPm57JsHA860wlB4D1II71oFNL8TFnJqTAvpSKe1AAP49S4mKB4PE0ElcDo7n+nEYt6ba8IMRDlMorsH85mUgCw==";
pipeStringToStdin(invalidBase64);
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("dearmor");
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void dearmorCertificate() throws IOException {
PGPSecretKeyRing secretKey = PGPainless.readKeyRing().secretKeyRing(key);
PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKey);
String armoredCert = PGPainless.asciiArmor(certificate);
pipeStringToStdin(armoredCert);
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("dearmor"));
assertArrayEquals(certificate.getEncoded(), out.toByteArray());
}
@Test
public void dearmorMessage() throws IOException {
String armored = "-----BEGIN PGP MESSAGE-----\n" +
"Version: BCPG v1.69\n" +
"\n" +
"SGVsbG8sIFdvcmxkCg==\n" +
"=fkLo\n" +
"-----END PGP MESSAGE-----";
pipeStringToStdin(armored);
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("dearmor"));
assertEquals("Hello, World\n", out.toString());
}
@Test
public void dearmorGarbageEmitsEmpty() throws IOException {
String noArmoredData = "This is not armored.";
pipeStringToStdin(noArmoredData);
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("dearmor"));
assertTrue(out.toString().isEmpty());
}
}

View file

@ -1,91 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import com.ginsberg.junit.exit.FailOnSystemExit;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.cli.PGPainlessCLI;
public class DearmorTest {
private PrintStream originalSout;
@BeforeEach
public void saveSout() {
this.originalSout = System.out;
}
@AfterEach
public void restoreSout() {
System.setOut(originalSout);
}
@Test
@FailOnSystemExit
public void dearmorSecretKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
.modernKeyRing("alice@pgpainless.org", null);
String armored = PGPainless.asciiArmor(secretKey);
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
PGPainlessCLI.execute("dearmor");
assertArrayEquals(secretKey.getEncoded(), out.toByteArray());
}
@Test
@FailOnSystemExit
public void dearmorCertificate() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
.modernKeyRing("alice@pgpainless.org", null);
PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKey);
String armored = PGPainless.asciiArmor(certificate);
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
PGPainlessCLI.execute("dearmor");
assertArrayEquals(certificate.getEncoded(), out.toByteArray());
}
@Test
@FailOnSystemExit
public void dearmorMessage() {
String armored = "-----BEGIN PGP MESSAGE-----\n" +
"Version: BCPG v1.69\n" +
"\n" +
"SGVsbG8sIFdvcmxkCg==\n" +
"=fkLo\n" +
"-----END PGP MESSAGE-----";
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
PGPainlessCLI.execute("dearmor");
assertEquals("Hello, World\n", out.toString());
}
}

View file

@ -1,193 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.pgpainless.cli.PGPainlessCLI;
import org.pgpainless.cli.TestUtils;
import sop.exception.SOPGPException;
public class DetachInbandSignatureAndMessageTest {
private PrintStream originalSout;
private static File tempDir;
private static File certFile;
private static final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"Version: BCPG v1.64\n" +
"\n" +
"mFIEXhtfCBMIKoZIzj0DAQcCAwTGSFMBUOSLusXS8hdNHbdK3gN8hS7jd4ky7Czl\n" +
"mSti+oVyRJUwQAFZJ1NMsg1H8flSJP1/9YbHd9FBU4bHKGKPtBE8ZW1pbEBlbWFp\n" +
"bC51c2VyPoh1BBMTCgAdBQJeG18IAhsjBRYCAwEABAsJCAcFFQoJCAsCHgEACgkQ\n" +
"VzbmkxrPNwz8rAD/S/VCQc5NJLArgTDkgrt3Q573HiYfrIQo1uk3dwV15WIBAMiq\n" +
"oDmRMb8jzOBv6FGW4P5WAubPdnAvDD7XmArD+TSeuFYEXhtfCBIIKoZIzj0DAQcC\n" +
"AwTgWDWmHJLQUQ35Qg/rINmUhkUhj1E4O5t6Y2PipbqlGfDufLmIKnX40BoJPS4G\n" +
"HW7U0QXfwSaTXa1BAaNsMUomAwEIB4h1BBgTCgAdBQJeG18IAhsMBRYCAwEABAsJ\n" +
"CAcFFQoJCAsCHgEACgkQVzbmkxrPNwxOcwEA19Fnhw7XwpQoT61Fqg54vroAwTZ3\n" +
"T5A+LOdevAtzNOUA/RWeKfOGk6D+vKYRNpMJyqsHi/vBeKwXoeN0n6HuExVF\n" +
"=a1W7\n" +
"-----END PGP PUBLIC KEY BLOCK-----";
@BeforeAll
public static void createTempDir() throws IOException {
tempDir = TestUtils.createTempDirectory();
certFile = new File(tempDir, "cert.asc");
assertTrue(certFile.createNewFile());
try (FileOutputStream out = new FileOutputStream(certFile)) {
ByteArrayInputStream in = new ByteArrayInputStream(CERT.getBytes(StandardCharsets.UTF_8));
Streams.pipeAll(in, out);
}
}
@BeforeEach
public void saveSout() {
this.originalSout = System.out;
}
@AfterEach
public void restoreSout() {
System.setOut(originalSout);
}
private static final String CLEAR_SIGNED_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----\n" +
"Hash: SHA512\n" +
"\n" +
"Ah, Juliet, if the measure of thy joy\n" +
"Be heaped like mine, and that thy skill be more\n" +
"To blazon it, then sweeten with thy breath\n" +
"This neighbor air, and let rich musics tongue\n" +
"Unfold the imagined happiness that both\n" +
"Receive in either by this dear encounter.\n" +
"-----BEGIN PGP SIGNATURE-----\n" +
"\n" +
"iHUEARMKAB0WIQRPZlxNwsRmC8ZCXkFXNuaTGs83DAUCYJ/x5gAKCRBXNuaTGs83\n" +
"DFRwAP9/4wMvV3WcX59Clo7mkRce6iwW3VBdiN+yMu3tjmHB2wD/RfE28Q1v4+eo\n" +
"ySNgbyvqYYsNr0fnBwaG3aaj+u5ExiE=\n" +
"=Z2SO\n" +
"-----END PGP SIGNATURE-----";
private static final String CLEAR_SIGNED_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n" +
"\n" +
"iHUEARMKAB0WIQRPZlxNwsRmC8ZCXkFXNuaTGs83DAUCYJ/x5gAKCRBXNuaTGs83\n" +
"DFRwAP9/4wMvV3WcX59Clo7mkRce6iwW3VBdiN+yMu3tjmHB2wD/RfE28Q1v4+eo\n" +
"ySNgbyvqYYsNr0fnBwaG3aaj+u5ExiE=\n" +
"=Z2SO\n" +
"-----END PGP SIGNATURE-----";
private static final String CLEAR_SIGNED_BODY = "Ah, Juliet, if the measure of thy joy\n" +
"Be heaped like mine, and that thy skill be more\n" +
"To blazon it, then sweeten with thy breath\n" +
"This neighbor air, and let rich musics tongue\n" +
"Unfold the imagined happiness that both\n" +
"Receive in either by this dear encounter.";
@Test
public void detachInbandSignatureAndMessage() throws IOException {
// Clearsigned In
ByteArrayInputStream clearSignedIn = new ByteArrayInputStream(CLEAR_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8));
System.setIn(clearSignedIn);
// Plaintext Out
ByteArrayOutputStream msgOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(msgOut));
// Detach
File tempSigFile = new File(tempDir, "sig.out");
PGPainlessCLI.main(new String[] {"detach-inband-signature-and-message", "--signatures-out=" + tempSigFile.getAbsolutePath()});
// Test equality with expected values
assertEquals(CLEAR_SIGNED_BODY, msgOut.toString());
try (FileInputStream sigIn = new FileInputStream(tempSigFile)) {
ByteArrayOutputStream sigBytes = new ByteArrayOutputStream();
Streams.pipeAll(sigIn, sigBytes);
String sig = sigBytes.toString();
TestUtils.assertSignatureIsArmored(sigBytes.toByteArray());
TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE, sig);
} catch (FileNotFoundException e) {
fail("Signature File must have been written.", e);
}
// Check if produced signature still checks out
System.setIn(new ByteArrayInputStream(msgOut.toByteArray()));
ByteArrayOutputStream verifyOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(verifyOut));
PGPainlessCLI.main(new String[] {"verify", tempSigFile.getAbsolutePath(), certFile.getAbsolutePath()});
assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C\n", verifyOut.toString());
}
@Test
public void detachInbandSignatureAndMessageNoArmor() throws IOException {
// Clearsigned In
ByteArrayInputStream clearSignedIn = new ByteArrayInputStream(CLEAR_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8));
System.setIn(clearSignedIn);
// Plaintext Out
ByteArrayOutputStream msgOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(msgOut));
// Detach
File tempSigFile = new File(tempDir, "sig.asc");
PGPainlessCLI.main(new String[] {"detach-inband-signature-and-message", "--signatures-out=" + tempSigFile.getAbsolutePath(), "--no-armor"});
// Test equality with expected values
assertEquals(CLEAR_SIGNED_BODY, msgOut.toString());
try (FileInputStream sigIn = new FileInputStream(tempSigFile)) {
ByteArrayOutputStream sigBytes = new ByteArrayOutputStream();
Streams.pipeAll(sigIn, sigBytes);
byte[] sig = sigBytes.toByteArray();
TestUtils.assertSignatureIsNotArmored(sig);
TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE.getBytes(StandardCharsets.UTF_8), sig);
} catch (FileNotFoundException e) {
fail("Signature File must have been written.", e);
}
// Check if produced signature still checks out
System.setIn(new ByteArrayInputStream(msgOut.toByteArray()));
ByteArrayOutputStream verifyOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(verifyOut));
PGPainlessCLI.main(new String[] {"verify", tempSigFile.getAbsolutePath(), certFile.getAbsolutePath()});
assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C\n", verifyOut.toString());
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.OutputExists.EXIT_CODE)
public void existingSignatureOutCausesException() throws IOException {
// Clearsigned In
ByteArrayInputStream clearSignedIn = new ByteArrayInputStream(CLEAR_SIGNED_MESSAGE.getBytes(StandardCharsets.UTF_8));
System.setIn(clearSignedIn);
// Plaintext Out
ByteArrayOutputStream msgOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(msgOut));
// Detach
File existingSigFile = new File(tempDir, "sig.existing");
assertTrue(existingSigFile.createNewFile());
PGPainlessCLI.main(new String[] {"detach-inband-signature-and-message", "--signatures-out=" + existingSigFile.getAbsolutePath()});
}
}

View file

@ -1,115 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import com.ginsberg.junit.exit.FailOnSystemExit;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.pgpainless.cli.PGPainlessCLI;
import org.pgpainless.cli.TestUtils;
public class EncryptDecryptTest {
private static File tempDir;
private static PrintStream originalSout;
@BeforeAll
public static void prepare() throws IOException {
tempDir = TestUtils.createTempDirectory();
}
@Test
@FailOnSystemExit
public void encryptAndDecryptAMessage() throws IOException {
originalSout = System.out;
File julietKeyFile = new File(tempDir, "juliet.key");
assertTrue(julietKeyFile.createNewFile());
File julietCertFile = new File(tempDir, "juliet.asc");
assertTrue(julietCertFile.createNewFile());
File romeoKeyFile = new File(tempDir, "romeo.key");
assertTrue(romeoKeyFile.createNewFile());
File romeoCertFile = new File(tempDir, "romeo.asc");
assertTrue(romeoCertFile.createNewFile());
File msgAscFile = new File(tempDir, "msg.asc");
assertTrue(msgAscFile.createNewFile());
OutputStream julietKeyOut = new FileOutputStream(julietKeyFile);
System.setOut(new PrintStream(julietKeyOut));
PGPainlessCLI.execute("generate-key", "Juliet Capulet <juliet@capulet.lit>");
julietKeyOut.close();
FileInputStream julietKeyIn = new FileInputStream(julietKeyFile);
System.setIn(julietKeyIn);
OutputStream julietCertOut = new FileOutputStream(julietCertFile);
System.setOut(new PrintStream(julietCertOut));
PGPainlessCLI.execute("extract-cert");
julietKeyIn.close();
julietCertOut.close();
OutputStream romeoKeyOut = new FileOutputStream(romeoKeyFile);
System.setOut(new PrintStream(romeoKeyOut));
PGPainlessCLI.execute("generate-key", "Romeo Montague <romeo@montague.lit>");
romeoKeyOut.close();
FileInputStream romeoKeyIn = new FileInputStream(romeoKeyFile);
System.setIn(romeoKeyIn);
OutputStream romeoCertOut = new FileOutputStream(romeoCertFile);
System.setOut(new PrintStream(romeoCertOut));
PGPainlessCLI.execute("extract-cert");
romeoKeyIn.close();
romeoCertOut.close();
String msg = "Hello World!\n";
ByteArrayInputStream msgIn = new ByteArrayInputStream(msg.getBytes(StandardCharsets.UTF_8));
System.setIn(msgIn);
OutputStream msgAscOut = new FileOutputStream(msgAscFile);
System.setOut(new PrintStream(msgAscOut));
PGPainlessCLI.execute("encrypt",
"--sign-with", romeoKeyFile.getAbsolutePath(),
julietCertFile.getAbsolutePath());
msgAscOut.close();
File verifyFile = new File(tempDir, "verify.txt");
FileInputStream msgAscIn = new FileInputStream(msgAscFile);
System.setIn(msgAscIn);
ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintStream pOut = new PrintStream(out);
System.setOut(pOut);
PGPainlessCLI.execute("decrypt",
"--verify-out", verifyFile.getAbsolutePath(),
"--verify-with", romeoCertFile.getAbsolutePath(),
julietKeyFile.getAbsolutePath());
msgAscIn.close();
assertEquals(msg, out.toString());
}
@AfterAll
public static void after() {
System.setOut(originalSout);
// CHECKSTYLE:OFF
System.out.println(tempDir.getAbsolutePath());
// CHECKSTYLE:ON
}
}

View file

@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.key.info.KeyRingInfo;
import org.slf4j.LoggerFactory;
import sop.exception.SOPGPException;
public class ExtractCertCmdTest extends CLITest {
public ExtractCertCmdTest() {
super(LoggerFactory.getLogger(ExtractCertCmdTest.class));
}
@Test
public void testExtractCert()
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
.simpleEcKeyRing("Juliet Capulet <juliet@capulet.lit>");
pipeBytesToStdin(secretKeys.getEncoded());
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("extract-cert", "--armor"));
assertTrue(out.toString().startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"));
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(out.toByteArray());
KeyRingInfo info = PGPainless.inspectKeyRing(publicKeys);
assertFalse(info.isSecretKey());
assertTrue(info.isUserIdValid("Juliet Capulet <juliet@capulet.lit>"));
}
@Test
public void testExtractCertFromCertFails() throws IOException {
// Generate key
File keyFile = pipeStdoutToFile("key.asc");
assertSuccess(executeCommand("generate-key", "Alice <alice@pgpainless.org>"));
// extract cert from key (success)
pipeFileToStdin(keyFile);
File certFile = pipeStdoutToFile("cert.asc");
assertSuccess(executeCommand("extract-cert"));
// extract cert from cert (fail)
pipeFileToStdin(certFile);
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("extract-cert");
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void extractCertFromGarbageFails() throws IOException {
pipeStringToStdin("This is a bunch of garbage!");
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("extract-cert");
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void testExtractCertUnarmored() throws IOException {
// Generate key
File keyFile = pipeStdoutToFile("key.asc");
assertSuccess(executeCommand("generate-key", "Alice <alice@pgpainless.org>"));
// extract cert from key (success)
pipeFileToStdin(keyFile);
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("extract-cert", "--no-armor"));
assertFalse(out.toString().startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"));
pipeBytesToStdin(out.toByteArray());
ByteArrayOutputStream armored = pipeStdoutToStream();
assertSuccess(executeCommand("armor"));
assertTrue(armored.toString().startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----\n"));
}
}

View file

@ -1,45 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import com.ginsberg.junit.exit.FailOnSystemExit;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.cli.PGPainlessCLI;
import org.pgpainless.key.info.KeyRingInfo;
public class ExtractCertTest {
@Test
@FailOnSystemExit
public void testExtractCert() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
.simpleEcKeyRing("Juliet Capulet <juliet@capulet.lit>");
ByteArrayInputStream inputStream = new ByteArrayInputStream(secretKeys.getEncoded());
System.setIn(inputStream);
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
PGPainlessCLI.execute("extract-cert");
PGPPublicKeyRing publicKeys = PGPainless.readKeyRing().publicKeyRing(out.toByteArray());
KeyRingInfo info = PGPainless.inspectKeyRing(publicKeys);
assertFalse(info.isSecretKey());
assertTrue(info.isUserIdValid("Juliet Capulet <juliet@capulet.lit>"));
}
}

View file

@ -1,53 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.pgpainless.cli.TestUtils.ARMOR_PRIVATE_KEY_HEADER_BYTES;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import com.ginsberg.junit.exit.FailOnSystemExit;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.cli.PGPainlessCLI;
import org.pgpainless.key.info.KeyRingInfo;
public class GenerateCertTest {
@Test
@FailOnSystemExit
public void testKeyGeneration() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
PGPainlessCLI.execute("generate-key", "--armor", "Juliet Capulet <juliet@capulet.lit>");
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(out.toByteArray());
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
assertTrue(info.isUserIdValid("Juliet Capulet <juliet@capulet.lit>"));
byte[] outBegin = new byte[37];
System.arraycopy(out.toByteArray(), 0, outBegin, 0, 37);
assertArrayEquals(outBegin, ARMOR_PRIVATE_KEY_HEADER_BYTES);
}
@Test
@FailOnSystemExit
public void testNoArmor() {
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
PGPainlessCLI.execute("generate-key", "--no-armor", "Test <test@test.test>");
byte[] outBegin = new byte[37];
System.arraycopy(out.toByteArray(), 0, outBegin, 0, 37);
assertFalse(Arrays.equals(outBegin, ARMOR_PRIVATE_KEY_HEADER_BYTES));
}
}

View file

@ -0,0 +1,98 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.util.encoders.Hex;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.protection.UnlockSecretKey;
import org.pgpainless.util.Passphrase;
import org.slf4j.LoggerFactory;
import sop.exception.SOPGPException;
public class GenerateKeyCmdTest extends CLITest {
public GenerateKeyCmdTest() {
super(LoggerFactory.getLogger(GenerateKeyCmdTest.class));
}
@Test
public void testGenerateKey() throws IOException {
File keyFile = pipeStdoutToFile("key.asc");
assertSuccess(executeCommand("generate-key", "Alice <alice@pgpainless.org>"));
String key = readStringFromFile(keyFile);
assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n"));
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(key);
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
assertTrue(info.isFullyDecrypted());
assertEquals(Collections.singletonList("Alice <alice@pgpainless.org>"), info.getUserIds());
}
@Test
public void testGenerateBinaryKey() throws IOException {
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("generate-key", "--no-armor",
"Alice <alice@pgpainless.org>"));
byte[] key = out.toByteArray();
String firstHexOctet = Hex.toHexString(key, 0, 1);
assertTrue(firstHexOctet.equals("c5") || firstHexOctet.equals("94"));
}
@Test
public void testGenerateKeyWithMultipleUserIds() throws IOException {
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("generate-key",
"Alice <alice@pgpainless.org>", "Alice <alice@openpgp.org>"));
String key = out.toString();
assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n"));
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(key);
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
assertTrue(info.isFullyDecrypted());
assertEquals(Arrays.asList("Alice <alice@pgpainless.org>", "Alice <alice@openpgp.org>"), info.getUserIds());
}
@Test
public void testPasswordProtectedKey() throws IOException, PGPException {
File passwordFile = writeFile("password", "sw0rdf1sh");
passwordFile.deleteOnExit();
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("generate-key",
"--with-key-password", passwordFile.getAbsolutePath(), "Alice <alice@pgpainless.org>"));
String key = out.toString();
assertTrue(key.startsWith("-----BEGIN PGP PRIVATE KEY BLOCK-----\n"));
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(key);
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
assertTrue(info.isFullyEncrypted());
assertNotNull(UnlockSecretKey
.unlockSecretKey(secretKeys.getSecretKey(), Passphrase.fromPassword("sw0rdf1sh")));
}
@Test
public void testGeneratePasswordProtectedKey_missingPasswordFile() throws IOException {
int exit = executeCommand("generate-key",
"--with-key-password", "nonexistent", "Alice <alice@pgpainless.org>");
assertEquals(SOPGPException.MissingInput.EXIT_CODE, exit,
"Expected MISSING_INPUT (" + SOPGPException.MissingInput.EXIT_CODE + ")");
}
}

View file

@ -0,0 +1,155 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
import org.pgpainless.cli.TestUtils;
import org.slf4j.LoggerFactory;
import sop.exception.SOPGPException;
public class InlineDetachCmdTest extends CLITest {
private static final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"Version: BCPG v1.64\n" +
"\n" +
"mFIEXhtfCBMIKoZIzj0DAQcCAwTGSFMBUOSLusXS8hdNHbdK3gN8hS7jd4ky7Czl\n" +
"mSti+oVyRJUwQAFZJ1NMsg1H8flSJP1/9YbHd9FBU4bHKGKPtBE8ZW1pbEBlbWFp\n" +
"bC51c2VyPoh1BBMTCgAdBQJeG18IAhsjBRYCAwEABAsJCAcFFQoJCAsCHgEACgkQ\n" +
"VzbmkxrPNwz8rAD/S/VCQc5NJLArgTDkgrt3Q573HiYfrIQo1uk3dwV15WIBAMiq\n" +
"oDmRMb8jzOBv6FGW4P5WAubPdnAvDD7XmArD+TSeuFYEXhtfCBIIKoZIzj0DAQcC\n" +
"AwTgWDWmHJLQUQ35Qg/rINmUhkUhj1E4O5t6Y2PipbqlGfDufLmIKnX40BoJPS4G\n" +
"HW7U0QXfwSaTXa1BAaNsMUomAwEIB4h1BBgTCgAdBQJeG18IAhsMBRYCAwEABAsJ\n" +
"CAcFFQoJCAsCHgEACgkQVzbmkxrPNwxOcwEA19Fnhw7XwpQoT61Fqg54vroAwTZ3\n" +
"T5A+LOdevAtzNOUA/RWeKfOGk6D+vKYRNpMJyqsHi/vBeKwXoeN0n6HuExVF\n" +
"=a1W7\n" +
"-----END PGP PUBLIC KEY BLOCK-----";
private static final String CLEAR_SIGNED_MESSAGE = "-----BEGIN PGP SIGNED MESSAGE-----\n" +
"Hash: SHA512\n" +
"\n" +
"Ah, Juliet, if the measure of thy joy\n" +
"Be heaped like mine, and that thy skill be more\n" +
"To blazon it, then sweeten with thy breath\n" +
"This neighbor air, and let rich musics tongue\n" +
"Unfold the imagined happiness that both\n" +
"Receive in either by this dear encounter.\n" +
"-----BEGIN PGP SIGNATURE-----\n" +
"\n" +
"iHUEARMKAB0WIQRPZlxNwsRmC8ZCXkFXNuaTGs83DAUCYJ/x5gAKCRBXNuaTGs83\n" +
"DFRwAP9/4wMvV3WcX59Clo7mkRce6iwW3VBdiN+yMu3tjmHB2wD/RfE28Q1v4+eo\n" +
"ySNgbyvqYYsNr0fnBwaG3aaj+u5ExiE=\n" +
"=Z2SO\n" +
"-----END PGP SIGNATURE-----";
private static final String CLEAR_SIGNED_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n" +
"\n" +
"iHUEARMKAB0WIQRPZlxNwsRmC8ZCXkFXNuaTGs83DAUCYJ/x5gAKCRBXNuaTGs83\n" +
"DFRwAP9/4wMvV3WcX59Clo7mkRce6iwW3VBdiN+yMu3tjmHB2wD/RfE28Q1v4+eo\n" +
"ySNgbyvqYYsNr0fnBwaG3aaj+u5ExiE=\n" +
"=Z2SO\n" +
"-----END PGP SIGNATURE-----";
private static final String CLEAR_SIGNED_BODY = "Ah, Juliet, if the measure of thy joy\n" +
"Be heaped like mine, and that thy skill be more\n" +
"To blazon it, then sweeten with thy breath\n" +
"This neighbor air, and let rich musics tongue\n" +
"Unfold the imagined happiness that both\n" +
"Receive in either by this dear encounter.";
public InlineDetachCmdTest() {
super(LoggerFactory.getLogger(InlineDetachCmdTest.class));
}
@Test
public void detachInbandSignatureAndMessage() throws IOException {
pipeStringToStdin(CLEAR_SIGNED_MESSAGE);
ByteArrayOutputStream msgOut = pipeStdoutToStream();
File sigFile = nonExistentFile("sig.out");
assertSuccess(executeCommand("inline-detach", "--signatures-out", sigFile.getAbsolutePath()));
assertTrue(sigFile.exists(), "Signature file must have been written.");
// Test equality with expected values
assertEquals(CLEAR_SIGNED_BODY, msgOut.toString());
String sig = readStringFromFile(sigFile);
TestUtils.assertSignatureIsArmored(sig.getBytes());
TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE, sig);
// Check if produced signature still checks out
File certFile = writeFile("cert.asc", CERT);
pipeStringToStdin(msgOut.toString());
ByteArrayOutputStream verifyOut = pipeStdoutToStream();
assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), certFile.getAbsolutePath()));
assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C mode:text\n",
verifyOut.toString());
}
@Test
public void detachInbandSignatureAndMessageNoArmor() throws IOException {
pipeStringToStdin(CLEAR_SIGNED_MESSAGE);
ByteArrayOutputStream msgOut = pipeStdoutToStream();
File sigFile = nonExistentFile("sig.out");
assertSuccess(executeCommand("inline-detach", "--signatures-out", sigFile.getAbsolutePath(), "--no-armor"));
// Test equality with expected values
assertEquals(CLEAR_SIGNED_BODY, msgOut.toString());
assertTrue(sigFile.exists(), "Signature file must have been written.");
byte[] sig = readBytesFromFile(sigFile);
TestUtils.assertSignatureIsNotArmored(sig);
TestUtils.assertSignatureEquals(CLEAR_SIGNED_SIGNATURE.getBytes(StandardCharsets.UTF_8), sig);
// Check if produced signature still checks out
pipeBytesToStdin(msgOut.toByteArray());
ByteArrayOutputStream verifyOut = pipeStdoutToStream();
File certFile = writeFile("cert.asc", CERT);
assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), certFile.getAbsolutePath()));
assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C mode:text\n",
verifyOut.toString());
}
@Test
public void existingSignatureOutCausesException() throws IOException {
pipeStringToStdin(CLEAR_SIGNED_MESSAGE);
ByteArrayOutputStream msgOut = pipeStdoutToStream();
File existingSigFile = writeFile("sig.asc", CLEAR_SIGNED_SIGNATURE);
int exit = executeCommand("inline-detach", "--signatures-out", existingSigFile.getAbsolutePath());
assertEquals(SOPGPException.OutputExists.EXIT_CODE, exit);
assertEquals(0, msgOut.size());
}
@Test
public void detachNonOpenPgpDataFails() throws IOException {
File sig = nonExistentFile("sig.asc");
pipeStringToStdin("This is non-OpenPGP data and therefore we cannot detach any signatures from it.");
int exitCode = executeCommand("inline-detach", "--signatures-out", sig.getAbsolutePath());
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
}
@Test
public void detachMissingSignaturesFromCleartextSignedMessageFails() throws IOException {
String cleartextSignedNoSigs = "-----BEGIN PGP SIGNED MESSAGE-----\n" +
"\n" +
"Hello, World!\n" +
"What's Up!??\n" +
"\n" +
"\n";
pipeStringToStdin(cleartextSignedNoSigs);
File sig = nonExistentFile("sig.asc");
int exitCode = executeCommand("inline-detach", "--signatures-out", sig.getAbsolutePath());
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
}
}

View file

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
public class ListProfilesCmdTest extends CLITest {
public ListProfilesCmdTest() {
super(LoggerFactory.getLogger(ListProfilesCmdTest.class));
}
@Test
public void listProfilesWithoutCommand() throws IOException {
assertNotEquals(0, executeCommand("list-profiles"));
}
@Test
public void listProfileOfGenerateKey() throws IOException {
ByteArrayOutputStream output = pipeStdoutToStream();
assertSuccess(executeCommand("list-profiles", "generate-key"));
assertTrue(output.toString().contains("rfc4880"));
}
@Test
public void listProfilesOfEncrypt() throws IOException {
ByteArrayOutputStream output = pipeStdoutToStream();
assertSuccess(executeCommand("list-profiles", "encrypt"));
assertTrue(output.toString().contains("rfc4880"));
}
}

View file

@ -0,0 +1,673 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.key.generation.KeySpec;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.key.generation.type.eddsa_legacy.EdDSALegacyCurve;
import org.pgpainless.key.generation.type.xdh_legacy.XDHLegacySpec;
import org.slf4j.LoggerFactory;
import sop.exception.SOPGPException;
public class RoundTripEncryptDecryptCmdTest extends CLITest {
public RoundTripEncryptDecryptCmdTest() {
super(LoggerFactory.getLogger(RoundTripEncryptDecryptCmdTest.class));
}
private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
"Version: PGPainless\n" +
"Comment: A2EC 077F C977 E15D D799 EFF9 2C0D 3C12 3CF5 1C08\n" +
"Comment: Alice <alice@pgpainless.org>\n" +
"\n" +
"lFgEY2veRhYJKwYBBAHaRw8BAQdAeJYBoCcnGPQ3nchyyBrWQ83q3hqJnfZn2mqh\n" +
"d1M7WwsAAP0R1ELnfdJhXcfjaYPLHzwy1i34FxP5g3tvdgg9Q7VmchActBxBbGlj\n" +
"ZSA8YWxpY2VAcGdwYWlubGVzcy5vcmc+iI8EExYKAEEFAmNr3kYJECwNPBI89RwI\n" +
"FiEEouwHf8l34V3Xme/5LA08Ejz1HAgCngECmwEFFgIDAQAECwkIBwUVCgkICwKZ\n" +
"AQAAe6YA/2sO483Vi2Fgs4ejv8FykyO96IVrMoYhw3Od4LyWEyDfAQDi15LxJJm6\n" +
"T2sXdENVigdwDJiELxjOtbmivuJutxkWCJxdBGNr3kYSCisGAQQBl1UBBQEBB0CS\n" +
"zXjySHqlicxG3QlrVeTIqwKitL1sWsx0MCDmT1C8dAMBCAcAAP9VNkfMQvYAlYSP\n" +
"aYEkwEOc8/XpiloVKtPzxwVCPlXFeBDCiHUEGBYKAB0FAmNr3kYCngECmwwFFgID\n" +
"AQAECwkIBwUVCgkICwAKCRAsDTwSPPUcCOT4AQDZcN5a/e8Qr+LNBIyXXLgJWGsL\n" +
"59nsKHBbDURnxbEnMQEAybS8u+Rsb82yW4CfaA4CLRTC3eDc5Y4QwYWzLogWNwic\n" +
"WARja95GFgkrBgEEAdpHDwEBB0DcdwQufWLq6ASku4JWBBd9JplRVhK0cXWuTE73\n" +
"uWltuwABAI0bVQXvgDnxTs6kUO7JIWtokM5lI/1bfG4L1YOfnXIgD7CI1QQYFgoA\n" +
"fQUCY2veRgKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNr3kYACgkQ\n" +
"7NC/hj9lyaWVAwEA3ze1LCi1reGfB5tS3Au6A8aalyk4UV0iVOXxwV5r+E4BAJGz\n" +
"ZMFF/iQ/rOcSAsHPp4ggezZALDIkT2Hrn6iLDdsLAAoJECwNPBI89RwIuBIBAMxG\n" +
"u/s4maOFozcO4JoCZTsLHGy70SG6UuVQjK0EyJJ1APoDEfK+qTlC7/FoijMA6Ew9\n" +
"aesZ2IHgpwA7jlyHSgwLDw==\n" +
"=H3HU\n" +
"-----END PGP PRIVATE KEY BLOCK-----";
private static final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"Version: PGPainless\n" +
"Comment: A2EC 077F C977 E15D D799 EFF9 2C0D 3C12 3CF5 1C08\n" +
"Comment: Alice <alice@pgpainless.org>\n" +
"\n" +
"mDMEY2veRhYJKwYBBAHaRw8BAQdAeJYBoCcnGPQ3nchyyBrWQ83q3hqJnfZn2mqh\n" +
"d1M7Wwu0HEFsaWNlIDxhbGljZUBwZ3BhaW5sZXNzLm9yZz6IjwQTFgoAQQUCY2ve\n" +
"RgkQLA08Ejz1HAgWIQSi7Ad/yXfhXdeZ7/ksDTwSPPUcCAKeAQKbAQUWAgMBAAQL\n" +
"CQgHBRUKCQgLApkBAAB7pgD/aw7jzdWLYWCzh6O/wXKTI73ohWsyhiHDc53gvJYT\n" +
"IN8BAOLXkvEkmbpPaxd0Q1WKB3AMmIQvGM61uaK+4m63GRYIuDgEY2veRhIKKwYB\n" +
"BAGXVQEFAQEHQJLNePJIeqWJzEbdCWtV5MirAqK0vWxazHQwIOZPULx0AwEIB4h1\n" +
"BBgWCgAdBQJja95GAp4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQLA08Ejz1HAjk\n" +
"+AEA2XDeWv3vEK/izQSMl1y4CVhrC+fZ7ChwWw1EZ8WxJzEBAMm0vLvkbG/NsluA\n" +
"n2gOAi0Uwt3g3OWOEMGFsy6IFjcIuDMEY2veRhYJKwYBBAHaRw8BAQdA3HcELn1i\n" +
"6ugEpLuCVgQXfSaZUVYStHF1rkxO97lpbbuI1QQYFgoAfQUCY2veRgKeAQKbAgUW\n" +
"AgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNr3kYACgkQ7NC/hj9lyaWVAwEA3ze1\n" +
"LCi1reGfB5tS3Au6A8aalyk4UV0iVOXxwV5r+E4BAJGzZMFF/iQ/rOcSAsHPp4gg\n" +
"ezZALDIkT2Hrn6iLDdsLAAoJECwNPBI89RwIuBIBAMxGu/s4maOFozcO4JoCZTsL\n" +
"HGy70SG6UuVQjK0EyJJ1APoDEfK+qTlC7/FoijMA6Ew9aesZ2IHgpwA7jlyHSgwL\n" +
"Dw==\n" +
"=c1PZ\n" +
"-----END PGP PUBLIC KEY BLOCK-----";
@Test
public void encryptAndDecryptAMessage() throws IOException {
// Juliets key and cert
File julietKeyFile = pipeStdoutToFile("juliet.key");
assertSuccess(executeCommand("generate-key", "Juliet <juliet@capulet.lit>"));
pipeFileToStdin(julietKeyFile);
File julietCertFile = pipeStdoutToFile("juliet.cert");
assertSuccess(executeCommand("extract-cert"));
// Romeos key and cert
File romeoKeyFile = pipeStdoutToFile("romeo.key");
assertSuccess(executeCommand("generate-key", "Romeo <romeo@montague.lit>"));
File romeoCertFile = pipeStdoutToFile("romeo.cert");
pipeFileToStdin(romeoKeyFile);
assertSuccess(executeCommand("extract-cert"));
// Romeo encrypts signs and encrypts for Juliet and himself
String msg = "Hello World!\n";
File encryptedMessageFile = pipeStdoutToFile("msg.asc");
pipeStringToStdin(msg);
assertSuccess(executeCommand("encrypt", "--sign-with", romeoKeyFile.getAbsolutePath(),
julietCertFile.getAbsolutePath(), romeoCertFile.getAbsolutePath()));
// Juliet can decrypt and verify with Romeos cert
pipeFileToStdin(encryptedMessageFile);
File verificationsFile = nonExistentFile("verifications");
ByteArrayOutputStream decrypted = pipeStdoutToStream();
assertSuccess(executeCommand("decrypt", "--verifications-out", verificationsFile.getAbsolutePath(),
"--verify-with", romeoCertFile.getAbsolutePath(),
julietKeyFile.getAbsolutePath()));
assertEquals(msg, decrypted.toString());
// Romeo can decrypt and verify too
pipeFileToStdin(encryptedMessageFile);
File anotherVerificationsFile = nonExistentFile("anotherVerifications");
decrypted = pipeStdoutToStream();
assertSuccess(executeCommand("decrypt", "--verifications-out", anotherVerificationsFile.getAbsolutePath(),
"--verify-with", romeoCertFile.getAbsolutePath(),
romeoKeyFile.getAbsolutePath()));
assertEquals(msg, decrypted.toString());
String julietsVerif = readStringFromFile(verificationsFile);
String romeosVerif = readStringFromFile(anotherVerificationsFile);
assertEquals(julietsVerif, romeosVerif);
assertFalse(julietsVerif.isEmpty());
assertEquals(115, julietsVerif.length()); // 115 is number of symbols in [DATE, FINGER, FINGER, MODE] for V4
}
@Test
public void testMissingArgumentsIfNoArgsSupplied() throws IOException {
int exit = executeCommand("encrypt");
assertEquals(SOPGPException.MissingArg.EXIT_CODE, exit);
}
@Test
@Disabled("Disabled, since we now read certificates from secret keys")
public void testEncryptingForKeyFails() throws IOException {
File notACert = writeFile("key.asc", KEY);
pipeStringToStdin("Hello, World!");
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("encrypt", notACert.getAbsolutePath());
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void testEncrypt_SignWithCertFails() throws IOException {
File cert = writeFile("cert.asc", CERT);
// noinspection UnnecessaryLocalVariable
File notAKey = cert;
pipeStringToStdin("Hello, World!");
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("encrypt", "--sign-with", notAKey.getAbsolutePath(), cert.getAbsolutePath());
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void testDecryptVerifyOut_withoutVerifyWithFails() throws IOException {
File key = writeFile("key.asc", KEY);
File verifications = nonExistentFile("verifications");
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("decrypt", "--verifications-out",
verifications.getAbsolutePath(), key.getAbsolutePath());
assertEquals(SOPGPException.IncompleteVerification.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void testVerificationsOutAlreadyExistFails() throws IOException {
File key = writeFile("key.asc", KEY);
File cert = writeFile("cert.asc", CERT);
File verifications = writeFile("verifications", "this file is not empty");
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("decrypt", "--verify-with", cert.getAbsolutePath(),
"--verifications-out", verifications.getAbsolutePath(),
key.getAbsolutePath());
assertEquals(SOPGPException.OutputExists.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void testSessionKeyOutWritesSessionKeyOut() throws IOException {
File key = writeFile("key.asc", KEY);
File sessionKeyFile = nonExistentFile("session.key");
String plaintext = "Hello, World!\n";
String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
"Version: PGPainless\n" +
"\n" +
"hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
"48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
"0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
"/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
"K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
"LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
"FpVU\n" +
"=edS5\n" +
"-----END PGP MESSAGE-----";
String sessionKey = "9:B6FAD96B7ED2DA27D8A36EAEA75DAB7AC587180B14D8A24BD7263524F3DDECC3\n";
pipeStringToStdin(ciphertext);
ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
assertSuccess(executeCommand("decrypt", "--session-key-out",
sessionKeyFile.getAbsolutePath(), key.getAbsolutePath()));
assertEquals(plaintext, plaintextOut.toString());
String resultSessionKey = readStringFromFile(sessionKeyFile);
assertEquals(sessionKey, resultSessionKey);
}
@Test
public void decryptMessageWithSessionKey() throws IOException {
String plaintext = "Hello, World!\n";
String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
"Version: PGPainless\n" +
"\n" +
"hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
"48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
"0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
"/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
"K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
"LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
"FpVU\n" +
"=edS5\n" +
"-----END PGP MESSAGE-----";
String sessionKey = "9:B6FAD96B7ED2DA27D8A36EAEA75DAB7AC587180B14D8A24BD7263524F3DDECC3\n";
File sessionKeyFile = writeFile("session.key", sessionKey);
pipeStringToStdin(ciphertext);
ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
assertSuccess(executeCommand("decrypt", "--with-session-key", sessionKeyFile.getAbsolutePath()));
assertEquals(plaintext, plaintextOut.toString());
}
@Test
public void testDecryptWithSessionKeyVerifyWithYieldsExpectedVerifications() throws IOException {
String plaintext = "Hello, World!\n";
String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
"Version: PGPainless\n" +
"\n" +
"hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
"48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
"0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
"/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
"K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
"LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
"FpVU\n" +
"=edS5\n" +
"-----END PGP MESSAGE-----";
String sessionKey = "9:B6FAD96B7ED2DA27D8A36EAEA75DAB7AC587180B14D8A24BD7263524F3DDECC3\n";
File cert = writeFile("cert.asc", CERT);
File sessionKeyFile = writeFile("session.key", sessionKey);
File verifications = nonExistentFile("verifications");
pipeStringToStdin(ciphertext);
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("decrypt", "--with-session-key", sessionKeyFile.getAbsolutePath(),
"--verify-with", cert.getAbsolutePath(), "--verifications-out", verifications.getAbsolutePath()));
assertEquals(plaintext, out.toString());
String verificationString = readStringFromFile(verifications);
assertEquals("2022-11-09T17:22:48Z C0DCEC44B1A173664B05DABCECD0BF863F65C9A5 A2EC077FC977E15DD799EFF92C0D3C123CF51C08 mode:binary\n",
verificationString);
}
@Test
public void encryptAndDecryptMessageWithPassphrase() throws IOException {
File passwordFile = writeFile("password", "c1tiz€n4");
String message = "I cannot think of meaningful messages for test vectors rn";
pipeStringToStdin(message);
ByteArrayOutputStream ciphertext = pipeStdoutToStream();
assertSuccess(executeCommand("encrypt", "--with-password", passwordFile.getAbsolutePath()));
String ciphertextString = ciphertext.toString();
assertTrue(ciphertextString.startsWith("-----BEGIN PGP MESSAGE-----\n"));
pipeBytesToStdin(ciphertext.toByteArray());
ByteArrayOutputStream plaintext = pipeStdoutToStream();
assertSuccess(executeCommand("decrypt", "--with-password", passwordFile.getAbsolutePath()));
assertEquals(message, plaintext.toString());
}
@Test
public void testEncryptWithIncapableCert() throws PGPException,
InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing()
.addUserId("No Crypt <no@crypt.key>")
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519),
KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA))
.build();
PGPPublicKeyRing cert = PGPainless.extractCertificate(secretKeys);
File certFile = writeFile("cert.pgp", cert.getEncoded());
pipeStringToStdin("Hello, World!\n");
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("encrypt", certFile.getAbsolutePath());
assertEquals(SOPGPException.CertCannotEncrypt.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void testSignWithIncapableKey()
throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing()
.addUserId("Cannot Sign <cannot@sign.key>")
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.CERTIFY_OTHER))
.addSubkey(KeySpec.getBuilder(
KeyType.XDH_LEGACY(XDHLegacySpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
.build();
File keyFile = writeFile("key.pgp", secretKeys.getEncoded());
File certFile = writeFile("cert.pgp", PGPainless.extractCertificate(secretKeys).getEncoded());
pipeStringToStdin("Hello, World!\n");
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(),
certFile.getAbsolutePath());
assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void testEncryptDecryptRoundTripWithPasswordProtectedKey() throws IOException {
// generate password protected key
File passwordFile = writeFile("password", "fooBarBaz420");
File keyFile = pipeStdoutToFile("key.asc");
assertSuccess(executeCommand("generate-key",
"--with-key-password", passwordFile.getAbsolutePath(),
"Pascal Password <pascal@password.protected>"));
// extract cert
File certFile = pipeStdoutToFile("cert.asc");
pipeFileToStdin(keyFile);
assertSuccess(executeCommand("extract-cert"));
// encrypt and sign message
String msg = "Hello, World!\n";
pipeStringToStdin(msg);
File encryptedFile = pipeStdoutToFile("msg.asc");
assertSuccess(executeCommand("encrypt",
"--sign-with", keyFile.getAbsolutePath(),
"--with-key-password", passwordFile.getAbsolutePath(),
"--no-armor",
"--as", "binary",
certFile.getAbsolutePath()));
// Decrypt
File verificationsFile = nonExistentFile("verifications");
pipeFileToStdin(encryptedFile);
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("decrypt",
"--verify-with", certFile.getAbsolutePath(),
"--verifications-out", verificationsFile.getAbsolutePath(),
"--with-key-password", passwordFile.getAbsolutePath(),
keyFile.getAbsolutePath()));
assertEquals(msg, out.toString());
}
@Test
public void decryptGarbageFails() throws IOException {
File keyFile = writeFile("key.asc", KEY);
pipeStringToStdin("Some Garbage!");
int exitCode = executeCommand("decrypt", keyFile.getAbsolutePath());
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
}
@Test
public void decryptMessageWithWrongKeyFails() throws IOException {
File keyFile = pipeStdoutToFile("key.asc");
assertSuccess(executeCommand("generate-key", "Bob <bob@pgpainless.org>"));
// message was *not* created with key above
String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
"Version: PGPainless\n" +
"\n" +
"hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
"48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
"0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
"/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
"K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
"LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
"FpVU\n" +
"=edS5\n" +
"-----END PGP MESSAGE-----";
pipeStringToStdin(ciphertext);
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("decrypt", keyFile.getAbsolutePath());
assertEquals(SOPGPException.CannotDecrypt.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void encryptWithPasswordADecryptWithPasswordBFails() throws IOException {
File password1 = writeFile("password1", "swordfish");
File password2 = writeFile("password2", "orange");
pipeStringToStdin("Bonjour, le monde!\n");
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
assertSuccess(executeCommand("encrypt", "--with-password", password1.getAbsolutePath()));
pipeBytesToStdin(ciphertextOut.toByteArray());
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("decrypt", "--with-password", password2.getAbsolutePath());
assertEquals(SOPGPException.CannotDecrypt.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void encryptWithGarbageCertFails() throws IOException {
File garbageCert = writeFile("cert.asc", "This is garbage!");
pipeStringToStdin("Hallo, Welt!\n");
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("encrypt", garbageCert.getAbsolutePath());
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void encrypt_signWithGarbageKeyFails() throws IOException {
File cert = writeFile("cert.asc", CERT);
File garbageKey = writeFile("key.asc", "This is not a key!");
pipeStringToStdin("Salut!\n");
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("encrypt", "--sign-with", garbageKey.getAbsolutePath(),
cert.getAbsolutePath());
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void decrypt_withGarbageKeyFails() throws IOException {
File key = writeFile("key.asc", "this is garbage");
String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
"Version: PGPainless\n" +
"\n" +
"hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
"48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
"0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
"/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
"K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
"LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
"FpVU\n" +
"=edS5\n" +
"-----END PGP MESSAGE-----";
pipeStringToStdin(ciphertext);
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("decrypt", key.getAbsolutePath());
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void decrypt_verifyWithGarbageCertFails() throws IOException {
File key = writeFile("key.asc", KEY);
File cert = writeFile("cert.asc", "now this is garbage");
String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
"Version: PGPainless\n" +
"\n" +
"hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
"48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
"0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
"/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
"K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
"LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
"FpVU\n" +
"=edS5\n" +
"-----END PGP MESSAGE-----";
File verificationsFile = nonExistentFile("verifications");
pipeStringToStdin(ciphertext);
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("decrypt", key.getAbsolutePath(),
"--verify-with", cert.getAbsolutePath(),
"--verifications-out", verificationsFile.getAbsolutePath());
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void encryptWithProtectedKey_wrongPassphraseFails() throws IOException {
File password = writeFile("passphrase1", "orange");
File wrongPassword = writeFile("passphrase2", "blue");
File keyFile = pipeStdoutToFile("key.asc");
assertSuccess(executeCommand("generate-key", "Pedro <pedro@pgpainless.org>",
"--with-key-password", password.getAbsolutePath()));
File certFile = pipeStdoutToFile("cert.asc");
pipeFileToStdin(keyFile);
assertSuccess(executeCommand("extract-cert"));
// Use no passphrase to unlock the key
String msg = "Guten Tag, Welt!\n";
pipeStringToStdin(msg);
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(),
certFile.getAbsolutePath());
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
assertEquals(0, out.size());
// use wrong passphrase to unlock key when signing message
pipeStringToStdin("Guten Tag, Welt!\n");
out = pipeStdoutToStream();
exitCode = executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(),
"--with-key-password", wrongPassword.getAbsolutePath(),
certFile.getAbsolutePath());
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
assertEquals(0, out.size());
// use correct passphrase and encrypt+sign message
pipeStringToStdin("Guten Tag, Welt!\n");
out = pipeStdoutToStream();
assertSuccess(executeCommand("encrypt", "--sign-with", keyFile.getAbsolutePath(),
"--with-key-password", password.getAbsolutePath(),
certFile.getAbsolutePath()));
String ciphertext = out.toString();
// Use no passphrase to decrypt key when decrypting
pipeStringToStdin(ciphertext);
out = pipeStdoutToStream();
exitCode = executeCommand("decrypt", keyFile.getAbsolutePath());
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
assertEquals(0, out.size());
// Use wrong passphrase to decrypt key when decrypting
pipeStringToStdin(ciphertext);
out = pipeStdoutToStream();
exitCode = executeCommand("decrypt", "--with-key-password", wrongPassword.getAbsolutePath(),
keyFile.getAbsolutePath());
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
assertEquals(0, out.size());
// User correct passphrase to decrypt key when decrypting
pipeStringToStdin(ciphertext);
out = pipeStdoutToStream();
assertSuccess(executeCommand("decrypt", "--with-key-password", password.getAbsolutePath(),
keyFile.getAbsolutePath()));
assertEquals(msg, out.toString());
}
@Test
public void decryptMalformedMessageYieldsBadData() throws IOException {
// Message contains encrypted data packet which contains the plaintext directly - no literal data packet.
// It is therefore malformed.
String malformed = "-----BEGIN PGP MESSAGE-----\n" +
"Version: BCPG v1.72b04\n" +
"\n" +
"hF4D831k4umlLu4SAQdApKA6VDKSLQvwS2kbWqlhcXD8XHdFkSccqv5tBptZnBgw\n" +
"nZNXVhwUpap0ymb4jPTD+EVPKOfPyy04ouIGZAJKkfYDeSL/8sKcbnPPuQJYYjGQ\n" +
"ySDNmidrtTonwcSuwAfRyn74BBqOVhrr8GXkVIfevIlZFQ==\n" +
"=wIgl\n" +
"-----END PGP MESSAGE-----";
File key = writeFile("key.asc", KEY);
pipeStringToStdin(malformed);
int exitCode = executeCommand("decrypt", key.getAbsolutePath());
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
}
@Test
public void decryptWithPasswordWithPendingWhitespaceWorks() throws IOException {
assertEncryptWithPasswordADecryptWithPasswordBWorks("sw0rdf1sh", "sw0rdf1sh \n");
}
@Test
public void encryptWithTrailingWhitespaceDecryptWithoutWorks() throws IOException {
assertEncryptWithPasswordADecryptWithPasswordBWorks("sw0rdf1sh \n", "sw0rdf1sh");
}
@Test
public void decryptWithWhitespacePasswordWorks() throws IOException {
// is encrypted for "sw0rdf1sh \n"
String encryptedToPasswordWithTrailingWhitespace = "-----BEGIN PGP MESSAGE-----\n" +
"\n" +
"jA0ECQMC32tEJug0BCpg0kABfT3dKgA4K8XGpk2ul67BXLZD//fCCSmIQIWnNhE1\n" +
"6q97xFQ628K8f/58+XoBzLqLDT+LEz9Bz+Yg9QfzkEFy\n" +
"=2Y+K\n" +
"-----END PGP MESSAGE-----";
pipeStringToStdin(encryptedToPasswordWithTrailingWhitespace);
File password = writeFile("password", "sw0rdf1sh \n");
ByteArrayOutputStream plaintext = pipeStdoutToStream();
assertSuccess(executeCommand("decrypt", "--with-password", password.getAbsolutePath()));
assertEquals("Hello, World!\n", plaintext.toString());
}
private void assertEncryptWithPasswordADecryptWithPasswordBWorks(String passwordA, String passwordB)
throws IOException {
File passwordAFile = writeFile("password", passwordA);
File passwordBFile = writeFile("passwordWithWS", passwordB);
String msg = "Hello, World!\n";
pipeStringToStdin(msg);
ByteArrayOutputStream ciphertext = pipeStdoutToStream();
assertSuccess(executeCommand("encrypt", "--with-password", passwordAFile.getAbsolutePath()));
pipeStringToStdin(ciphertext.toString());
ByteArrayOutputStream plaintext = pipeStdoutToStream();
assertSuccess(executeCommand("decrypt", "--with-password", passwordBFile.getAbsolutePath()));
assertEquals(msg, plaintext.toString());
}
@Test
public void testDecryptWithoutDecryptionOptionFails() throws IOException {
String ciphertext = "-----BEGIN PGP MESSAGE-----\n" +
"Version: PGPainless\n" +
"\n" +
"hF4D831k4umlLu4SAQdAYisjZTDRm217LHQbqjB766tm62CKTkRj3Gd0wYxVRCgw\n" +
"48SnOJINCJoPgDsxk2NiJmLCImoiET7IElqxN9htdDXQJwcRK+71r/ZyO4YJpWuX\n" +
"0sAAAcEFc3nT+un31sOi8KoBJlc5n+MemntQvcWDs8B87BEW/Ncjrs0s4pJpZKBQ\n" +
"/AWc4wLCI3ylfMQJB2pICqaOO3KP3WepgTIw5fuZm6YfriKQi7uZvVx1N+uaCIoa\n" +
"K2IVVf/7O9KZJ9GbsGYdpBj9IdaIZiVS3Xi8rwgQl3haI/EeHC3nnCsWyj23Fjt3\n" +
"LjbMqpHbSnp8U1cQ8rXavrREaKv69PFeJio6/hRg32TzJqn05dPALRxHMEkxxa4h\n" +
"FpVU\n" +
"=edS5\n" +
"-----END PGP MESSAGE-----";
pipeStringToStdin(ciphertext);
int exitCode = executeCommand("decrypt");
assertEquals(SOPGPException.MissingArg.EXIT_CODE, exitCode);
}
@Test
public void testEncryptDecryptWithFreshRSAKey() throws IOException {
// Generate key
File passwordFile = writeFile("password", "sw0rdf1sh");
File keyFile = pipeStdoutToFile("key.asc");
assertSuccess(executeCommand("generate-key", "--profile=rfc4880", "--with-key-password", passwordFile.getAbsolutePath(), "Alice <alice@example.org>"));
File certFile = pipeStdoutToFile("cert.asc");
pipeFileToStdin(keyFile);
assertSuccess(executeCommand("extract-cert"));
// Write plaintext
File plaintextFile = writeFile("msg.txt", "Hello, World!\n");
// Encrypt
File ciphertextFile = pipeStdoutToFile("msg.asc");
pipeFileToStdin(plaintextFile);
assertSuccess(executeCommand("encrypt", "--profile=rfc4880", certFile.getAbsolutePath()));
ByteArrayOutputStream decrypted = pipeStdoutToStream();
pipeFileToStdin(ciphertextFile);
assertSuccess(executeCommand("decrypt", "--with-key-password", passwordFile.getAbsolutePath(), keyFile.getAbsolutePath()));
assertEquals("Hello, World!\n", decrypted.toString());
}
}

View file

@ -0,0 +1,466 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.encryption_signing.EncryptionStream;
import org.pgpainless.encryption_signing.ProducerOptions;
import org.pgpainless.encryption_signing.SigningOptions;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.slf4j.LoggerFactory;
import sop.exception.SOPGPException;
public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest {
public RoundTripInlineSignInlineVerifyCmdTest() {
super(LoggerFactory.getLogger(RoundTripInlineSignInlineVerifyCmdTest.class));
}
private static final String KEY_1_PASSWORD = "takeDemHobbits2Isengard";
private static final String KEY_1 = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
"Version: PGPainless\n" +
"Comment: 59F4 EC7D 4A87 3E69 7029 8FDE 9FF0 8738 DFC0 0224\n" +
"Comment: Legolas <legolas@fellowship.ring>\n" +
"\n" +
"lIYEY2wKdxYJKwYBBAHaRw8BAQdALfUbOSOsPDg4IgX7Mrub3EtkX0rp02orL/0j\n" +
"2VpV1rf+CQMCVICwUO0SkvdgcPdvXO1cW4KIp6HCVVV6VgU5cvBlmrk9PNUQVBkb\n" +
"6S7oXQu0CgGwJ+QdbooBQqOjMy2MDy+UXaURTaVyWcmetsZJZzD2wrQhTGVnb2xh\n" +
"cyA8bGVnb2xhc0BmZWxsb3dzaGlwLnJpbmc+iI8EExYKAEEFAmNsCncJEJ/whzjf\n" +
"wAIkFiEEWfTsfUqHPmlwKY/en/CHON/AAiQCngECmwEFFgIDAQAECwkIBwUVCgkI\n" +
"CwKZAQAAE10BAN9tN4Le1p4giS6P/yFuKFlDBOeiq1S4EqwYG7qdcqemAP45O3w4\n" +
"3sXliOJBGDR/l/lOMHdPcTOb7VRwWbpIqx8LBJyLBGNsCncSCisGAQQBl1UBBQEB\n" +
"B0AMc+7s6uBqAQcDvfKkD5zYbmB9ZfwIjRWQq/XF+g8KQwMBCAf+CQMCVICwUO0S\n" +
"kvdgHLmKhKW1xxCNZAqQcIHa9F/cqb6Sq/oVFHj2bEYzmGVvFCVUpP7KJWGTeFT+\n" +
"BYK779quIqjxHOfzC3Jmo3BHkUPWYOa0rIh1BBgWCgAdBQJjbAp3Ap4BApsMBRYC\n" +
"AwEABAsJCAcFFQoJCAsACgkQn/CHON/AAiRUewD9HtKrCUf3S1yR28emzITWPgJS\n" +
"UA5mkzEMnYspV7zU4jgA/R6jj/5QqPszElCQNZGtvsDUwYo10iRlQkxPshcPNakJ\n" +
"nIYEY2wKdxYJKwYBBAHaRw8BAQdAYxpRGib/f/tu65gbsV22nmysVVmVgiQuDxyH\n" +
"rz7VCi/+CQMCVICwUO0SkvdgOYYbWltjQRDM3SW/Zw/DiZN9MYZYa0MTgs0SHoaM\n" +
"5LU7jMxNmPR1UtSqEO36QqW91q4fpEkGrdWE4gwjm1bth8pyYKiSFojVBBgWCgB9\n" +
"BQJjbAp3Ap4BApsCBRYCAwEABAsJCAcFFQoJCAtfIAQZFgoABgUCY2wKdwAKCRCW\n" +
"K491s9xIMHwKAQDpSWQqiFxFvls9eRGtJ1eQT+L3Z2rDel5zNV44IdTf/wEA0vnJ\n" +
"ouSKKuiH6Ck2OEkXbElH6gdQvOCYA7Z9gVeeHQoACgkQn/CHON/AAiSD6QD+LTZx\n" +
"NU+t4wQlWOkSsjOLsH/Sk5DZq+4HyQnStlxUJpUBALZFkZps65IP03VkPnQWigfs\n" +
"YgztJA1z/rmm3fmFgMMG\n" +
"=daDH\n" +
"-----END PGP PRIVATE KEY BLOCK-----";
private static final String CERT_1 = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"Version: PGPainless\n" +
"Comment: 59F4 EC7D 4A87 3E69 7029 8FDE 9FF0 8738 DFC0 0224\n" +
"Comment: Legolas <legolas@fellowship.ring>\n" +
"\n" +
"mDMEY2wKdxYJKwYBBAHaRw8BAQdALfUbOSOsPDg4IgX7Mrub3EtkX0rp02orL/0j\n" +
"2VpV1re0IUxlZ29sYXMgPGxlZ29sYXNAZmVsbG93c2hpcC5yaW5nPoiPBBMWCgBB\n" +
"BQJjbAp3CRCf8Ic438ACJBYhBFn07H1Khz5pcCmP3p/whzjfwAIkAp4BApsBBRYC\n" +
"AwEABAsJCAcFFQoJCAsCmQEAABNdAQDfbTeC3taeIIkuj/8hbihZQwTnoqtUuBKs\n" +
"GBu6nXKnpgD+OTt8ON7F5YjiQRg0f5f5TjB3T3Ezm+1UcFm6SKsfCwS4OARjbAp3\n" +
"EgorBgEEAZdVAQUBAQdADHPu7OrgagEHA73ypA+c2G5gfWX8CI0VkKv1xfoPCkMD\n" +
"AQgHiHUEGBYKAB0FAmNsCncCngECmwwFFgIDAQAECwkIBwUVCgkICwAKCRCf8Ic4\n" +
"38ACJFR7AP0e0qsJR/dLXJHbx6bMhNY+AlJQDmaTMQydiylXvNTiOAD9HqOP/lCo\n" +
"+zMSUJA1ka2+wNTBijXSJGVCTE+yFw81qQm4MwRjbAp3FgkrBgEEAdpHDwEBB0Bj\n" +
"GlEaJv9/+27rmBuxXbaebKxVWZWCJC4PHIevPtUKL4jVBBgWCgB9BQJjbAp3Ap4B\n" +
"ApsCBRYCAwEABAsJCAcFFQoJCAtfIAQZFgoABgUCY2wKdwAKCRCWK491s9xIMHwK\n" +
"AQDpSWQqiFxFvls9eRGtJ1eQT+L3Z2rDel5zNV44IdTf/wEA0vnJouSKKuiH6Ck2\n" +
"OEkXbElH6gdQvOCYA7Z9gVeeHQoACgkQn/CHON/AAiSD6QD+LTZxNU+t4wQlWOkS\n" +
"sjOLsH/Sk5DZq+4HyQnStlxUJpUBALZFkZps65IP03VkPnQWigfsYgztJA1z/rmm\n" +
"3fmFgMMG\n" +
"=/lYl\n" +
"-----END PGP PUBLIC KEY BLOCK-----";
private static final String CERT_1_SIGNING_KEY =
"D8906FEB9842569834FEDA9E962B8F75B3DC4830 59F4EC7D4A873E6970298FDE9FF08738DFC00224";
private static final String KEY_2 = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
"Version: PGPainless\n" +
"Comment: AEA0 FD2C 899D 3FC0 7781 5F00 2656 0D2A E53D B86F\n" +
"Comment: Gollum <gollum@deep.cave>\n" +
"\n" +
"lFgEY2wKphYJKwYBBAHaRw8BAQdA9MXACulaJvjIuMKbsc+/fLJ523lODbHmuTpc\n" +
"jpPdjaEAAP9Edg7yeIGEeNP0GrndUpNeZyFAXAlCHJObDbS80G6BBw9ktBlHb2xs\n" +
"dW0gPGdvbGx1bUBkZWVwLmNhdmU+iI8EExYKAEEFAmNsCqYJECZWDSrlPbhvFiEE\n" +
"rqD9LImdP8B3gV8AJlYNKuU9uG8CngECmwEFFgIDAQAECwkIBwUVCgkICwKZAQAA\n" +
"KSkBAOMq6ymNH83E5CBA/mn3DYLhnujzC9cVf/iX2zrsdXMvAQCWdfFy/PlGhP3K\n" +
"M+ej6WIRsx24Yy/NhNPcRJUzcv6dC5xdBGNsCqYSCisGAQQBl1UBBQEBB0DiN/5n\n" +
"AFQafWjnSkKhctFCNkfVRrnAea/2T/D8fYWeYwMBCAcAAP9HbxOhwxqz8I+pwk3e\n" +
"kZXNolWqagrYZkpNvqlBb/JJWBGViHUEGBYKAB0FAmNsCqYCngECmwwFFgIDAQAE\n" +
"CwkIBwUVCgkICwAKCRAmVg0q5T24bw2EAP4pUHVA2pkVspzEttIaQxdoHcnbwjae\n" +
"q12TmWqWDFFvwgD+O2EqHn0iXW49EOQrlP8g+bdWUlT0ZIW3C3Fv7nNA3AScWARj\n" +
"bAqmFgkrBgEEAdpHDwEBB0BHsmdF1Q0aU3YRVDeXGb904Nb7H/cxcasDhcbu2FTo\n" +
"HAAA/j1+WzozN/3lefo76eyENKkXl4f1rQlUreqytuaTsb0WEq6I1QQYFgoAfQUC\n" +
"Y2wKpgKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNsCqYACgkQj73T\n" +
"bQGDFnN9OwD/QDDi1qq7DrGlENQf2mPDh36YgM7bREY1vHEbbUNoqy4A/RJzMuwt\n" +
"L1M49UzQS7OIGP12/9cT66XPGjpCL+6zLPwCAAoJECZWDSrlPbhvw3ABAOE7/Iit\n" +
"ntMexrSK5jCd9JdCCNb2rjR6XA18rXFGOrVBAPwLKAogNFQlP2kUsObTnIaTCro2\n" +
"cjK8WE1pfIwQ0ArPCQ==\n" +
"=SzrG\n" +
"-----END PGP PRIVATE KEY BLOCK-----";
private static final String CERT_2 = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"Version: PGPainless\n" +
"Comment: AEA0 FD2C 899D 3FC0 7781 5F00 2656 0D2A E53D B86F\n" +
"Comment: Gollum <gollum@deep.cave>\n" +
"\n" +
"mDMEY2wKphYJKwYBBAHaRw8BAQdA9MXACulaJvjIuMKbsc+/fLJ523lODbHmuTpc\n" +
"jpPdjaG0GUdvbGx1bSA8Z29sbHVtQGRlZXAuY2F2ZT6IjwQTFgoAQQUCY2wKpgkQ\n" +
"JlYNKuU9uG8WIQSuoP0siZ0/wHeBXwAmVg0q5T24bwKeAQKbAQUWAgMBAAQLCQgH\n" +
"BRUKCQgLApkBAAApKQEA4yrrKY0fzcTkIED+afcNguGe6PML1xV/+JfbOux1cy8B\n" +
"AJZ18XL8+UaE/coz56PpYhGzHbhjL82E09xElTNy/p0LuDgEY2wKphIKKwYBBAGX\n" +
"VQEFAQEHQOI3/mcAVBp9aOdKQqFy0UI2R9VGucB5r/ZP8Px9hZ5jAwEIB4h1BBgW\n" +
"CgAdBQJjbAqmAp4BApsMBRYCAwEABAsJCAcFFQoJCAsACgkQJlYNKuU9uG8NhAD+\n" +
"KVB1QNqZFbKcxLbSGkMXaB3J28I2nqtdk5lqlgxRb8IA/jthKh59Il1uPRDkK5T/\n" +
"IPm3VlJU9GSFtwtxb+5zQNwEuDMEY2wKphYJKwYBBAHaRw8BAQdAR7JnRdUNGlN2\n" +
"EVQ3lxm/dODW+x/3MXGrA4XG7thU6ByI1QQYFgoAfQUCY2wKpgKeAQKbAgUWAgMB\n" +
"AAQLCQgHBRUKCQgLXyAEGRYKAAYFAmNsCqYACgkQj73TbQGDFnN9OwD/QDDi1qq7\n" +
"DrGlENQf2mPDh36YgM7bREY1vHEbbUNoqy4A/RJzMuwtL1M49UzQS7OIGP12/9cT\n" +
"66XPGjpCL+6zLPwCAAoJECZWDSrlPbhvw3ABAOE7/IitntMexrSK5jCd9JdCCNb2\n" +
"rjR6XA18rXFGOrVBAPwLKAogNFQlP2kUsObTnIaTCro2cjK8WE1pfIwQ0ArPCQ==\n" +
"=j1LR\n" +
"-----END PGP PUBLIC KEY BLOCK-----";
private static final String CERT_2_SIGNING_KEY =
"7A073EDF273C902796D259528FBDD36D01831673 AEA0FD2C899D3FC077815F0026560D2AE53DB86F";
private static final String MESSAGE = "One does not simply use OpenPGP!\n" +
"\n" +
"There is only one Lord of the Keys, only one who can bend them to his will. And he does not share power.";
private static final String MESSAGE_CRLF = "One does not simply use OpenPGP!\r\n" +
"\r\n" +
"There is only one Lord of the Keys, only one who can bend them to his will. And he does not share power.";
@Test
public void createCleartextSignedMessage() throws IOException {
File key = writeFile("key.asc", KEY_1);
File password = writeFile("password", KEY_1_PASSWORD);
pipeStringToStdin(MESSAGE);
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
assertSuccess(executeCommand("inline-sign",
"--as", "clearsigned",
key.getAbsolutePath(),
"--with-key-password", password.getAbsolutePath()));
String cleartextSigned = ciphertextOut.toString();
assertTrue(cleartextSigned.startsWith("-----BEGIN PGP SIGNED MESSAGE-----\n" +
"Hash: "));
assertTrue(cleartextSigned.contains(MESSAGE_CRLF));
assertTrue(cleartextSigned.contains("\n-----BEGIN PGP SIGNATURE-----\n"));
assertTrue(cleartextSigned.endsWith("-----END PGP SIGNATURE-----\n"));
}
@Test
public void createAndVerifyCleartextSignedMessage() throws IOException {
File key = writeFile("key.asc", KEY_1);
File password = writeFile("password", KEY_1_PASSWORD);
pipeStringToStdin(MESSAGE);
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
assertSuccess(executeCommand("inline-sign",
"--as", "clearsigned",
key.getAbsolutePath(),
"--with-key-password", password.getAbsolutePath()));
File cert = writeFile("cert.asc", CERT_1);
File verifications = nonExistentFile("verifications");
pipeStringToStdin(ciphertextOut.toString());
ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
assertSuccess(executeCommand("inline-verify",
"--verifications-out", verifications.getAbsolutePath(),
cert.getAbsolutePath()));
assertEquals(MESSAGE, plaintextOut.toString());
String verificationString = readStringFromFile(verifications);
assertTrue(verificationString.contains(CERT_1_SIGNING_KEY));
}
@Test
public void createAndVerifyTextSignedMessage() throws IOException {
File key = writeFile("key.asc", KEY_1);
File password = writeFile("password", KEY_1_PASSWORD);
pipeStringToStdin(MESSAGE);
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
assertSuccess(executeCommand("inline-sign",
"--as", "text",
key.getAbsolutePath(),
"--with-key-password", password.getAbsolutePath()));
File cert = writeFile("cert.asc", CERT_1);
File verifications = nonExistentFile("verifications");
pipeStringToStdin(ciphertextOut.toString());
ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
assertSuccess(executeCommand("inline-verify",
"--verifications-out", verifications.getAbsolutePath(),
cert.getAbsolutePath()));
assertEquals(MESSAGE_CRLF, plaintextOut.toString());
String verificationString = readStringFromFile(verifications);
assertTrue(verificationString.contains(CERT_1_SIGNING_KEY));
}
@Test
public void createSignedMessageWithKeyAAndVerifyWithKeyBFails() throws IOException {
File key = writeFile("key.asc", KEY_1);
File password = writeFile("password", KEY_1_PASSWORD);
File cert = writeFile("cert.asc", CERT_2); // mismatch
pipeStringToStdin(MESSAGE);
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
assertSuccess(executeCommand("inline-sign",
key.getAbsolutePath(),
"--with-key-password", password.getAbsolutePath()));
File verifications = nonExistentFile("verifications");
pipeStringToStdin(ciphertextOut.toString());
ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
int exitCode = executeCommand("inline-verify",
"--verifications-out", verifications.getAbsolutePath(),
cert.getAbsolutePath());
assertEquals(SOPGPException.NoSignature.EXIT_CODE, exitCode);
assertEquals(MESSAGE, plaintextOut.toString()); // message is emitted nonetheless
assertFalse(verifications.exists(), "Verifications file MUST NOT be written.");
}
@Test
public void createAndVerifyMultiKeyBinarySignedMessage() throws IOException {
File key1Pass = writeFile("password", KEY_1_PASSWORD);
File key1 = writeFile("key1.asc", KEY_1);
File key2 = writeFile("key2.asc", KEY_2);
pipeStringToStdin(MESSAGE);
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
assertSuccess(executeCommand("inline-sign",
"--as", "binary",
"--no-armor",
key2.getAbsolutePath(),
"--with-key-password", key1Pass.getAbsolutePath(),
key1.getAbsolutePath()));
assertFalse(ciphertextOut.toString().startsWith("-----BEGIN PGP SIGNED MESSAGE-----\n"));
byte[] unarmoredMessage = ciphertextOut.toByteArray();
File cert1 = writeFile("cert1.asc", CERT_1);
File cert2 = writeFile("cert2.asc", CERT_2);
File verificationFile = nonExistentFile("verifications");
pipeBytesToStdin(unarmoredMessage);
ByteArrayOutputStream plaintextOut = pipeStdoutToStream();
assertSuccess(executeCommand("inline-verify",
"--verifications-out", verificationFile.getAbsolutePath(),
cert1.getAbsolutePath(), cert2.getAbsolutePath()));
assertEquals(MESSAGE, plaintextOut.toString());
String verification = readStringFromFile(verificationFile);
assertTrue(verification.contains(CERT_1_SIGNING_KEY));
assertTrue(verification.contains(CERT_2_SIGNING_KEY));
}
@Test
public void createTextSignedMessageInlineDetachAndDetachedVerify() throws IOException {
File key = writeFile("key.asc", KEY_1);
File password = writeFile("password", KEY_1_PASSWORD);
pipeStringToStdin(MESSAGE);
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
assertSuccess(executeCommand("inline-sign",
"--as", "clearsigned",
key.getAbsolutePath(),
"--with-key-password", password.getAbsolutePath()));
File sigFile = nonExistentFile("sig.asc");
pipeStringToStdin(ciphertextOut.toString());
ByteArrayOutputStream msgOut = pipeStdoutToStream();
assertSuccess(executeCommand("inline-detach",
"--signatures-out", sigFile.getAbsolutePath()));
assertEquals(MESSAGE, msgOut.toString());
File cert = writeFile("cert.asc", CERT_1);
pipeStringToStdin(msgOut.toString());
ByteArrayOutputStream verificationsOut = pipeStdoutToStream();
assertSuccess(executeCommand("verify", "--stacktrace",
sigFile.getAbsolutePath(),
cert.getAbsolutePath()));
String verificationString = verificationsOut.toString();
assertTrue(verificationString.contains(CERT_1_SIGNING_KEY));
}
@Test
public void testUnlockKeyWithOneOfMultiplePasswords() throws IOException {
File key = writeFile("key.asc", KEY_1);
File wrong1 = writeFile("wrong_1", "BuzzAldr1n");
File correct = writeFile("correct", KEY_1_PASSWORD);
File wrong2 = writeFile("wrong_2", "NeilArmstr0ng");
pipeStringToStdin(MESSAGE);
ByteArrayOutputStream ciphertextOut = pipeStdoutToStream();
assertSuccess(executeCommand("inline-sign",
key.getAbsolutePath(),
"--with-key-password", wrong1.getAbsolutePath(),
"--with-key-password", correct.getAbsolutePath(),
"--with-key-password", wrong2.getAbsolutePath()));
File cert = writeFile("cert.asc", CERT_1);
pipeStringToStdin(ciphertextOut.toString());
ByteArrayOutputStream msgOut = pipeStdoutToStream();
File verificationsFile = nonExistentFile("verifications");
assertSuccess(executeCommand("inline-verify",
"--verifications-out", verificationsFile.getAbsolutePath(),
cert.getAbsolutePath()));
assertEquals(MESSAGE, msgOut.toString());
String verificationString = readStringFromFile(verificationsFile);
assertTrue(verificationString.contains(CERT_1_SIGNING_KEY));
}
@Test
public void cannotVerifyEncryptedMessage() throws IOException {
File key = writeFile("key.asc", KEY_2);
File cert = writeFile("cert.asc", CERT_2);
String msg = "Hello, World!\n";
pipeStringToStdin(msg);
ByteArrayOutputStream ciphertext = pipeStdoutToStream();
assertSuccess(executeCommand("encrypt", cert.getAbsolutePath(),
"--sign-with", key.getAbsolutePath()));
File verifications = nonExistentFile("verifications");
pipeBytesToStdin(ciphertext.toByteArray());
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("inline-verify", cert.getAbsolutePath(),
"--verifications-out", verifications.getAbsolutePath());
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void createMalformedMessage() throws IOException, PGPException {
String msg = "Hello, World!\n";
PGPSecretKeyRing key = PGPainless.readKeyRing().secretKeyRing(KEY_2);
ByteArrayOutputStream ciphertext = new ByteArrayOutputStream();
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
.onOutputStream(ciphertext)
.withOptions(ProducerOptions.sign(SigningOptions.get()
.addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), key)
).overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED)
.setAsciiArmor(false));
encryptionStream.write(msg.getBytes(StandardCharsets.UTF_8));
encryptionStream.close();
PGPSignature sig = encryptionStream.getResult().getDetachedSignatures().entrySet()
.iterator().next().getValue().iterator().next();
ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out);
armorOut.write(ciphertext.toByteArray());
armorOut.write(sig.getEncoded());
armorOut.close();
}
@Test
public void cannotVerifyMalformedMessage() throws IOException {
// appended signature -> malformed
String malformedSignedMessage = "-----BEGIN PGP MESSAGE-----\n" +
"Version: BCPG v1.72b04\n" +
"\n" +
"yxRiAAAAAABIZWxsbywgV29ybGQhCoh1BAAWCgAnBQJjd52aCRCPvdNtAYMWcxYh\n" +
"BHoHPt8nPJAnltJZUo+9020BgxZzAACThwD/Vr7CMitMOul40VK12XXjOv5f8vgi\n" +
"ksqhrI2ysItID9oA/0Csgf3Sv2YenYVzqnd0hhiPe5IVPl8w4sTZKpriYMIG\n" +
"=DPPU\n" +
"-----END PGP MESSAGE-----";
File cert = writeFile("cert.asc", CERT_2);
File verifications = nonExistentFile("verifications");
pipeStringToStdin(malformedSignedMessage);
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("inline-verify", cert.getAbsolutePath(),
"--verifications-out", verifications.getAbsolutePath());
assertEquals(SOPGPException.BadData.EXIT_CODE, exitCode);
assertEquals("Hello, World!\n", out.toString());
}
@Test
public void verifyPrependedSignedMessage() throws IOException {
// message with prepended signature
String malformedSignedMessage = "-----BEGIN PGP SIGNATURE-----\n" +
"Version: BCPG v1.72b04\n" +
"\n" +
"iHUEABYKACcFAmN3nOUJEI+9020BgxZzFiEEegc+3yc8kCeW0llSj73TbQGDFnMA\n" +
"ANPKAPkBxLVHvgeCkX/tTHdBH3CDeuUQF2wmtUmGXqhZA1IFtwD/dK0XQBHO3RO+\n" +
"GHpzA7fDAroqF0zM72tu2W4PPw04FgKjATstksQAAh6pOTn5Ogrh+UU5KYpcAA==\n" +
"=xtik\n" +
"-----END PGP SIGNATURE-----";
File cert = writeFile("cert.asc", CERT_2);
File verifications = nonExistentFile("verifications");
pipeStringToStdin(malformedSignedMessage);
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("inline-verify", cert.getAbsolutePath(),
"--verifications-out", verifications.getAbsolutePath()));
assertEquals("Hello, World!\n", out.toString());
String ver = readStringFromFile(verifications);
assertEquals(
"2022-11-18T14:55:33Z 7A073EDF273C902796D259528FBDD36D01831673 AEA0FD2C899D3FC077815F0026560D2AE53DB86F mode:binary\n", ver);
}
@Test
public void testInlineSignWithMissingSecretKeysFails() throws IOException {
String missingSecretKeys = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
"Comment: 8677 37CA 1979 28FA 325A DE56 B455 9329 9882 36BE\n" +
"Comment: Mrs. Secret Key <miss@secret.key>\n" +
"\n" +
"lEwEY3t3pRYJKwYBBAHaRw8BAQdA7lifUc85s7omw7eYNIaIj2mZrGeZ9KkG0WX2\n" +
"hAx5qXT+AGUAR05VAhAAAAAAAAAAAAAAAAAAAAAAtCFNcnMuIFNlY3JldCBLZXkg\n" +
"PG1pc3NAc2VjcmV0LmtleT6IjwQTFgoAQQUCY3t3pQkQtFWTKZiCNr4WIQSGdzfK\n" +
"GXko+jJa3la0VZMpmII2vgKeAQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAABNTQEA\n" +
"uU5L9hJ1QKWxL5wetJwR08rXJTzsuX1LRfy8dlnlJl0BAKPSqydLoTEVlJQ/2sjO\n" +
"xQmc6aedoOoXKKVNDW5ibrsEnFEEY3t3pRIKKwYBBAGXVQEFAQEHQA/WdwR+NFaY\n" +
"7NeZnRwI3X9sI5fMq0vtEauMLfZjqTc/AwEIB/4AZQBHTlUCEAAAAAAAAAAAAAAA\n" +
"AAAAAACIdQQYFgoAHQUCY3t3pQKeAQKbDAUWAgMBAAQLCQgHBRUKCQgLAAoJELRV\n" +
"kymYgja+8XMA/1quBVvaSf4QxbB2S7rKt93rAynDLqGQD8hC6wiZc+ihAQC87n2r\n" +
"meZ9kiYLYiQuBTGvXyzDBtw5m7wQtMWTfXisBpxMBGN7d6UWCSsGAQQB2kcPAQEH\n" +
"QMguDhFon0ZI//CIpC2ZndmtvKdJhcEAeVNkdcsIZajl/gBlAEdOVQIQAAAAAAAA\n" +
"AAAAAAAAAAAAAIjVBBgWCgB9BQJje3elAp4BApsCBRYCAwEABAsJCAcFFQoJCAtf\n" +
"IAQZFgoABgUCY3t3pQAKCRC14KclsvqqOstPAQDYiL7+4HucWKmd7dcd9XJZpdB6\n" +
"lneoK0qku0wvTVjX7gEAtUt2eXMlBE4ox+ZmY964PCc2gEHuC7PBtsAzuF7GSQwA\n" +
"CgkQtFWTKZiCNr7JKwEA3aLsOWAYzqvKgiboYSzle+SVBUb3chKlzf3YmckjmwgA\n" +
"/3YN1W8CiQFvE9NvetZkr2wXB+QVkuL6cxM0ogEo4lAG\n" +
"=9ZMl\n" +
"-----END PGP PRIVATE KEY BLOCK-----\n";
File key = writeFile("key.asc", missingSecretKeys);
pipeStringToStdin("Hello, World!\n");
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("inline-sign", key.getAbsolutePath());
assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void signWithProtectedKeyWithWrongPassphraseFails() throws IOException {
File key = writeFile("key.asc", KEY_1);
File password = writeFile("password.asc", "not_correct!");
pipeStringToStdin("Hello, World!\n");
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("inline-sign", key.getAbsolutePath(),
"--with-key-password", password.getAbsolutePath());
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
}

View file

@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
public class RoundTripInlineSignVerifyCmdTest extends CLITest {
public RoundTripInlineSignVerifyCmdTest() {
super(LoggerFactory.getLogger(RoundTripInlineSignVerifyCmdTest.class));
}
@Test
public void encryptAndDecryptAMessage() throws IOException {
// write password file
File password = writeFile("password", "sw0rdf1sh");
// generate key
File sigmundKey = pipeStdoutToFile("sigmund.key");
assertSuccess(executeCommand("generate-key", "--with-key-password=" + password.getAbsolutePath(),
"Sigmund Freud <sigmund@pgpainless.org>"));
// extract cert
File sigmundCert = pipeStdoutToFile("sigmund.cert");
pipeFileToStdin(sigmundKey);
assertSuccess(executeCommand("extract-cert"));
// sign message
pipeBytesToStdin("Hello, World!\n".getBytes(StandardCharsets.UTF_8));
File signedMsg = pipeStdoutToFile("signed.asc");
assertSuccess(executeCommand("inline-sign", "--with-key-password=" + password.getAbsolutePath(),
sigmundKey.getAbsolutePath()));
// verify message
File verifyFile = nonExistentFile("verify.txt");
pipeFileToStdin(signedMsg);
assertSuccess(executeCommand("inline-verify", "--verifications-out", verifyFile.getAbsolutePath(),
sigmundCert.getAbsolutePath()));
String verifications = readStringFromFile(verifyFile);
assertFalse(verifications.trim().isEmpty());
}
}

View file

@ -0,0 +1,335 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.Date;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.generation.KeySpec;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.key.generation.type.eddsa_legacy.EdDSALegacyCurve;
import org.pgpainless.key.generation.type.xdh_legacy.XDHLegacySpec;
import org.pgpainless.key.info.KeyRingInfo;
import org.slf4j.LoggerFactory;
import sop.exception.SOPGPException;
import sop.util.UTCUtil;
public class RoundTripSignVerifyCmdTest extends CLITest {
public RoundTripSignVerifyCmdTest() {
super(LoggerFactory.getLogger(RoundTripSignVerifyCmdTest.class));
}
private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
"Version: PGPainless\n" +
"Comment: 9DA0 9423 C9F9 4BA4 CCA3 0951 099B 11BF 296A 373E\n" +
"Comment: Sigmund <sigmund@pgpainless.org>\n" +
"\n" +
"lFgEY2vzkhYJKwYBBAHaRw8BAQdA+Z2OAFQf0k64Au7hIZfXh/ijclabddvwh7Nh\n" +
"kedJ3ZUAAQCZy5p1cvQvRIWUopHwhnrD/oVAa1dNT/nA3cihQ5gkZBHPtCBTaWdt\n" +
"dW5kIDxzaWdtdW5kQHBncGFpbmxlc3Mub3JnPoiPBBMWCgBBBQJja/OSCRAJmxG/\n" +
"KWo3PhYhBJ2glCPJ+UukzKMJUQmbEb8pajc+Ap4BApsBBRYCAwEABAsJCAcFFQoJ\n" +
"CAsCmQEAACM9AP9APloI2waD5gXsJqzenRVU4n/VmZUvcdUyhlbpab/0HQEAlaTw\n" +
"ZvxVyaf8EMFSJOY+LcgacHaZDHRPA1nS3bIfKwycXQRja/OSEgorBgEEAZdVAQUB\n" +
"AQdA1WL4QKgRxbvzW91ICM6PoICSNh2QHK6j0pIdN/cqXz0DAQgHAAD/bOk3WqbF\n" +
"QAE8xxm0w/KDZzL1N0yPcBQ5z4XKmu77FCgQ04h1BBgWCgAdBQJja/OSAp4BApsM\n" +
"BRYCAwEABAsJCAcFFQoJCAsACgkQCZsRvylqNz6rgQEAzoG6HnPCYi2i2c6/ufuy\n" +
"pBkLby2u1JjD0CWSbrM4dZ0A/j/pI4a9b8LcrZcuY2QwHqsXPAJp8QtOOQN6gTvN\n" +
"WcQNnFgEY2vzkhYJKwYBBAHaRw8BAQdAsxcDCvst/GbWxQvvOpChSvmbqWeuBgm3\n" +
"1vRoujFVFcYAAP9Ww46yfWipb8OivTSX+PvgdUhEeVgxENpsyOQLLhQP/RFziNUE\n" +
"GBYKAH0FAmNr85ICngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJja/OS\n" +
"AAoJENqfQTmGIR3GtsMBAL+b1Zo5giQKJGEyx5aGwAz3AwtGiT6QDS9FH6HyM855\n" +
"AP4uAXDiaNxYTugqnG471jYX/hhJqIROeDGrEIkkAp+qDwAKCRAJmxG/KWo3PhOX\n" +
"AP45LPV6I4+D3h8etdiEA2DVvNcpRA8l4WkNcq4q8H1SjwD/c/rX3FCUIWLlAHoR\n" +
"WxCFj+gDgqDNLzwoA4iNo1VMtQc=\n" +
"=/Np6\n" +
"-----END PGP PRIVATE KEY BLOCK-----";
private static final String CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
"Version: PGPainless\n" +
"Comment: 9DA0 9423 C9F9 4BA4 CCA3 0951 099B 11BF 296A 373E\n" +
"Comment: Sigmund <sigmund@pgpainless.org>\n" +
"\n" +
"mDMEY2vzkhYJKwYBBAHaRw8BAQdA+Z2OAFQf0k64Au7hIZfXh/ijclabddvwh7Nh\n" +
"kedJ3ZW0IFNpZ211bmQgPHNpZ211bmRAcGdwYWlubGVzcy5vcmc+iI8EExYKAEEF\n" +
"AmNr85IJEAmbEb8pajc+FiEEnaCUI8n5S6TMowlRCZsRvylqNz4CngECmwEFFgID\n" +
"AQAECwkIBwUVCgkICwKZAQAAIz0A/0A+WgjbBoPmBewmrN6dFVTif9WZlS9x1TKG\n" +
"Vulpv/QdAQCVpPBm/FXJp/wQwVIk5j4tyBpwdpkMdE8DWdLdsh8rDLg4BGNr85IS\n" +
"CisGAQQBl1UBBQEBB0DVYvhAqBHFu/Nb3UgIzo+ggJI2HZAcrqPSkh039ypfPQMB\n" +
"CAeIdQQYFgoAHQUCY2vzkgKeAQKbDAUWAgMBAAQLCQgHBRUKCQgLAAoJEAmbEb8p\n" +
"ajc+q4EBAM6Buh5zwmItotnOv7n7sqQZC28trtSYw9Alkm6zOHWdAP4/6SOGvW/C\n" +
"3K2XLmNkMB6rFzwCafELTjkDeoE7zVnEDbgzBGNr85IWCSsGAQQB2kcPAQEHQLMX\n" +
"Awr7Lfxm1sUL7zqQoUr5m6lnrgYJt9b0aLoxVRXGiNUEGBYKAH0FAmNr85ICngEC\n" +
"mwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJja/OSAAoJENqfQTmGIR3GtsMB\n" +
"AL+b1Zo5giQKJGEyx5aGwAz3AwtGiT6QDS9FH6HyM855AP4uAXDiaNxYTugqnG47\n" +
"1jYX/hhJqIROeDGrEIkkAp+qDwAKCRAJmxG/KWo3PhOXAP45LPV6I4+D3h8etdiE\n" +
"A2DVvNcpRA8l4WkNcq4q8H1SjwD/c/rX3FCUIWLlAHoRWxCFj+gDgqDNLzwoA4iN\n" +
"o1VMtQc=\n" +
"=KuJ4\n" +
"-----END PGP PUBLIC KEY BLOCK-----";
private static final String PLAINTEXT = "Hello, World!\n";
private static final String BINARY_SIG = "-----BEGIN PGP SIGNATURE-----\n" +
"Version: PGPainless\n" +
"\n" +
"iHUEABYKACcFAmNr9BgJENqfQTmGIR3GFiEEREwQqwEe+EJMg/Cp2p9BOYYhHcYA\n" +
"AKocAP48P2C3TU33T3Zy73clw0eBa1oW9pwxTGuFxhgOBzmoSwEArj0781GlpTB0\n" +
"Vnr2PjPYEqzB+ZuOzOnGhsVGob4c3Ao=\n" +
"=VWAZ\n" +
"-----END PGP SIGNATURE-----";
private static final String BINARY_SIG_VERIFICATION =
"2022-11-09T18:40:24Z 444C10AB011EF8424C83F0A9DA9F413986211DC6 9DA09423C9F94BA4CCA30951099B11BF296A373E mode:binary\n";
private static final String TEXT_SIG = "-----BEGIN PGP SIGNATURE-----\n" +
"Version: PGPainless\n" +
"\n" +
"iHUEARYKACcFAmNr9E4JENqfQTmGIR3GFiEEREwQqwEe+EJMg/Cp2p9BOYYhHcYA\n" +
"AG+CAQD1B3GAAlyxahSiGhvJv7YAI1m6qGcI7dIXcV7FkAFPSgEAlZ0UpCC8oGR+\n" +
"hi/mQlex4z0hDWSA4abAjclPTJ+qkAI=\n" +
"=s5xn\n" +
"-----END PGP SIGNATURE-----";
private static final String TEXT_SIG_VERIFICATION =
"2022-11-09T18:41:18Z 444C10AB011EF8424C83F0A9DA9F413986211DC6 9DA09423C9F94BA4CCA30951099B11BF296A373E mode:text\n";
private static final Date TEXT_SIG_CREATION;
static {
try {
TEXT_SIG_CREATION = UTCUtil.parseUTCDate("2022-11-09T18:41:18Z");
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
@Test
public void createArmoredSignature() throws IOException {
File keyFile = writeFile("key.asc", KEY);
pipeStringToStdin(PLAINTEXT);
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("sign", "--as", "text", keyFile.getAbsolutePath()));
assertTrue(out.toString().startsWith("-----BEGIN PGP SIGNATURE-----\n"));
}
@Test
public void createUnarmoredSignature() throws IOException {
File keyFile = writeFile("key.asc", KEY);
pipeStringToStdin(PLAINTEXT);
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("sign", "--no-armor", keyFile.getAbsolutePath()));
assertFalse(out.toString().startsWith("-----BEGIN PGP SIGNATURE-----\n"));
}
@Test
public void unarmorArmoredSigAndVerify() throws IOException {
File certFile = writeFile("cert.asc", CERT);
pipeStringToStdin(BINARY_SIG);
File unarmoredSigFile = pipeStdoutToFile("sig.pgp");
assertSuccess(executeCommand("dearmor"));
pipeStringToStdin(PLAINTEXT);
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("verify", unarmoredSigFile.getAbsolutePath(), certFile.getAbsolutePath()));
assertEquals(BINARY_SIG_VERIFICATION, out.toString());
}
@Test
public void testNotBefore() throws IOException {
File cert = writeFile("cert.asc", CERT);
File sigFile = writeFile("sig.asc", TEXT_SIG);
Date plus1Minute = new Date(TEXT_SIG_CREATION.getTime() + 1000 * 60);
pipeStringToStdin(PLAINTEXT);
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath(),
"--not-before", UTCUtil.formatUTCDate(plus1Minute));
assertEquals(SOPGPException.NoSignature.EXIT_CODE, exitCode);
assertEquals(0, out.size());
Date minus1Minute = new Date(TEXT_SIG_CREATION.getTime() - 1000 * 60);
pipeStringToStdin(PLAINTEXT);
out = pipeStdoutToStream();
exitCode = executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath(),
"--not-before", UTCUtil.formatUTCDate(minus1Minute));
assertSuccess(exitCode);
assertEquals(TEXT_SIG_VERIFICATION, out.toString());
}
@Test
public void testNotAfter() throws IOException {
File cert = writeFile("cert.asc", CERT);
File sigFile = writeFile("sig.asc", TEXT_SIG);
Date minus1Minute = new Date(TEXT_SIG_CREATION.getTime() - 1000 * 60);
pipeStringToStdin(PLAINTEXT);
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath(),
"--not-after", UTCUtil.formatUTCDate(minus1Minute));
assertEquals(SOPGPException.NoSignature.EXIT_CODE, exitCode);
assertEquals(0, out.size());
Date plus1Minute = new Date(TEXT_SIG_CREATION.getTime() + 1000 * 60);
pipeStringToStdin(PLAINTEXT);
out = pipeStdoutToStream();
exitCode = executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath(),
"--not-after", UTCUtil.formatUTCDate(plus1Minute));
assertSuccess(exitCode);
assertEquals(TEXT_SIG_VERIFICATION, out.toString());
}
@Test
public void testSignWithIncapableKey()
throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
PGPSecretKeyRing secretKeys = PGPainless.buildKeyRing()
.addUserId("Cannot Sign <cannot@sign.key>")
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.CERTIFY_OTHER))
.addSubkey(KeySpec.getBuilder(KeyType.XDH_LEGACY(XDHLegacySpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
.build();
File keyFile = writeFile("key.pgp", secretKeys.getEncoded());
pipeStringToStdin("Hello, World!\n");
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("sign", keyFile.getAbsolutePath());
assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void testSignatureCreationAndVerification()
throws IOException {
// Create key and cert
File aliceKeyFile = pipeStdoutToFile("alice.key");
assertSuccess(executeCommand("generate-key", "Alice <alice@pgpainless.org>"));
File aliceCertFile = pipeStdoutToFile("alice.cert");
pipeFileToStdin(aliceKeyFile);
assertSuccess(executeCommand("extract-cert"));
File micalgOut = nonExistentFile("micalg");
String msg = "If privacy is outlawed, only outlaws will have privacy.\n";
File dataFile = writeFile("data", msg);
// sign data
File sigFile = pipeStdoutToFile("sig.asc");
pipeFileToStdin(dataFile);
assertSuccess(executeCommand("sign",
"--armor",
"--as", "binary",
"--micalg-out", micalgOut.getAbsolutePath(),
aliceKeyFile.getAbsolutePath()));
// verify test data signature
pipeFileToStdin(dataFile);
ByteArrayOutputStream verificationsOut = pipeStdoutToStream();
assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), aliceCertFile.getAbsolutePath()));
// Test verification output
PGPPublicKeyRing cert = PGPainless.readKeyRing().publicKeyRing(readBytesFromFile(aliceCertFile));
KeyRingInfo info = PGPainless.inspectKeyRing(cert);
// [date] [signing-key-fp] [primary-key-fp] signed by [key.pub]
String verification = verificationsOut.toString();
String[] split = verification.split(" ");
OpenPgpV4Fingerprint primaryKeyFingerprint = new OpenPgpV4Fingerprint(cert);
OpenPgpV4Fingerprint signingKeyFingerprint = new OpenPgpV4Fingerprint(info.getSigningSubkeys().get(0));
assertEquals(signingKeyFingerprint.toString(), split[1].trim(), verification);
assertEquals(primaryKeyFingerprint.toString(), split[2].trim());
// Test micalg output
String content = readStringFromFile(micalgOut);
assertEquals("pgp-sha512", content);
}
private static final String PROTECTED_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
"Version: PGPainless\n" +
"Comment: 738E EAB2 503D 322D 613A C42A B18E 8BF8 884F C050\n" +
"Comment: Axel <axel@pgpainless.org>\n" +
"\n" +
"lIYEY2v6aRYJKwYBBAHaRw8BAQdA3PXtH19zYpVQ9zTU3zlY+iXUptelAO3z4vK/\n" +
"M2FkmrP+CQMCYgVa6K+InVJguITSDIA+HQ6vhOZ5Dbanqx7GFbJbJLD2fWrxhTSr\n" +
"BUWGaUWTqN647auD/kUI8phH1cedVL6CzVR+YWvaWj9zZHr/CYXLobQaQXhlbCA8\n" +
"YXhlbEBwZ3BhaW5sZXNzLm9yZz6IjwQTFgoAQQUCY2v6aQkQsY6L+IhPwFAWIQRz\n" +
"juqyUD0yLWE6xCqxjov4iE/AUAKeAQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAACq\n" +
"zgEAkxB+dUI7Jjcg5zRvT1EfE9DKCI1qTsxOAU/ZXLcSXLkBAJtWRsyptetZvjzB\n" +
"Ze2A7ArOl4q+IvKvun/d783YyRMInIsEY2v6aRIKKwYBBAGXVQEFAQEHQPFmlZ+o\n" +
"jCGEo2X0474vJfRG7blctuZXmCbC0sLO7MgzAwEIB/4JAwJiBVror4idUmDFhBq4\n" +
"lEhJxjCVc6aSD6+EWRT3YdplqCmNdynnrPombUFst6LfJFzns3H3d0rCeXHfQr93\n" +
"GrHTLkHfW8G3x0PJJPiqFkBviHUEGBYKAB0FAmNr+mkCngECmwwFFgIDAQAECwkI\n" +
"BwUVCgkICwAKCRCxjov4iE/AUNC2AP9WDx4lHt9oYFLSrM8vMLRFI31U8TkYrtCe\n" +
"pYICE76cIAEA5+wEbtE5vQrLxOqIRueVVdzwK9kTeMvSIQfc9PNoyQKchgRja/pp\n" +
"FgkrBgEEAdpHDwEBB0CyAEVlCUbFr3dBBG3MQ84hjCPfYqSx9kYsTN8j5Og6uP4J\n" +
"AwJiBVror4idUmCIFuAYXia0YpEhEpB/Lrn/D6/WAUPEgZjNLMvJzL//EmhkWfEa\n" +
"OfQz/fslj1erWNjLKNiW5C/TvGapDfjbn596AkNlcd1JiNUEGBYKAH0FAmNr+mkC\n" +
"ngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJja/ppAAoJELRgil1uCuQj\n" +
"VUYBAJecbedwwqWQITVqucEBIraTRoc6ZGkN8jytDp8z9CsBAQDrb/W/J/kze6ln\n" +
"nRyJSriWF3SjcKOGIRkUslmdJEPPCQAKCRCxjov4iE/AUAvbAQDBBgQFG8acTT5L\n" +
"cyIi1Ix9/XBG7G23SSs6l7Beap8M+wEAmK13NYuq7Mv/mct8iIKZbBFH9aAiY+nX\n" +
"3Uct4Q5f0w0=\n" +
"=K65R\n" +
"-----END PGP PRIVATE KEY BLOCK-----";
private static final String PASSPHRASE = "orange";
private static final String SIGNING_KEY = "9846F3606EE875FB77EC8808B4608A5D6E0AE423 738EEAB2503D322D613AC42AB18E8BF8884FC050";
@Test
public void signWithProtectedKey_missingPassphraseFails() throws IOException {
File key = writeFile("key.asc", PROTECTED_KEY);
pipeStringToStdin(PLAINTEXT);
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("sign", key.getAbsolutePath());
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void signWithProtectedKey_wrongPassphraseFails() throws IOException {
File password = writeFile("password", "blue");
File key = writeFile("key.asc", PROTECTED_KEY);
pipeStringToStdin(PLAINTEXT);
ByteArrayOutputStream out = pipeStdoutToStream();
int exitCode = executeCommand("sign", key.getAbsolutePath(),
"--with-key-password", password.getAbsolutePath());
assertEquals(SOPGPException.KeyIsProtected.EXIT_CODE, exitCode);
assertEquals(0, out.size());
}
@Test
public void signWithProtectedKey() throws IOException {
File password = writeFile("password", PASSPHRASE);
File key = writeFile("key.asc", PROTECTED_KEY);
pipeStringToStdin(PROTECTED_KEY);
File cert = pipeStdoutToFile("cert.asc");
assertSuccess(executeCommand("extract-cert"));
pipeStringToStdin(PLAINTEXT);
File sigFile = pipeStdoutToFile("sig.asc");
assertSuccess(executeCommand("sign", key.getAbsolutePath(),
"--with-key-password", password.getAbsolutePath()));
pipeStringToStdin(PLAINTEXT);
ByteArrayOutputStream verificationOut = pipeStdoutToStream();
assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), cert.getAbsolutePath()));
assertTrue(verificationOut.toString().contains(SIGNING_KEY));
}
}

View file

@ -1,118 +0,0 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import com.ginsberg.junit.exit.FailOnSystemExit;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.cli.PGPainlessCLI;
import org.pgpainless.cli.TestUtils;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.util.KeyRingUtils;
public class SignVerifyTest {
private static File tempDir;
private static PrintStream originalSout;
@BeforeAll
public static void prepare() throws IOException {
tempDir = TestUtils.createTempDirectory();
}
@Test
@FailOnSystemExit
public void testSignatureCreationAndVerification() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
originalSout = System.out;
InputStream originalIn = System.in;
// Write alice key to disc
File aliceKeyFile = new File(tempDir, "alice.key");
assertTrue(aliceKeyFile.createNewFile());
PGPSecretKeyRing aliceKeys = PGPainless.generateKeyRing()
.modernKeyRing("alice", null);
OutputStream aliceKeyOut = new FileOutputStream(aliceKeyFile);
Streams.pipeAll(new ByteArrayInputStream(aliceKeys.getEncoded()), aliceKeyOut);
aliceKeyOut.close();
// Write alice pub key to disc
File aliceCertFile = new File(tempDir, "alice.pub");
assertTrue(aliceCertFile.createNewFile());
PGPPublicKeyRing alicePub = KeyRingUtils.publicKeyRingFrom(aliceKeys);
OutputStream aliceCertOut = new FileOutputStream(aliceCertFile);
Streams.pipeAll(new ByteArrayInputStream(alicePub.getEncoded()), aliceCertOut);
aliceCertOut.close();
// Write test data to disc
String data = "If privacy is outlawed, only outlaws will have privacy.\n";
File dataFile = new File(tempDir, "data");
assertTrue(dataFile.createNewFile());
FileOutputStream dataOut = new FileOutputStream(dataFile);
Streams.pipeAll(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), dataOut);
dataOut.close();
// Sign test data
FileInputStream dataIn = new FileInputStream(dataFile);
System.setIn(dataIn);
File sigFile = new File(tempDir, "sig.asc");
assertTrue(sigFile.createNewFile());
FileOutputStream sigOut = new FileOutputStream(sigFile);
System.setOut(new PrintStream(sigOut));
PGPainlessCLI.execute("sign", "--armor", aliceKeyFile.getAbsolutePath());
sigOut.close();
// verify test data signature
ByteArrayOutputStream verifyOut = new ByteArrayOutputStream();
System.setOut(new PrintStream(verifyOut));
dataIn = new FileInputStream(dataFile);
System.setIn(dataIn);
PGPainlessCLI.execute("verify", sigFile.getAbsolutePath(), aliceCertFile.getAbsolutePath());
dataIn.close();
// Test verification output
// [date] [signing-key-fp] [primary-key-fp] signed by [key.pub]
String verification = verifyOut.toString();
String[] split = verification.split(" ");
OpenPgpV4Fingerprint primaryKeyFingerprint = new OpenPgpV4Fingerprint(aliceKeys);
OpenPgpV4Fingerprint signingKeyFingerprint = new OpenPgpV4Fingerprint(new KeyRingInfo(alicePub, new Date()).getSigningSubkeys().get(0));
assertEquals(signingKeyFingerprint.toString(), split[1].trim());
assertEquals(primaryKeyFingerprint.toString(), split[2].trim());
System.setIn(originalIn);
}
@AfterAll
public static void after() {
System.setOut(originalSout);
// CHECKSTYLE:OFF
System.out.println(tempDir.getAbsolutePath());
// CHECKSTYLE:ON
}
}

View file

@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.cli.commands;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
public class VersionCmdTest extends CLITest {
public VersionCmdTest() {
super(LoggerFactory.getLogger(VersionCmdTest.class));
}
@Test
public void testVersion() throws IOException {
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("version"));
assertTrue(out.toString().startsWith("PGPainless-SOP "));
}
@Test
public void testGetBackendVersion() throws IOException {
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("version", "--backend"));
assertTrue(out.toString().startsWith("PGPainless "));
}
@Test
public void testExtendedVersion() throws IOException {
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("version", "--extended"));
String info = out.toString();
assertTrue(info.startsWith("PGPainless-SOP "));
assertTrue(info.contains("Bouncy Castle"));
assertTrue(info.contains("Stateless OpenPGP Protocol"));
}
@Test
public void testSopSpecVersion() throws IOException {
ByteArrayOutputStream out = pipeStdoutToStream();
assertSuccess(executeCommand("version", "--sop-spec"));
String info = out.toString();
assertTrue(info.startsWith("draft-dkg-openpgp-stateless-cli-"));
}
}

View file

@ -4,28 +4,18 @@
package org.pgpainless.cli.misc;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import com.ginsberg.junit.exit.ExpectSystemExitWithStatus;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.pgpainless.cli.PGPainlessCLI;
import org.pgpainless.cli.TestUtils;
import org.pgpainless.cli.commands.CLITest;
import org.slf4j.LoggerFactory;
import sop.exception.SOPGPException;
public class SignUsingPublicKeyBehaviorTest {
public class SignUsingPublicKeyBehaviorTest extends CLITest {
public static final String KEY_THAT_IS_A_CERT = "" +
"-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
@ -89,61 +79,24 @@ public class SignUsingPublicKeyBehaviorTest {
"=oJQ2\n" +
"-----END PGP PUBLIC KEY BLOCK-----";
private static File tempDir;
private static PrintStream originalSout;
@BeforeAll
public static void prepare() throws IOException {
tempDir = TestUtils.createTempDirectory();
public SignUsingPublicKeyBehaviorTest() {
super(LoggerFactory.getLogger(SignUsingPublicKeyBehaviorTest.class));
}
@Test
@ExpectSystemExitWithStatus(SOPGPException.BadData.EXIT_CODE)
public void testSignatureCreationAndVerification() throws IOException {
originalSout = System.out;
InputStream originalIn = System.in;
// Write alice key to disc
File aliceKeyFile = new File(tempDir, "alice.key");
assertTrue(aliceKeyFile.createNewFile());
OutputStream aliceKeyOut = new FileOutputStream(aliceKeyFile);
Streams.pipeAll(new ByteArrayInputStream(KEY_THAT_IS_A_CERT.getBytes(StandardCharsets.UTF_8)), aliceKeyOut);
aliceKeyOut.close();
// Write alice pub key to disc
File aliceCertFile = new File(tempDir, "alice.pub");
assertTrue(aliceCertFile.createNewFile());
OutputStream aliceCertOut = new FileOutputStream(aliceCertFile);
Streams.pipeAll(new ByteArrayInputStream(KEY_THAT_IS_A_CERT.getBytes(StandardCharsets.UTF_8)), aliceCertOut);
aliceCertOut.close();
File aliceKeyFile = writeFile("alice.key", KEY_THAT_IS_A_CERT);
// Write test data to disc
String data = "If privacy is outlawed, only outlaws will have privacy.\n";
File dataFile = new File(tempDir, "data");
assertTrue(dataFile.createNewFile());
FileOutputStream dataOut = new FileOutputStream(dataFile);
Streams.pipeAll(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)), dataOut);
dataOut.close();
File dataFile = writeFile("data", "If privacy is outlawed, only outlaws will have privacy.\n");
// Sign test data
FileInputStream dataIn = new FileInputStream(dataFile);
System.setIn(dataIn);
File sigFile = new File(tempDir, "sig.asc");
assertTrue(sigFile.createNewFile());
FileOutputStream sigOut = new FileOutputStream(sigFile);
System.setOut(new PrintStream(sigOut));
PGPainlessCLI.execute("sign", "--armor", aliceKeyFile.getAbsolutePath());
File sigFile = pipeStdoutToFile("sig.asc");
pipeFileToStdin(dataFile);
assertEquals(SOPGPException.KeyCannotSign.EXIT_CODE,
executeCommand("sign", "--armor", aliceKeyFile.getAbsolutePath()));
System.setIn(originalIn);
}
@AfterAll
public static void after() {
System.setOut(originalSout);
// CHECKSTYLE:OFF
System.out.println(tempDir.getAbsolutePath());
// CHECKSTYLE:ON
assertTrue(readStringFromFile(sigFile).trim().isEmpty());
}
}

View file

@ -6,7 +6,10 @@ SPDX-License-Identifier: Apache-2.0
# PGPainless-Core
Wrapper around Bouncycastle's OpenPGP implementation.
[![javadoc](https://javadoc.io/badge2/org.pgpainless/pgpainless-core/javadoc.svg)](https://javadoc.io/doc/org.pgpainless/pgpainless-core)
[![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/pgpainless-core)](https://search.maven.org/artifact/org.pgpainless/pgpainless-core)
Wrapper around Bouncy Castle's OpenPGP implementation.
## Protection Against Attacks
@ -50,4 +53,4 @@ It is therefore responsibility of the consumer to ensure that an attacker on the
It is highly advised to store both secret and public keys in a secure key storage which protects against modifications.
Furthermore, PGPainless cannot verify key authenticity, so it is up to the application that uses PGPainless to check,
if a key really belongs to a certain user.
if a key really belongs to a certain user.

View file

@ -7,17 +7,31 @@ plugins {
}
dependencies {
// JUnit
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// Mocking Components
testImplementation "org.mockito:mockito-core:$mockitoVersion"
// Logging
api "org.slf4j:slf4j-api:$slf4jVersion"
testImplementation "ch.qos.logback:logback-classic:$logbackVersion"
api "org.bouncycastle:bcprov-jdk15on:$bouncyCastleVersion"
api "org.bouncycastle:bcpg-jdk15on:$bouncyCastleVersion"
// Bouncy Castle
api "org.bouncycastle:bcprov-jdk18on:$bouncyCastleVersion"
api "org.bouncycastle:bcpg-jdk18on:$bouncyPgVersion"
api "org.bouncycastle:bcutil-jdk18on:$bouncyCastleVersion"
// api(files("../libs/bcpg-jdk18on-1.70.jar"))
// https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
// @Nullable, @Nonnull annotations
implementation "com.google.code.findbugs:jsr305:3.0.2"
}
// https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_modular_auto
tasks.named('jar') {
manifest {
attributes('Automatic-Module-Name': 'org.pgpainless.core')
}
}

Some files were not shown because too many files have changed in this diff Show more