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

Compare commits

...

4 commits

14 changed files with 109 additions and 44 deletions

View file

@ -247,7 +247,8 @@ 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

View file

@ -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("\ttrimThis@user.id ")
.addUserId("\twithWhitespace@user.id ")
.setExpirationDate(expiration)
.build()
.getPGPSecretKeyRing();
@ -52,7 +52,7 @@ public class GenerateKeyWithAdditionalUserIdTest {
assertEquals("<primary@user.id>", userIds.next());
assertEquals("<additional@user.id>", userIds.next());
assertEquals("<additional2@user.id>", userIds.next());
assertEquals("trimThis@user.id", userIds.next());
assertEquals("\twithWhitespace@user.id ", userIds.next());
assertFalse(userIds.hasNext());
}
}

View file

@ -34,19 +34,20 @@ public class PassphraseTest {
@Test
public void testTrimming() {
Passphrase leadingSpace = Passphrase.fromPassword(" space");
Passphrase leadingSpace = Passphrase.fromPassword(" space").withTrimmedWhitespace();
assertArrayEquals("space".toCharArray(), leadingSpace.getChars());
assertFalse(leadingSpace.isEmpty());
Passphrase trailingSpace = Passphrase.fromPassword("space ");
Passphrase trailingSpace = Passphrase.fromPassword("space ").withTrimmedWhitespace();
assertArrayEquals("space".toCharArray(), trailingSpace.getChars());
assertFalse(trailingSpace.isEmpty());
Passphrase leadingTrailingWhitespace = new Passphrase("\t Such whitespace, much wow\n ".toCharArray());
Passphrase leadingTrailingWhitespace = new Passphrase("\t Such whitespace, much wow\n ".toCharArray())
.withTrimmedWhitespace();
assertArrayEquals("Such whitespace, much wow".toCharArray(), leadingTrailingWhitespace.getChars());
assertFalse(leadingTrailingWhitespace.isEmpty());
Passphrase fromEmptyChars = new Passphrase(" ".toCharArray());
Passphrase fromEmptyChars = new Passphrase(" ".toCharArray()).withTrimmedWhitespace();
assertNull(fromEmptyChars.getChars());
assertTrue(fromEmptyChars.isEmpty());
}
@ -54,7 +55,7 @@ public class PassphraseTest {
@ParameterizedTest
@ValueSource(strings = {"", " ", " ", "\t", "\t\t"})
public void testEmptyPassphrases(String empty) {
Passphrase passphrase = Passphrase.fromPassword(empty);
Passphrase passphrase = Passphrase.fromPassword(empty).withTrimmedWhitespace();
assertTrue(passphrase.isEmpty());
assertEquals(Passphrase.emptyPassphrase(), passphrase);

View file

@ -10,7 +10,6 @@ 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
@ -79,6 +78,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 {
protector.addPassphrase(Passphrase.fromPassword(String(password)))
PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector)
}
}

View file

@ -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 {
oldProtector.addPassphrase(Passphrase.fromPassword(oldPassphrase))
PasswordHelper.addPassphrasePlusRemoveWhitespace(oldPassphrase, oldProtector)
}
}

View file

@ -16,13 +16,11 @@ 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 {
@ -98,16 +96,11 @@ class DecryptImpl(private val api: PGPainless) : Decrypt {
}
override fun withKeyPassword(password: ByteArray): Decrypt = apply {
protector.addPassphrase(Passphrase.fromPassword(String(password, UTF8Util.UTF8)))
PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector)
}
override fun withPassword(password: String): Decrypt = apply {
consumerOptions.addMessagePassphrase(Passphrase.fromPassword(password))
password.trimEnd().let {
if (it != password) {
consumerOptions.addMessagePassphrase(Passphrase.fromPassword(it))
}
}
PasswordHelper.addMessagePassphrasePlusRemoveWhitespace(password, consumerOptions)
}
override fun withSessionKey(sessionKey: SessionKey): Decrypt = apply {

View file

@ -19,14 +19,12 @@ 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 {
@ -108,7 +106,7 @@ class DetachedSignImpl(private val api: PGPainless) : DetachedSign {
override fun noArmor(): DetachedSign = apply { armor = false }
override fun withKeyPassword(password: ByteArray): DetachedSign = apply {
protector.addPassphrase(Passphrase.fromPassword(String(password, UTF8Util.UTF8)))
PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector)
}
private fun modeToSigType(mode: SignAs): DocumentSignatureType {

View file

@ -30,7 +30,6 @@ 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 {
@ -148,11 +147,12 @@ class EncryptImpl(private val api: PGPainless) : Encrypt {
}
override fun withKeyPassword(password: ByteArray): Encrypt = apply {
protector.addPassphrase(Passphrase.fromPassword(String(password, UTF8Util.UTF8)))
PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector)
}
override fun withPassword(password: String): Encrypt = apply {
encryptionOptions.addMessagePassphrase(Passphrase.fromPassword(password))
encryptionOptions.addMessagePassphrase(
Passphrase.fromPassword(password).withTrimmedWhitespace())
}
private fun modeToStreamEncoding(mode: EncryptAs): StreamEncoding {

View file

@ -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)
this.passphrase = Passphrase.fromPassword(password).withTrimmedWhitespace()
}
private fun generateKeyWithProfile(

View file

@ -18,12 +18,10 @@ 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 {
@ -112,7 +110,7 @@ class InlineSignImpl(private val api: PGPainless) : InlineSign {
override fun noArmor(): InlineSign = apply { armor = false }
override fun withKeyPassword(password: ByteArray): InlineSign = apply {
protector.addPassphrase(Passphrase.fromPassword(String(password, UTF8Util.UTF8)))
PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector)
}
private fun modeToSigType(mode: InlineSignAs): DocumentSignatureType {

View file

@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <info@pgpainless.org>
//
// 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)
}
}
}
}
}

View file

@ -15,11 +15,9 @@ 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 {
@ -78,14 +76,6 @@ class RevokeKeyImpl(private val api: PGPainless) : RevokeKey {
override fun noArmor(): RevokeKey = apply { armor = false }
override fun withKeyPassword(password: ByteArray): RevokeKey = apply {
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))
PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector)
}
}

View file

@ -13,7 +13,6 @@ 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
@ -81,6 +80,6 @@ class UpdateKeyImpl(private val api: PGPainless) : UpdateKey {
override fun signingOnly(): UpdateKey = apply { signingOnly = true }
override fun withKeyPassword(password: ByteArray): UpdateKey = apply {
protector.addPassphrase(Passphrase.fromPassword(String(password)))
PasswordHelper.addPassphrasePlusRemoveWhitespace(password, protector)
}
}

View file

@ -15,6 +15,7 @@ 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;
@ -108,6 +109,7 @@ public class GenerateKeyTest {
.generate()
.getBytes();
assertTrue(new String(keyBytes).contains("Foo\\n\\nBar"));
OpenPGPKey key = PGPainless.getInstance().readKey().parseKey(keyBytes);
assertTrue(key.getValidUserIds().get(0).getUserId().equals("Foo\n\nBar"));
}
}