diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt index 8f2d6002..92da93c5 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/ArmorUtils.kt @@ -247,8 +247,7 @@ class ArmorUtils { .add(OpenPgpFingerprint.of(publicKey).prettyPrint()) // Primary / First User ID (primary ?: first)?.let { - headerMap - .getOrPut(HEADER_COMMENT) { mutableSetOf() } + headerMap.getOrPut(HEADER_COMMENT) { mutableSetOf() } .add(it.replace("\n", "\\n").replace("\r", "\\r")) } // X-1 further identities diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateKeyWithAdditionalUserIdTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateKeyWithAdditionalUserIdTest.java index 8e607d07..2cdd4131 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateKeyWithAdditionalUserIdTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateKeyWithAdditionalUserIdTest.java @@ -40,7 +40,7 @@ public class GenerateKeyWithAdditionalUserIdTest { .addUserId(UserId.onlyEmail("primary@user.id")) .addUserId(UserId.onlyEmail("additional@user.id")) .addUserId(UserId.onlyEmail("additional2@user.id")) - .addUserId("\twithWhitespace@user.id ") + .addUserId("\ttrimThis@user.id ") .setExpirationDate(expiration) .build() .getPGPSecretKeyRing(); @@ -52,7 +52,7 @@ public class GenerateKeyWithAdditionalUserIdTest { assertEquals("", userIds.next()); assertEquals("", userIds.next()); assertEquals("", userIds.next()); - assertEquals("\twithWhitespace@user.id ", userIds.next()); + assertEquals("trimThis@user.id", userIds.next()); assertFalse(userIds.hasNext()); } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseTest.java index fb428349..83477b28 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseTest.java @@ -34,20 +34,19 @@ public class PassphraseTest { @Test public void testTrimming() { - Passphrase leadingSpace = Passphrase.fromPassword(" space").withTrimmedWhitespace(); + Passphrase leadingSpace = Passphrase.fromPassword(" space"); assertArrayEquals("space".toCharArray(), leadingSpace.getChars()); assertFalse(leadingSpace.isEmpty()); - Passphrase trailingSpace = Passphrase.fromPassword("space ").withTrimmedWhitespace(); + Passphrase trailingSpace = Passphrase.fromPassword("space "); assertArrayEquals("space".toCharArray(), trailingSpace.getChars()); assertFalse(trailingSpace.isEmpty()); - Passphrase leadingTrailingWhitespace = new Passphrase("\t Such whitespace, much wow\n ".toCharArray()) - .withTrimmedWhitespace(); + Passphrase leadingTrailingWhitespace = new Passphrase("\t Such whitespace, much wow\n ".toCharArray()); assertArrayEquals("Such whitespace, much wow".toCharArray(), leadingTrailingWhitespace.getChars()); assertFalse(leadingTrailingWhitespace.isEmpty()); - Passphrase fromEmptyChars = new Passphrase(" ".toCharArray()).withTrimmedWhitespace(); + Passphrase fromEmptyChars = new Passphrase(" ".toCharArray()); assertNull(fromEmptyChars.getChars()); assertTrue(fromEmptyChars.isEmpty()); } @@ -55,7 +54,7 @@ public class PassphraseTest { @ParameterizedTest @ValueSource(strings = {"", " ", " ", "\t", "\t\t"}) public void testEmptyPassphrases(String empty) { - Passphrase passphrase = Passphrase.fromPassword(empty).withTrimmedWhitespace(); + Passphrase passphrase = Passphrase.fromPassword(empty); assertTrue(passphrase.isEmpty()); assertEquals(Passphrase.emptyPassphrase(), passphrase); diff --git a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/CertifyUserIdImpl.kt b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/CertifyUserIdImpl.kt index 2eedc13e..cbd97934 100644 --- a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/CertifyUserIdImpl.kt +++ b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/CertifyUserIdImpl.kt @@ -10,6 +10,7 @@ import org.bouncycastle.openpgp.api.OpenPGPKey import org.pgpainless.PGPainless import org.pgpainless.exception.KeyException import org.pgpainless.util.OpenPGPCertificateUtil +import org.pgpainless.util.Passphrase import sop.Ready import sop.exception.SOPGPException import sop.operation.CertifyUserId @@ -78,6 +79,6 @@ class CertifyUserIdImpl(private val api: PGPainless) : CertifyUserId { override fun userId(userId: String): CertifyUserId = apply { this.userIds.add(userId) } override fun withKeyPassword(password: ByteArray): CertifyUserId = apply { - PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector) + protector.addPassphrase(Passphrase.fromPassword(String(password))) } } diff --git a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/ChangeKeyPasswordImpl.kt b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/ChangeKeyPasswordImpl.kt index 0cdfdbf7..a7d0c530 100644 --- a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/ChangeKeyPasswordImpl.kt +++ b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/ChangeKeyPasswordImpl.kt @@ -73,6 +73,6 @@ class ChangeKeyPasswordImpl(private val api: PGPainless) : ChangeKeyPassword { override fun noArmor(): ChangeKeyPassword = apply { armor = false } override fun oldKeyPassphrase(oldPassphrase: String): ChangeKeyPassword = apply { - PasswordHelper.addPassphrasePlusRemoveWhitespace(oldPassphrase, oldProtector) + oldProtector.addPassphrase(Passphrase.fromPassword(oldPassphrase)) } } diff --git a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/DecryptImpl.kt b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/DecryptImpl.kt index e7993492..ddd11d50 100644 --- a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/DecryptImpl.kt +++ b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/DecryptImpl.kt @@ -16,11 +16,13 @@ import org.pgpainless.decryption_verification.ConsumerOptions import org.pgpainless.exception.MalformedOpenPgpMessageException import org.pgpainless.exception.MissingDecryptionMethodException import org.pgpainless.exception.WrongPassphraseException +import org.pgpainless.util.Passphrase import sop.DecryptionResult import sop.ReadyWithResult import sop.SessionKey import sop.exception.SOPGPException import sop.operation.Decrypt +import sop.util.UTF8Util /** Implementation of the `decrypt` operation using PGPainless. */ class DecryptImpl(private val api: PGPainless) : Decrypt { @@ -96,11 +98,16 @@ class DecryptImpl(private val api: PGPainless) : Decrypt { } override fun withKeyPassword(password: ByteArray): Decrypt = apply { - PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector) + protector.addPassphrase(Passphrase.fromPassword(String(password, UTF8Util.UTF8))) } override fun withPassword(password: String): Decrypt = apply { - PasswordHelper.addMessagePassphrasePlusRemoveWhitespace(password, consumerOptions) + consumerOptions.addMessagePassphrase(Passphrase.fromPassword(password)) + password.trimEnd().let { + if (it != password) { + consumerOptions.addMessagePassphrase(Passphrase.fromPassword(it)) + } + } } override fun withSessionKey(sessionKey: SessionKey): Decrypt = apply { diff --git a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/DetachedSignImpl.kt b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/DetachedSignImpl.kt index ca661ff2..e23ca1c3 100644 --- a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/DetachedSignImpl.kt +++ b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/DetachedSignImpl.kt @@ -19,12 +19,14 @@ import org.pgpainless.encryption_signing.SigningOptions import org.pgpainless.exception.KeyException.MissingSecretKeyException import org.pgpainless.exception.KeyException.UnacceptableSigningKeyException import org.pgpainless.util.ArmoredOutputStreamFactory +import org.pgpainless.util.Passphrase import sop.MicAlg import sop.ReadyWithResult import sop.SigningResult import sop.enums.SignAs import sop.exception.SOPGPException import sop.operation.DetachedSign +import sop.util.UTF8Util /** Implementation of the `sign` operation using PGPainless. */ class DetachedSignImpl(private val api: PGPainless) : DetachedSign { @@ -106,7 +108,7 @@ class DetachedSignImpl(private val api: PGPainless) : DetachedSign { override fun noArmor(): DetachedSign = apply { armor = false } override fun withKeyPassword(password: ByteArray): DetachedSign = apply { - PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector) + protector.addPassphrase(Passphrase.fromPassword(String(password, UTF8Util.UTF8))) } private fun modeToSigType(mode: SignAs): DocumentSignatureType { diff --git a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/EncryptImpl.kt b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/EncryptImpl.kt index 6c083acd..c8a71c24 100644 --- a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/EncryptImpl.kt +++ b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/EncryptImpl.kt @@ -30,6 +30,7 @@ import sop.SessionKey import sop.enums.EncryptAs import sop.exception.SOPGPException import sop.operation.Encrypt +import sop.util.UTF8Util /** Implementation of the `encrypt` operation using PGPainless. */ class EncryptImpl(private val api: PGPainless) : Encrypt { @@ -147,12 +148,11 @@ class EncryptImpl(private val api: PGPainless) : Encrypt { } override fun withKeyPassword(password: ByteArray): Encrypt = apply { - PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector) + protector.addPassphrase(Passphrase.fromPassword(String(password, UTF8Util.UTF8))) } override fun withPassword(password: String): Encrypt = apply { - encryptionOptions.addMessagePassphrase( - Passphrase.fromPassword(password).withTrimmedWhitespace()) + encryptionOptions.addMessagePassphrase(Passphrase.fromPassword(password)) } private fun modeToStreamEncoding(mode: EncryptAs): StreamEncoding { diff --git a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/GenerateKeyImpl.kt b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/GenerateKeyImpl.kt index 72998b69..e220f1bf 100644 --- a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/GenerateKeyImpl.kt +++ b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/GenerateKeyImpl.kt @@ -99,7 +99,7 @@ class GenerateKeyImpl(private val api: PGPainless) : GenerateKey { override fun userId(userId: String): GenerateKey = apply { userIds.add(userId) } override fun withKeyPassword(password: String): GenerateKey = apply { - this.passphrase = Passphrase.fromPassword(password).withTrimmedWhitespace() + this.passphrase = Passphrase.fromPassword(password) } private fun generateKeyWithProfile( diff --git a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/InlineSignImpl.kt b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/InlineSignImpl.kt index 4c9024f7..fd7abfac 100644 --- a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/InlineSignImpl.kt +++ b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/InlineSignImpl.kt @@ -18,10 +18,12 @@ import org.pgpainless.encryption_signing.ProducerOptions import org.pgpainless.encryption_signing.SigningOptions import org.pgpainless.exception.KeyException.MissingSecretKeyException import org.pgpainless.exception.KeyException.UnacceptableSigningKeyException +import org.pgpainless.util.Passphrase import sop.Ready import sop.enums.InlineSignAs import sop.exception.SOPGPException import sop.operation.InlineSign +import sop.util.UTF8Util /** Implementation of the `inline-sign` operation using PGPainless. */ class InlineSignImpl(private val api: PGPainless) : InlineSign { @@ -110,7 +112,7 @@ class InlineSignImpl(private val api: PGPainless) : InlineSign { override fun noArmor(): InlineSign = apply { armor = false } override fun withKeyPassword(password: ByteArray): InlineSign = apply { - PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector) + protector.addPassphrase(Passphrase.fromPassword(String(password, UTF8Util.UTF8))) } private fun modeToSigType(mode: InlineSignAs): DocumentSignatureType { diff --git a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/PasswordHelper.kt b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/PasswordHelper.kt deleted file mode 100644 index 3baa6337..00000000 --- a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/PasswordHelper.kt +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.sop - -import org.pgpainless.decryption_verification.ConsumerOptions -import org.pgpainless.util.Passphrase -import sop.exception.SOPGPException -import sop.util.UTF8Util - -class PasswordHelper { - companion object { - - /** - * Add the given [password] as a message passphrase to the given [consumerOptions] instance. - * If the [password] contains trailing or leading whitespace, additionally add the - * [password] with these whitespace characters removed. - * - * @param password password - * @param consumerOptions consumer options for message decryption - */ - @JvmStatic - fun addMessagePassphrasePlusRemoveWhitespace( - password: String, - consumerOptions: ConsumerOptions - ) { - Passphrase.fromPassword(password).let { - consumerOptions.addMessagePassphrase(it) - val trimmed = it.withTrimmedWhitespace() - if (!it.getChars().contentEquals(trimmed.getChars())) { - consumerOptions.addMessagePassphrase(trimmed) - } - } - } - - /** - * Add the given [password] to the given [protector] instance. If the [password] contains - * trailing or leading whitespace, additionally add the [password] with these whitespace - * characters removed. - * - * @param password password - * @param protector secret key ring protector - * @throws SOPGPException.PasswordNotHumanReadable if the password is not a valid UTF-8 - * string representation. - */ - @JvmStatic - fun addPassphrasePlusRemoveWhitespace( - password: ByteArray, - protector: MatchMakingSecretKeyRingProtector - ) { - val string = - try { - UTF8Util.decodeUTF8(password) - } catch (e: CharacterCodingException) { - throw SOPGPException.PasswordNotHumanReadable( - "Cannot UTF8-decode password: ${e.stackTraceToString()}") - } - addPassphrasePlusRemoveWhitespace(string, protector) - } - - /** - * Add the given [password] to the given [protector] instance. If the [password] contains - * trailing or leading whitespace, additionally add the [password] with these whitespace - * characters removed. - * - * @param password password - * @param protector secret key ring protector - */ - @JvmStatic - fun addPassphrasePlusRemoveWhitespace( - password: String, - protector: MatchMakingSecretKeyRingProtector - ) { - Passphrase.fromPassword(password).let { - protector.addPassphrase(it) - val trimmed = it.withTrimmedWhitespace() - if (!it.getChars().contentEquals(trimmed.getChars())) { - protector.addPassphrase(trimmed) - } - } - } - } -} diff --git a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/RevokeKeyImpl.kt b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/RevokeKeyImpl.kt index 0fe4c6be..0c7e3585 100644 --- a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/RevokeKeyImpl.kt +++ b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/RevokeKeyImpl.kt @@ -15,9 +15,11 @@ import org.pgpainless.exception.WrongPassphraseException import org.pgpainless.key.util.KeyRingUtils import org.pgpainless.key.util.RevocationAttributes import org.pgpainless.util.OpenPGPCertificateUtil +import org.pgpainless.util.Passphrase import sop.Ready import sop.exception.SOPGPException import sop.operation.RevokeKey +import sop.util.UTF8Util class RevokeKeyImpl(private val api: PGPainless) : RevokeKey { @@ -76,6 +78,14 @@ class RevokeKeyImpl(private val api: PGPainless) : RevokeKey { override fun noArmor(): RevokeKey = apply { armor = false } override fun withKeyPassword(password: ByteArray): RevokeKey = apply { - PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector) + val string = + try { + UTF8Util.decodeUTF8(password) + } catch (e: CharacterCodingException) { + // TODO: Add cause + throw SOPGPException.PasswordNotHumanReadable( + "Cannot UTF8-decode password: ${e.stackTraceToString()}") + } + protector.addPassphrase(Passphrase.fromPassword(string)) } } diff --git a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/UpdateKeyImpl.kt b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/UpdateKeyImpl.kt index dd801367..37767424 100644 --- a/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/UpdateKeyImpl.kt +++ b/pgpainless-sop/src/main/kotlin/org/pgpainless/sop/UpdateKeyImpl.kt @@ -13,6 +13,7 @@ import org.bouncycastle.openpgp.api.OpenPGPCertificate import org.pgpainless.PGPainless import org.pgpainless.key.modification.secretkeyring.OpenPGPKeyUpdater import org.pgpainless.util.OpenPGPCertificateUtil +import org.pgpainless.util.Passphrase import sop.Ready import sop.operation.UpdateKey @@ -80,6 +81,6 @@ class UpdateKeyImpl(private val api: PGPainless) : UpdateKey { override fun signingOnly(): UpdateKey = apply { signingOnly = true } override fun withKeyPassword(password: ByteArray): UpdateKey = apply { - PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector) + protector.addPassphrase(Passphrase.fromPassword(String(password))) } } 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 3abf80bc..521cdfe0 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/GenerateKeyTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/GenerateKeyTest.java @@ -15,7 +15,6 @@ import java.io.IOException; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.api.OpenPGPKey; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; @@ -109,7 +108,6 @@ public class GenerateKeyTest { .generate() .getBytes(); - OpenPGPKey key = PGPainless.getInstance().readKey().parseKey(keyBytes); - assertTrue(key.getValidUserIds().get(0).getUserId().equals("Foo\n\nBar")); + assertTrue(new String(keyBytes).contains("Foo\\n\\nBar")); } }