From a25ea542d69651fa0dfbccf0d3cb1151c83f772a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 9 Feb 2023 21:58:37 +0100 Subject: [PATCH 001/528] PGPainless 1.4.5-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 2593724d..55e9b151 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '1.4.4' - isSnapshot = false + shortVersion = '1.4.5' + isSnapshot = true pgpainlessMinAndroidSdk = 10 javaSourceCompatibility = 1.8 bouncyCastleVersion = '1.72' From ed2c53f5d6362afec315e43a84cb35c5745ccd55 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 25 Feb 2023 11:26:58 +0100 Subject: [PATCH 002/528] Make getLastModified() @Nonnull --- .../src/main/java/org/pgpainless/key/info/KeyRingInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fa4168dd..46dd500b 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 @@ -611,7 +611,7 @@ public class KeyRingInfo { * * @return last modification date. */ - public @Nullable Date getLastModified() { + public @Nonnull Date getLastModified() { PGPSignature mostRecent = getMostRecentSignature(); if (mostRecent == null) { // No sigs found. Return public key creation date instead. From acb5d3fd9e98041948e6742804b3c1c439f124e1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 7 Apr 2023 11:26:38 +0200 Subject: [PATCH 003/528] getEncryptionSubkeys(): Compare expirations against reference date --- .../src/main/java/org/pgpainless/key/info/KeyRingInfo.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 46dd500b..1ebd023e 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 @@ -903,7 +903,7 @@ public class KeyRingInfo { */ public @Nonnull List getEncryptionSubkeys(EncryptionPurpose purpose) { Date primaryExpiration = getPrimaryKeyExpirationDate(); - if (primaryExpiration != null && primaryExpiration.before(new Date())) { + if (primaryExpiration != null && primaryExpiration.before(referenceDate)) { return Collections.emptyList(); } @@ -917,7 +917,7 @@ public class KeyRingInfo { } Date subkeyExpiration = getSubkeyExpirationDate(OpenPgpFingerprint.of(subKey)); - if (subkeyExpiration != null && subkeyExpiration.before(new Date())) { + if (subkeyExpiration != null && subkeyExpiration.before(referenceDate)) { continue; } From e744668f5ae63dec785b02e48d13f7bb5f8aedd6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 7 Apr 2023 11:47:40 +0200 Subject: [PATCH 004/528] Deprecate OpenPgpFingerprint.parse() methods --- .../src/main/java/org/pgpainless/key/OpenPgpFingerprint.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java index 86ac8265..0525f4c9 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java @@ -55,7 +55,9 @@ public abstract class OpenPgpFingerprint implements CharSequence, Comparable Date: Fri, 7 Apr 2023 12:28:27 +0200 Subject: [PATCH 005/528] Introduce OpenPgpv6Fingerprint --- .../pgpainless/key/OpenPgpFingerprint.java | 11 +- .../pgpainless/key/OpenPgpV5Fingerprint.java | 67 +------- .../pgpainless/key/OpenPgpV6Fingerprint.java | 58 +++++++ .../pgpainless/key/_64DigitFingerprint.java | 119 ++++++++++++++ .../key/OpenPgpV5FingerprintTest.java | 69 +------- .../key/OpenPgpV6FingerprintTest.java | 154 ++++++++++++++++++ .../key/_64DigitFingerprintTest.java | 85 ++++++++++ 7 files changed, 430 insertions(+), 133 deletions(-) create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpV6Fingerprint.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/key/OpenPgpV6FingerprintTest.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/key/_64DigitFingerprintTest.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java index 0525f4c9..1ac900a0 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/OpenPgpFingerprint.java @@ -36,6 +36,9 @@ public abstract class OpenPgpFingerprint implements CharSequence, Comparable +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key; + +import javax.annotation.Nonnull; + +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; + +/** + * This class represents a hex encoded, upper case OpenPGP v6 fingerprint. + */ +public class OpenPgpV6Fingerprint extends _64DigitFingerprint { + + /** + * Create an {@link OpenPgpV6Fingerprint}. + * + * @param fingerprint uppercase hexadecimal fingerprint of length 64 + */ + public OpenPgpV6Fingerprint(@Nonnull String fingerprint) { + super(fingerprint); + } + + public OpenPgpV6Fingerprint(@Nonnull byte[] bytes) { + super(bytes); + } + + public OpenPgpV6Fingerprint(@Nonnull PGPPublicKey key) { + super(key); + } + + public OpenPgpV6Fingerprint(@Nonnull PGPSecretKey key) { + this(key.getPublicKey()); + } + + public OpenPgpV6Fingerprint(@Nonnull PGPPublicKeyRing ring) { + super(ring); + } + + public OpenPgpV6Fingerprint(@Nonnull PGPSecretKeyRing ring) { + super(ring); + } + + public OpenPgpV6Fingerprint(@Nonnull PGPKeyRing ring) { + super(ring); + } + + @Override + public int getVersion() { + return 6; + } + +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.java b/pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.java new file mode 100644 index 00000000..11f18058 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/_64DigitFingerprint.java @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import javax.annotation.Nonnull; + +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.util.encoders.Hex; + +/** + * This class represents a hex encoded, upper case OpenPGP v5 or v6 fingerprint. + * Since both fingerprints use the same format, this class is used when parsing the fingerprint without knowing the + * key version. + */ +public class _64DigitFingerprint extends OpenPgpFingerprint { + + /** + * Create an {@link _64DigitFingerprint}. + * + * @param fingerprint uppercase hexadecimal fingerprint of length 64 + */ + protected _64DigitFingerprint(@Nonnull String fingerprint) { + super(fingerprint); + } + + protected _64DigitFingerprint(@Nonnull byte[] bytes) { + super(Hex.encode(bytes)); + } + + protected _64DigitFingerprint(@Nonnull PGPPublicKey key) { + super(key); + } + + protected _64DigitFingerprint(@Nonnull PGPSecretKey key) { + this(key.getPublicKey()); + } + + protected _64DigitFingerprint(@Nonnull PGPPublicKeyRing ring) { + super(ring); + } + + protected _64DigitFingerprint(@Nonnull PGPSecretKeyRing ring) { + super(ring); + } + + protected _64DigitFingerprint(@Nonnull PGPKeyRing ring) { + super(ring); + } + + @Override + public int getVersion() { + return -1; // might be v5 or v6 + } + + @Override + protected boolean isValid(@Nonnull String fp) { + return fp.matches("^[0-9A-F]{64}$"); + } + + @Override + public long getKeyId() { + byte[] bytes = Hex.decode(toString().getBytes(utf8)); + ByteBuffer buf = ByteBuffer.wrap(bytes); + + // The key id is the left-most 8 bytes (conveniently a long). + // We have to cast here in order to be compatible with java 8 + // https://github.com/eclipse/jetty.project/issues/3244 + ((Buffer) buf).position(0); + + return buf.getLong(); + } + + @Override + public String prettyPrint() { + String fp = toString(); + StringBuilder pretty = new StringBuilder(); + + for (int i = 0; i < 4; i++) { + pretty.append(fp, i * 8, (i + 1) * 8).append(' '); + } + pretty.append(' '); + for (int i = 4; i < 7; i++) { + pretty.append(fp, i * 8, (i + 1) * 8).append(' '); + } + pretty.append(fp, 56, 64); + return pretty.toString(); + } + + @Override + public boolean equals(Object other) { + if (other == null) { + return false; + } + + if (!(other instanceof CharSequence)) { + return false; + } + + return this.toString().equals(other.toString()); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public int compareTo(OpenPgpFingerprint openPgpFingerprint) { + return toString().compareTo(openPgpFingerprint.toString()); + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/OpenPgpV5FingerprintTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/OpenPgpV5FingerprintTest.java index a250bef4..57c98928 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/OpenPgpV5FingerprintTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/OpenPgpV5FingerprintTest.java @@ -5,8 +5,6 @@ package org.pgpainless.key; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -29,77 +27,12 @@ public class OpenPgpV5FingerprintTest { OpenPgpV5Fingerprint fingerprint = new OpenPgpV5Fingerprint(fp); assertEquals(fp, fingerprint.toString()); assertEquals(pretty, fingerprint.prettyPrint()); + assertEquals(5, fingerprint.getVersion()); long id = fingerprint.getKeyId(); assertEquals("76543210abcdefab", Long.toHexString(id)); } - @Test - public void testParse() { - String prettyPrint = "76543210 ABCDEFAB 01AB23CD 1C0FFEE1 1EEFF0C1 DC32BA10 BAFEDCBA 01234567"; - OpenPgpFingerprint parsed = OpenPgpFingerprint.parse(prettyPrint); - - assertTrue(parsed instanceof OpenPgpV5Fingerprint); - OpenPgpV5Fingerprint v5fp = (OpenPgpV5Fingerprint) parsed; - assertEquals(prettyPrint, v5fp.prettyPrint()); - assertEquals(5, v5fp.getVersion()); - } - - @Test - public void testParseFromBinary() { - String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA01234567"; - byte[] binary = Hex.decode(hex); - - OpenPgpFingerprint fingerprint = OpenPgpFingerprint.parseFromBinary(binary); - assertTrue(fingerprint instanceof OpenPgpV5Fingerprint); - assertEquals(hex, fingerprint.toString()); - - OpenPgpV5Fingerprint constructed = new OpenPgpV5Fingerprint(binary); - assertEquals(fingerprint, constructed); - } - - @Test - public void testParseFromBinary_leadingZeros() { - String hex = "000000000000000001AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA01234567"; - byte[] binary = Hex.decode(hex); - - OpenPgpFingerprint fingerprint = OpenPgpFingerprint.parseFromBinary(binary); - assertTrue(fingerprint instanceof OpenPgpV5Fingerprint); - assertEquals(hex, fingerprint.toString()); - } - - @Test - public void testParseFromBinary_trailingZeros() { - String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA100000000000000000"; - byte[] binary = Hex.decode(hex); - - OpenPgpFingerprint fingerprint = OpenPgpFingerprint.parseFromBinary(binary); - assertTrue(fingerprint instanceof OpenPgpV5Fingerprint); - assertEquals(hex, fingerprint.toString()); - } - - @Test - public void testParseFromBinary_wrongLength() { - String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA012345"; // missing 2 digits - byte[] binary = Hex.decode(hex); - - assertThrows(IllegalArgumentException.class, () -> OpenPgpFingerprint.parseFromBinary(binary)); - } - - @Test - public void equalsTest() { - String prettyPrint = "76543210 ABCDEFAB 01AB23CD 1C0FFEE1 1EEFF0C1 DC32BA10 BAFEDCBA 01234567"; - OpenPgpFingerprint parsed = OpenPgpFingerprint.parse(prettyPrint); - - assertNotEquals(parsed, null); - assertNotEquals(parsed, new Object()); - assertEquals(parsed, parsed.toString()); - - OpenPgpFingerprint parsed2 = new OpenPgpV5Fingerprint(prettyPrint); - assertEquals(parsed.hashCode(), parsed2.hashCode()); - assertEquals(0, parsed.compareTo(parsed2)); - } - @Test public void constructFromMockedPublicKey() { String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA01234567"; diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/OpenPgpV6FingerprintTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/OpenPgpV6FingerprintTest.java new file mode 100644 index 00000000..7a7b0e45 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/OpenPgpV6FingerprintTest.java @@ -0,0 +1,154 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key; + +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.util.encoders.Hex; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class OpenPgpV6FingerprintTest { + + @Test + public void testFingerprintFormatting() { + String pretty = "76543210 ABCDEFAB 01AB23CD 1C0FFEE1 1EEFF0C1 DC32BA10 BAFEDCBA 01234567"; + String fp = pretty.replace(" ", ""); + + OpenPgpV6Fingerprint fingerprint = new OpenPgpV6Fingerprint(fp); + assertEquals(fp, fingerprint.toString()); + assertEquals(pretty, fingerprint.prettyPrint()); + assertEquals(6, fingerprint.getVersion()); + + long id = fingerprint.getKeyId(); + assertEquals("76543210abcdefab", Long.toHexString(id)); + } + + @Test + public void testParseFromBinary_leadingZeros() { + String hex = "000000000000000001AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA01234567"; + byte[] binary = Hex.decode(hex); + + OpenPgpFingerprint fingerprint = new OpenPgpV6Fingerprint(binary); + assertEquals(hex, fingerprint.toString()); + } + + @Test + public void testParseFromBinary_trailingZeros() { + String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA100000000000000000"; + byte[] binary = Hex.decode(hex); + + OpenPgpFingerprint fingerprint = new OpenPgpV6Fingerprint(binary); + assertEquals(hex, fingerprint.toString()); + } + + @Test + public void testParseFromBinary_wrongLength() { + String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA012345"; // missing 2 digits + byte[] binary = Hex.decode(hex); + + assertThrows(IllegalArgumentException.class, () -> new OpenPgpV6Fingerprint(binary)); + } + + @Test + public void equalsTest() { + String prettyPrint = "76543210 ABCDEFAB 01AB23CD 1C0FFEE1 1EEFF0C1 DC32BA10 BAFEDCBA 01234567"; + OpenPgpFingerprint parsed = new OpenPgpV6Fingerprint(prettyPrint); + + assertNotEquals(parsed, null); + assertNotEquals(parsed, new Object()); + assertEquals(parsed, parsed.toString()); + + OpenPgpFingerprint parsed2 = new OpenPgpV6Fingerprint(prettyPrint); + assertEquals(parsed.hashCode(), parsed2.hashCode()); + assertEquals(0, parsed.compareTo(parsed2)); + } + + @Test + public void constructFromMockedPublicKey() { + String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA01234567"; + PGPPublicKey publicKey = getMockedPublicKey(hex); + + OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(publicKey); + assertTrue(fingerprint instanceof OpenPgpV6Fingerprint); + assertEquals(6, fingerprint.getVersion()); + assertEquals(hex, fingerprint.toString()); + } + + @Test + public void constructFromMockedSecretKey() { + String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA01234567"; + PGPPublicKey publicKey = getMockedPublicKey(hex); + PGPSecretKey secretKey = mock(PGPSecretKey.class); + when(secretKey.getPublicKey()).thenReturn(publicKey); + + OpenPgpFingerprint fingerprint = new OpenPgpV6Fingerprint(secretKey); + assertEquals(6, fingerprint.getVersion()); + assertEquals(hex, fingerprint.toString()); + } + + @Test + public void constructFromMockedPublicKeyRing() { + String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA01234567"; + PGPPublicKey publicKey = getMockedPublicKey(hex); + PGPPublicKeyRing publicKeys = mock(PGPPublicKeyRing.class); + when(publicKeys.getPublicKey()).thenReturn(publicKey); + + OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(publicKeys); + assertEquals(6, fingerprint.getVersion()); + assertEquals(hex, fingerprint.toString()); + + fingerprint = new OpenPgpV6Fingerprint(publicKeys); + assertEquals(hex, fingerprint.toString()); + } + + @Test + public void constructFromMockedSecretKeyRing() { + String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA01234567"; + PGPPublicKey publicKey = getMockedPublicKey(hex); + PGPSecretKeyRing secretKeys = mock(PGPSecretKeyRing.class); + when(secretKeys.getPublicKey()).thenReturn(publicKey); + + OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(secretKeys); + assertEquals(6, fingerprint.getVersion()); + assertEquals(hex, fingerprint.toString()); + + fingerprint = new OpenPgpV6Fingerprint(secretKeys); + assertEquals(hex, fingerprint.toString()); + } + + @Test + public void constructFromMockedKeyRing() { + String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA01234567"; + PGPPublicKey publicKey = getMockedPublicKey(hex); + PGPKeyRing keys = mock(PGPKeyRing.class); + when(keys.getPublicKey()).thenReturn(publicKey); + + OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(keys); + assertEquals(6, fingerprint.getVersion()); + assertEquals(hex, fingerprint.toString()); + + fingerprint = new OpenPgpV6Fingerprint(keys); + assertEquals(hex, fingerprint.toString()); + } + + private PGPPublicKey getMockedPublicKey(String hex) { + byte[] binary = Hex.decode(hex); + + PGPPublicKey mocked = mock(PGPPublicKey.class); + when(mocked.getVersion()).thenReturn(6); + when(mocked.getFingerprint()).thenReturn(binary); + return mocked; + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/_64DigitFingerprintTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/_64DigitFingerprintTest.java new file mode 100644 index 00000000..21ff31c3 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/_64DigitFingerprintTest.java @@ -0,0 +1,85 @@ +package org.pgpainless.key; + +import org.bouncycastle.util.encoders.Hex; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class _64DigitFingerprintTest { + + @Test + public void testParse() { + String prettyPrint = "76543210 ABCDEFAB 01AB23CD 1C0FFEE1 1EEFF0C1 DC32BA10 BAFEDCBA 01234567"; + OpenPgpFingerprint parsed = OpenPgpFingerprint.parse(prettyPrint); + + assertTrue(parsed instanceof _64DigitFingerprint); + assertEquals(prettyPrint, parsed.prettyPrint()); + assertEquals(-1, parsed.getVersion()); + } + + @Test + public void testParseFromBinary() { + String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA01234567"; + byte[] binary = Hex.decode(hex); + + OpenPgpFingerprint fingerprint = OpenPgpFingerprint.parseFromBinary(binary); + assertTrue(fingerprint instanceof _64DigitFingerprint); + assertEquals(hex, fingerprint.toString()); + + OpenPgpV5Fingerprint v5 = new OpenPgpV5Fingerprint(binary); + assertEquals(fingerprint, v5); + + OpenPgpV6Fingerprint v6 = new OpenPgpV6Fingerprint(binary); + assertEquals(fingerprint, v6); + } + + @Test + public void testParseFromBinary_leadingZeros() { + String hex = "000000000000000001AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA01234567"; + byte[] binary = Hex.decode(hex); + + OpenPgpFingerprint fingerprint = OpenPgpFingerprint.parseFromBinary(binary); + assertTrue(fingerprint instanceof _64DigitFingerprint); + assertEquals(hex, fingerprint.toString()); + } + + @Test + public void testParseFromBinary_trailingZeros() { + String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA100000000000000000"; + byte[] binary = Hex.decode(hex); + + OpenPgpFingerprint fingerprint = OpenPgpFingerprint.parseFromBinary(binary); + assertTrue(fingerprint instanceof _64DigitFingerprint); + assertEquals(hex, fingerprint.toString()); + } + + @Test + public void testParseFromBinary_wrongLength() { + String hex = "76543210ABCDEFAB01AB23CD1C0FFEE11EEFF0C1DC32BA10BAFEDCBA012345"; // missing 2 digits + byte[] binary = Hex.decode(hex); + + assertThrows(IllegalArgumentException.class, () -> OpenPgpFingerprint.parseFromBinary(binary)); + } + + @Test + public void equalsTest() { + String prettyPrint = "76543210 ABCDEFAB 01AB23CD 1C0FFEE1 1EEFF0C1 DC32BA10 BAFEDCBA 01234567"; + OpenPgpFingerprint parsed = OpenPgpFingerprint.parse(prettyPrint); + + assertNotEquals(parsed, null); + assertNotEquals(parsed, new Object()); + assertEquals(parsed, parsed.toString()); + + OpenPgpFingerprint v5 = new OpenPgpV5Fingerprint(prettyPrint); + assertEquals(parsed.hashCode(), v5.hashCode()); + assertEquals(0, parsed.compareTo(v5)); + + OpenPgpFingerprint v6 = new OpenPgpV6Fingerprint(prettyPrint); + assertEquals(parsed.hashCode(), v6.hashCode()); + assertEquals(0, parsed.compareTo(v6)); + } + +} From 2587f19df345075b6c55949aee06dd08c346e156 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 9 Apr 2023 18:49:20 +0200 Subject: [PATCH 006/528] BC173: Fix CRC error detection by improving error check --- .../pgpainless/decryption_verification/TeeBCPGInputStream.java | 2 +- 1 file changed, 1 insertion(+), 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 28b415e0..bdbd9bcd 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 @@ -120,7 +120,7 @@ public class TeeBCPGInputStream { last = inputStream.read(); return last; } catch (IOException e) { - if ("crc check failed in armored message.".equals(e.getMessage())) { + if (e.getMessage().contains("crc check failed in armored message")) { throw e; } return -1; From 44608744c26f2df63daba7f3a8e24d706b8d5db0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 16:17:58 +0200 Subject: [PATCH 007/528] Add missing license header --- .../test/java/org/pgpainless/key/_64DigitFingerprintTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/_64DigitFingerprintTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/_64DigitFingerprintTest.java index 21ff31c3..a38fa61d 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/_64DigitFingerprintTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/_64DigitFingerprintTest.java @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + package org.pgpainless.key; import org.bouncycastle.util.encoders.Hex; From e35287a66658af3213628be09b6d025f401cb40c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:31:48 +0200 Subject: [PATCH 008/528] Add support for SOP05 features --- .../org/pgpainless/sop/GenerateKeyImpl.java | 48 ++++++++++++++++++- .../org/pgpainless/sop/ListProfilesImpl.java | 29 +++++++++++ .../main/java/org/pgpainless/sop/SOPImpl.java | 6 +++ version.gradle | 2 +- 4 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 pgpainless-sop/src/main/java/org/pgpainless/sop/ListProfilesImpl.java 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 da99c854..693ca454 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/GenerateKeyImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/GenerateKeyImpl.java @@ -8,18 +8,22 @@ import java.io.IOException; import java.io.OutputStream; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.pgpainless.PGPainless; +import org.pgpainless.key.generation.type.rsa.RsaLength; import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditorInterface; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.util.ArmorUtils; import org.pgpainless.util.Passphrase; +import sop.Profile; import sop.Ready; import sop.exception.SOPGPException; import sop.operation.GenerateKey; @@ -29,9 +33,16 @@ import sop.operation.GenerateKey; */ public class GenerateKeyImpl implements GenerateKey { + public static final Profile DEFAULT_PROFILE = new Profile("default", "Generate keys based on XDH and EdDSA"); + public static final Profile RSA3072_PROFILE = new Profile("rfc4880-rsa3072@pgpainless.org", "Generate 3072-bit RSA keys"); + public static final Profile RSA4096_PROFILE = new Profile("rfc4880-rsa4096@pgpainless.org", "Generate 4096-bit RSA keys"); + + public static final List SUPPORTED_PROFILES = Arrays.asList(DEFAULT_PROFILE, RSA3072_PROFILE, RSA4096_PROFILE); + private boolean armor = true; private final Set userIds = new LinkedHashSet<>(); private Passphrase passphrase = Passphrase.emptyPassphrase(); + private String profile = DEFAULT_PROFILE.getName(); @Override public GenerateKey noArmor() { @@ -51,6 +62,18 @@ public class GenerateKeyImpl implements GenerateKey { return this; } + @Override + public GenerateKey profile(String profileName) { + for (Profile profile : SUPPORTED_PROFILES) { + if (profile.getName().equals(profileName)) { + this.profile = profileName; + return this; + } + } + + throw new SOPGPException.UnsupportedProfile("generate-key", profileName); + } + @Override public Ready generate() throws SOPGPException.MissingArg, SOPGPException.UnsupportedAsymmetricAlgo { Iterator userIdIterator = userIds.iterator(); @@ -58,8 +81,7 @@ public class GenerateKeyImpl implements GenerateKey { PGPSecretKeyRing key; try { String primaryUserId = userIdIterator.hasNext() ? userIdIterator.next() : null; - key = PGPainless.generateKeyRing() - .modernKeyRing(primaryUserId, passphrase); + key = generateKeyWithProfile(profile, primaryUserId, passphrase); if (userIdIterator.hasNext()) { SecretKeyRingEditorInterface editor = PGPainless.modifyKeyRing(key); @@ -90,4 +112,26 @@ public class GenerateKeyImpl implements GenerateKey { throw new RuntimeException(e); } } + + private PGPSecretKeyRing generateKeyWithProfile(String profile, String primaryUserId, Passphrase passphrase) + throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + PGPSecretKeyRing key; + // XDH + EdDSA + if (profile.equals(DEFAULT_PROFILE.getName())) { + key = PGPainless.generateKeyRing() + .modernKeyRing(primaryUserId, passphrase); + } + else if (profile.equals(RSA3072_PROFILE.getName())) { + key = PGPainless.generateKeyRing() + .simpleRsaKeyRing(primaryUserId, RsaLength._3072, passphrase); + } + else if (profile.equals(RSA4096_PROFILE.getName())) { + key = PGPainless.generateKeyRing() + .simpleRsaKeyRing(primaryUserId, RsaLength._4096, passphrase); + } + else { + throw new SOPGPException.UnsupportedProfile("generate-key", profile); + } + return key; + } } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/ListProfilesImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/ListProfilesImpl.java new file mode 100644 index 00000000..06519bb3 --- /dev/null +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/ListProfilesImpl.java @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.sop; + +import java.util.List; + +import sop.Profile; +import sop.exception.SOPGPException; +import sop.operation.ListProfiles; + +public class ListProfilesImpl implements ListProfiles { + + @Override + public List subcommand(String command) { + if (command == null) { + throw new SOPGPException.UnsupportedProfile("null"); + } + + switch (command) { + case "generate-key": + return GenerateKeyImpl.SUPPORTED_PROFILES; + + default: + throw new SOPGPException.UnsupportedProfile(command); + } + } +} diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/SOPImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/SOPImpl.java index a49f7e34..a0e5f631 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/SOPImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/SOPImpl.java @@ -17,6 +17,7 @@ import sop.operation.ExtractCert; import sop.operation.GenerateKey; import sop.operation.InlineSign; import sop.operation.InlineVerify; +import sop.operation.ListProfiles; import sop.operation.Version; /** @@ -96,6 +97,11 @@ public class SOPImpl implements SOP { return new DearmorImpl(); } + @Override + public ListProfiles listProfiles() { + return new ListProfilesImpl(); + } + @Override public InlineDetach inlineDetach() { return new InlineDetachImpl(); diff --git a/version.gradle b/version.gradle index 55e9b151..1adf151a 100644 --- a/version.gradle +++ b/version.gradle @@ -18,6 +18,6 @@ allprojects { logbackVersion = '1.2.11' mockitoVersion = '4.5.1' slf4jVersion = '1.7.36' - sopJavaVersion = '4.1.1' + sopJavaVersion = '5.0.0-SNAPSHOT' } } From b79e706d65c33defebd132e9ec3db5f6725c89db Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:35:55 +0200 Subject: [PATCH 009/528] Bump SOP version in VersionImpl to 05 --- .../src/main/java/org/pgpainless/sop/VersionImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java index 4449af10..13a5dc94 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java @@ -18,7 +18,7 @@ import sop.operation.Version; public class VersionImpl implements Version { // draft version - private static final String SOP_VERSION = "04"; + private static final String SOP_VERSION = "05"; @Override public String getName() { From f3a4a01d199bd3782d2e34b065ac16c47dd5253b Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 16:17:32 +0200 Subject: [PATCH 010/528] Add basic tests for new functionality --- .../org/pgpainless/sop/GenerateKeyTest.java | 14 ++++++++ .../org/pgpainless/sop/ListProfilesTest.java | 34 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 pgpainless-sop/src/test/java/org/pgpainless/sop/ListProfilesTest.java diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/GenerateKeyTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/GenerateKeyTest.java index 3a6e4476..5894bfa7 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/GenerateKeyTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/GenerateKeyTest.java @@ -7,6 +7,7 @@ package org.pgpainless.sop; 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; import java.io.IOException; @@ -21,6 +22,7 @@ import org.pgpainless.key.info.KeyRingInfo; import org.pgpainless.key.protection.UnlockSecretKey; import org.pgpainless.util.Passphrase; import sop.SOP; +import sop.exception.SOPGPException; public class GenerateKeyTest { @@ -92,4 +94,16 @@ public class GenerateKeyTest { assertNotNull(UnlockSecretKey.unlockSecretKey(key, Passphrase.fromPassword("sw0rdf1sh"))); } } + + @Test + public void invalidProfile() { + assertThrows(SOPGPException.UnsupportedProfile.class, () -> + sop.generateKey().profile("invalid")); + } + + @Test + public void nullProfile() { + assertThrows(SOPGPException.UnsupportedProfile.class, () -> + sop.generateKey().profile((String) null)); + } } diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/ListProfilesTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/ListProfilesTest.java new file mode 100644 index 00000000..0103bf4d --- /dev/null +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/ListProfilesTest.java @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.sop; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import sop.SOP; +import sop.exception.SOPGPException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ListProfilesTest { + + private SOP sop; + + @BeforeEach + public void prepare() { + this.sop = new SOPImpl(); + } + + @Test + public void listProfilesOfGenerateKey() { + assertFalse(sop.listProfiles().subcommand("generate-key").isEmpty()); + } + + @Test + public void listProfilesOfHelpCommandThrows() { + assertThrows(SOPGPException.UnsupportedProfile.class, () -> + sop.listProfiles().subcommand("help")); + } +} From 702fdf085c2eef616be59b233988d5ab2a28f417 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 12:49:23 +0200 Subject: [PATCH 011/528] Thin out and rename profiles of generate-key --- .../java/org/pgpainless/sop/GenerateKeyImpl.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) 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 693ca454..d2baf29b 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/GenerateKeyImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/GenerateKeyImpl.java @@ -33,16 +33,15 @@ import sop.operation.GenerateKey; */ public class GenerateKeyImpl implements GenerateKey { - public static final Profile DEFAULT_PROFILE = new Profile("default", "Generate keys based on XDH and EdDSA"); - public static final Profile RSA3072_PROFILE = new Profile("rfc4880-rsa3072@pgpainless.org", "Generate 3072-bit RSA keys"); - public static final Profile RSA4096_PROFILE = new Profile("rfc4880-rsa4096@pgpainless.org", "Generate 4096-bit RSA keys"); + public static final Profile CURVE25519_PROFILE = new Profile("draft-koch-eddsa-for-openpgp-00", "Generate EdDSA / ECDH keys using Curve25519"); + public static final Profile RSA4096_PROFILE = new Profile("rfc4880", "Generate 4096-bit RSA keys"); - public static final List SUPPORTED_PROFILES = Arrays.asList(DEFAULT_PROFILE, RSA3072_PROFILE, RSA4096_PROFILE); + public static final List SUPPORTED_PROFILES = Arrays.asList(CURVE25519_PROFILE, RSA4096_PROFILE); private boolean armor = true; private final Set userIds = new LinkedHashSet<>(); private Passphrase passphrase = Passphrase.emptyPassphrase(); - private String profile = DEFAULT_PROFILE.getName(); + private String profile = CURVE25519_PROFILE.getName(); @Override public GenerateKey noArmor() { @@ -117,14 +116,11 @@ public class GenerateKeyImpl implements GenerateKey { throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { PGPSecretKeyRing key; // XDH + EdDSA - if (profile.equals(DEFAULT_PROFILE.getName())) { + if (profile.equals(CURVE25519_PROFILE.getName())) { key = PGPainless.generateKeyRing() .modernKeyRing(primaryUserId, passphrase); } - else if (profile.equals(RSA3072_PROFILE.getName())) { - key = PGPainless.generateKeyRing() - .simpleRsaKeyRing(primaryUserId, RsaLength._3072, passphrase); - } + // RSA 4096 else if (profile.equals(RSA4096_PROFILE.getName())) { key = PGPainless.generateKeyRing() .simpleRsaKeyRing(primaryUserId, RsaLength._4096, passphrase); From 63714859292c7bac51b3e40d127439cf469a93ad Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 14:51:50 +0200 Subject: [PATCH 012/528] Add some clarifying comments to GenerateKeyImpl --- .../src/main/java/org/pgpainless/sop/GenerateKeyImpl.java | 4 ++++ 1 file changed, 4 insertions(+) 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 d2baf29b..ba788dac 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/GenerateKeyImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/GenerateKeyImpl.java @@ -63,13 +63,16 @@ public class GenerateKeyImpl implements GenerateKey { @Override public GenerateKey profile(String profileName) { + // Sanitize the profile name to make sure we support the given profile for (Profile profile : SUPPORTED_PROFILES) { if (profile.getName().equals(profileName)) { this.profile = profileName; + // return if we found the profile return this; } } + // profile not found, throw throw new SOPGPException.UnsupportedProfile("generate-key", profileName); } @@ -126,6 +129,7 @@ public class GenerateKeyImpl implements GenerateKey { .simpleRsaKeyRing(primaryUserId, RsaLength._4096, passphrase); } else { + // Missing else-if branch for profile. Oops. throw new SOPGPException.UnsupportedProfile("generate-key", profile); } return key; From 11eda9be953487e2124fba4b48c31f72dbb13a94 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 15:09:15 +0200 Subject: [PATCH 013/528] Bump sop-java to 5.0.0 --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index 1adf151a..4a532368 100644 --- a/version.gradle +++ b/version.gradle @@ -18,6 +18,6 @@ allprojects { logbackVersion = '1.2.11' mockitoVersion = '4.5.1' slf4jVersion = '1.7.36' - sopJavaVersion = '5.0.0-SNAPSHOT' + sopJavaVersion = '5.0.0' } } From 772a98b4ae0701571136be3dd7ff66f7c17042c0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 15:22:38 +0200 Subject: [PATCH 014/528] Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd7f3505..6212d8c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ SPDX-License-Identifier: CC0-1.0 # PGPainless Changelog +## 1.5.0-SNAPSHOT +- Introduce `OpenPgpv6Fingerprint` class +- Bump `sop-java` to `5.0.0`, implementing [SOP Spec Revision 05](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-05.html) + - Add support for `list-profiles` subcommand (`generate-key` only for now) + - `generate-key`: Add support for `--profile=` option + - Add profile `draft-koch-eddsa-for-openpgp-00` which represents status quo. + - Add profile `rfc4880` which generates keys based on 4096-bit RSA. + ## 1.4.4 - Fix expectations on subpackets of v3 signatures (thanks @bjansen) - Properly verify v3 signatures, which do not yet have signature subpackets, yet we required them to have From 66d81660052e6a3720eec3026509dcd9d1288491 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:35:11 +0200 Subject: [PATCH 015/528] Bump sop-java to 6.0.0-SNAPSHOT --- version.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.gradle b/version.gradle index 4a532368..aa025a9e 100644 --- a/version.gradle +++ b/version.gradle @@ -18,6 +18,6 @@ allprojects { logbackVersion = '1.2.11' mockitoVersion = '4.5.1' slf4jVersion = '1.7.36' - sopJavaVersion = '5.0.0' + sopJavaVersion = '6.0.0' } } From 446d121777b47f82aa4bfd993d13ef27880e9d77 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:36:31 +0200 Subject: [PATCH 016/528] Bump SOP version in VersionImpl to 06 --- .../src/main/java/org/pgpainless/sop/VersionImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java index 13a5dc94..5e851706 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java @@ -18,7 +18,7 @@ import sop.operation.Version; public class VersionImpl implements Version { // draft version - private static final String SOP_VERSION = "05"; + private static final String SOP_VERSION = "06"; @Override public String getName() { From 5b363de6e42a9cec16175f6afb5552548b389384 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 14:36:57 +0200 Subject: [PATCH 017/528] Implement VersionImpl.getSopSpecVersion() --- .../src/main/java/org/pgpainless/sop/VersionImpl.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java index 5e851706..8986e295 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java @@ -63,4 +63,9 @@ public class VersionImpl implements Version { "Using " + getBackendVersion() + "\n" + "https://www.bouncycastle.org/java.html"; } + + @Override + public String getSopSpecVersion() { + return "draft-dkg-openpgp-stateless-cli-" + SOP_VERSION; + } } From 3b1edb076c5aa74403777f58f82064d38ef9d2ce Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 15:13:43 +0200 Subject: [PATCH 018/528] Basic support for sop encrypt --profile=XXX --- .../java/org/pgpainless/sop/EncryptImpl.java | 20 +++++++++++++++++++ .../org/pgpainless/sop/ListProfilesImpl.java | 3 +++ 2 files changed, 23 insertions(+) diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java index 62d20bf0..689e07be 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java @@ -8,7 +8,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.bouncycastle.openpgp.PGPException; @@ -28,6 +30,7 @@ import org.pgpainless.exception.WrongPassphraseException; import org.pgpainless.key.OpenPgpFingerprint; import org.pgpainless.key.info.KeyRingInfo; import org.pgpainless.util.Passphrase; +import sop.Profile; import sop.Ready; import sop.enums.EncryptAs; import sop.exception.SOPGPException; @@ -39,10 +42,15 @@ import sop.util.ProxyOutputStream; */ public class EncryptImpl implements Encrypt { + private static final Profile DEFAULT_PROFILE = new Profile("default", "Use the implementer's recommendations"); + + public static final List SUPPORTED_PROFILES = Arrays.asList(DEFAULT_PROFILE); + EncryptionOptions encryptionOptions = EncryptionOptions.get(); SigningOptions signingOptions = null; MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector(); private final Set signingKeys = new HashSet<>(); + private String profile = DEFAULT_PROFILE.getName(); // TODO: Use in future releases private EncryptAs encryptAs = EncryptAs.Binary; boolean armor = true; @@ -111,6 +119,18 @@ public class EncryptImpl implements Encrypt { return this; } + @Override + public Encrypt profile(String profileName) { + for (Profile profile : SUPPORTED_PROFILES) { + if (profile.getName().equals(profileName)) { + this.profile = profile.getName(); + return this; + } + } + + throw new SOPGPException.UnsupportedProfile("encrypt", profileName); + } + @Override public Ready plaintext(InputStream plaintext) throws IOException { if (!encryptionOptions.hasEncryptionMethod()) { diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/ListProfilesImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/ListProfilesImpl.java index 06519bb3..c0f5027a 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/ListProfilesImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/ListProfilesImpl.java @@ -22,6 +22,9 @@ public class ListProfilesImpl implements ListProfiles { case "generate-key": return GenerateKeyImpl.SUPPORTED_PROFILES; + case "encrypt": + return EncryptImpl.SUPPORTED_PROFILES; + default: throw new SOPGPException.UnsupportedProfile(command); } From 926e540016dd6edf8ec7de2234847e3fbbbbc4dc Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 14 Apr 2023 15:57:29 +0200 Subject: [PATCH 019/528] Test fine-grained SOP spec version --- .../cli/commands/VersionCmdTest.java | 8 +++++++ .../java/org/pgpainless/sop/VersionImpl.java | 21 +++++++++++++---- .../java/org/pgpainless/sop/VersionTest.java | 23 +++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/VersionCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/VersionCmdTest.java index 2e4aa7e4..87f535a8 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/VersionCmdTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/VersionCmdTest.java @@ -41,4 +41,12 @@ public class VersionCmdTest extends CLITest { assertTrue(info.contains("Bouncy Castle")); assertTrue(info.contains("Stateless OpenPGP Protocol")); } + + @Test + public void testSopSpecVersion() throws IOException { + ByteArrayOutputStream out = pipeStdoutToStream(); + assertSuccess(executeCommand("version", "--sop-spec")); + String info = out.toString(); + assertTrue(info.startsWith("draft-dkg-openpgp-stateless-cli-")); + } } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java index 8986e295..82995edf 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java @@ -18,7 +18,7 @@ import sop.operation.Version; public class VersionImpl implements Version { // draft version - private static final String SOP_VERSION = "06"; + private static final int SOP_VERSION = 6; @Override public String getName() { @@ -51,11 +51,12 @@ public class VersionImpl implements Version { @Override public String getExtendedVersion() { + String FORMAT_VERSION = String.format("%02d", SOP_VERSION); return getName() + " " + getVersion() + "\n" + "https://codeberg.org/PGPainless/pgpainless/src/branch/master/pgpainless-sop\n" + "\n" + - "Implementation of the Stateless OpenPGP Protocol Version " + SOP_VERSION + "\n" + - "https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-" + SOP_VERSION + "\n" + + "Implementation of the Stateless OpenPGP Protocol Version " + FORMAT_VERSION + "\n" + + "https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-" + FORMAT_VERSION + "\n" + "\n" + "Based on pgpainless-core " + getVersion() + "\n" + "https://pgpainless.org\n" + @@ -65,7 +66,17 @@ public class VersionImpl implements Version { } @Override - public String getSopSpecVersion() { - return "draft-dkg-openpgp-stateless-cli-" + SOP_VERSION; + public int getSopSpecVersionNumber() { + return SOP_VERSION; + } + + @Override + public boolean isSopSpecImplementationIncomplete() { + return false; + } + + @Override + public String getSopSpecImplementationIncompletenessRemarks() { + return null; } } diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/VersionTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/VersionTest.java index c9739471..fa4af6a8 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/VersionTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/VersionTest.java @@ -7,6 +7,7 @@ package org.pgpainless.sop; 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.assertTrue; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -49,4 +50,26 @@ public class VersionTest { String firstLine = extendedVersion.split("\n")[0]; assertEquals(sop.version().getName() + " " + sop.version().getVersion(), firstLine); } + + @Test + public void testGetSopSpecVersion() { + boolean incomplete = sop.version().isSopSpecImplementationIncomplete(); + int revisionNumber = sop.version().getSopSpecVersionNumber(); + + String revisionString = sop.version().getSopSpecRevisionString(); + assertEquals("draft-dkg-openpgp-stateless-cli-" + String.format("%02d", revisionNumber), revisionString); + + String incompletenessRemarks = sop.version().getSopSpecImplementationIncompletenessRemarks(); + + String fullSopSpecVersion = sop.version().getSopSpecVersion(); + if (incomplete) { + assertTrue(fullSopSpecVersion.startsWith("~" + revisionString)); + } else { + assertTrue(fullSopSpecVersion.startsWith(revisionString)); + } + + if (incompletenessRemarks != null) { + assertTrue(fullSopSpecVersion.endsWith(incompletenessRemarks)); + } + } } From 676e7d166a22854b53f01a50c6416a8c2f5c1920 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 16:06:45 +0200 Subject: [PATCH 020/528] EncryptImpl: Rename default profile, add documentation --- .../src/main/java/org/pgpainless/sop/EncryptImpl.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java index 689e07be..18bcabcc 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java @@ -42,15 +42,15 @@ import sop.util.ProxyOutputStream; */ public class EncryptImpl implements Encrypt { - private static final Profile DEFAULT_PROFILE = new Profile("default", "Use the implementer's recommendations"); + private static final Profile RFC4880_PROFILE = new Profile("rfc4880", "Follow the packet format of rfc4880"); - public static final List SUPPORTED_PROFILES = Arrays.asList(DEFAULT_PROFILE); + public static final List SUPPORTED_PROFILES = Arrays.asList(RFC4880_PROFILE); EncryptionOptions encryptionOptions = EncryptionOptions.get(); SigningOptions signingOptions = null; MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector(); private final Set signingKeys = new HashSet<>(); - private String profile = DEFAULT_PROFILE.getName(); // TODO: Use in future releases + private String profile = RFC4880_PROFILE.getName(); // TODO: Use in future releases private EncryptAs encryptAs = EncryptAs.Binary; boolean armor = true; @@ -121,13 +121,16 @@ public class EncryptImpl implements Encrypt { @Override public Encrypt profile(String profileName) { + // sanitize profile name to make sure we only accept supported profiles for (Profile profile : SUPPORTED_PROFILES) { if (profile.getName().equals(profileName)) { + // profile is supported, return this.profile = profile.getName(); return this; } } + // Profile is not supported, throw throw new SOPGPException.UnsupportedProfile("encrypt", profileName); } From 003423a165631e8015cbe05f424fc0555b3ae0cf Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 16:09:51 +0200 Subject: [PATCH 021/528] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6212d8c4..7d408157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ SPDX-License-Identifier: CC0-1.0 - `generate-key`: Add support for `--profile=` option - Add profile `draft-koch-eddsa-for-openpgp-00` which represents status quo. - Add profile `rfc4880` which generates keys based on 4096-bit RSA. +- Bump `sop-java` to `6.0.0`, implementing [SOP Spec Revision 06](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-06.html) + - `encrypt`: Add support for `--profile=` option + - Add profile `rfc4880` to reflect status quo + - `version`: Add support for `--sop-spec` option ## 1.4.4 - Fix expectations on subpackets of v3 signatures (thanks @bjansen) From e465ae60a7150faabfeedb32dee3d727fb01662e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 16:22:54 +0200 Subject: [PATCH 022/528] VersionImpl: Fix outdated method names --- .../src/main/java/org/pgpainless/sop/VersionImpl.java | 5 +++-- .../src/test/java/org/pgpainless/sop/VersionTest.java | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java index 82995edf..0794c708 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/VersionImpl.java @@ -66,7 +66,7 @@ public class VersionImpl implements Version { } @Override - public int getSopSpecVersionNumber() { + public int getSopSpecRevisionNumber() { return SOP_VERSION; } @@ -76,7 +76,8 @@ public class VersionImpl implements Version { } @Override - public String getSopSpecImplementationIncompletenessRemarks() { + public String getSopSpecImplementationRemarks() { return null; } + } diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/VersionTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/VersionTest.java index fa4af6a8..32d2c2e0 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/VersionTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/VersionTest.java @@ -54,12 +54,12 @@ public class VersionTest { @Test public void testGetSopSpecVersion() { boolean incomplete = sop.version().isSopSpecImplementationIncomplete(); - int revisionNumber = sop.version().getSopSpecVersionNumber(); + int revisionNumber = sop.version().getSopSpecRevisionNumber(); - String revisionString = sop.version().getSopSpecRevisionString(); + String revisionString = sop.version().getSopSpecRevisionName(); assertEquals("draft-dkg-openpgp-stateless-cli-" + String.format("%02d", revisionNumber), revisionString); - String incompletenessRemarks = sop.version().getSopSpecImplementationIncompletenessRemarks(); + String incompletenessRemarks = sop.version().getSopSpecImplementationRemarks(); String fullSopSpecVersion = sop.version().getSopSpecVersion(); if (incomplete) { From 3a3e193bb0466ec4b5618c5fc40645f0e2645a23 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 16:23:16 +0200 Subject: [PATCH 023/528] Bump bouncycastle to 1.73 --- version.gradle | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/version.gradle b/version.gradle index aa025a9e..cbd64528 100644 --- a/version.gradle +++ b/version.gradle @@ -8,12 +8,8 @@ allprojects { isSnapshot = true pgpainlessMinAndroidSdk = 10 javaSourceCompatibility = 1.8 - bouncyCastleVersion = '1.72' - // When using bouncyCastleVersion 1.72: - // unfortunately we rely on 1.72.1 or 1.72.3 for a patch for https://github.com/bcgit/bc-java/issues/1257 - // which is a bug we introduced with a PR against BC :/ oops - // When bouncyCastleVersion is 1.71, bouncyPgVersion can simply be set to 1.71 as well. - bouncyPgVersion = '1.72.3' + bouncyCastleVersion = '1.73' + bouncyPgVersion = bouncyCastleVersion junitVersion = '5.8.2' logbackVersion = '1.2.11' mockitoVersion = '4.5.1' From d8f32b668910b7c1a53ae288bcffbdb013464b07 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 16:24:55 +0200 Subject: [PATCH 024/528] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d408157..08184f70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ SPDX-License-Identifier: CC0-1.0 # PGPainless Changelog ## 1.5.0-SNAPSHOT +- Bump `bcpg-jdk15to18` to `1.73` +- Bump `bcprov-jdk15to18` to `1.73` - Introduce `OpenPgpv6Fingerprint` class - Bump `sop-java` to `5.0.0`, implementing [SOP Spec Revision 05](https://www.ietf.org/archive/id/draft-dkg-openpgp-stateless-cli-05.html) - Add support for `list-profiles` subcommand (`generate-key` only for now) From 94a609127e75f1569e42e17f68a32029c039389f Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 16:35:17 +0200 Subject: [PATCH 025/528] PGPainless 1.5.0 --- CHANGELOG.md | 2 +- README.md | 2 +- pgpainless-sop/README.md | 6 +++--- version.gradle | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08184f70..51f7a815 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ SPDX-License-Identifier: CC0-1.0 # PGPainless Changelog -## 1.5.0-SNAPSHOT +## 1.5.0 - Bump `bcpg-jdk15to18` to `1.73` - Bump `bcprov-jdk15to18` to `1.73` - Introduce `OpenPgpv6Fingerprint` class diff --git a/README.md b/README.md index 2a91789e..14ff388d 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ repositories { } dependencies { - implementation 'org.pgpainless:pgpainless-core:1.4.4' + implementation 'org.pgpainless:pgpainless-core:1.5.0' } ``` diff --git a/pgpainless-sop/README.md b/pgpainless-sop/README.md index 3aef6d05..b4e755dd 100644 --- a/pgpainless-sop/README.md +++ b/pgpainless-sop/README.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: Apache-2.0 # PGPainless-SOP -[![Spec Revision: 4](https://img.shields.io/badge/Spec%20Revision-4-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/) +[![Spec Revision: 6](https://img.shields.io/badge/Spec%20Revision-6-blue)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/) [![Maven Central](https://badgen.net/maven/v/maven-central/org.pgpainless/pgpainless-sop)](https://search.maven.org/artifact/org.pgpainless/pgpainless-sop) [![javadoc](https://javadoc.io/badge2/org.pgpainless/pgpainless-sop/javadoc.svg)](https://javadoc.io/doc/org.pgpainless/pgpainless-sop) @@ -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.4.4" + implementation "org.pgpainless:pgpainless-sop:1.5.0" ... } @@ -34,7 +34,7 @@ dependencies { org.pgpainless pgpainless-sop - 1.4.4 + 1.5.0 ... diff --git a/version.gradle b/version.gradle index cbd64528..86239e88 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '1.4.5' - isSnapshot = true + shortVersion = '1.5.0' + isSnapshot = false pgpainlessMinAndroidSdk = 10 javaSourceCompatibility = 1.8 bouncyCastleVersion = '1.73' From 36a52a3e34c76287b0a37ee22c70b8ec57657180 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 17 Apr 2023 16:37:37 +0200 Subject: [PATCH 026/528] PGPainless 1.5.1-SNAPSHOT --- version.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.gradle b/version.gradle index 86239e88..3d271a75 100644 --- a/version.gradle +++ b/version.gradle @@ -4,8 +4,8 @@ allprojects { ext { - shortVersion = '1.5.0' - isSnapshot = false + shortVersion = '1.5.1' + isSnapshot = true pgpainlessMinAndroidSdk = 10 javaSourceCompatibility = 1.8 bouncyCastleVersion = '1.73' From 9a0b60ac7e45e2f6cae2c6d88e334507348e17d1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Apr 2023 17:41:02 +0200 Subject: [PATCH 027/528] Update quickstart document --- docs/source/pgpainless-sop/quickstart.md | 40 +++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/source/pgpainless-sop/quickstart.md b/docs/source/pgpainless-sop/quickstart.md index 10ef0a72..60e1df29 100644 --- a/docs/source/pgpainless-sop/quickstart.md +++ b/docs/source/pgpainless-sop/quickstart.md @@ -75,10 +75,21 @@ In both cases, the resulting output will be the UTF8 encoded, ASCII armored Open To disable ASCII armoring, call `noArmor()` before calling `generate()`. -At the time of writing, the resulting OpenPGP secret key will consist of a certification-capable 256-bits +Revision `05` of the Stateless OpenPGP Protocol specification introduced the concept of profiles for +certain operations. +The key generation feature is the first operation to make use of profiles to specify different key algorithms. +To set a profile, simply call `profile(String profileName)` and pass in one of the available profile identifiers. + +To explore, which profiles are available, refer to the dedicated [section](#explore-profiles). + +The default profile used by `pgpainless-sop` is called `draft-koch-eddsa-for-openpgp-00`. +If this profile is used, the resulting OpenPGP secret key will consist of a certification-capable 256-bits ed25519 EdDSA primary key, a 256-bits ed25519 EdDSA subkey used for signing, as well as a 256-bits X25519 ECDH subkey for encryption. +Another profile defined by `pgpainless-sop` is `rfc4880`, which changes the key generation behaviour such that +the resulting key is a single 4096-bit RSA key capable of certifying, signing and encrypting. + The whole key does not have an expiration date set. ### Extract a Certificate @@ -186,6 +197,13 @@ If any keys used for signing are password protected, you need to provide the sig It does not matter in which order signing keys and key passwords are provided, the implementation will figure out matches on its own. If different key passwords are used, the `withKeyPassword(_)` method can be called multiple times. +You can modify the behaviour of the encrypt operation by switching between different profiles via the +`profile(String profileName)` method. +At the time of writing, the only available profile for this operation is `rfc4880` which applies encryption +as defined in [rfc4880](https://datatracker.ietf.org/doc/html/rfc4880). + +To explore, which profiles are available, refer to the dedicated [section](#explore-profiles). + By default, the encrypted message will be ASCII armored. To disable ASCII armor, call `noArmor()` before the `plaintext(_)` method call. @@ -464,3 +482,23 @@ By default, the signatures output will be ASCII armored. This can be disabled by prior to `message(_)`. The detached signatures can now be verified like in the section above. + +### Explore Profiles + +Certain operations allow modification of their behaviour by selecting between different profiles. +An example for this is the `generateKey()` operation, where different profiles result in different algorithms used +during key generation. + +To explore, which profiles are supported by a certain operation, you can use the `listProfiles()` operation. +For example, this is how you can get a list of profiles supported by the `generateKey()` operation: + +```java +List profiles = sop.listProfiles().subcommand("generate-key"); +``` + +:::{note} +As you can see, the argument passed into the `subcommand()` method must match the operation name as defined in the +[Stateless OpenPGP Protocol specification](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/). +::: + +At the time of writing (the latest revision of the SOP spec is 06), only `generate-key` and `encrypt` accept profiles. \ No newline at end of file From 05968533a5b64005a38702b7e2fdf787c59265ba Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Apr 2023 18:35:47 +0200 Subject: [PATCH 028/528] InlineVerifyImpl: Export signature mode in Verification result --- .../RoundTripInlineSignInlineVerifyCmdTest.java | 2 +- .../org/pgpainless/sop/InlineVerifyImpl.java | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripInlineSignInlineVerifyCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripInlineSignInlineVerifyCmdTest.java index d36ee58f..0676f213 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripInlineSignInlineVerifyCmdTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripInlineSignInlineVerifyCmdTest.java @@ -409,7 +409,7 @@ public class RoundTripInlineSignInlineVerifyCmdTest extends CLITest { assertEquals("Hello, World!\n", out.toString()); String ver = readStringFromFile(verifications); assertEquals( - "2022-11-18T14:55:33Z 7A073EDF273C902796D259528FBDD36D01831673 AEA0FD2C899D3FC077815F0026560D2AE53DB86F\n", ver); + "2022-11-18T14:55:33Z 7A073EDF273C902796D259528FBDD36D01831673 AEA0FD2C899D3FC077815F0026560D2AE53DB86F mode:binary\n", ver); } @Test diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java index 7665a7bb..eea5ebfb 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java @@ -13,6 +13,7 @@ import java.util.List; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.util.io.Streams; import org.pgpainless.PGPainless; import org.pgpainless.decryption_verification.ConsumerOptions; @@ -23,6 +24,7 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException; import org.pgpainless.exception.MissingDecryptionMethodException; import sop.ReadyWithResult; import sop.Verification; +import sop.enums.SignatureMode; import sop.exception.SOPGPException; import sop.operation.InlineVerify; @@ -96,6 +98,19 @@ public class InlineVerifyImpl implements InlineVerify { private Verification map(SignatureVerification sigVerification) { return new Verification(sigVerification.getSignature().getCreationTime(), sigVerification.getSigningKey().getSubkeyFingerprint().toString(), - sigVerification.getSigningKey().getPrimaryKeyFingerprint().toString()); + sigVerification.getSigningKey().getPrimaryKeyFingerprint().toString(), + getMode(sigVerification.getSignature()), + null); + } + + private static SignatureMode getMode(PGPSignature signature) { + if (signature.getSignatureType() == PGPSignature.BINARY_DOCUMENT) { + return SignatureMode.binary; + } + if (signature.getSignatureType() == PGPSignature.CANONICAL_TEXT_DOCUMENT) { + return SignatureMode.text; + } + + return null; } } From 2ec176e9388e281f98171b89008e84d0f0cb125c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Apr 2023 18:39:52 +0200 Subject: [PATCH 029/528] DetachedVerifyImpl: Export signature mode in Verification result --- .../cli/commands/InlineDetachCmdTest.java | 4 ++-- .../commands/RoundTripSignVerifyCmdTest.java | 4 ++-- .../org/pgpainless/sop/DetachedVerifyImpl.java | 17 ++++++++++++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/InlineDetachCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/InlineDetachCmdTest.java index 8854d837..19bc9aa5 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/InlineDetachCmdTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/InlineDetachCmdTest.java @@ -90,7 +90,7 @@ public class InlineDetachCmdTest extends CLITest { pipeStringToStdin(msgOut.toString()); ByteArrayOutputStream verifyOut = pipeStdoutToStream(); assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), certFile.getAbsolutePath())); - assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C\n", + assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C mode:text\n", verifyOut.toString()); } @@ -115,7 +115,7 @@ public class InlineDetachCmdTest extends CLITest { ByteArrayOutputStream verifyOut = pipeStdoutToStream(); File certFile = writeFile("cert.asc", CERT); assertSuccess(executeCommand("verify", sigFile.getAbsolutePath(), certFile.getAbsolutePath())); - assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C\n", + assertEquals("2021-05-15T16:08:06Z 4F665C4DC2C4660BC6425E415736E6931ACF370C 4F665C4DC2C4660BC6425E415736E6931ACF370C mode:text\n", verifyOut.toString()); } 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 6196a847..97bfae7e 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 @@ -94,7 +94,7 @@ public class RoundTripSignVerifyCmdTest extends CLITest { "=VWAZ\n" + "-----END PGP SIGNATURE-----"; private static final String BINARY_SIG_VERIFICATION = - "2022-11-09T18:40:24Z 444C10AB011EF8424C83F0A9DA9F413986211DC6 9DA09423C9F94BA4CCA30951099B11BF296A373E\n"; + "2022-11-09T18:40:24Z 444C10AB011EF8424C83F0A9DA9F413986211DC6 9DA09423C9F94BA4CCA30951099B11BF296A373E mode:binary\n"; private static final String TEXT_SIG = "-----BEGIN PGP SIGNATURE-----\n" + "Version: PGPainless\n" + "\n" + @@ -104,7 +104,7 @@ public class RoundTripSignVerifyCmdTest extends CLITest { "=s5xn\n" + "-----END PGP SIGNATURE-----"; private static final String TEXT_SIG_VERIFICATION = - "2022-11-09T18:41:18Z 444C10AB011EF8424C83F0A9DA9F413986211DC6 9DA09423C9F94BA4CCA30951099B11BF296A373E\n"; + "2022-11-09T18:41:18Z 444C10AB011EF8424C83F0A9DA9F413986211DC6 9DA09423C9F94BA4CCA30951099B11BF296A373E mode:text\n"; private static final Date TEXT_SIG_CREATION = UTCUtil.parseUTCDate("2022-11-09T18:41:18Z"); @Test diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedVerifyImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedVerifyImpl.java index 93ad398c..f0cb1161 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedVerifyImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedVerifyImpl.java @@ -12,6 +12,7 @@ import java.util.List; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.util.io.Streams; import org.pgpainless.PGPainless; import org.pgpainless.decryption_verification.ConsumerOptions; @@ -20,6 +21,7 @@ import org.pgpainless.decryption_verification.MessageMetadata; import org.pgpainless.decryption_verification.SignatureVerification; import org.pgpainless.exception.MalformedOpenPgpMessageException; import sop.Verification; +import sop.enums.SignatureMode; import sop.exception.SOPGPException; import sop.operation.DetachedVerify; @@ -94,6 +96,19 @@ public class DetachedVerifyImpl implements DetachedVerify { private Verification map(SignatureVerification sigVerification) { return new Verification(sigVerification.getSignature().getCreationTime(), sigVerification.getSigningKey().getSubkeyFingerprint().toString(), - sigVerification.getSigningKey().getPrimaryKeyFingerprint().toString()); + sigVerification.getSigningKey().getPrimaryKeyFingerprint().toString(), + getMode(sigVerification.getSignature()), + null); + } + + private static SignatureMode getMode(PGPSignature signature) { + if (signature.getSignatureType() == PGPSignature.BINARY_DOCUMENT) { + return SignatureMode.binary; + } + if (signature.getSignatureType() == PGPSignature.CANONICAL_TEXT_DOCUMENT) { + return SignatureMode.text; + } + + return null; } } From b0974c6ade4569c9f7342884d45d1a80a212e5c4 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Apr 2023 18:40:25 +0200 Subject: [PATCH 030/528] Add more tests for inline-sign-verify roundtrips --- .../sop/InlineSignVerifyRoundtripTest.java | 50 +++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) 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 b24b729c..e5ce518b 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/InlineSignVerifyRoundtripTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/InlineSignVerifyRoundtripTest.java @@ -9,13 +9,14 @@ 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.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; public class InlineSignVerifyRoundtripTest { @@ -46,7 +47,11 @@ public class InlineSignVerifyRoundtripTest { byte[] verified = result.getBytes(); - assertFalse(result.getResult().isEmpty()); + List verificationList = result.getResult(); + assertEquals(1, verificationList.size()); + Verification verification = verificationList.get(0); + assertEquals(SignatureMode.text, verification.getSignatureMode()); + assertArrayEquals(message, verified); } @@ -65,6 +70,7 @@ public class InlineSignVerifyRoundtripTest { byte[] inlineSigned = sop.inlineSign() .key(key) .withKeyPassword("sw0rdf1sh") + .mode(InlineSignAs.binary) .data(message).getBytes(); ByteArrayAndResult> result = sop.inlineVerify() @@ -74,7 +80,45 @@ public class InlineSignVerifyRoundtripTest { byte[] verified = result.getBytes(); - assertFalse(result.getResult().isEmpty()); + List verificationList = result.getResult(); + assertEquals(1, verificationList.size()); + Verification verification = verificationList.get(0); + assertEquals(SignatureMode.binary, verification.getSignatureMode()); + + assertArrayEquals(message, verified); + } + + + @Test + public void testInlineSignAndVerifyWithTextSignatures() throws IOException { + byte[] key = sop.generateKey() + .userId("Mark") + .withKeyPassword("y3110w5ubm4r1n3") + .generate().getBytes(); + + byte[] cert = sop.extractCert() + .key(key).getBytes(); + + byte[] message = "Give me a plaintext that I can sign and verify, pls.".getBytes(StandardCharsets.UTF_8); + + byte[] inlineSigned = sop.inlineSign() + .key(key) + .withKeyPassword("y3110w5ubm4r1n3") + .mode(InlineSignAs.text) + .data(message).getBytes(); + + ByteArrayAndResult> result = sop.inlineVerify() + .cert(cert) + .data(inlineSigned) + .toByteArrayAndResult(); + + byte[] verified = result.getBytes(); + + List verificationList = result.getResult(); + assertEquals(1, verificationList.size()); + Verification verification = verificationList.get(0); + assertEquals(SignatureMode.text, verification.getSignatureMode()); + assertArrayEquals(message, verified); } From 06c924d41dff1a358ea07eb9ddefe4b1984c6185 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Apr 2023 18:43:56 +0200 Subject: [PATCH 031/528] Add tests for mode to DetachedSignTest --- .../org/pgpainless/sop/DetachedSignTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) 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 c6fcc267..4f274691 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/DetachedSignTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/DetachedSignTest.java @@ -23,6 +23,7 @@ import org.pgpainless.signature.SignatureUtils; import sop.SOP; import sop.Verification; import sop.enums.SignAs; +import sop.enums.SignatureMode; import sop.exception.SOPGPException; public class DetachedSignTest { @@ -49,6 +50,7 @@ public class DetachedSignTest { public void signArmored() throws IOException { byte[] signature = sop.sign() .key(key) + .mode(SignAs.Binary) .data(data) .toByteArrayAndResult().getBytes(); @@ -62,6 +64,7 @@ public class DetachedSignTest { .data(data); assertEquals(1, verifications.size()); + assertEquals(SignatureMode.binary, verifications.get(0).getSignatureMode()); } @Test @@ -84,6 +87,26 @@ public class DetachedSignTest { assertEquals(1, verifications.size()); } + @Test + public void textSig() throws IOException { + byte[] signature = sop.sign() + .key(key) + .noArmor() + .mode(SignAs.Text) + .data(data) + .toByteArrayAndResult().getBytes(); + + List verifications = sop.verify() + .cert(cert) + .notAfter(new Date(new Date().getTime() + 10000)) + .notBefore(new Date(new Date().getTime() - 10000)) + .signatures(signature) + .data(data); + + assertEquals(1, verifications.size()); + assertEquals(SignatureMode.text, verifications.get(0).getSignatureMode()); + } + @Test public void rejectSignatureAsTooOld() throws IOException { byte[] signature = sop.sign() From e3bacdbe35cef9aa7bf0d36d08fe7399747d5478 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Apr 2023 18:53:50 +0200 Subject: [PATCH 032/528] Introduce VerificationHelper class and export signature mode in decrypt operation --- .../RoundTripEncryptDecryptCmdTest.java | 4 +- .../java/org/pgpainless/sop/DecryptImpl.java | 8 +-- .../pgpainless/sop/DetachedVerifyImpl.java | 23 +------- .../org/pgpainless/sop/InlineVerifyImpl.java | 23 +------- .../pgpainless/sop/VerificationHelper.java | 52 +++++++++++++++++++ 5 files changed, 57 insertions(+), 53 deletions(-) create mode 100644 pgpainless-sop/src/main/java/org/pgpainless/sop/VerificationHelper.java diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripEncryptDecryptCmdTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripEncryptDecryptCmdTest.java index d314de20..8c294e59 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripEncryptDecryptCmdTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/RoundTripEncryptDecryptCmdTest.java @@ -129,7 +129,7 @@ public class RoundTripEncryptDecryptCmdTest extends CLITest { String romeosVerif = readStringFromFile(anotherVerificationsFile); assertEquals(julietsVerif, romeosVerif); assertFalse(julietsVerif.isEmpty()); - assertEquals(103, julietsVerif.length()); // 103 is number of symbols in [DATE, FINGER, FINGER] for V4 + assertEquals(115, julietsVerif.length()); // 115 is number of symbols in [DATE, FINGER, FINGER, MODE] for V4 } @Test @@ -274,7 +274,7 @@ public class RoundTripEncryptDecryptCmdTest extends CLITest { assertEquals(plaintext, out.toString()); String verificationString = readStringFromFile(verifications); - assertEquals("2022-11-09T17:22:48Z C0DCEC44B1A173664B05DABCECD0BF863F65C9A5 A2EC077FC977E15DD799EFF92C0D3C123CF51C08\n", + assertEquals("2022-11-09T17:22:48Z C0DCEC44B1A173664B05DABCECD0BF863F65C9A5 A2EC077FC977E15DD799EFF92C0D3C123CF51C08 mode:binary\n", verificationString); } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java index f7876799..d15713ca 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java @@ -147,7 +147,7 @@ public class DecryptImpl implements Decrypt { List verificationList = new ArrayList<>(); for (SignatureVerification signatureVerification : metadata.getVerifiedInlineSignatures()) { - verificationList.add(map(signatureVerification)); + verificationList.add(VerificationHelper.mapVerification(signatureVerification)); } SessionKey sessionKey = null; @@ -163,10 +163,4 @@ public class DecryptImpl implements Decrypt { } }; } - - private Verification map(SignatureVerification sigVerification) { - return new Verification(sigVerification.getSignature().getCreationTime(), - sigVerification.getSigningKey().getSubkeyFingerprint().toString(), - sigVerification.getSigningKey().getPrimaryKeyFingerprint().toString()); - } } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedVerifyImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedVerifyImpl.java index f0cb1161..cdae0215 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedVerifyImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedVerifyImpl.java @@ -12,7 +12,6 @@ import java.util.List; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.util.io.Streams; import org.pgpainless.PGPainless; import org.pgpainless.decryption_verification.ConsumerOptions; @@ -21,7 +20,6 @@ import org.pgpainless.decryption_verification.MessageMetadata; import org.pgpainless.decryption_verification.SignatureVerification; import org.pgpainless.exception.MalformedOpenPgpMessageException; import sop.Verification; -import sop.enums.SignatureMode; import sop.exception.SOPGPException; import sop.operation.DetachedVerify; @@ -78,7 +76,7 @@ public class DetachedVerifyImpl implements DetachedVerify { List verificationList = new ArrayList<>(); for (SignatureVerification signatureVerification : metadata.getVerifiedDetachedSignatures()) { - verificationList.add(map(signatureVerification)); + verificationList.add(VerificationHelper.mapVerification(signatureVerification)); } if (!options.getCertificateSource().getExplicitCertificates().isEmpty()) { @@ -92,23 +90,4 @@ public class DetachedVerifyImpl implements DetachedVerify { throw new SOPGPException.BadData(e); } } - - private Verification map(SignatureVerification sigVerification) { - return new Verification(sigVerification.getSignature().getCreationTime(), - sigVerification.getSigningKey().getSubkeyFingerprint().toString(), - sigVerification.getSigningKey().getPrimaryKeyFingerprint().toString(), - getMode(sigVerification.getSignature()), - null); - } - - private static SignatureMode getMode(PGPSignature signature) { - if (signature.getSignatureType() == PGPSignature.BINARY_DOCUMENT) { - return SignatureMode.binary; - } - if (signature.getSignatureType() == PGPSignature.CANONICAL_TEXT_DOCUMENT) { - return SignatureMode.text; - } - - return null; - } } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java index eea5ebfb..aecb891b 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java @@ -13,7 +13,6 @@ import java.util.List; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.util.io.Streams; import org.pgpainless.PGPainless; import org.pgpainless.decryption_verification.ConsumerOptions; @@ -24,7 +23,6 @@ import org.pgpainless.exception.MalformedOpenPgpMessageException; import org.pgpainless.exception.MissingDecryptionMethodException; import sop.ReadyWithResult; import sop.Verification; -import sop.enums.SignatureMode; import sop.exception.SOPGPException; import sop.operation.InlineVerify; @@ -76,7 +74,7 @@ public class InlineVerifyImpl implements InlineVerify { metadata.getVerifiedInlineSignatures(); for (SignatureVerification signatureVerification : verifications) { - verificationList.add(map(signatureVerification)); + verificationList.add(VerificationHelper.mapVerification(signatureVerification)); } if (!options.getCertificateSource().getExplicitCertificates().isEmpty()) { @@ -94,23 +92,4 @@ public class InlineVerifyImpl implements InlineVerify { } }; } - - private Verification map(SignatureVerification sigVerification) { - return new Verification(sigVerification.getSignature().getCreationTime(), - sigVerification.getSigningKey().getSubkeyFingerprint().toString(), - sigVerification.getSigningKey().getPrimaryKeyFingerprint().toString(), - getMode(sigVerification.getSignature()), - null); - } - - private static SignatureMode getMode(PGPSignature signature) { - if (signature.getSignatureType() == PGPSignature.BINARY_DOCUMENT) { - return SignatureMode.binary; - } - if (signature.getSignatureType() == PGPSignature.CANONICAL_TEXT_DOCUMENT) { - return SignatureMode.text; - } - - return null; - } } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/VerificationHelper.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/VerificationHelper.java new file mode 100644 index 00000000..126a5e3b --- /dev/null +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/VerificationHelper.java @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.sop; + +import org.bouncycastle.openpgp.PGPSignature; +import org.pgpainless.decryption_verification.SignatureVerification; +import sop.Verification; +import sop.enums.SignatureMode; + +/** + * Helper class for shared methods related to {@link Verification Verifications}. + */ +public class VerificationHelper { + + /** + * Map a {@link SignatureVerification} object to a {@link Verification}. + * + * @param sigVerification signature verification + * @return verification + */ + public static Verification mapVerification(SignatureVerification sigVerification) { + return new Verification( + sigVerification.getSignature().getCreationTime(), + sigVerification.getSigningKey().getSubkeyFingerprint().toString(), + sigVerification.getSigningKey().getPrimaryKeyFingerprint().toString(), + getMode(sigVerification.getSignature()), + null); + } + + /** + * Map an OpenPGP signature type to a {@link SignatureMode} enum. + * Note: This method only maps {@link PGPSignature#BINARY_DOCUMENT} and {@link PGPSignature#CANONICAL_TEXT_DOCUMENT}. + * Other values are mapped to
null
. + * + * @param signature signature + * @return signature mode enum or null + */ + private static SignatureMode getMode(PGPSignature signature) { + + if (signature.getSignatureType() == PGPSignature.BINARY_DOCUMENT) { + return SignatureMode.binary; + } + + if (signature.getSignatureType() == PGPSignature.CANONICAL_TEXT_DOCUMENT) { + return SignatureMode.text; + } + + return null; + } +} From d5f3dc80bca5706c7381e8163f42a0584bde6fb7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 18 Apr 2023 19:00:33 +0200 Subject: [PATCH 033/528] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51f7a815..893638c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ SPDX-License-Identifier: CC0-1.0 # PGPainless Changelog +## 1.5.1-SNAPSHOT +- SOP: Emit signature `mode:{binary|text}` in `Verification` results + ## 1.5.0 - Bump `bcpg-jdk15to18` to `1.73` - Bump `bcprov-jdk15to18` to `1.73` From d10841c57a0a76a2092ef7f7ab5d9436d2fcf51c Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 24 Apr 2023 16:13:11 +0200 Subject: [PATCH 034/528] Add workflow for pull requests --- .github/workflows/pr.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/pr.yml diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 00000000..cc1ca4e0 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2021 Paul Schaub +# +# SPDX-License-Identifier: Apache-2.0 + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: Build + +on: + pull_request: + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + - name: Build, Check and Coverage + uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 + with: + arguments: check jacocoRootReport From 0cb088525193ce845ee4cb51d66ac7c21513ddc1 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 25 Apr 2023 13:28:07 +0200 Subject: [PATCH 035/528] Relax constraints on decryption keys to improve interop with faulty, broken legacy clients that have been very naughty and need punishment --- .../OpenPgpMessageInputStream.java | 9 ++-- .../org/pgpainless/key/info/KeyRingInfo.java | 46 +++++++++++++++++++ ...ntDecryptionUsingNonEncryptionKeyTest.java | 18 ++++++++ 3 files changed, 68 insertions(+), 5 deletions(-) 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 7fe11bbf..19a01fbf 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 @@ -43,15 +43,14 @@ import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; import org.bouncycastle.util.io.TeeInputStream; import org.pgpainless.PGPainless; import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.EncryptionPurpose; import org.pgpainless.algorithm.OpenPgpPacket; import org.pgpainless.algorithm.StreamEncoding; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.decryption_verification.cleartext_signatures.ClearsignedMessageUtil; +import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy; import org.pgpainless.decryption_verification.syntax_check.InputSymbol; import org.pgpainless.decryption_verification.syntax_check.PDA; import org.pgpainless.decryption_verification.syntax_check.StackSymbol; -import org.pgpainless.decryption_verification.cleartext_signatures.ClearsignedMessageUtil; -import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy; import org.pgpainless.exception.MalformedOpenPgpMessageException; import org.pgpainless.exception.MessageNotIntegrityProtectedException; import org.pgpainless.exception.MissingDecryptionMethodException; @@ -674,7 +673,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { for (PGPSecretKeyRing secretKeys : options.getDecryptionKeys()) { KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); - for (PGPPublicKey publicKey : info.getEncryptionSubkeys(EncryptionPurpose.ANY)) { + for (PGPPublicKey publicKey : info.getDecryptionSubkeys()) { if (publicKey.getAlgorithm() == algorithm && info.isSecretKeyAvailable(publicKey.getKeyID())) { PGPSecretKey candidate = secretKeys.getSecretKey(publicKey.getKeyID()); decryptionKeyCandidates.add(new Tuple<>(secretKeys, candidate)); @@ -692,7 +691,7 @@ public class OpenPgpMessageInputStream extends DecryptionStream { } KeyRingInfo info = new KeyRingInfo(secretKeys, policy, new Date()); - List encryptionKeys = info.getEncryptionSubkeys(EncryptionPurpose.ANY); + List encryptionKeys = info.getDecryptionSubkeys(); for (PGPPublicKey key : encryptionKeys) { if (key.getKeyID() == keyID) { return secretKeys; 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 1ebd023e..75d9e16c 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 @@ -41,11 +41,15 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.exception.KeyException; import org.pgpainless.key.OpenPgpFingerprint; import org.pgpainless.key.SubkeyIdentifier; +import org.pgpainless.key.util.KeyIdUtil; import org.pgpainless.key.util.RevocationAttributes; import org.pgpainless.policy.Policy; import org.pgpainless.signature.SignatureUtils; import org.pgpainless.signature.consumer.SignaturePicker; import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil; +import org.pgpainless.util.DateUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Utility class to quickly extract certain information from a {@link PGPPublicKeyRing}/{@link PGPSecretKeyRing}. @@ -55,6 +59,8 @@ public class KeyRingInfo { private static final Pattern PATTERN_EMAIL_FROM_USERID = Pattern.compile("<([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)>"); private static final Pattern PATTERN_EMAIL_EXPLICIT = Pattern.compile("^([a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+)$"); + private static final Logger LOGGER = LoggerFactory.getLogger(KeyRingInfo.class); + private final PGPKeyRing keys; private final Signatures signatures; private final Date referenceDate; @@ -904,6 +910,7 @@ public class KeyRingInfo { public @Nonnull List getEncryptionSubkeys(EncryptionPurpose purpose) { Date primaryExpiration = getPrimaryKeyExpirationDate(); if (primaryExpiration != null && primaryExpiration.before(referenceDate)) { + LOGGER.debug("Certificate is expired: Primary key is expired on " + DateUtil.formatUTCDate(primaryExpiration)); return Collections.emptyList(); } @@ -913,15 +920,18 @@ public class KeyRingInfo { PGPPublicKey subKey = subkeys.next(); if (!isKeyValidlyBound(subKey.getKeyID())) { + LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " is not validly bound."); continue; } Date subkeyExpiration = getSubkeyExpirationDate(OpenPgpFingerprint.of(subKey)); if (subkeyExpiration != null && subkeyExpiration.before(referenceDate)) { + LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " is expired on " + DateUtil.formatUTCDate(subkeyExpiration)); continue; } if (!subKey.isEncryptionKey()) { + LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " algorithm is not capable of encryption."); continue; } @@ -947,6 +957,42 @@ public class KeyRingInfo { return encryptionKeys; } + /** + * Return a list of all subkeys that could potentially be used to decrypt a message. + * Contrary to {@link #getEncryptionSubkeys(EncryptionPurpose)}, this method also includes revoked, expired keys, + * as well as keys which do not carry any encryption keyflags. + * Merely keys which use algorithms that cannot be used for encryption at all are excluded. + * That way, decryption of messages produced by faulty implementations can still be decrypted. + * + * @return decryption keys + */ + public @Nonnull List getDecryptionSubkeys() { + Iterator subkeys = keys.getPublicKeys(); + List decryptionKeys = new ArrayList<>(); + + while (subkeys.hasNext()) { + PGPPublicKey subKey = subkeys.next(); + + // subkeys have been valid at some point + if (subKey.getKeyID() != getKeyId()) { + PGPSignature binding = signatures.subkeyBindings.get(subKey.getKeyID()); + if (binding == null) { + LOGGER.debug("Subkey " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " was never validly bound."); + continue; + } + } + + // Public-Key algorithm can encrypt + if (!subKey.isEncryptionKey()) { + LOGGER.debug("(Sub?)-Key " + KeyIdUtil.formatKeyId(subKey.getKeyID()) + " is not encryption-capable."); + continue; + } + + decryptionKeys.add(subKey); + } + return decryptionKeys; + } + /** * Return a list of all keys which carry the provided key flag in their signature. * diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PreventDecryptionUsingNonEncryptionKeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PreventDecryptionUsingNonEncryptionKeyTest.java index ba80c69d..04a98265 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PreventDecryptionUsingNonEncryptionKeyTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PreventDecryptionUsingNonEncryptionKeyTest.java @@ -14,6 +14,7 @@ import java.nio.charset.StandardCharsets; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.util.io.Streams; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; import org.pgpainless.exception.MissingDecryptionMethodException; @@ -189,6 +190,23 @@ public class PreventDecryptionUsingNonEncryptionKeyTest { } @Test + public void canDecryptMessageDespiteMissingKeyFlag() throws IOException, PGPException { + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(ENCRYPTION_INCAPABLE_KEY); + + ByteArrayInputStream msgIn = new ByteArrayInputStream(MSG.getBytes(StandardCharsets.UTF_8)); + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() + .onInputStream(msgIn) + .withOptions(new ConsumerOptions().addDecryptionKey(secretKeys)); + + Streams.drain(decryptionStream); + decryptionStream.close(); + OpenPgpMetadata metadata = decryptionStream.getResult(); + + assertEquals(new SubkeyIdentifier(secretKeys, secretKeys.getPublicKey().getKeyID()), metadata.getDecryptionKey()); + } + + @Test + @Disabled public void nonEncryptionKeyCannotDecrypt() throws IOException { PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(ENCRYPTION_INCAPABLE_KEY); From 699381238c97089b4c9e3e27cb9c091ed687c654 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 25 Apr 2023 13:28:38 +0200 Subject: [PATCH 036/528] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 893638c3..22e452e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ SPDX-License-Identifier: CC0-1.0 ## 1.5.1-SNAPSHOT - SOP: Emit signature `mode:{binary|text}` in `Verification` results +- core: Relax constraints on decryption subkeys to improve interoperability with broken clients + - Allow decryption with revoked keys + - Allow decryption with expired keys + - Allow decryption with erroneously addressed keys without encryption key flags ## 1.5.0 - Bump `bcpg-jdk15to18` to `1.73` From bf2bb31b711d14ca872f6d6d6f49034f98540797 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 25 Apr 2023 13:36:48 +0200 Subject: [PATCH 037/528] PGPainless 1.5.1 --- CHANGELOG.md | 2 +- README.md | 2 +- pgpainless-sop/README.md | 4 ++-- version.gradle | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e452e6..d59bf372 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ SPDX-License-Identifier: CC0-1.0 # PGPainless Changelog -## 1.5.1-SNAPSHOT +## 1.5.1 - SOP: Emit signature `mode:{binary|text}` in `Verification` results - core: Relax constraints on decryption subkeys to improve interoperability with broken clients - Allow decryption with revoked keys diff --git a/README.md b/README.md index 14ff388d..06e9f2ed 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ repositories { } dependencies { - implementation 'org.pgpainless:pgpainless-core:1.5.0' + implementation 'org.pgpainless:pgpainless-core:1.5.1' } ``` diff --git a/pgpainless-sop/README.md b/pgpainless-sop/README.md index b4e755dd..7ae0a63c 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.0" + implementation "org.pgpainless:pgpainless-sop:1.5.1" ... } @@ -34,7 +34,7 @@ dependencies { org.pgpainless pgpainless-sop - 1.5.0 + 1.5.1 ... diff --git a/version.gradle b/version.gradle index 3d271a75..88c3babd 100644 --- a/version.gradle +++ b/version.gradle @@ -5,7 +5,7 @@ allprojects { ext { shortVersion = '1.5.1' - isSnapshot = true + isSnapshot = false pgpainlessMinAndroidSdk = 10 javaSourceCompatibility = 1.8 bouncyCastleVersion = '1.73' From 23fd630670e1d734d22effad5843a5e9ada49b49 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 25 Apr 2023 13:39:44 +0200 Subject: [PATCH 038/528] 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 039/528] 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 040/528] 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 041/528] 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 042/528] 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 043/528] 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 044/528] 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 045/528] 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 046/528] 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 047/528] 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 048/528] 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 049/528] 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 050/528] 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 051/528] 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 052/528] 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 053/528] 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 054/528] 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 055/528] 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 056/528] 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 057/528] 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 058/528] 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 059/528] 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 060/528] 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 061/528] 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 062/528] 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 063/528] 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: *