From 23fd630670e1d734d22effad5843a5e9ada49b49 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 25 Apr 2023 13:39:44 +0200 Subject: [PATCH 001/491] PGPainless 1.5.2-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 88c3babd..c53ce511 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '1.5.1' - isSnapshot = false + shortVersion = '1.5.2' + isSnapshot = true pgpainlessMinAndroidSdk = 10 javaSourceCompatibility = 1.8 bouncyCastleVersion = '1.73' From eb1ff27a900e56d97872fd0b167dbb2b07f3ac2c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 15:15:42 +0200 Subject: [PATCH 002/491] Bump sop-java to 6.1.0 --- .../commands/RoundTripSignVerifyCmdTest.java | 11 ++++++- .../sop/CarolKeySignEncryptRoundtripTest.java | 10 +++--- .../org/pgpainless/sop/DetachedSignTest.java | 14 +++++--- .../sop/EncryptDecryptRoundTripTest.java | 31 ++++++++++++------ .../org/pgpainless/sop/InlineDetachTest.java | 24 ++++++++------ .../sop/InlineSignVerifyRoundtripTest.java | 32 +++++++++---------- version.gradle | 2 +- 7 files changed, 79 insertions(+), 45 deletions(-) diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripSignVerifyCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripSignVerifyCmdTest.java index 97bfae7e..0ff83144 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripSignVerifyCmdTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripSignVerifyCmdTest.java @@ -13,6 +13,7 @@ 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; @@ -105,7 +106,15 @@ public class RoundTripSignVerifyCmdTest extends CLITest { "-----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 = UTCUtil.parseUTCDate("2022-11-09T18:41:18Z"); + 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 { diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/CarolKeySignEncryptRoundtripTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/CarolKeySignEncryptRoundtripTest.java index 81bf7645..58dfaa62 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/CarolKeySignEncryptRoundtripTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/CarolKeySignEncryptRoundtripTest.java @@ -4,15 +4,15 @@ package org.pgpainless.sop; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + import java.io.IOException; import org.junit.jupiter.api.Test; import sop.ByteArrayAndResult; import sop.DecryptionResult; import sop.Ready; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; +import sop.testsuite.assertions.VerificationListAssert; public class CarolKeySignEncryptRoundtripTest { @@ -290,6 +290,8 @@ public class CarolKeySignEncryptRoundtripTest { byte[] plaintext = decryption.getBytes(); assertArrayEquals(msg, plaintext); - assertEquals(1, decryption.getResult().getVerifications().size()); + VerificationListAssert.assertThatVerificationList(decryption.getResult().getVerifications()) + .hasSingleItem() + .issuedBy("71FFDA004409E5DDB0C3E8F19BA789DC76D6849A", "71FFDA004409E5DDB0C3E8F19BA789DC76D6849A"); } } diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/DetachedSignTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/DetachedSignTest.java index 4f274691..316c4a37 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/DetachedSignTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/DetachedSignTest.java @@ -25,6 +25,7 @@ import sop.Verification; import sop.enums.SignAs; import sop.enums.SignatureMode; import sop.exception.SOPGPException; +import sop.testsuite.assertions.VerificationListAssert; public class DetachedSignTest { @@ -63,8 +64,9 @@ public class DetachedSignTest { .signatures(signature) .data(data); - assertEquals(1, verifications.size()); - assertEquals(SignatureMode.binary, verifications.get(0).getSignatureMode()); + VerificationListAssert.assertThatVerificationList(verifications) + .hasSingleItem() + .hasMode(SignatureMode.binary); } @Test @@ -84,7 +86,8 @@ public class DetachedSignTest { .signatures(signature) .data(data); - assertEquals(1, verifications.size()); + VerificationListAssert.assertThatVerificationList(verifications) + .hasSingleItem(); } @Test @@ -103,8 +106,9 @@ public class DetachedSignTest { .signatures(signature) .data(data); - assertEquals(1, verifications.size()); - assertEquals(SignatureMode.text, verifications.get(0).getSignatureMode()); + VerificationListAssert.assertThatVerificationList(verifications) + .hasSingleItem() + .hasMode(SignatureMode.text); } @Test diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptRoundTripTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptRoundTripTest.java index 22af5b70..f51b711d 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptRoundTripTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/EncryptDecryptRoundTripTest.java @@ -21,7 +21,9 @@ import sop.ByteArrayAndResult; import sop.DecryptionResult; import sop.SOP; import sop.SessionKey; +import sop.enums.SignatureMode; import sop.exception.SOPGPException; +import sop.testsuite.assertions.VerificationListAssert; public class EncryptDecryptRoundTripTest { @@ -75,7 +77,8 @@ public class EncryptDecryptRoundTripTest { assertArrayEquals(message, decrypted.toByteArray()); DecryptionResult result = bytesAndResult.getResult(); - assertEquals(1, result.getVerifications().size()); + VerificationListAssert.assertThatVerificationList(result.getVerifications()) + .hasSingleItem(); } @Test @@ -106,7 +109,8 @@ public class EncryptDecryptRoundTripTest { assertArrayEquals(message, decrypted); DecryptionResult result = bytesAndResult.getResult(); - assertEquals(1, result.getVerifications().size()); + VerificationListAssert.assertThatVerificationList(result.getVerifications()) + .hasSingleItem(); } @Test @@ -125,7 +129,8 @@ public class EncryptDecryptRoundTripTest { assertArrayEquals(message, decrypted); DecryptionResult result = bytesAndResult.getResult(); - assertEquals(0, result.getVerifications().size()); + VerificationListAssert.assertThatVerificationList(result.getVerifications()) + .isEmpty(); } @Test @@ -144,7 +149,8 @@ public class EncryptDecryptRoundTripTest { assertArrayEquals(message, decrypted); DecryptionResult result = bytesAndResult.getResult(); - assertEquals(0, result.getVerifications().size()); + VerificationListAssert.assertThatVerificationList(result.getVerifications()) + .isEmpty(); } @Test @@ -163,7 +169,8 @@ public class EncryptDecryptRoundTripTest { assertArrayEquals(message, decrypted); DecryptionResult result = bytesAndResult.getResult(); - assertEquals(0, result.getVerifications().size()); + VerificationListAssert.assertThatVerificationList(result.getVerifications()) + .isEmpty(); } @Test @@ -180,7 +187,8 @@ public class EncryptDecryptRoundTripTest { .toByteArrayAndResult() .getResult(); - assertTrue(result.getVerifications().isEmpty()); + VerificationListAssert.assertThatVerificationList(result.getVerifications()) + .isEmpty(); } @Test @@ -486,14 +494,19 @@ public class EncryptDecryptRoundTripTest { sop.decrypt().withKey(key).verifyWithCert(cert).ciphertext(ciphertext).toByteArrayAndResult(); assertEquals(sessionKey, bytesAndResult.getResult().getSessionKey().get().toString()); assertArrayEquals(plaintext, bytesAndResult.getBytes()); - assertEquals(1, bytesAndResult.getResult().getVerifications().size()); - + VerificationListAssert.assertThatVerificationList(bytesAndResult.getResult().getVerifications()) + .hasSingleItem() + .issuedBy("9C26EFAB1C6500A228E8A9C2658EE420C824D191") + .hasMode(SignatureMode.binary); // Decrypt with session key bytesAndResult = sop.decrypt().withSessionKey(SessionKey.fromString(sessionKey)) .verifyWithCert(cert).ciphertext(ciphertext).toByteArrayAndResult(); assertEquals(sessionKey, bytesAndResult.getResult().getSessionKey().get().toString()); assertArrayEquals(plaintext, bytesAndResult.getBytes()); - assertEquals(1, bytesAndResult.getResult().getVerifications().size()); + VerificationListAssert.assertThatVerificationList(bytesAndResult.getResult().getVerifications()) + .hasSingleItem() + .issuedBy("9C26EFAB1C6500A228E8A9C2658EE420C824D191") + .hasMode(SignatureMode.binary); } @Test diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/InlineDetachTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/InlineDetachTest.java index 1054babb..98279e4f 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/InlineDetachTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/InlineDetachTest.java @@ -5,8 +5,6 @@ package org.pgpainless.sop; import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -34,7 +32,9 @@ import sop.SOP; import sop.Signatures; import sop.Verification; import sop.enums.InlineSignAs; +import sop.enums.SignatureMode; import sop.exception.SOPGPException; +import sop.testsuite.assertions.VerificationListAssert; public class InlineDetachTest { @@ -79,9 +79,11 @@ public class InlineDetachTest { .signatures(signature) .data(message); - assertFalse(verificationList.isEmpty()); - assertEquals(1, verificationList.size()); - assertEquals(new OpenPgpV4Fingerprint(secretKey).toString(), verificationList.get(0).getSigningCertFingerprint()); + VerificationListAssert.assertThatVerificationList(verificationList) + .hasSingleItem() + .issuedBy(new OpenPgpV4Fingerprint(secretKey).toString()) + .hasMode(SignatureMode.text); + assertArrayEquals(data, message); } @@ -121,8 +123,10 @@ public class InlineDetachTest { .signatures(signature) .data(message); - assertFalse(verificationList.isEmpty()); - assertEquals(1, verificationList.size()); + VerificationListAssert.assertThatVerificationList(verificationList) + .hasSingleItem() + .hasMode(SignatureMode.binary); + assertArrayEquals(data, message); } @@ -191,8 +195,10 @@ public class InlineDetachTest { .signatures(signature) .data(message); - assertFalse(verificationList.isEmpty()); - assertEquals(1, verificationList.size()); + VerificationListAssert.assertThatVerificationList(verificationList) + .hasSingleItem() + .hasMode(SignatureMode.binary); + assertArrayEquals(data, message); } } diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/InlineSignVerifyRoundtripTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/InlineSignVerifyRoundtripTest.java index e5ce518b..f3a50fc3 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/InlineSignVerifyRoundtripTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/InlineSignVerifyRoundtripTest.java @@ -4,19 +4,19 @@ package org.pgpainless.sop; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + import org.junit.jupiter.api.Test; import sop.ByteArrayAndResult; import sop.SOP; import sop.Verification; import sop.enums.InlineSignAs; import sop.enums.SignatureMode; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; +import sop.testsuite.assertions.VerificationListAssert; public class InlineSignVerifyRoundtripTest { @@ -48,9 +48,9 @@ public class InlineSignVerifyRoundtripTest { byte[] verified = result.getBytes(); List verificationList = result.getResult(); - assertEquals(1, verificationList.size()); - Verification verification = verificationList.get(0); - assertEquals(SignatureMode.text, verification.getSignatureMode()); + VerificationListAssert.assertThatVerificationList(verificationList) + .hasSingleItem() + .hasMode(SignatureMode.text); assertArrayEquals(message, verified); } @@ -81,9 +81,9 @@ public class InlineSignVerifyRoundtripTest { byte[] verified = result.getBytes(); List verificationList = result.getResult(); - assertEquals(1, verificationList.size()); - Verification verification = verificationList.get(0); - assertEquals(SignatureMode.binary, verification.getSignatureMode()); + VerificationListAssert.assertThatVerificationList(verificationList) + .hasSingleItem() + .hasMode(SignatureMode.binary); assertArrayEquals(message, verified); } @@ -115,9 +115,9 @@ public class InlineSignVerifyRoundtripTest { byte[] verified = result.getBytes(); List verificationList = result.getResult(); - assertEquals(1, verificationList.size()); - Verification verification = verificationList.get(0); - assertEquals(SignatureMode.text, verification.getSignatureMode()); + VerificationListAssert.assertThatVerificationList(verificationList) + .hasSingleItem() + .hasMode(SignatureMode.text); assertArrayEquals(message, verified); } diff --git a/version.gradle b/version.gradle index c53ce511..ce7eebc7 100644 --- a/version.gradle +++ b/version.gradle @@ -14,6 +14,6 @@ allprojects { logbackVersion = '1.2.11' mockitoVersion = '4.5.1' slf4jVersion = '1.7.36' - sopJavaVersion = '6.0.0' + sopJavaVersion = '6.1.0' } } From f51685c1266a17ae55bdd209ce1515c042e9f8c6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 15:16:37 +0200 Subject: [PATCH 003/491] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d59bf372..506ded3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ SPDX-License-Identifier: CC0-1.0 # PGPainless Changelog +## 1.5.2-SNAPSHOT +- Bump `sop-java` to `6.1.0` + ## 1.5.1 - SOP: Emit signature `mode:{binary|text}` in `Verification` results - core: Relax constraints on decryption subkeys to improve interoperability with broken clients From eb45dee04fdeb9634e78cde1c766b2bef8bec9e9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 15:22:43 +0200 Subject: [PATCH 004/491] 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 --- pgpainless-cli/rewriteManPages.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpainless-cli/rewriteManPages.sh b/pgpainless-cli/rewriteManPages.sh index 321dbdde..51d2aa04 100755 --- a/pgpainless-cli/rewriteManPages.sh +++ b/pgpainless-cli/rewriteManPages.sh @@ -4,7 +4,7 @@ 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." && exit 1; +[ ! -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 From 558036c485d6afbade4380ba5aaddf68ada25906 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 27 Apr 2023 15:23:37 +0200 Subject: [PATCH 005/491] Update man pages --- .../packaging/man/pgpainless-cli-decrypt.1 | 36 ++++----------- .../packaging/man/pgpainless-cli-encrypt.1 | 12 +++-- .../man/pgpainless-cli-generate-completion.1 | 10 ++++ .../man/pgpainless-cli-generate-key.1 | 9 +++- .../packaging/man/pgpainless-cli-help.1 | 10 ++++ .../man/pgpainless-cli-inline-sign.1 | 9 ++-- .../man/pgpainless-cli-list-profiles.1 | 46 +++++++++++++++++++ .../packaging/man/pgpainless-cli-sign.1 | 4 +- .../packaging/man/pgpainless-cli-verify.1 | 13 +++++- .../packaging/man/pgpainless-cli-version.1 | 7 ++- pgpainless-cli/packaging/man/pgpainless-cli.1 | 15 ++++++ 11 files changed, 129 insertions(+), 42 deletions(-) create mode 100644 pgpainless-cli/packaging/man/pgpainless-cli-list-profiles.1 diff --git a/pgpainless-cli/packaging/man/pgpainless-cli-decrypt.1 b/pgpainless-cli/packaging/man/pgpainless-cli-decrypt.1 index 17d59134..258078aa 100644 --- a/pgpainless-cli/packaging/man/pgpainless-cli-decrypt.1 +++ b/pgpainless-cli/packaging/man/pgpainless-cli-decrypt.1 @@ -30,41 +30,21 @@ pgpainless\-cli\-decrypt \- Decrypt a message from standard input .SH "SYNOPSIS" .sp -\fBpgpainless\-cli decrypt\fP [\fB\-\-stacktrace\fP] [\fB\-\-not\-after\fP=\fIDATE\fP] [\fB\-\-not\-before\fP=\fIDATE\fP] -[\fB\-\-session\-key\-out\fP=\fISESSIONKEY\fP] [\fB\-\-verify\-out\fP=\fIVERIFICATIONS\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...] +\fBpgpainless\-cli decrypt\fP [\fB\-\-stacktrace\fP] [\fB\-\-session\-key\-out\fP=\fISESSIONKEY\fP] +[\fB\-\-verify\-not\-after\fP=\fIDATE\fP] [\fB\-\-verify\-not\-before\fP=\fIDATE\fP] +[\fB\-\-verify\-out\fP=\fIVERIFICATIONS\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\-\-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\-\-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\-\-session\-key\-out\fP=\fISESSIONKEY\fP .RS 4 Can be used to learn the session key on successful decryption .RE .sp -\fB\-\-stacktrace\fP, \fB\-\-verify\-out, \-\-verifications\-out\fP=\fIVERIFICATIONS\fP +\fB\-\-stacktrace\fP, \fB\-\-verify\-not\-after\fP=\fIDATE\fP, \fB\-\-verify\-not\-before\fP=\fIDATE\fP, \fB\-\-verify\-out, \-\-verifications\-out\fP=\fIVERIFICATIONS\fP .RS 4 Emits signature verification status to the designated output .RE @@ -87,7 +67,7 @@ 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...) +Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). .RE .sp \fB\-\-with\-session\-key\fP=\fISESSIONKEY\fP @@ -96,7 +76,7 @@ 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...) +Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). .RE .SH "ARGUMENTS" .sp diff --git a/pgpainless-cli/packaging/man/pgpainless-cli-encrypt.1 b/pgpainless-cli/packaging/man/pgpainless-cli-encrypt.1 index f1d804d0..79002302 100644 --- a/pgpainless-cli/packaging/man/pgpainless-cli-encrypt.1 +++ b/pgpainless-cli/packaging/man/pgpainless-cli-encrypt.1 @@ -31,8 +31,9 @@ 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\-\-sign\-with\fP=\fIKEY\fP]... [\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP]... -[\fB\-\-with\-password\fP=\fIPASSWORD\fP]... [\fICERTS\fP...] +[\fB\-\-profile\fP=\fIPROFILE\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" @@ -47,6 +48,11 @@ Type of the input data. Defaults to \(aqbinary\(aq ASCII armor the output .RE .sp +\fB\-\-profile\fP=\fIPROFILE\fP +.RS 4 +Profile identifier to switch between profiles +.RE +.sp \fB\-\-sign\-with\fP=\fIKEY\fP .RS 4 Sign the output with a private key @@ -63,7 +69,7 @@ Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). .RS 4 Encrypt the message with a password. .sp -Is an INDIRECT data type (e.g. file, environment variable, file descriptor...) +Is an INDIRECT data type (e.g. file, environment variable, file descriptor...). .RE .SH "ARGUMENTS" .sp diff --git a/pgpainless-cli/packaging/man/pgpainless-cli-generate-completion.1 b/pgpainless-cli/packaging/man/pgpainless-cli-generate-completion.1 index 5ab3d673..5c50ee96 100644 --- a/pgpainless-cli/packaging/man/pgpainless-cli-generate-completion.1 +++ b/pgpainless-cli/packaging/man/pgpainless-cli-generate-completion.1 @@ -152,4 +152,14 @@ Ambiguous input (a filename matching the designator already exists) \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 \ No newline at end of file diff --git a/pgpainless-cli/packaging/man/pgpainless-cli-generate-key.1 b/pgpainless-cli/packaging/man/pgpainless-cli-generate-key.1 index 96b069f3..a5317665 100644 --- a/pgpainless-cli/packaging/man/pgpainless-cli-generate-key.1 +++ b/pgpainless-cli/packaging/man/pgpainless-cli-generate-key.1 @@ -30,8 +30,8 @@ pgpainless\-cli\-generate\-key \- Generate a secret key .SH "SYNOPSIS" .sp -\fBpgpainless\-cli generate\-key\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP] -[\fIUSERID\fP...] +\fBpgpainless\-cli generate\-key\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-profile\fP=\fIPROFILE\fP] +[\fB\-\-with\-key\-password\fP=\fIPASSWORD\fP] [\fIUSERID\fP...] .SH "DESCRIPTION" .SH "OPTIONS" @@ -41,6 +41,11 @@ pgpainless\-cli\-generate\-key \- Generate a secret key ASCII armor the output .RE .sp +\fB\-\-profile\fP=\fIPROFILE\fP +.RS 4 +Profile identifier to switch between profiles +.RE +.sp \fB\-\-stacktrace\fP, \fB\-\-with\-key\-password\fP=\fIPASSWORD\fP .RS 4 Password to protect the private key with diff --git a/pgpainless-cli/packaging/man/pgpainless-cli-help.1 b/pgpainless-cli/packaging/man/pgpainless-cli-help.1 index 6152fc87..1e7c2b08 100644 --- a/pgpainless-cli/packaging/man/pgpainless-cli-help.1 +++ b/pgpainless-cli/packaging/man/pgpainless-cli-help.1 @@ -147,4 +147,14 @@ Ambiguous input (a filename matching the designator already exists) \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 \ No newline at end of file diff --git a/pgpainless-cli/packaging/man/pgpainless-cli-inline-sign.1 b/pgpainless-cli/packaging/man/pgpainless-cli-inline-sign.1 index 7deb568c..db041c0c 100644 --- a/pgpainless-cli/packaging/man/pgpainless-cli-inline-sign.1 +++ b/pgpainless-cli/packaging/man/pgpainless-cli-inline-sign.1 @@ -30,20 +30,19 @@ pgpainless\-cli\-inline\-sign \- Create an inline\-signed message from data on standard input .SH "SYNOPSIS" .sp -\fBpgpainless\-cli inline\-sign\fP [\fB\-\-[no\-]armor\fP] [\fB\-\-stacktrace\fP] [\fB\-\-as\fP= -\fI{binary|text|cleartextsigned}\fP] +\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|cleartextsigned}\fP +\fB\-\-as\fP=\fI{binary|text|clearsigned}\fP .RS 4 -Specify the signature format of the signed message +Specify the signature format of the signed message. .sp \(aqtext\(aq and \(aqbinary\(aq will produce inline\-signed messages. .sp -\(aqcleartextsigned\(aq will make use of the cleartext signature framework. +\(aqclearsigned\(aq will make use of the cleartext signature framework. .sp Defaults to \(aqbinary\(aq. .sp diff --git a/pgpainless-cli/packaging/man/pgpainless-cli-list-profiles.1 b/pgpainless-cli/packaging/man/pgpainless-cli-list-profiles.1 new file mode 100644 index 00000000..9bcfa17f --- /dev/null +++ b/pgpainless-cli/packaging/man/pgpainless-cli-list-profiles.1 @@ -0,0 +1,46 @@ +'\" 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 +.RE +.SH "ARGUMENTS" +.sp +\fICOMMAND\fP +.RS 4 +Subcommand for which to list profiles +.RE \ No newline at end of file diff --git a/pgpainless-cli/packaging/man/pgpainless-cli-sign.1 b/pgpainless-cli/packaging/man/pgpainless-cli-sign.1 index 6519e0ec..5bb22e90 100644 --- a/pgpainless-cli/packaging/man/pgpainless-cli-sign.1 +++ b/pgpainless-cli/packaging/man/pgpainless-cli-sign.1 @@ -38,7 +38,7 @@ pgpainless\-cli\-sign \- Create a detached signature on the data from standard i .sp \fB\-\-as\fP=\fI{binary|text}\fP .RS 4 -Specify the output format of the signed message +Specify the output format of the signed message. .sp Defaults to \(aqbinary\(aq. .sp @@ -47,7 +47,7 @@ If \(aq\-\-as=text\(aq and the input data is not valid UTF\-8, sign fails with r .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) +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 diff --git a/pgpainless-cli/packaging/man/pgpainless-cli-verify.1 b/pgpainless-cli/packaging/man/pgpainless-cli-verify.1 index 5cf0020c..714064f6 100644 --- a/pgpainless-cli/packaging/man/pgpainless-cli-verify.1 +++ b/pgpainless-cli/packaging/man/pgpainless-cli-verify.1 @@ -36,7 +36,18 @@ pgpainless\-cli\-verify \- Verify a detached signature over the data from standa .SH "OPTIONS" .sp -\fB\-\-not\-after\fP=\fIDATE\fP, \fB\-\-not\-before\fP=\fIDATE\fP +\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 diff --git a/pgpainless-cli/packaging/man/pgpainless-cli-version.1 b/pgpainless-cli/packaging/man/pgpainless-cli-version.1 index 003e549f..f1bea312 100644 --- a/pgpainless-cli/packaging/man/pgpainless-cli-version.1 +++ b/pgpainless-cli/packaging/man/pgpainless-cli-version.1 @@ -30,7 +30,7 @@ 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] +\fBpgpainless\-cli version\fP [\fB\-\-stacktrace\fP] [\fB\-\-extended\fP | \fB\-\-backend\fP | \fB\-\-pgpainless\-cli\-spec\fP] .SH "DESCRIPTION" .SH "OPTIONS" @@ -45,6 +45,11 @@ Print information about the cryptographic backend 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\-\-stacktrace\fP .RS 4 .RE \ No newline at end of file diff --git a/pgpainless-cli/packaging/man/pgpainless-cli.1 b/pgpainless-cli/packaging/man/pgpainless-cli.1 index 686f728f..e5cc8129 100644 --- a/pgpainless-cli/packaging/man/pgpainless-cli.1 +++ b/pgpainless-cli/packaging/man/pgpainless-cli.1 @@ -101,6 +101,11 @@ Create an inline\-signed message from data on standard input Verify inline\-signed data from standard input .RE .sp +\fBlist\-profiles\fP +.RS 4 +Emit a list of profiles supported by the identified subcommand +.RE +.sp \fBversion\fP .RS 4 Display version information about the tool @@ -205,4 +210,14 @@ Ambiguous input (a filename matching the designator already exists) \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 \ No newline at end of file From 52fa7e4d46331bacb6839b73012d612d147d6bfa Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 May 2023 09:35:28 +0200 Subject: [PATCH 006/491] OpenPgpMessageInputStream: Return -1 instead of throwing MalformedOpenPgpMessageException when calling read() on drained stream --- .../OpenPgpMessageInputStream.java | 2 ++ .../syntax_check/OpenPgpMessageSyntax.java | 4 ++++ .../OpenPgpMessageInputStreamTest.java | 17 +++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java index 19a01fbf..d51a6cf7 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java @@ -744,6 +744,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { throws IOException { if (nestedInputStream == null) { if (packetInputStream != null) { + syntaxVerifier.next(InputSymbol.EndOfSequence); syntaxVerifier.assertValid(); } return -1; @@ -774,6 +775,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { super.close(); if (closed) { if (packetInputStream != null) { + syntaxVerifier.next(InputSymbol.EndOfSequence); syntaxVerifier.assertValid(); } return; diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.java index 6abb507a..9d20e0a8 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/syntax_check/OpenPgpMessageSyntax.java @@ -132,6 +132,10 @@ public class OpenPgpMessageSyntax implements Syntax { @Nonnull Transition fromValid(@Nonnull InputSymbol input, @Nullable StackSymbol stackItem) throws MalformedOpenPgpMessageException { + if (input == InputSymbol.EndOfSequence) { + // allow subsequent read() calls. + return new Transition(State.Valid); + } // There is no applicable transition rule out of Valid throw new MalformedOpenPgpMessageException(State.Valid, input, stackItem); } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStreamTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStreamTest.java index 01966bbe..a0ec7c25 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStreamTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStreamTest.java @@ -34,6 +34,7 @@ import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.util.io.Streams; import org.junit.JUtils; import org.junit.jupiter.api.Named; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -652,6 +653,22 @@ public class OpenPgpMessageInputStreamTest { assertTrue(metadata.getRejectedInlineSignatures().isEmpty()); } + @Test + public void readAfterCloseTest() throws PGPException, IOException { + OpenPgpMessageInputStream pgpIn = get(SENC_LIT, ConsumerOptions.get() + .addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE))); + Streams.drain(pgpIn); // read all + + byte[] buf = new byte[1024]; + assertEquals(-1, pgpIn.read(buf)); + assertEquals(-1, pgpIn.read()); + assertEquals(-1, pgpIn.read(buf)); + assertEquals(-1, pgpIn.read()); + + pgpIn.close(); + pgpIn.getMetadata(); + } + private static Tuple processReadBuffered(String armoredMessage, ConsumerOptions options) throws PGPException, IOException { OpenPgpMessageInputStream in = get(armoredMessage, options); From 2e730a2c4873b01940bee213dd4a4d8390f2d223 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 May 2023 10:10:50 +0200 Subject: [PATCH 007/491] Update changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 506ded3a..59868c3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,11 @@ SPDX-License-Identifier: CC0-1.0 # PGPainless Changelog -## 1.5.2-SNAPSHOT +## 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 From de5926fc472976b7a9fe24f0568364c51204c7f8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 May 2023 10:12:37 +0200 Subject: [PATCH 008/491] PGPainless 1.5.2-rc1 --- README.md | 2 +- pgpainless-sop/README.md | 4 ++-- version.gradle | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 06e9f2ed..903d21b8 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ repositories { } dependencies { - implementation 'org.pgpainless:pgpainless-core:1.5.1' + implementation 'org.pgpainless:pgpainless-core:1.5.2-rc1' } ``` diff --git a/pgpainless-sop/README.md b/pgpainless-sop/README.md index 7ae0a63c..a082f09c 100644 --- a/pgpainless-sop/README.md +++ b/pgpainless-sop/README.md @@ -23,7 +23,7 @@ To start using pgpainless-sop in your code, include the following lines in your ... dependencies { ... - implementation "org.pgpainless:pgpainless-sop:1.5.1" + implementation "org.pgpainless:pgpainless-sop:1.5.2-rc1" ... } @@ -34,7 +34,7 @@ dependencies { org.pgpainless pgpainless-sop - 1.5.1 + 1.5.2-rc1 ... diff --git a/version.gradle b/version.gradle index ce7eebc7..46a173b3 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '1.5.2' - isSnapshot = true + shortVersion = '1.5.2-rc1' + isSnapshot = false pgpainlessMinAndroidSdk = 10 javaSourceCompatibility = 1.8 bouncyCastleVersion = '1.73' From 671d45a9116e36a3124610e735c3fb122968923b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 1 May 2023 10:15:24 +0200 Subject: [PATCH 009/491] PGPainless 1.5.2-rc2-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 46a173b3..564100d4 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '1.5.2-rc1' - isSnapshot = false + shortVersion = '1.5.2-rc2' + isSnapshot = true pgpainlessMinAndroidSdk = 10 javaSourceCompatibility = 1.8 bouncyCastleVersion = '1.73' From 9c81137f4884c9d401dbc0435d40ba872dcb704e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 13:51:34 +0200 Subject: [PATCH 010/491] Add template methods to generate RSA keys with primary and subkeys --- .../key/generation/KeyRingTemplates.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingTemplates.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingTemplates.java index 42eb7efa..07f2235f 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingTemplates.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingTemplates.java @@ -26,6 +26,78 @@ public final class KeyRingTemplates { } + /** + * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, + * a dedicated RSA subkey used for signing and a third RSA subkey used for encryption. + * + * @param userId userId or null + * @param length length of the RSA keys + * @return key + * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters + * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider + * @throws PGPException in case of an OpenPGP related error + */ + public PGPSecretKeyRing rsaKeyRing(@Nullable CharSequence userId, + @Nonnull RsaLength length) + throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + return rsaKeyRing(userId, length, Passphrase.emptyPassphrase()); + } + + /** + * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, + * a dedicated RSA subkey used for signing and a third RSA subkey used for encryption. + * + * @param userId userId or null + * @param length length of the RSA keys + * @param password passphrase to encrypt the key with + * @return key + * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters + * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider + * @throws PGPException in case of an OpenPGP related error + */ + public PGPSecretKeyRing rsaKeyRing(@Nullable CharSequence userId, + @Nonnull RsaLength length, + @Nonnull String password) + throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + Passphrase passphrase = Passphrase.emptyPassphrase(); + if (!isNullOrEmpty(password)) { + passphrase = Passphrase.fromPassword(password); + } + return rsaKeyRing(userId, length, passphrase); + } + + /** + * Generate an RSA OpenPGP key consisting of an RSA primary key used for certification, + * a dedicated RSA subkey used for signing and a third RSA subkey used for encryption. + * + * @param userId userId or null + * @param length length of the RSA keys + * @param passphrase passphrase to encrypt the key with + * @return key + * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters + * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider + * @throws PGPException in case of an OpenPGP related error + */ + public PGPSecretKeyRing rsaKeyRing(@Nullable CharSequence userId, + @Nonnull RsaLength length, + @Nonnull Passphrase passphrase) + throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + KeyRingBuilder builder = PGPainless.buildKeyRing() + .setPrimaryKey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER)) + .addSubkey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.SIGN_DATA)) + .addSubkey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)); + + if (userId != null) { + builder.addUserId(userId.toString()); + } + + if (!passphrase.isEmpty()) { + builder.setPassphrase(passphrase); + } + + return builder.build(); + } + /** * Creates a simple, unencrypted RSA KeyPair of length {@code length} with user-id {@code userId}. * The KeyPair consists of a single RSA master key which is used for signing, encryption and certification. From 8869d9bd783ead32c70725f7dd1cd17d070f085f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 13:51:59 +0200 Subject: [PATCH 011/491] Simplify key template methods by replacing String and UserID args with CharSequence --- .../key/generation/KeyRingTemplates.java | 113 +++--------------- 1 file changed, 19 insertions(+), 94 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingTemplates.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingTemplates.java index 07f2235f..6966232b 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingTemplates.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingTemplates.java @@ -17,7 +17,6 @@ import org.pgpainless.key.generation.type.KeyType; import org.pgpainless.key.generation.type.eddsa.EdDSACurve; import org.pgpainless.key.generation.type.rsa.RsaLength; import org.pgpainless.key.generation.type.xdh.XDHSpec; -import org.pgpainless.key.util.UserId; import org.pgpainless.util.Passphrase; public final class KeyRingTemplates { @@ -111,46 +110,20 @@ public final class KeyRingTemplates { * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider * @throws PGPException in case of an OpenPGP related error */ - public PGPSecretKeyRing simpleRsaKeyRing(@Nullable UserId userId, @Nonnull RsaLength length) - throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { - return simpleRsaKeyRing(userId == null ? null : userId.toString(), length); - } - - /** - * Creates a simple, unencrypted RSA KeyPair of length {@code length} with user-id {@code userId}. - * The KeyPair consists of a single RSA master key which is used for signing, encryption and certification. - * - * @param userId user id. - * @param length length in bits. - * - * @return {@link PGPSecretKeyRing} containing the KeyPair. - * - * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters - * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider - * @throws PGPException in case of an OpenPGP related error - */ - public PGPSecretKeyRing simpleRsaKeyRing(@Nullable String userId, @Nonnull RsaLength length) + public PGPSecretKeyRing simpleRsaKeyRing(@Nullable CharSequence userId, @Nonnull RsaLength length) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { return simpleRsaKeyRing(userId, length, Passphrase.emptyPassphrase()); } - /** - * Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. - * The KeyPair consists of a single RSA master key which is used for signing, encryption and certification. - * - * @param userId user id. - * @param length length in bits. - * @param password Password of the key. Can be null for unencrypted keys. - * - * @return {@link PGPSecretKeyRing} containing the KeyPair. - * - * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters - * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider - * @throws PGPException in case of an OpenPGP related error - */ - public PGPSecretKeyRing simpleRsaKeyRing(@Nullable UserId userId, @Nonnull RsaLength length, @Nullable String password) - throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { - return simpleRsaKeyRing(userId == null ? null : userId.toString(), length, password); + public PGPSecretKeyRing simpleRsaKeyRing(@Nullable CharSequence userId, @Nonnull RsaLength length, @Nonnull Passphrase passphrase) + throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + KeyRingBuilder builder = PGPainless.buildKeyRing() + .setPrimaryKey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA, KeyFlag.ENCRYPT_COMMS)) + .setPassphrase(passphrase); + if (userId != null) { + builder.addUserId(userId.toString()); + } + return builder.build(); } /** @@ -167,7 +140,7 @@ public final class KeyRingTemplates { * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider * @throws PGPException in case of an OpenPGP related error */ - public PGPSecretKeyRing simpleRsaKeyRing(@Nullable String userId, @Nonnull RsaLength length, @Nullable String password) + public PGPSecretKeyRing simpleRsaKeyRing(@Nullable CharSequence userId, @Nonnull RsaLength length, @Nullable String password) throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { Passphrase passphrase = Passphrase.emptyPassphrase(); if (!isNullOrEmpty(password)) { @@ -176,17 +149,6 @@ public final class KeyRingTemplates { return simpleRsaKeyRing(userId, length, passphrase); } - public PGPSecretKeyRing simpleRsaKeyRing(@Nullable String userId, @Nonnull RsaLength length, @Nonnull Passphrase passphrase) - throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { - KeyRingBuilder builder = PGPainless.buildKeyRing() - .setPrimaryKey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA, KeyFlag.ENCRYPT_COMMS)) - .setPassphrase(passphrase); - if (userId != null) { - builder.addUserId(userId); - } - return builder.build(); - } - /** * Creates a key ring consisting of an ed25519 EdDSA primary key and a curve25519 XDH subkey. * The EdDSA primary key is used for signing messages and certifying the sub key. @@ -200,48 +162,11 @@ public final class KeyRingTemplates { * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider * @throws PGPException in case of an OpenPGP related error */ - public PGPSecretKeyRing simpleEcKeyRing(@Nullable UserId userId) - throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { - return simpleEcKeyRing(userId == null ? null : userId.toString()); - } - - /** - * Creates a key ring consisting of an ed25519 EdDSA primary key and a curve25519 XDH subkey. - * The EdDSA primary key is used for signing messages and certifying the sub key. - * The XDH subkey is used for encryption and decryption of messages. - * - * @param userId user-id - * - * @return {@link PGPSecretKeyRing} containing the key pairs. - * - * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters - * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider - * @throws PGPException in case of an OpenPGP related error - */ - public PGPSecretKeyRing simpleEcKeyRing(@Nullable String userId) + public PGPSecretKeyRing simpleEcKeyRing(@Nullable CharSequence userId) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { return simpleEcKeyRing(userId, Passphrase.emptyPassphrase()); } - /** - * Creates a key ring consisting of an ed25519 EdDSA primary key and a curve25519 XDH subkey. - * The EdDSA primary key is used for signing messages and certifying the sub key. - * The XDH subkey is used for encryption and decryption of messages. - * - * @param userId user-id - * @param password Password of the private key. Can be null for an unencrypted key. - * - * @return {@link PGPSecretKeyRing} containing the key pairs. - * - * @throws InvalidAlgorithmParameterException in case of invalid key generation parameters - * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider - * @throws PGPException in case of an OpenPGP related error - */ - public PGPSecretKeyRing simpleEcKeyRing(@Nullable UserId userId, String password) - throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { - return simpleEcKeyRing(userId == null ? null : userId.toString(), password); - } - /** * Creates a key ring consisting of an ed25519 EdDSA primary key and a X25519 XDH subkey. * The EdDSA primary key is used for signing messages and certifying the sub key. @@ -256,7 +181,7 @@ public final class KeyRingTemplates { * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider * @throws PGPException in case of an OpenPGP related error */ - public PGPSecretKeyRing simpleEcKeyRing(@Nullable String userId, String password) + public PGPSecretKeyRing simpleEcKeyRing(@Nullable CharSequence userId, String password) throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { Passphrase passphrase = Passphrase.emptyPassphrase(); if (!isNullOrEmpty(password)) { @@ -265,14 +190,14 @@ public final class KeyRingTemplates { return simpleEcKeyRing(userId, passphrase); } - public PGPSecretKeyRing simpleEcKeyRing(@Nullable String userId, @Nonnull Passphrase passphrase) + public PGPSecretKeyRing simpleEcKeyRing(@Nullable CharSequence userId, @Nonnull Passphrase passphrase) throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { KeyRingBuilder builder = PGPainless.buildKeyRing() .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) .addSubkey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)) .setPassphrase(passphrase); if (userId != null) { - builder.addUserId(userId); + builder.addUserId(userId.toString()); } return builder.build(); } @@ -288,7 +213,7 @@ public final class KeyRingTemplates { * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider * @throws PGPException in case of an OpenPGP related error */ - public PGPSecretKeyRing modernKeyRing(@Nullable String userId) throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + public PGPSecretKeyRing modernKeyRing(@Nullable CharSequence userId) throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { return modernKeyRing(userId, Passphrase.emptyPassphrase()); } @@ -304,13 +229,13 @@ public final class KeyRingTemplates { * @throws NoSuchAlgorithmException in case of missing algorithm implementation in the crypto provider * @throws PGPException in case of an OpenPGP related error */ - public PGPSecretKeyRing modernKeyRing(@Nullable String userId, @Nullable String password) + public PGPSecretKeyRing modernKeyRing(@Nullable CharSequence userId, @Nullable String password) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { Passphrase passphrase = (password != null ? Passphrase.fromPassword(password) : Passphrase.emptyPassphrase()); return modernKeyRing(userId, passphrase); } - public PGPSecretKeyRing modernKeyRing(@Nullable String userId, @Nonnull Passphrase passphrase) + public PGPSecretKeyRing modernKeyRing(@Nullable CharSequence userId, @Nonnull Passphrase passphrase) throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { KeyRingBuilder builder = PGPainless.buildKeyRing() .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER)) @@ -318,7 +243,7 @@ public final class KeyRingTemplates { .addSubkey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA)) .setPassphrase(passphrase); if (userId != null) { - builder.addUserId(userId); + builder.addUserId(userId.toString()); } return builder.build(); } From a8ab93a49a3faff3f0c9bd7d3074e53fa0f46cee Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 14:07:33 +0200 Subject: [PATCH 012/491] SOP: GenerateKey with --profile=rfc4880 now generates RSA key with subkeys --- .../src/main/java/org/pgpainless/sop/GenerateKeyImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/GenerateKeyImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/GenerateKeyImpl.java index ba788dac..f86d2893 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/GenerateKeyImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/GenerateKeyImpl.java @@ -126,7 +126,7 @@ public class GenerateKeyImpl implements GenerateKey { // RSA 4096 else if (profile.equals(RSA4096_PROFILE.getName())) { key = PGPainless.generateKeyRing() - .simpleRsaKeyRing(primaryUserId, RsaLength._4096, passphrase); + .rsaKeyRing(primaryUserId, RsaLength._4096, passphrase); } else { // Missing else-if branch for profile. Oops. From 15f6cc70b1979882a0ccb0b68db4c0c32e534a86 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 14:30:08 +0200 Subject: [PATCH 013/491] Add MessageMetadata.getRecipientKeyIds() Fixes #376 --- .../decryption_verification/MessageMetadata.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java index 648b99ab..cc97e81a 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageMetadata.java @@ -92,6 +92,21 @@ public class MessageMetadata { return false; } + /** + * Return a list containing all recipient keyIDs. + * + * @return list of recipients + */ + public List getRecipientKeyIds() { + List keyIds = new ArrayList<>(); + Iterator encLayers = getEncryptionLayers(); + while (encLayers.hasNext()) { + EncryptedData layer = encLayers.next(); + keyIds.addAll(layer.getRecipients()); + } + return keyIds; + } + public @Nonnull Iterator getEncryptionLayers() { return new LayerIterator(message) { @Override From 304350fe5c7b771fb6ab3f8a0dd17cbbd5efd0c2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 14:38:38 +0200 Subject: [PATCH 014/491] Add p-tags to EncryptionOptions javadoc --- .../pgpainless/encryption_signing/EncryptionOptions.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionOptions.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionOptions.java index b5baee83..2d0fb156 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionOptions.java +++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionOptions.java @@ -33,7 +33,7 @@ import org.pgpainless.util.Passphrase; /** * Options for the encryption process. * This class can be used to set encryption parameters, like encryption keys and passphrases, algorithms etc. - * + *

* A typical use might look like follows: *

  * {@code
@@ -42,11 +42,11 @@ import org.pgpainless.util.Passphrase;
  * opt.addPassphrase(Passphrase.fromPassword("AdditionalDecryptionPassphrase123"));
  * }
  * 
- * + *

* To use a custom symmetric encryption algorithm, use {@link #overrideEncryptionAlgorithm(SymmetricKeyAlgorithm)}. * This will cause PGPainless to use the provided algorithm for message encryption, instead of negotiating an algorithm * by inspecting the provided recipient keys. - * + *

* By default, PGPainless will encrypt to all suitable, encryption capable subkeys on each recipient's certificate. * This behavior can be changed per recipient, e.g. by calling *

@@ -83,7 +83,7 @@ public class EncryptionOptions {
      * Factory method to create an {@link EncryptionOptions} object which will encrypt for keys
      * which carry either the {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_COMMS} or
      * {@link org.pgpainless.algorithm.KeyFlag#ENCRYPT_STORAGE} flag.
-     *
+     * 

* Use this if you are not sure. * * @return encryption options From 64c6d7a90409c57837e47cb5ee1eb4f95c73a5a4 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 14:38:52 +0200 Subject: [PATCH 015/491] Annotate EncryptionOptions methods with @Nonnull --- .../encryption_signing/EncryptionOptions.java | 34 +++++++++++-------- .../EncryptionOptionsTest.java | 6 ++-- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionOptions.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionOptions.java index 2d0fb156..63320853 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionOptions.java +++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionOptions.java @@ -75,7 +75,7 @@ public class EncryptionOptions { this(EncryptionPurpose.ANY); } - public EncryptionOptions(EncryptionPurpose purpose) { + public EncryptionOptions(@Nonnull EncryptionPurpose purpose) { this.purpose = purpose; } @@ -118,7 +118,7 @@ public class EncryptionOptions { * @param keys keys * @return this */ - public EncryptionOptions addRecipients(Iterable keys) { + public EncryptionOptions addRecipients(@Nonnull Iterable keys) { if (!keys.iterator().hasNext()) { throw new IllegalArgumentException("Set of recipient keys cannot be empty."); } @@ -154,7 +154,7 @@ public class EncryptionOptions { * @param userId user id * @return this */ - public EncryptionOptions addRecipient(PGPPublicKeyRing key, String userId) { + public EncryptionOptions addRecipient(@Nonnull PGPPublicKeyRing key, @Nonnull CharSequence userId) { return addRecipient(key, userId, encryptionKeySelector); } @@ -167,11 +167,13 @@ public class EncryptionOptions { * @param encryptionKeySelectionStrategy strategy to select one or more encryption subkeys to encrypt to * @return this */ - public EncryptionOptions addRecipient(PGPPublicKeyRing key, String userId, EncryptionKeySelector encryptionKeySelectionStrategy) { + public EncryptionOptions addRecipient(@Nonnull PGPPublicKeyRing key, + @Nonnull CharSequence userId, + @Nonnull EncryptionKeySelector encryptionKeySelectionStrategy) { KeyRingInfo info = new KeyRingInfo(key, new Date()); List encryptionSubkeys = encryptionKeySelectionStrategy - .selectEncryptionSubkeys(info.getEncryptionSubkeys(userId, purpose)); + .selectEncryptionSubkeys(info.getEncryptionSubkeys(userId.toString(), purpose)); if (encryptionSubkeys.isEmpty()) { throw new KeyException.UnacceptableEncryptionKeyException(OpenPgpFingerprint.of(key)); } @@ -179,7 +181,7 @@ public class EncryptionOptions { for (PGPPublicKey encryptionSubkey : encryptionSubkeys) { SubkeyIdentifier keyId = new SubkeyIdentifier(key, encryptionSubkey.getKeyID()); keyRingInfo.put(keyId, info); - keyViews.put(keyId, new KeyAccessor.ViaUserId(info, keyId, userId)); + keyViews.put(keyId, new KeyAccessor.ViaUserId(info, keyId, userId.toString())); addRecipientKey(key, encryptionSubkey); } @@ -192,7 +194,7 @@ public class EncryptionOptions { * @param key key ring * @return this */ - public EncryptionOptions addRecipient(PGPPublicKeyRing key) { + public EncryptionOptions addRecipient(@Nonnull PGPPublicKeyRing key) { return addRecipient(key, encryptionKeySelector); } @@ -203,7 +205,8 @@ public class EncryptionOptions { * @param encryptionKeySelectionStrategy strategy used to select one or multiple encryption subkeys. * @return this */ - public EncryptionOptions addRecipient(PGPPublicKeyRing key, EncryptionKeySelector encryptionKeySelectionStrategy) { + public EncryptionOptions addRecipient(@Nonnull PGPPublicKeyRing key, + @Nonnull EncryptionKeySelector encryptionKeySelectionStrategy) { Date evaluationDate = new Date(); KeyRingInfo info; info = new KeyRingInfo(key, evaluationDate); @@ -234,7 +237,8 @@ public class EncryptionOptions { return this; } - private void addRecipientKey(PGPPublicKeyRing keyRing, PGPPublicKey key) { + private void addRecipientKey(@Nonnull PGPPublicKeyRing keyRing, + @Nonnull PGPPublicKey key) { encryptionKeys.add(new SubkeyIdentifier(keyRing, key.getKeyID())); PGPKeyEncryptionMethodGenerator encryptionMethod = ImplementationFactory .getInstance().getPublicKeyKeyEncryptionMethodGenerator(key); @@ -247,7 +251,7 @@ public class EncryptionOptions { * @param passphrase passphrase * @return this */ - public EncryptionOptions addPassphrase(Passphrase passphrase) { + public EncryptionOptions addPassphrase(@Nonnull Passphrase passphrase) { if (passphrase.isEmpty()) { throw new IllegalArgumentException("Passphrase must not be empty."); } @@ -267,7 +271,7 @@ public class EncryptionOptions { * @param encryptionMethod encryption method * @return this */ - public EncryptionOptions addEncryptionMethod(PGPKeyEncryptionMethodGenerator encryptionMethod) { + public EncryptionOptions addEncryptionMethod(@Nonnull PGPKeyEncryptionMethodGenerator encryptionMethod) { encryptionMethods.add(encryptionMethod); return this; } @@ -303,7 +307,7 @@ public class EncryptionOptions { * @param encryptionAlgorithm encryption algorithm override * @return this */ - public EncryptionOptions overrideEncryptionAlgorithm(SymmetricKeyAlgorithm encryptionAlgorithm) { + public EncryptionOptions overrideEncryptionAlgorithm(@Nonnull SymmetricKeyAlgorithm encryptionAlgorithm) { if (encryptionAlgorithm == SymmetricKeyAlgorithm.NULL) { throw new IllegalArgumentException("Plaintext encryption can only be used to denote unencrypted secret keys."); } @@ -322,7 +326,7 @@ public class EncryptionOptions { } public interface EncryptionKeySelector { - List selectEncryptionSubkeys(List encryptionCapableKeys); + List selectEncryptionSubkeys(@Nonnull List encryptionCapableKeys); } /** @@ -333,7 +337,7 @@ public class EncryptionOptions { public static EncryptionKeySelector encryptToFirstSubkey() { return new EncryptionKeySelector() { @Override - public List selectEncryptionSubkeys(List encryptionCapableKeys) { + public List selectEncryptionSubkeys(@Nonnull List encryptionCapableKeys) { return encryptionCapableKeys.isEmpty() ? Collections.emptyList() : Collections.singletonList(encryptionCapableKeys.get(0)); } }; @@ -347,7 +351,7 @@ public class EncryptionOptions { public static EncryptionKeySelector encryptToAllCapableSubkeys() { return new EncryptionKeySelector() { @Override - public List selectEncryptionSubkeys(List encryptionCapableKeys) { + public List selectEncryptionSubkeys(@Nonnull List encryptionCapableKeys) { return encryptionCapableKeys; } }; diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptionOptionsTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptionOptionsTest.java index 3436ba69..7d2fa453 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptionOptionsTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptionOptionsTest.java @@ -36,6 +36,8 @@ import org.pgpainless.key.generation.type.xdh.XDHSpec; import org.pgpainless.key.util.KeyRingUtils; import org.pgpainless.util.Passphrase; +import javax.annotation.Nonnull; + public class EncryptionOptionsTest { private static PGPSecretKeyRing secretKeys; @@ -149,7 +151,7 @@ public class EncryptionOptionsTest { assertThrows(KeyException.UnacceptableEncryptionKeyException.class, () -> options.addRecipient(publicKeys, new EncryptionOptions.EncryptionKeySelector() { @Override - public List selectEncryptionSubkeys(List encryptionCapableKeys) { + public List selectEncryptionSubkeys(@Nonnull List encryptionCapableKeys) { return Collections.emptyList(); } })); @@ -157,7 +159,7 @@ public class EncryptionOptionsTest { assertThrows(KeyException.UnacceptableEncryptionKeyException.class, () -> options.addRecipient(publicKeys, "test@pgpainless.org", new EncryptionOptions.EncryptionKeySelector() { @Override - public List selectEncryptionSubkeys(List encryptionCapableKeys) { + public List selectEncryptionSubkeys(@Nonnull List encryptionCapableKeys) { return Collections.emptyList(); } })); From 1d26751b45b00519894f91f80dbf5d3bc52b4d04 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 15:59:21 +0200 Subject: [PATCH 016/491] Remove unused KeyRingEditorTest --- .../key/modification/KeyRingEditorTest.java | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 pgpainless-core/src/test/java/org/pgpainless/key/modification/KeyRingEditorTest.java diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/modification/KeyRingEditorTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/modification/KeyRingEditorTest.java deleted file mode 100644 index 68774cfc..00000000 --- a/pgpainless-core/src/test/java/org/pgpainless/key/modification/KeyRingEditorTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.modification; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; -import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor; - -public class KeyRingEditorTest { - - @Test - public void testConstructorThrowsNpeForNull() { - assertThrows(NullPointerException.class, - () -> new SecretKeyRingEditor(null)); - } -} From 3b8a1b47d7e2cd24d63a562cd246254159b645b5 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 16:03:12 +0200 Subject: [PATCH 017/491] Add javadoc p-tags --- .../src/main/java/org/pgpainless/PGPainless.java | 6 +++--- .../encryption_signing/SigningOptions.java | 12 ++++++------ .../java/org/pgpainless/key/info/KeyAccessor.java | 2 +- .../java/org/pgpainless/key/info/KeyRingInfo.java | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java b/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java index 6da77c80..16928210 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java +++ b/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java @@ -152,7 +152,7 @@ public final class PGPainless { /** * Make changes to a secret key. * This method can be used to change key expiration dates and passphrases, or add/revoke subkeys. - * + *

* After making the desired changes in the builder, the modified key ring can be extracted using {@link SecretKeyRingEditorInterface#done()}. * * @param secretKeys secret key ring @@ -165,7 +165,7 @@ public final class PGPainless { /** * Make changes to a secret key at the given reference time. * This method can be used to change key expiration dates and passphrases, or add/revoke user-ids and subkeys. - * + *

* After making the desired changes in the builder, the modified key can be extracted using {@link SecretKeyRingEditorInterface#done()}. * * @param secretKeys secret key ring @@ -179,7 +179,7 @@ public final class PGPainless { /** * Quickly access information about a {@link org.bouncycastle.openpgp.PGPPublicKeyRing} / {@link PGPSecretKeyRing}. * This method can be used to determine expiration dates, key flags and other information about a key. - * + *

* To evaluate a key at a given date (e.g. to determine if the key was allowed to create a certain signature) * use {@link #inspectKeyRing(PGPKeyRing, Date)} instead. * diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java index 0af07fc9..77f95efb 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java +++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java @@ -158,7 +158,7 @@ public final class SigningOptions { * Add an inline-signature. * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use * of one-pass-signature packets. - * + *

* This method uses the passed in user-id to select user-specific hash algorithms. * * @param secretKeyDecryptor decryptor to unlock the signing secret key @@ -182,7 +182,7 @@ public final class SigningOptions { * Add an inline-signature. * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use * of one-pass-signature packets. - * + *

* This method uses the passed in user-id to select user-specific hash algorithms. * * @param secretKeyDecryptor decryptor to unlock the signing secret key @@ -295,7 +295,7 @@ public final class SigningOptions { * Detached signatures are not being added into the PGP message itself. * Instead, they can be distributed separately to the message. * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file). - * + *

* This method uses the passed in user-id to select user-specific hash algorithms. * * @param secretKeyDecryptor decryptor to unlock the secret signing key @@ -320,7 +320,7 @@ public final class SigningOptions { * Detached signatures are not being added into the PGP message itself. * Instead, they can be distributed separately to the message. * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file). - * + *

* This method uses the passed in user-id to select user-specific hash algorithms. * * @param secretKeyDecryptor decryptor to unlock the secret signing key @@ -406,7 +406,7 @@ public final class SigningOptions { /** * Negotiate, which hash algorithm to use. - * + *

* This method gives the highest priority to the algorithm override, which can be set via {@link #overrideHashAlgorithm(HashAlgorithm)}. * After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm. * Lastly, should no acceptable algorithm be found, the {@link Policy Policies} default signature hash algorithm is @@ -451,7 +451,7 @@ public final class SigningOptions { /** * Override hash algorithm negotiation by dictating which hash algorithm needs to be used. * If no override has been set, an accetable algorithm will be negotiated instead. - * + *

* Note: To override the hash algorithm for signing, call this method *before* calling * {@link #addInlineSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)} or * {@link #addDetachedSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)}. diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyAccessor.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyAccessor.java index 5fa71d46..48102931 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyAccessor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyAccessor.java @@ -28,7 +28,7 @@ public abstract class KeyAccessor { /** * Depending on the way we address the key (key-id or user-id), return the respective {@link PGPSignature} * which contains the algorithm preferences we are going to use. - * + *

* If we address a key via its user-id, we want to rely on the algorithm preferences in the user-id certification, * while we would instead rely on those in the direct-key signature if we'd address the key by key-id. * diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java index 75d9e16c..45de52f3 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java @@ -292,7 +292,7 @@ public class KeyRingInfo { /** * Return the current primary user-id of the key ring. - * + *

* Note: If no user-id is marked as primary key using a {@link PrimaryUserID} packet, * this method returns the first user-id on the key, otherwise null. * @@ -472,7 +472,7 @@ public class KeyRingInfo { /** * Return the latest direct-key self signature. - * + *

* Note: This signature might be expired (check with {@link SignatureUtils#isSignatureExpired(PGPSignature)}). * * @return latest direct key self-signature or null @@ -782,7 +782,7 @@ public class KeyRingInfo { * Return the latest date on which the key ring is still usable for the given key flag. * If only a subkey is carrying the required flag and the primary key expires earlier than the subkey, * the expiry date of the primary key is returned. - * + *

* This method might return null, if the primary key and a subkey with the required flag does not expire. * @param use key flag representing the use case, e.g. {@link KeyFlag#SIGN_DATA} or * {@link KeyFlag#ENCRYPT_COMMS}/{@link KeyFlag#ENCRYPT_STORAGE}. @@ -1133,7 +1133,7 @@ public class KeyRingInfo { /** * Returns true, if this {@link KeyRingInfo} is based on a {@link PGPSecretKeyRing}, which has a valid signing key * which is ready to be used (i.e. secret key is present and is not on a smart-card). - * + *

* If you just want to check, whether a key / certificate has signing capable subkeys, * use {@link #isSigningCapable()} instead. * From 953206b4ed39b692a4c7caa9d9b586382bf81729 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 16:03:50 +0200 Subject: [PATCH 018/491] Make more of the API null-safe by using @Nonnull/@Nullable --- .../main/java/org/pgpainless/PGPainless.java | 26 ++- .../OpenPgpMessageInputStream.java | 2 +- .../BcPGPHashContextContentSignerBuilder.java | 9 +- .../encryption_signing/SigningOptions.java | 114 ++++++----- .../org/pgpainless/key/info/KeyAccessor.java | 39 +++- .../org/pgpainless/key/info/KeyRingInfo.java | 183 ++++++++++++------ .../secretkeyring/SecretKeyRingEditor.java | 10 +- .../algorithm/RevocationStateTest.java | 5 - .../key/info/UserIdRevocationTest.java | 1 + .../SignatureSubpacketsUtilTest.java | 3 + ...artyCertificationSignatureBuilderTest.java | 6 +- .../subpackets/SignatureSubpacketsTest.java | 2 +- 12 files changed, 263 insertions(+), 137 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java b/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java index 16928210..3da54177 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java +++ b/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java @@ -40,6 +40,7 @@ public final class PGPainless { * Generate a fresh OpenPGP key ring from predefined templates. * @return templates */ + @Nonnull public static KeyRingTemplates generateKeyRing() { return new KeyRingTemplates(); } @@ -49,6 +50,7 @@ public final class PGPainless { * * @return builder */ + @Nonnull public static KeyRingBuilder buildKeyRing() { return new KeyRingBuilder(); } @@ -57,6 +59,7 @@ public final class PGPainless { * Read an existing OpenPGP key ring. * @return builder */ + @Nonnull public static KeyRingReader readKeyRing() { return new KeyRingReader(); } @@ -67,6 +70,7 @@ public final class PGPainless { * @param secretKey secret key * @return public key certificate */ + @Nonnull public static PGPPublicKeyRing extractCertificate(@Nonnull PGPSecretKeyRing secretKey) { return KeyRingUtils.publicKeyRingFrom(secretKey); } @@ -79,6 +83,7 @@ public final class PGPainless { * @return merged certificate * @throws PGPException in case of an error */ + @Nonnull public static PGPPublicKeyRing mergeCertificate( @Nonnull PGPPublicKeyRing originalCopy, @Nonnull PGPPublicKeyRing updatedCopy) @@ -94,6 +99,7 @@ public final class PGPainless { * * @throws IOException in case of an error in the {@link ArmoredOutputStream} */ + @Nonnull public static String asciiArmor(@Nonnull PGPKeyRing key) throws IOException { if (key instanceof PGPSecretKeyRing) { @@ -111,6 +117,7 @@ public final class PGPainless { * * @throws IOException in case of an error in the {@link ArmoredOutputStream} */ + @Nonnull public static String asciiArmor(@Nonnull PGPSignature signature) throws IOException { return ArmorUtils.toAsciiArmoredString(signature); @@ -136,6 +143,7 @@ public final class PGPainless { * * @return builder */ + @Nonnull public static EncryptionBuilder encryptAndOrSign() { return new EncryptionBuilder(); } @@ -145,6 +153,7 @@ public final class PGPainless { * * @return builder */ + @Nonnull public static DecryptionBuilder decryptAndOrVerify() { return new DecryptionBuilder(); } @@ -158,8 +167,9 @@ public final class PGPainless { * @param secretKeys secret key ring * @return builder */ - public static SecretKeyRingEditorInterface modifyKeyRing(PGPSecretKeyRing secretKeys) { - return modifyKeyRing(secretKeys, null); + @Nonnull + public static SecretKeyRingEditorInterface modifyKeyRing(@Nonnull PGPSecretKeyRing secretKeys) { + return modifyKeyRing(secretKeys, new Date()); } /** @@ -172,7 +182,9 @@ public final class PGPainless { * @param referenceTime reference time used as signature creation date * @return builder */ - public static SecretKeyRingEditorInterface modifyKeyRing(PGPSecretKeyRing secretKeys, Date referenceTime) { + @Nonnull + public static SecretKeyRingEditorInterface modifyKeyRing(@Nonnull PGPSecretKeyRing secretKeys, + @Nonnull Date referenceTime) { return new SecretKeyRingEditor(secretKeys, referenceTime); } @@ -186,7 +198,8 @@ public final class PGPainless { * @param keyRing key ring * @return access object */ - public static KeyRingInfo inspectKeyRing(PGPKeyRing keyRing) { + @Nonnull + public static KeyRingInfo inspectKeyRing(@Nonnull PGPKeyRing keyRing) { return new KeyRingInfo(keyRing); } @@ -198,7 +211,8 @@ public final class PGPainless { * @param referenceTime date of inspection * @return access object */ - public static KeyRingInfo inspectKeyRing(PGPKeyRing keyRing, Date referenceTime) { + @Nonnull + public static KeyRingInfo inspectKeyRing(@Nonnull PGPKeyRing keyRing, @Nonnull Date referenceTime) { return new KeyRingInfo(keyRing, referenceTime); } @@ -207,6 +221,7 @@ public final class PGPainless { * * @return policy */ + @Nonnull public static Policy getPolicy() { return Policy.getInstance(); } @@ -216,6 +231,7 @@ public final class PGPainless { * * @return builder */ + @Nonnull public static CertifyCertificate certify() { return new CertifyCertificate(); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java index d51a6cf7..04d823d1 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java @@ -359,7 +359,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { PGPCompressedData compressedData = packetInputStream.readCompressedData(); // Extract Metadata MessageMetadata.CompressedData compressionLayer = new MessageMetadata.CompressedData( - CompressionAlgorithm.fromId(compressedData.getAlgorithm()), + CompressionAlgorithm.requireFromId(compressedData.getAlgorithm()), metadata.depth + 1); LOGGER.debug("Compressed Data Packet (" + compressionLayer.algorithm + ") at depth " + metadata.depth + " encountered"); diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.java index df6f6ca3..5cdf9e36 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/BcPGPHashContextContentSignerBuilder.java @@ -44,10 +44,15 @@ class BcPGPHashContextContentSignerBuilder extends PGPHashContextContentSignerBu BcPGPHashContextContentSignerBuilder(MessageDigest messageDigest) { this.messageDigest = messageDigest; - this.hashAlgorithm = HashAlgorithm.fromName(messageDigest.getAlgorithm()); + this.hashAlgorithm = requireFromName(messageDigest.getAlgorithm()); + } + + private static HashAlgorithm requireFromName(String digestName) { + HashAlgorithm hashAlgorithm = HashAlgorithm.fromName(digestName); if (hashAlgorithm == null) { - throw new IllegalArgumentException("Cannot recognize OpenPGP Hash Algorithm: " + messageDigest.getAlgorithm()); + throw new IllegalArgumentException("Cannot recognize OpenPGP Hash Algorithm: " + digestName); } + return hashAlgorithm; } @Override diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java index 77f95efb..a899bd12 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java +++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.bouncycastle.openpgp.PGPException; @@ -46,7 +47,9 @@ public final class SigningOptions { private final boolean detached; private final HashAlgorithm hashAlgorithm; - private SigningMethod(PGPSignatureGenerator signatureGenerator, boolean detached, HashAlgorithm hashAlgorithm) { + private SigningMethod(@Nonnull PGPSignatureGenerator signatureGenerator, + boolean detached, + @Nonnull HashAlgorithm hashAlgorithm) { this.signatureGenerator = signatureGenerator; this.detached = detached; this.hashAlgorithm = hashAlgorithm; @@ -60,7 +63,8 @@ public final class SigningOptions { * @param hashAlgorithm hash algorithm used to generate the signature * @return inline signing method */ - public static SigningMethod inlineSignature(PGPSignatureGenerator signatureGenerator, HashAlgorithm hashAlgorithm) { + public static SigningMethod inlineSignature(@Nonnull PGPSignatureGenerator signatureGenerator, + @Nonnull HashAlgorithm hashAlgorithm) { return new SigningMethod(signatureGenerator, false, hashAlgorithm); } @@ -73,7 +77,8 @@ public final class SigningOptions { * @param hashAlgorithm hash algorithm used to generate the signature * @return detached signing method */ - public static SigningMethod detachedSignature(PGPSignatureGenerator signatureGenerator, HashAlgorithm hashAlgorithm) { + public static SigningMethod detachedSignature(@Nonnull PGPSignatureGenerator signatureGenerator, + @Nonnull HashAlgorithm hashAlgorithm) { return new SigningMethod(signatureGenerator, true, hashAlgorithm); } @@ -93,6 +98,7 @@ public final class SigningOptions { private final Map signingMethods = new HashMap<>(); private HashAlgorithm hashAlgorithmOverride; + @Nonnull public static SigningOptions get() { return new SigningOptions(); } @@ -107,8 +113,9 @@ public final class SigningOptions { * @throws KeyException if something is wrong with the key * @throws PGPException if the key cannot be unlocked or a signing method cannot be created */ - public SigningOptions addSignature(SecretKeyRingProtector signingKeyProtector, - PGPSecretKeyRing signingKey) + @Nonnull + public SigningOptions addSignature(@Nonnull SecretKeyRingProtector signingKeyProtector, + @Nonnull PGPSecretKeyRing signingKey) throws PGPException { return addInlineSignature(signingKeyProtector, signingKey, DocumentSignatureType.BINARY_DOCUMENT); } @@ -124,9 +131,10 @@ public final class SigningOptions { * @throws KeyException if something is wrong with any of the keys * @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created */ - public SigningOptions addInlineSignatures(SecretKeyRingProtector secrectKeyDecryptor, - Iterable signingKeys, - DocumentSignatureType signatureType) + @Nonnull + public SigningOptions addInlineSignatures(@Nonnull SecretKeyRingProtector secrectKeyDecryptor, + @Nonnull Iterable signingKeys, + @Nonnull DocumentSignatureType signatureType) throws KeyException, PGPException { for (PGPSecretKeyRing signingKey : signingKeys) { addInlineSignature(secrectKeyDecryptor, signingKey, signatureType); @@ -147,9 +155,10 @@ public final class SigningOptions { * @throws KeyException if something is wrong with the key * @throws PGPException if the key cannot be unlocked or the signing method cannot be created */ - public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor, - PGPSecretKeyRing secretKey, - DocumentSignatureType signatureType) + @Nonnull + public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, + @Nonnull PGPSecretKeyRing secretKey, + @Nonnull DocumentSignatureType signatureType) throws KeyException, PGPException { return addInlineSignature(secretKeyDecryptor, secretKey, null, signatureType); } @@ -170,10 +179,11 @@ public final class SigningOptions { * @throws KeyException if something is wrong with the key * @throws PGPException if the key cannot be unlocked or the signing method cannot be created */ - public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor, - PGPSecretKeyRing secretKey, - String userId, - DocumentSignatureType signatureType) + @Nonnull + public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, + @Nonnull PGPSecretKeyRing secretKey, + @Nullable CharSequence userId, + @Nonnull DocumentSignatureType signatureType) throws KeyException, PGPException { return addInlineSignature(secretKeyDecryptor, secretKey, userId, signatureType, null); } @@ -195,17 +205,18 @@ public final class SigningOptions { * @throws KeyException if the key is invalid * @throws PGPException if the key cannot be unlocked or the signing method cannot be created */ - public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor, - PGPSecretKeyRing secretKey, - String userId, - DocumentSignatureType signatureType, + @Nonnull + public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, + @Nonnull PGPSecretKeyRing secretKey, + @Nullable CharSequence userId, + @Nonnull DocumentSignatureType signatureType, @Nullable BaseSignatureSubpackets.Callback subpacketsCallback) throws KeyException, PGPException { KeyRingInfo keyRingInfo = new KeyRingInfo(secretKey, new Date()); if (userId != null && !keyRingInfo.isUserIdValid(userId)) { throw new KeyException.UnboundUserIdException( OpenPgpFingerprint.of(secretKey), - userId, + userId.toString(), keyRingInfo.getLatestUserIdCertification(userId), keyRingInfo.getUserIdRevocation(userId) ); @@ -242,9 +253,10 @@ public final class SigningOptions { * @throws KeyException if something is wrong with any of the keys * @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created */ - public SigningOptions addDetachedSignatures(SecretKeyRingProtector secretKeyDecryptor, - Iterable signingKeys, - DocumentSignatureType signatureType) + @Nonnull + public SigningOptions addDetachedSignatures(@Nonnull SecretKeyRingProtector secretKeyDecryptor, + @Nonnull Iterable signingKeys, + @Nonnull DocumentSignatureType signatureType) throws PGPException { for (PGPSecretKeyRing signingKey : signingKeys) { addDetachedSignature(secretKeyDecryptor, signingKey, signatureType); @@ -263,8 +275,9 @@ public final class SigningOptions { * @throws KeyException if something is wrong with the key * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created */ - public SigningOptions addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor, - PGPSecretKeyRing signingKey) + @Nonnull + public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, + @Nonnull PGPSecretKeyRing signingKey) throws PGPException { return addDetachedSignature(secretKeyDecryptor, signingKey, DocumentSignatureType.BINARY_DOCUMENT); } @@ -283,9 +296,10 @@ public final class SigningOptions { * @throws KeyException if something is wrong with the key * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created */ - public SigningOptions addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor, - PGPSecretKeyRing secretKey, - DocumentSignatureType signatureType) + @Nonnull + public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, + @Nonnull PGPSecretKeyRing secretKey, + @Nonnull DocumentSignatureType signatureType) throws PGPException { return addDetachedSignature(secretKeyDecryptor, secretKey, null, signatureType); } @@ -307,10 +321,11 @@ public final class SigningOptions { * @throws KeyException if something is wrong with the key * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created */ - public SigningOptions addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor, - PGPSecretKeyRing secretKey, - String userId, - DocumentSignatureType signatureType) + @Nonnull + public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, + @Nonnull PGPSecretKeyRing secretKey, + @Nullable CharSequence userId, + @Nonnull DocumentSignatureType signatureType) throws PGPException { return addDetachedSignature(secretKeyDecryptor, secretKey, userId, signatureType, null); } @@ -333,17 +348,18 @@ public final class SigningOptions { * @throws KeyException if something is wrong with the key * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created */ - public SigningOptions addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor, - PGPSecretKeyRing secretKey, - String userId, - DocumentSignatureType signatureType, + @Nonnull + public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, + @Nonnull PGPSecretKeyRing secretKey, + @Nullable CharSequence userId, + @Nonnull DocumentSignatureType signatureType, @Nullable BaseSignatureSubpackets.Callback subpacketCallback) throws PGPException { KeyRingInfo keyRingInfo = new KeyRingInfo(secretKey, new Date()); if (userId != null && !keyRingInfo.isUserIdValid(userId)) { throw new KeyException.UnboundUserIdException( OpenPgpFingerprint.of(secretKey), - userId, + userId.toString(), keyRingInfo.getLatestUserIdCertification(userId), keyRingInfo.getUserIdRevocation(userId) ); @@ -369,11 +385,11 @@ public final class SigningOptions { return this; } - private void addSigningMethod(PGPSecretKeyRing secretKey, - PGPPrivateKey signingSubkey, + private void addSigningMethod(@Nonnull PGPSecretKeyRing secretKey, + @Nonnull PGPPrivateKey signingSubkey, @Nullable BaseSignatureSubpackets.Callback subpacketCallback, - HashAlgorithm hashAlgorithm, - DocumentSignatureType signatureType, + @Nonnull HashAlgorithm hashAlgorithm, + @Nonnull DocumentSignatureType signatureType, boolean detached) throws PGPException { SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(secretKey, signingSubkey.getKeyID()); @@ -416,7 +432,9 @@ public final class SigningOptions { * @param policy policy * @return selected hash algorithm */ - private HashAlgorithm negotiateHashAlgorithm(Set preferences, Policy policy) { + @Nonnull + private HashAlgorithm negotiateHashAlgorithm(@Nonnull Set preferences, + @Nonnull Policy policy) { if (hashAlgorithmOverride != null) { return hashAlgorithmOverride; } @@ -425,9 +443,10 @@ public final class SigningOptions { .negotiateHashAlgorithm(preferences); } - private PGPSignatureGenerator createSignatureGenerator(PGPPrivateKey privateKey, - HashAlgorithm hashAlgorithm, - DocumentSignatureType signatureType) + @Nonnull + private PGPSignatureGenerator createSignatureGenerator(@Nonnull PGPPrivateKey privateKey, + @Nonnull HashAlgorithm hashAlgorithm, + @Nonnull DocumentSignatureType signatureType) throws PGPException { int publicKeyAlgorithm = privateKey.getPublicKeyPacket().getAlgorithm(); PGPContentSignerBuilder signerBuilder = ImplementationFactory.getInstance() @@ -444,6 +463,7 @@ public final class SigningOptions { * * @return signing methods */ + @Nonnull Map getSigningMethods() { return Collections.unmodifiableMap(signingMethods); } @@ -459,7 +479,8 @@ public final class SigningOptions { * @param hashAlgorithmOverride override hash algorithm * @return this */ - public SigningOptions overrideHashAlgorithm(HashAlgorithm hashAlgorithmOverride) { + @Nonnull + public SigningOptions overrideHashAlgorithm(@Nonnull HashAlgorithm hashAlgorithmOverride) { this.hashAlgorithmOverride = hashAlgorithmOverride; return this; } @@ -469,6 +490,7 @@ public final class SigningOptions { * * @return hash algorithm override */ + @Nullable public HashAlgorithm getHashAlgorithmOverride() { return hashAlgorithmOverride; } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyAccessor.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyAccessor.java index 48102931..8ab8a9c4 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyAccessor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyAccessor.java @@ -20,7 +20,7 @@ public abstract class KeyAccessor { protected final KeyRingInfo info; protected final SubkeyIdentifier key; - KeyAccessor(KeyRingInfo info, SubkeyIdentifier key) { + KeyAccessor(@Nonnull KeyRingInfo info, @Nonnull SubkeyIdentifier key) { this.info = info; this.key = key; } @@ -34,13 +34,15 @@ public abstract class KeyAccessor { * * @return signature */ - public abstract @Nonnull PGPSignature getSignatureWithPreferences(); + @Nonnull + public abstract PGPSignature getSignatureWithPreferences(); /** * Return preferred symmetric key encryption algorithms. * * @return preferred symmetric algorithms */ + @Nonnull public Set getPreferredSymmetricKeyAlgorithms() { return SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(getSignatureWithPreferences()); } @@ -50,6 +52,7 @@ public abstract class KeyAccessor { * * @return preferred hash algorithms */ + @Nonnull public Set getPreferredHashAlgorithms() { return SignatureSubpacketsUtil.parsePreferredHashAlgorithms(getSignatureWithPreferences()); } @@ -59,6 +62,7 @@ public abstract class KeyAccessor { * * @return preferred compression algorithms */ + @Nonnull public Set getPreferredCompressionAlgorithms() { return SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(getSignatureWithPreferences()); } @@ -78,13 +82,16 @@ public abstract class KeyAccessor { * @param key id of the subkey * @param userId user-id */ - public ViaUserId(KeyRingInfo info, SubkeyIdentifier key, String userId) { + public ViaUserId(@Nonnull KeyRingInfo info, + @Nonnull SubkeyIdentifier key, + @Nonnull String userId) { super(info, key); this.userId = userId; } @Override - public @Nonnull PGPSignature getSignatureWithPreferences() { + @Nonnull + public PGPSignature getSignatureWithPreferences() { PGPSignature signature = info.getLatestUserIdCertification(userId); if (signature != null) { return signature; @@ -104,19 +111,26 @@ public abstract class KeyAccessor { * @param info info about the key at a given date * @param key key-id */ - public ViaKeyId(KeyRingInfo info, SubkeyIdentifier key) { + public ViaKeyId(@Nonnull KeyRingInfo info, + @Nonnull SubkeyIdentifier key) { super(info, key); } @Override - public @Nonnull PGPSignature getSignatureWithPreferences() { + @Nonnull + public PGPSignature getSignatureWithPreferences() { String primaryUserId = info.getPrimaryUserId(); // If the key is located by Key ID, the algorithm of the primary User ID of the key provides the // preferred symmetric algorithm. - PGPSignature signature = info.getLatestUserIdCertification(primaryUserId); + PGPSignature signature = null; + if (primaryUserId != null) { + signature = info.getLatestUserIdCertification(primaryUserId); + } + if (signature == null) { signature = info.getLatestDirectKeySelfSignature(); } + if (signature == null) { throw new IllegalStateException("No valid signature found."); } @@ -126,22 +140,27 @@ public abstract class KeyAccessor { public static class SubKey extends KeyAccessor { - public SubKey(KeyRingInfo info, SubkeyIdentifier key) { + public SubKey(@Nonnull KeyRingInfo info, + @Nonnull SubkeyIdentifier key) { super(info, key); } @Override - public @Nonnull PGPSignature getSignatureWithPreferences() { + @Nonnull + public PGPSignature getSignatureWithPreferences() { PGPSignature signature; if (key.getPrimaryKeyId() == key.getSubkeyId()) { signature = info.getLatestDirectKeySelfSignature(); - if (signature == null) { + if (signature == null && info.getPrimaryUserId() != null) { signature = info.getLatestUserIdCertification(info.getPrimaryUserId()); } } else { signature = info.getCurrentSubkeyBindingSignature(key.getSubkeyId()); } + if (signature == null) { + throw new IllegalStateException("No valid signature found."); + } return signature; } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java index 45de52f3..1c20a06c 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java @@ -74,7 +74,9 @@ public class KeyRingInfo { * @param signature signature * @return info of key ring at signature creation time */ - public static KeyRingInfo evaluateForSignature(PGPKeyRing keyRing, PGPSignature signature) { + @Nonnull + public static KeyRingInfo evaluateForSignature(@Nonnull PGPKeyRing keyRing, + @Nonnull PGPSignature signature) { return new KeyRingInfo(keyRing, signature.getCreationTime()); } @@ -83,7 +85,7 @@ public class KeyRingInfo { * * @param keys key ring */ - public KeyRingInfo(PGPKeyRing keys) { + public KeyRingInfo(@Nonnull PGPKeyRing keys) { this(keys, new Date()); } @@ -93,7 +95,8 @@ public class KeyRingInfo { * @param keys key ring * @param referenceDate date of validation */ - public KeyRingInfo(PGPKeyRing keys, Date referenceDate) { + public KeyRingInfo(@Nonnull PGPKeyRing keys, + @Nonnull Date referenceDate) { this(keys, PGPainless.getPolicy(), referenceDate); } @@ -104,14 +107,17 @@ public class KeyRingInfo { * @param policy policy * @param referenceDate validation date */ - public KeyRingInfo(PGPKeyRing keys, Policy policy, Date referenceDate) { - this.referenceDate = referenceDate != null ? referenceDate : new Date(); + public KeyRingInfo(@Nonnull PGPKeyRing keys, + @Nonnull Policy policy, + @Nonnull Date referenceDate) { + this.referenceDate = referenceDate; this.keys = keys; this.signatures = new Signatures(keys, this.referenceDate, policy); this.primaryUserId = findPrimaryUserId(); this.revocationState = findRevocationState(); } + @Nonnull private RevocationState findRevocationState() { PGPSignature revocation = signatures.primaryKeyRevocation; if (revocation != null) { @@ -126,6 +132,7 @@ public class KeyRingInfo { * * @return public key */ + @Nonnull public PGPPublicKey getPublicKey() { return keys.getPublicKey(); } @@ -136,7 +143,8 @@ public class KeyRingInfo { * @param fingerprint fingerprint * @return public key or null */ - public @Nullable PGPPublicKey getPublicKey(OpenPgpFingerprint fingerprint) { + @Nullable + public PGPPublicKey getPublicKey(@Nonnull OpenPgpFingerprint fingerprint) { return getPublicKey(fingerprint.getKeyId()); } @@ -146,7 +154,8 @@ public class KeyRingInfo { * @param keyId key id * @return public key or null */ - public @Nullable PGPPublicKey getPublicKey(long keyId) { + @Nullable + public PGPPublicKey getPublicKey(long keyId) { return getPublicKey(keys, keyId); } @@ -157,7 +166,8 @@ public class KeyRingInfo { * @param keyId key id * @return public key or null */ - public static @Nullable PGPPublicKey getPublicKey(PGPKeyRing keyRing, long keyId) { + @Nullable + public static PGPPublicKey getPublicKey(@Nonnull PGPKeyRing keyRing, long keyId) { return keyRing.getPublicKey(keyId); } @@ -210,6 +220,7 @@ public class KeyRingInfo { * * @return list of public keys */ + @Nonnull public List getPublicKeys() { Iterator iterator = keys.getPublicKeys(); List list = iteratorToList(iterator); @@ -221,7 +232,8 @@ public class KeyRingInfo { * * @return primary secret key or null if the key ring is public */ - public @Nullable PGPSecretKey getSecretKey() { + @Nullable + public PGPSecretKey getSecretKey() { if (keys instanceof PGPSecretKeyRing) { PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) keys; return secretKeys.getSecretKey(); @@ -235,7 +247,8 @@ public class KeyRingInfo { * @param fingerprint fingerprint * @return secret key or null */ - public @Nullable PGPSecretKey getSecretKey(OpenPgpFingerprint fingerprint) { + @Nullable + public PGPSecretKey getSecretKey(@Nonnull OpenPgpFingerprint fingerprint) { return getSecretKey(fingerprint.getKeyId()); } @@ -245,7 +258,8 @@ public class KeyRingInfo { * @param keyId key id * @return secret key or null */ - public @Nullable PGPSecretKey getSecretKey(long keyId) { + @Nullable + public PGPSecretKey getSecretKey(long keyId) { if (keys instanceof PGPSecretKeyRing) { return ((PGPSecretKeyRing) keys).getSecretKey(keyId); } @@ -259,6 +273,7 @@ public class KeyRingInfo { * * @return list of secret keys */ + @Nonnull public List getSecretKeys() { if (keys instanceof PGPSecretKeyRing) { PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) keys; @@ -282,11 +297,13 @@ public class KeyRingInfo { * * @return fingerprint */ + @Nonnull public OpenPgpFingerprint getFingerprint() { return OpenPgpFingerprint.of(getPublicKey()); } - public @Nullable String getPrimaryUserId() { + @Nullable + public String getPrimaryUserId() { return primaryUserId; } @@ -298,6 +315,7 @@ public class KeyRingInfo { * * @return primary user-id or null */ + @Nullable private String findPrimaryUserId() { String primaryUserId = null; Date currentModificationDate = null; @@ -342,10 +360,10 @@ public class KeyRingInfo { * * @return list of user-ids */ + @Nonnull public List getUserIds() { Iterator iterator = getPublicKey().getUserIDs(); - List userIds = iteratorToList(iterator); - return userIds; + return iteratorToList(iterator); } /** @@ -353,6 +371,7 @@ public class KeyRingInfo { * * @return valid user-ids */ + @Nonnull public List getValidUserIds() { List valid = new ArrayList<>(); List userIds = getUserIds(); @@ -369,6 +388,7 @@ public class KeyRingInfo { * * @return bound user-ids */ + @Nonnull public List getValidAndExpiredUserIds() { List probablyExpired = new ArrayList<>(); List userIds = getUserIds(); @@ -407,21 +427,25 @@ public class KeyRingInfo { * @param userId user-id * @return true if user-id is valid */ - public boolean isUserIdValid(String userId) { + public boolean isUserIdValid(@Nonnull CharSequence userId) { + if (primaryUserId == null) { + // No primary userID? No userID at all! + return false; + } + if (!userId.equals(primaryUserId)) { if (!isUserIdBound(primaryUserId)) { - // primary user-id not valid + // primary user-id not valid? UserID not valid! return false; } } return isUserIdBound(userId); } - - private boolean isUserIdBound(String userId) { - - PGPSignature certification = signatures.userIdCertifications.get(userId); - PGPSignature revocation = signatures.userIdRevocations.get(userId); + private boolean isUserIdBound(@Nonnull CharSequence userId) { + String userIdString = userId.toString(); + PGPSignature certification = signatures.userIdCertifications.get(userIdString); + PGPSignature revocation = signatures.userIdRevocations.get(userIdString); if (certification == null) { return false; @@ -453,6 +477,7 @@ public class KeyRingInfo { * * @return email addresses */ + @Nonnull public List getEmailAddresses() { List userIds = getUserIds(); List emails = new ArrayList<>(); @@ -477,7 +502,8 @@ public class KeyRingInfo { * * @return latest direct key self-signature or null */ - public @Nullable PGPSignature getLatestDirectKeySelfSignature() { + @Nullable + public PGPSignature getLatestDirectKeySelfSignature() { return signatures.primaryKeySelfSignature; } @@ -486,7 +512,8 @@ public class KeyRingInfo { * * @return revocation or null */ - public @Nullable PGPSignature getRevocationSelfSignature() { + @Nullable + public PGPSignature getRevocationSelfSignature() { return signatures.primaryKeyRevocation; } @@ -496,8 +523,9 @@ public class KeyRingInfo { * @param userId user-id * @return certification signature or null */ - public @Nullable PGPSignature getLatestUserIdCertification(String userId) { - return signatures.userIdCertifications.get(userId); + @Nullable + public PGPSignature getLatestUserIdCertification(@Nonnull CharSequence userId) { + return signatures.userIdCertifications.get(userId.toString()); } /** @@ -506,8 +534,9 @@ public class KeyRingInfo { * @param userId user-id * @return revocation or null */ - public @Nullable PGPSignature getUserIdRevocation(String userId) { - return signatures.userIdRevocations.get(userId); + @Nullable + public PGPSignature getUserIdRevocation(@Nonnull CharSequence userId) { + return signatures.userIdRevocations.get(userId.toString()); } /** @@ -516,7 +545,8 @@ public class KeyRingInfo { * @param keyId subkey id * @return subkey binding signature or null */ - public @Nullable PGPSignature getCurrentSubkeyBindingSignature(long keyId) { + @Nullable + public PGPSignature getCurrentSubkeyBindingSignature(long keyId) { return signatures.subkeyBindings.get(keyId); } @@ -526,7 +556,8 @@ public class KeyRingInfo { * @param keyId subkey id * @return subkey binding revocation or null */ - public @Nullable PGPSignature getSubkeyRevocationSignature(long keyId) { + @Nullable + public PGPSignature getSubkeyRevocationSignature(long keyId) { return signatures.subkeyRevocations.get(keyId); } @@ -535,7 +566,8 @@ public class KeyRingInfo { * @param keyId key-id * @return list of key flags */ - public @Nonnull List getKeyFlagsOf(long keyId) { + @Nonnull + public List getKeyFlagsOf(long keyId) { // key is primary key if (getPublicKey().getKeyID() == keyId) { @@ -575,7 +607,8 @@ public class KeyRingInfo { * @param userId user-id * @return key flags */ - public @Nonnull List getKeyFlagsOf(String userId) { + @Nonnull + public List getKeyFlagsOf(String userId) { if (!isUserIdValid(userId)) { return Collections.emptyList(); } @@ -607,6 +640,7 @@ public class KeyRingInfo { * * @return creation date */ + @Nonnull public Date getCreationDate() { return getPublicKey().getCreationTime(); } @@ -617,7 +651,8 @@ public class KeyRingInfo { * * @return last modification date. */ - public @Nonnull Date getLastModified() { + @Nonnull + public Date getLastModified() { PGPSignature mostRecent = getMostRecentSignature(); if (mostRecent == null) { // No sigs found. Return public key creation date instead. @@ -631,7 +666,8 @@ public class KeyRingInfo { * * @return latest key creation time */ - public @Nonnull Date getLatestKeyCreationDate() { + @Nonnull + public Date getLatestKeyCreationDate() { Date latestCreation = null; for (PGPPublicKey key : getPublicKeys()) { if (!isKeyValidlyBound(key.getKeyID())) { @@ -648,7 +684,8 @@ public class KeyRingInfo { return latestCreation; } - private @Nullable PGPSignature getMostRecentSignature() { + @Nullable + private PGPSignature getMostRecentSignature() { Set allSignatures = new HashSet<>(); PGPSignature mostRecentSelfSignature = getLatestDirectKeySelfSignature(); PGPSignature revocationSelfSignature = getRevocationSelfSignature(); @@ -668,6 +705,7 @@ public class KeyRingInfo { return mostRecent; } + @Nonnull public RevocationState getRevocationState() { return revocationState; } @@ -677,7 +715,8 @@ public class KeyRingInfo { * * @return revocation date or null */ - public @Nullable Date getRevocationDate() { + @Nullable + public Date getRevocationDate() { return getRevocationState().isSoftRevocation() ? getRevocationState().getDate() : null; } @@ -686,7 +725,8 @@ public class KeyRingInfo { * * @return expiration date */ - public @Nullable Date getPrimaryKeyExpirationDate() { + @Nullable + public Date getPrimaryKeyExpirationDate() { PGPSignature directKeySig = getLatestDirectKeySelfSignature(); Date directKeyExpirationDate = null; if (directKeySig != null) { @@ -722,6 +762,7 @@ public class KeyRingInfo { return userIdExpirationDate; } + @Nullable public String getPossiblyExpiredPrimaryUserId() { String validPrimaryUserId = getPrimaryUserId(); if (validPrimaryUserId != null) { @@ -760,7 +801,8 @@ public class KeyRingInfo { * @param fingerprint subkey fingerprint * @return expiration date or null */ - public @Nullable Date getSubkeyExpirationDate(OpenPgpFingerprint fingerprint) { + @Nullable + public Date getSubkeyExpirationDate(OpenPgpFingerprint fingerprint) { if (getPublicKey().getKeyID() == fingerprint.getKeyId()) { return getPrimaryKeyExpirationDate(); } @@ -788,6 +830,7 @@ public class KeyRingInfo { * {@link KeyFlag#ENCRYPT_COMMS}/{@link KeyFlag#ENCRYPT_STORAGE}. * @return latest date on which the key ring can be used for the given use case, or null if it can be used indefinitely. */ + @Nullable public Date getExpirationDateForUse(KeyFlag use) { if (use == KeyFlag.SPLIT || use == KeyFlag.SHARED) { throw new IllegalArgumentException("SPLIT and SHARED are not uses, but properties."); @@ -814,20 +857,18 @@ public class KeyRingInfo { } if (nonExpiringSubkeys.isEmpty()) { - if (latestSubkeyExpirationDate != null) { - if (primaryExpiration == null) { - return latestSubkeyExpirationDate; - } - if (latestSubkeyExpirationDate.before(primaryExpiration)) { - return latestSubkeyExpirationDate; - } + if (primaryExpiration == null) { + return latestSubkeyExpirationDate; + } + if (latestSubkeyExpirationDate.before(primaryExpiration)) { + return latestSubkeyExpirationDate; } } return primaryExpiration; } - public boolean isHardRevoked(String userId) { - PGPSignature revocation = signatures.userIdRevocations.get(userId); + public boolean isHardRevoked(@Nonnull CharSequence userId) { + PGPSignature revocation = signatures.userIdRevocations.get(userId.toString()); if (revocation == null) { return false; } @@ -907,7 +948,8 @@ public class KeyRingInfo { * @param purpose purpose (encrypt data at rest / communications) * @return encryption subkeys */ - public @Nonnull List getEncryptionSubkeys(EncryptionPurpose purpose) { + @Nonnull + public List getEncryptionSubkeys(@Nonnull EncryptionPurpose purpose) { Date primaryExpiration = getPrimaryKeyExpirationDate(); if (primaryExpiration != null && primaryExpiration.before(referenceDate)) { LOGGER.debug("Certificate is expired: Primary key is expired on " + DateUtil.formatUTCDate(primaryExpiration)); @@ -966,7 +1008,8 @@ public class KeyRingInfo { * * @return decryption keys */ - public @Nonnull List getDecryptionSubkeys() { + @Nonnull + public List getDecryptionSubkeys() { Iterator subkeys = keys.getPublicKeys(); List decryptionKeys = new ArrayList<>(); @@ -999,7 +1042,8 @@ public class KeyRingInfo { * @param flag flag * @return keys with flag */ - public List getKeysWithKeyFlag(KeyFlag flag) { + @Nonnull + public List getKeysWithKeyFlag(@Nonnull KeyFlag flag) { List keysWithFlag = new ArrayList<>(); for (PGPPublicKey key : getPublicKeys()) { List keyFlags = getKeyFlagsOf(key.getKeyID()); @@ -1021,11 +1065,13 @@ public class KeyRingInfo { * @param purpose encryption purpose * @return encryption subkeys */ - public @Nonnull List getEncryptionSubkeys(String userId, EncryptionPurpose purpose) { + @Nonnull + public List getEncryptionSubkeys(@Nullable CharSequence userId, + @Nonnull EncryptionPurpose purpose) { if (userId != null && !isUserIdValid(userId)) { throw new KeyException.UnboundUserIdException( OpenPgpFingerprint.of(keys), - userId, + userId.toString(), getLatestUserIdCertification(userId), getUserIdRevocation(userId) ); @@ -1039,7 +1085,8 @@ public class KeyRingInfo { * * @return signing keys */ - public @Nonnull List getSigningSubkeys() { + @Nonnull + public List getSigningSubkeys() { Iterator subkeys = keys.getPublicKeys(); List signingKeys = new ArrayList<>(); while (subkeys.hasNext()) { @@ -1057,39 +1104,48 @@ public class KeyRingInfo { return signingKeys; } + @Nonnull public Set getPreferredHashAlgorithms() { return getPreferredHashAlgorithms(getPrimaryUserId()); } - public Set getPreferredHashAlgorithms(String userId) { + @Nonnull + public Set getPreferredHashAlgorithms(@Nullable CharSequence userId) { return getKeyAccessor(userId, getKeyId()).getPreferredHashAlgorithms(); } + @Nonnull public Set getPreferredHashAlgorithms(long keyId) { return new KeyAccessor.SubKey(this, new SubkeyIdentifier(keys, keyId)) .getPreferredHashAlgorithms(); } + @Nonnull public Set getPreferredSymmetricKeyAlgorithms() { return getPreferredSymmetricKeyAlgorithms(getPrimaryUserId()); } - public Set getPreferredSymmetricKeyAlgorithms(String userId) { + @Nonnull + public Set getPreferredSymmetricKeyAlgorithms(@Nullable CharSequence userId) { return getKeyAccessor(userId, getKeyId()).getPreferredSymmetricKeyAlgorithms(); } + @Nonnull public Set getPreferredSymmetricKeyAlgorithms(long keyId) { return new KeyAccessor.SubKey(this, new SubkeyIdentifier(keys, keyId)).getPreferredSymmetricKeyAlgorithms(); } + @Nonnull public Set getPreferredCompressionAlgorithms() { return getPreferredCompressionAlgorithms(getPrimaryUserId()); } - public Set getPreferredCompressionAlgorithms(String userId) { + @Nonnull + public Set getPreferredCompressionAlgorithms(@Nullable CharSequence userId) { return getKeyAccessor(userId, getKeyId()).getPreferredCompressionAlgorithms(); } + @Nonnull public Set getPreferredCompressionAlgorithms(long keyId) { return new KeyAccessor.SubKey(this, new SubkeyIdentifier(keys, keyId)).getPreferredCompressionAlgorithms(); } @@ -1173,15 +1229,20 @@ public class KeyRingInfo { return true; } - private KeyAccessor getKeyAccessor(@Nullable String userId, long keyID) { + private KeyAccessor getKeyAccessor(@Nullable CharSequence userId, long keyID) { if (getPublicKey(keyID) == null) { throw new NoSuchElementException("No subkey with key id " + Long.toHexString(keyID) + " found on this key."); } - if (userId != null && !getUserIds().contains(userId)) { + + if (userId != null && !getUserIds().contains(userId.toString())) { throw new NoSuchElementException("No user-id '" + userId + "' found on this key."); } - return userId == null ? new KeyAccessor.ViaKeyId(this, new SubkeyIdentifier(keys, keyID)) - : new KeyAccessor.ViaUserId(this, new SubkeyIdentifier(keys, keyID), userId); + + if (userId != null) { + return new KeyAccessor.ViaUserId(this, new SubkeyIdentifier(keys, keyID), userId.toString()); + } else { + return new KeyAccessor.ViaKeyId(this, new SubkeyIdentifier(keys, keyID)); + } } public static class Signatures { @@ -1193,7 +1254,9 @@ public class KeyRingInfo { private final Map subkeyRevocations; private final Map subkeyBindings; - public Signatures(PGPKeyRing keyRing, Date referenceDate, Policy policy) { + public Signatures(@Nonnull PGPKeyRing keyRing, + @Nonnull Date referenceDate, + @Nonnull Policy policy) { primaryKeyRevocation = SignaturePicker.pickCurrentRevocationSelfSignature(keyRing, policy, referenceDate); primaryKeySelfSignature = SignaturePicker.pickLatestDirectKeySignature(keyRing, policy, referenceDate); userIdRevocations = new HashMap<>(); diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java index 01c09903..6210ef5b 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java @@ -72,14 +72,12 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { private PGPSecretKeyRing secretKeyRing; private final Date referenceTime; - public SecretKeyRingEditor(PGPSecretKeyRing secretKeyRing) { - this(secretKeyRing, null); + public SecretKeyRingEditor(@Nonnull PGPSecretKeyRing secretKeyRing) { + this(secretKeyRing, new Date()); } - public SecretKeyRingEditor(PGPSecretKeyRing secretKeyRing, Date referenceTime) { - if (secretKeyRing == null) { - throw new NullPointerException("SecretKeyRing MUST NOT be null."); - } + public SecretKeyRingEditor(@Nonnull PGPSecretKeyRing secretKeyRing, + @Nonnull Date referenceTime) { this.secretKeyRing = secretKeyRing; this.referenceTime = referenceTime; } diff --git a/pgpainless-core/src/test/java/org/pgpainless/algorithm/RevocationStateTest.java b/pgpainless-core/src/test/java/org/pgpainless/algorithm/RevocationStateTest.java index e23d92d3..c24084b4 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/algorithm/RevocationStateTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/algorithm/RevocationStateTest.java @@ -60,11 +60,6 @@ public class RevocationStateTest { assertEquals("softRevoked (2022-08-03 18:26:35 UTC)", state.toString()); } - @Test - public void testSoftRevokedNullDateThrows() { - assertThrows(NullPointerException.class, () -> RevocationState.softRevoked(null)); - } - @Test public void orderTest() { assertEquals(RevocationState.notRevoked(), RevocationState.notRevoked()); diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/info/UserIdRevocationTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/info/UserIdRevocationTest.java index fd665901..0caf8b75 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/info/UserIdRevocationTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/info/UserIdRevocationTest.java @@ -95,6 +95,7 @@ public class UserIdRevocationTest { KeyRingInfo info = new KeyRingInfo(secretKeys); PGPSignature signature = info.getUserIdRevocation("secondary@key.id"); + assertNotNull(signature); RevocationReason reason = (RevocationReason) signature.getHashedSubPackets() .getSubpacket(SignatureSubpacketTags.REVOCATION_REASON); assertNotNull(reason); diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureSubpacketsUtilTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureSubpacketsUtilTest.java index d5cfb4f5..5b93415e 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureSubpacketsUtilTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/signature/SignatureSubpacketsUtilTest.java @@ -145,6 +145,7 @@ public class SignatureSubpacketsUtilTest { PGPSignature signature = generator.generateCertification(secretKeys.getPublicKey()); Set featureSet = SignatureSubpacketsUtil.parseFeatures(signature); + assertNotNull(featureSet); assertEquals(2, featureSet.size()); assertTrue(featureSet.contains(Feature.MODIFICATION_DETECTION)); assertTrue(featureSet.contains(Feature.AEAD_ENCRYPTED_DATA)); @@ -216,6 +217,7 @@ public class SignatureSubpacketsUtilTest { PGPSignature signature = generator.generateCertification(secretKeys.getPublicKey()); RevocationKey revocationKey = SignatureSubpacketsUtil.getRevocationKey(signature); + assertNotNull(revocationKey); assertArrayEquals(secretKeys.getPublicKey().getFingerprint(), revocationKey.getFingerprint()); assertEquals(secretKeys.getPublicKey().getAlgorithm(), revocationKey.getAlgorithm()); } @@ -277,6 +279,7 @@ public class SignatureSubpacketsUtilTest { PGPSignature signature = generator.generateCertification(secretKeys.getPublicKey()); TrustSignature trustSignature = SignatureSubpacketsUtil.getTrustSignature(signature); + assertNotNull(trustSignature); assertEquals(10, trustSignature.getDepth()); assertEquals(3, trustSignature.getTrustAmount()); } diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/builder/ThirdPartyCertificationSignatureBuilderTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/builder/ThirdPartyCertificationSignatureBuilderTest.java index ced6a466..bf1cb694 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/signature/builder/ThirdPartyCertificationSignatureBuilderTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/signature/builder/ThirdPartyCertificationSignatureBuilderTest.java @@ -4,6 +4,7 @@ package org.pgpainless.signature.builder; +import org.bouncycastle.bcpg.sig.Exportable; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; @@ -22,6 +23,7 @@ import java.security.NoSuchAlgorithmException; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -61,7 +63,9 @@ public class ThirdPartyCertificationSignatureBuilderTest { assertEquals(SignatureType.GENERIC_CERTIFICATION, SignatureType.valueOf(certification.getSignatureType())); assertEquals(secretKeys.getPublicKey().getKeyID(), certification.getKeyID()); assertArrayEquals(secretKeys.getPublicKey().getFingerprint(), certification.getHashedSubPackets().getIssuerFingerprint().getFingerprint()); - assertFalse(SignatureSubpacketsUtil.getExportableCertification(certification).isExportable()); + Exportable exportable = SignatureSubpacketsUtil.getExportableCertification(certification); + assertNotNull(exportable); + assertFalse(exportable.isExportable()); // test sig correctness certification.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), secretKeys.getPublicKey()); diff --git a/pgpainless-core/src/test/java/org/pgpainless/signature/subpackets/SignatureSubpacketsTest.java b/pgpainless-core/src/test/java/org/pgpainless/signature/subpackets/SignatureSubpacketsTest.java index 988bc776..3c2eda91 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/signature/subpackets/SignatureSubpacketsTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/signature/subpackets/SignatureSubpacketsTest.java @@ -376,7 +376,7 @@ public class SignatureSubpacketsTest { public void testSetSignatureTarget() { byte[] hash = new byte[20]; new Random().nextBytes(hash); - wrapper.setSignatureTarget(PublicKeyAlgorithm.fromId(key.getAlgorithm()), HashAlgorithm.SHA512, hash); + wrapper.setSignatureTarget(PublicKeyAlgorithm.requireFromId(key.getAlgorithm()), HashAlgorithm.SHA512, hash); PGPSignatureSubpacketVector vector = SignatureSubpacketsHelper.toVector(wrapper); SignatureTarget target = vector.getSignatureTarget(); From d05ffd0451f47633c7def76d6e6fc834c4cc492d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 16:11:06 +0200 Subject: [PATCH 019/491] Make DateUtil null-safe --- .../src/main/java/org/pgpainless/util/DateUtil.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/DateUtil.java b/pgpainless-core/src/main/java/org/pgpainless/util/DateUtil.java index ad1fce09..f56020d4 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/DateUtil.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/DateUtil.java @@ -4,6 +4,7 @@ package org.pgpainless.util; +import javax.annotation.Nonnull; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -16,6 +17,7 @@ public final class DateUtil { } // Java's SimpleDateFormat is not thread-safe, therefore we return a new instance on every invocation. + @Nonnull public static SimpleDateFormat getParser() { SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); parser.setTimeZone(TimeZone.getTimeZone("UTC")); @@ -28,11 +30,12 @@ public final class DateUtil { * @param dateString timestamp * @return date */ - public static Date parseUTCDate(String dateString) { + @Nonnull + public static Date parseUTCDate(@Nonnull String dateString) { try { return getParser().parse(dateString); } catch (ParseException e) { - return null; + throw new IllegalArgumentException("Malformed UTC timestamp: " + dateString, e); } } @@ -42,6 +45,7 @@ public final class DateUtil { * @param date date * @return timestamp */ + @Nonnull public static String formatUTCDate(Date date) { return getParser().format(date); } @@ -51,7 +55,8 @@ public final class DateUtil { * @param date date * @return floored date */ - public static Date toSecondsPrecision(Date date) { + @Nonnull + public static Date toSecondsPrecision(@Nonnull Date date) { long millis = date.getTime(); long seconds = millis / 1000; long floored = seconds * 1000; @@ -63,6 +68,7 @@ public final class DateUtil { * * @return now */ + @Nonnull public static Date now() { return toSecondsPrecision(new Date()); } From 21ae48d8c11cc8686fb9c7b3d50ec02116f4a58d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 17:13:29 +0200 Subject: [PATCH 020/491] Use assert statements to flag impossible NPEs --- .../decryption_verification/OpenPgpMessageInputStream.java | 2 ++ .../pgpainless/key/certification/CertifyCertificate.java | 1 + .../key/modification/secretkeyring/SecretKeyRingEditor.java | 6 +++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java index 04d823d1..07098269 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java @@ -544,6 +544,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { for (Tuple k : postponedDueToMissingPassphrase) { PGPSecretKey key = k.getA(); PGPSecretKeyRing keys = getDecryptionKey(key.getKeyID()); + assert (keys != null); keyIds.add(new SubkeyIdentifier(keys, key.getKeyID())); } if (!keyIds.isEmpty()) { @@ -556,6 +557,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { PGPSecretKey secretKey = missingPassphrases.getA(); long keyId = secretKey.getKeyID(); PGPSecretKeyRing decryptionKey = getDecryptionKey(keyId); + assert (decryptionKey != null); SubkeyIdentifier decryptionKeyId = new SubkeyIdentifier(decryptionKey, keyId); if (hasUnsupportedS2KSpecifier(secretKey, decryptionKeyId)) { continue; diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/certification/CertifyCertificate.java b/pgpainless-core/src/main/java/org/pgpainless/key/certification/CertifyCertificate.java index 05e64879..9340d93d 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/certification/CertifyCertificate.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/certification/CertifyCertificate.java @@ -275,6 +275,7 @@ public class CertifyCertificate { // We only support certification-capable primary keys OpenPgpFingerprint fingerprint = info.getFingerprint(); PGPPublicKey certificationPubKey = info.getPublicKey(fingerprint); + assert (certificationPubKey != null); if (!info.isKeyValidlyBound(certificationPubKey.getKeyID())) { throw new KeyException.RevokedKeyException(fingerprint); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java index 6210ef5b..73c39c25 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java @@ -182,7 +182,10 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { } // We need to unmark this user-id as primary - if (info.getLatestUserIdCertification(otherUserId).getHashedSubPackets().isPrimaryUserID()) { + PGPSignature userIdCertification = info.getLatestUserIdCertification(otherUserId); + assert (userIdCertification != null); + + if (userIdCertification.getHashedSubPackets().isPrimaryUserID()) { addUserId(otherUserId, new SelfSignatureSubpackets.Callback() { @Override public void modifyHashedSubpackets(SelfSignatureSubpackets hashedSubpackets) { @@ -601,6 +604,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { } if (prevUserIdSig.getHashedSubPackets().isPrimaryUserID()) { + assert (primaryUserId != null); PGPSignature userIdSig = reissueNonPrimaryUserId(secretKeyRingProtector, userId, prevUserIdSig); secretKeyRing = KeyRingUtils.injectCertification(secretKeyRing, primaryUserId, userIdSig); } From 09bacd40d1701d52dbb23d3360c7dca1532e2000 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 17:14:18 +0200 Subject: [PATCH 021/491] SecretKeyRingEditor: referenceTime cannot be null anymore --- .../secretkeyring/SecretKeyRingEditor.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java index 73c39c25..7c577600 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java @@ -124,9 +124,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { } SelfSignatureBuilder builder = new SelfSignatureBuilder(primaryKey, protector); - if (referenceTime != null) { - builder.getHashedSubpackets().setSignatureCreationTime(referenceTime); - } + builder.getHashedSubpackets().setSignatureCreationTime(referenceTime); builder.setSignatureType(SignatureType.POSITIVE_CERTIFICATION); // Retain signature subpackets of previous signatures @@ -351,16 +349,12 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { .getV4FingerprintCalculator(), false, subkeyProtector.getEncryptor(subkey.getKeyID())); SubkeyBindingSignatureBuilder skBindingBuilder = new SubkeyBindingSignatureBuilder(primaryKey, primaryKeyProtector, hashAlgorithm); - if (referenceTime != null) { - skBindingBuilder.getHashedSubpackets().setSignatureCreationTime(referenceTime); - } + skBindingBuilder.getHashedSubpackets().setSignatureCreationTime(referenceTime); skBindingBuilder.getHashedSubpackets().setKeyFlags(flags); if (subkeyAlgorithm.isSigningCapable()) { PrimaryKeyBindingSignatureBuilder pkBindingBuilder = new PrimaryKeyBindingSignatureBuilder(secretSubkey, subkeyProtector, hashAlgorithm); - if (referenceTime != null) { - pkBindingBuilder.getHashedSubpackets().setSignatureCreationTime(referenceTime); - } + pkBindingBuilder.getHashedSubpackets().setSignatureCreationTime(referenceTime); PGPSignature pkBinding = pkBindingBuilder.build(primaryKey.getPublicKey()); skBindingBuilder.getHashedSubpackets().addEmbeddedSignature(pkBinding); } From 7a194c517ac9ce03d3e9eaffc770c12a1655580a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 17:15:30 +0200 Subject: [PATCH 022/491] Remove KeyRingUtils.removeSecretKey() in favor of stripSecretKey() --- .../org/pgpainless/key/util/KeyRingUtils.java | 24 ------------------- .../CertificateWithMissingSecretKeyTest.java | 2 +- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java index 2ce058fd..23361c13 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java @@ -440,30 +440,6 @@ public final class KeyRingUtils { return newSecretKey; } - /** - * Remove the secret key of the subkey identified by the given secret key id from the key ring. - * The public part stays attached to the key ring, so that it can still be used for encryption / verification of signatures. - * - * This method is intended to be used to remove secret primary keys from live keys when those are kept in offline storage. - * - * @param secretKeys secret key ring - * @param secretKeyId id of the secret key to remove - * @return secret key ring with removed secret key - * - * @throws IOException in case of an error during serialization / deserialization of the key - * @throws PGPException in case of a broken key - * - * @deprecated use {@link #stripSecretKey(PGPSecretKeyRing, long)} instead. - * TODO: Remove in 1.2.X - */ - @Nonnull - @Deprecated - public static PGPSecretKeyRing removeSecretKey(@Nonnull PGPSecretKeyRing secretKeys, - long secretKeyId) - throws IOException, PGPException { - return stripSecretKey(secretKeys, secretKeyId); - } - /** * Remove the secret key of the subkey identified by the given secret key id from the key ring. * The public part stays attached to the key ring, so that it can still be used for encryption / verification of signatures. diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CertificateWithMissingSecretKeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CertificateWithMissingSecretKeyTest.java index a53999a6..e5f9e370 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CertificateWithMissingSecretKeyTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CertificateWithMissingSecretKeyTest.java @@ -83,7 +83,7 @@ public class CertificateWithMissingSecretKeyTest { encryptionSubkeyId = PGPainless.inspectKeyRing(secretKeys) .getEncryptionSubkeys(EncryptionPurpose.ANY).get(0).getKeyID(); // remove the encryption/decryption secret key - missingDecryptionSecKey = KeyRingUtils.removeSecretKey(secretKeys, encryptionSubkeyId); + missingDecryptionSecKey = KeyRingUtils.stripSecretKey(secretKeys, encryptionSubkeyId); } @Test From 5c76f9046f28f013bf3e3ce70c7ac8f092abec6f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 17:16:10 +0200 Subject: [PATCH 023/491] Turn empty catch block into test failure --- .../IgnoreUnknownSignatureVersionsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/IgnoreUnknownSignatureVersionsTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/IgnoreUnknownSignatureVersionsTest.java index 2b60ea02..aa1da741 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/IgnoreUnknownSignatureVersionsTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/IgnoreUnknownSignatureVersionsTest.java @@ -5,6 +5,7 @@ package org.pgpainless.decryption_verification; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -71,7 +72,7 @@ public class IgnoreUnknownSignatureVersionsTest { try { cert = PGPainless.readKeyRing().publicKeyRing(CERT); } catch (IOException e) { - + fail("Cannot parse certificate.", e); } } From 78cb2ec3d0f3b14ff16ff87d4dc5343c803b6c8e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 17:16:56 +0200 Subject: [PATCH 024/491] Do not catch and immediatelly rethrow exception --- .../decryption_verification/TeeBCPGInputStream.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java index bdbd9bcd..e1d33f70 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java @@ -7,7 +7,6 @@ package org.pgpainless.decryption_verification; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.NoSuchElementException; import org.bouncycastle.bcpg.BCPGInputStream; import org.bouncycastle.bcpg.MarkerPacket; @@ -49,13 +48,7 @@ public class TeeBCPGInputStream { return null; } - OpenPgpPacket packet; - try { - packet = OpenPgpPacket.requireFromTag(tag); - } catch (NoSuchElementException e) { - throw e; - } - return packet; + return OpenPgpPacket.requireFromTag(tag); } public Packet readPacket() throws IOException { From 3cea98536506dfa2a44d5898c27e096cd53cdfb9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 17:19:18 +0200 Subject: [PATCH 025/491] TeeBCPGInputStream: Annotate byte[] arg as @Nonnull --- .../decryption_verification/TeeBCPGInputStream.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java index e1d33f70..725c6f6e 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java @@ -19,6 +19,8 @@ import org.bouncycastle.openpgp.PGPOnePassSignature; import org.bouncycastle.openpgp.PGPSignature; import org.pgpainless.algorithm.OpenPgpPacket; +import javax.annotation.Nonnull; + /** * Since we need to update signatures with data from the underlying stream, this class is used to tee out the data. * Unfortunately we cannot simply override {@link BCPGInputStream#read()} to tee the data out though, since @@ -121,7 +123,7 @@ public class TeeBCPGInputStream { } @Override - public int read(byte[] b, int off, int len) throws IOException { + public int read(@Nonnull byte[] b, int off, int len) throws IOException { if (last != -1) { outputStream.write(last); } From fb581f11c792c17f516ec50683b50bcdad24ca53 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 May 2023 17:20:02 +0200 Subject: [PATCH 026/491] UserId.parse(): Prevent self-referencing javadoc --- .../src/main/java/org/pgpainless/key/util/UserId.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/UserId.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/UserId.java index 427f9060..b115afda 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/UserId.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/UserId.java @@ -88,7 +88,7 @@ public final class UserId implements CharSequence { *

  • John Doe <john@pgpainless.org>
  • *
  • John Doe (work email) <john@pgpainless.org>
  • * - * In these cases, {@link #parse(String)} will detect email addresses, names and comments and expose those + * In these cases, this method will detect email addresses, names and comments and expose those * via the respective getters. * This method does not support parsing mail addresses of the following formats: *