1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-09 18:29:39 +02:00
This commit is contained in:
Paul Schaub 2025-07-23 10:40:45 +02:00
parent 8f41fb0f27
commit 880b0720db
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
23 changed files with 226 additions and 6 deletions

View file

@ -12,6 +12,9 @@ dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// Jazzer for Fuzzing
testImplementation "com.code-intelligence:jazzer-junit:$jazzerVersion"
// Mocking Components // Mocking Components
testImplementation "org.mockito:mockito-core:$mockitoVersion" testImplementation "org.mockito:mockito-core:$mockitoVersion"

View file

@ -45,7 +45,10 @@ class KeyRingBuilder : KeyRingBuilderInterface<KeyRingBuilder> {
} }
override fun addUserId(userId: CharSequence): KeyRingBuilder = apply { override fun addUserId(userId: CharSequence): KeyRingBuilder = apply {
userIds[userId.toString().trim()] = null require(!userId.contains("\n") && !userId.contains("\r")) {
"User-ID cannot contain newlines and/or carriage returns."
}
userIds[userId.toString()] = null
} }
override fun addUserId(userId: ByteArray): KeyRingBuilder = override fun addUserId(userId: ByteArray): KeyRingBuilder =

View file

@ -478,7 +478,7 @@ class SecretKeyRingEditor(
val prevBinding = val prevBinding =
inspectKeyRing(secretKeyRing).getCurrentSubkeyBindingSignature(keyId) inspectKeyRing(secretKeyRing).getCurrentSubkeyBindingSignature(keyId)
?: throw NoSuchElementException( ?: throw NoSuchElementException(
"Previous subkey binding signaure for ${keyId.openPgpKeyId()} MUST NOT be null.") "Previous subkey binding signature for ${keyId.openPgpKeyId()} MUST NOT be null.")
val bindingSig = reissueSubkeyBindingSignature(subkey, expiration, protector, prevBinding) val bindingSig = reissueSubkeyBindingSignature(subkey, expiration, protector, prevBinding)
secretKeyRing = injectCertification(secretKeyRing, subkey, bindingSig) secretKeyRing = injectCertification(secretKeyRing, subkey, bindingSig)
} }
@ -569,9 +569,11 @@ class SecretKeyRingEditor(
} }
private fun sanitizeUserId(userId: CharSequence): CharSequence = private fun sanitizeUserId(userId: CharSequence): CharSequence =
// TODO: Further research how to sanitize user IDs. userId.toString().also {
// e.g. what about newlines? require(!it.contains("\n") && !it.contains("\r")) {
userId.toString().trim() "UserId cannot contain newlines and/or carriage returns."
}
}
private fun callbackFromRevocationAttributes(attributes: RevocationAttributes?) = private fun callbackFromRevocationAttributes(attributes: RevocationAttributes?) =
object : RevocationSignatureSubpackets.Callback { object : RevocationSignatureSubpackets.Callback {

View file

@ -17,7 +17,7 @@ class Passphrase(chars: CharArray?) {
private val chars: CharArray? private val chars: CharArray?
init { init {
this.chars = trimWhitespace(chars) this.chars = chars;//trimWhitespace(chars)
} }
/** /**
@ -67,6 +67,8 @@ class Passphrase(chars: CharArray?) {
override fun hashCode(): Int = getChars()?.let { String(it) }.hashCode() override fun hashCode(): Int = getChars()?.let { String(it) }.hashCode()
fun withTrimmedWhitespace(): Passphrase = Passphrase(trimWhitespace(chars))
companion object { companion object {
/** /**

View file

@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.bouncycastle.fuzzing
import com.code_intelligence.jazzer.api.FuzzedDataProvider
import com.code_intelligence.jazzer.junit.DictionaryFile
import com.code_intelligence.jazzer.junit.FuzzTest
import org.bouncycastle.bcpg.ArmoredInputException
import org.bouncycastle.bcpg.UnsupportedPacketVersionException
import org.bouncycastle.openpgp.PGPException
import org.bouncycastle.openpgp.PGPUtil
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory
import java.io.EOFException
import java.io.IOException
class PGPObjectFactoryFuzzingTest {
@FuzzTest
@DictionaryFile(resourcePath = "ascii_armor.dict")
@DictionaryFile(resourcePath = "openpgp.dict")
fun parseFuzzedObjects(provider: FuzzedDataProvider) {
val encoding = provider.consumeRemainingAsBytes()
if (encoding.isEmpty()) {
return
}
try {
val decIn = PGPUtil.getDecoderStream(encoding.inputStream())
val objFac = BcPGPObjectFactory(decIn)
var obj = objFac.nextObject()
while (obj != null) {
obj = objFac.nextObject()
}
} catch (e: ArmoredInputException) {
return
} catch (e: PGPException) {
return
} catch (e: EOFException) {
return
} catch (e: IOException) {
return
} catch (e: UnsupportedPacketVersionException) {
return
}
}
}

View file

@ -0,0 +1,5 @@
-----BEGIN PGP MESSAGÚ-----
ywtiAAECAwTA/+66vg==
=pAS2
-----END PGP MESSAGE-----

View file

@ -0,0 +1,5 @@
-----BEGIN PGP MESSAGE-----
ywtiAAECAwTA/+66vg==
=pAS2
-----END PGP MESSAGE-----

View file

@ -0,0 +1,39 @@
#
# AFL Dictionary for OpenPGP (RFC9580)
# ------------------------------------------
#
# Created by Paul Schaub <info@pgpainless.org>
#
# ASCII Armor
#
BEGIN_PGP_MESSAGE="-----BEGIN PGP MESSAGE-----"
END_PGP_MESSAGE="-----END PGP MESSAGE-----"
BEGIN_PGP_SIGNATURE="-----BEGIN PGP SIGNATURE-----"
END_PGP_SIGNATURE="-----END PGP SIGNATURE-----"
BEGIN_PGP_PUBLIC_KEY="-----BEGIN PGP PUBLIC KEY-----"
END_PGP_PUBLIC_KEY="-----END PGP PUBLIC KEY-----"
BEGIN_PGP_PUBLIC_KEY_BLOCK="-----BEGIN PGP PUBLIC KEY BLOCK-----"
END_PGP_PUBLIC_KEY_BLOCK="-----END PGP PUBLIC KEY BLOCK-----"
BEGIN_PGP_PRIVATE_KEY="-----BEGIN PGP PRIVATE KEY-----"
END_PGP_PRIVATE_KEY="-----END PGP PRIVATE KEY-----"
BEGIN_PGP_PRIVATE_KEY_BLOCK="-----BEGIN PGP PRIVATE KEY BLOCK-----"
END_PGP_PRIVATE_KEY_BLOCK="-----END PGP PRIVATE KEY BLOCK-----"
BEGIN_PGP_SIGNED_MESSAGE="-----BEGIN PGP SIGNED MESSAGE-----"
HEADER_VERSION="Version"
HEADER_COMMENT="Comment"
HEADER_HASH="Hash"
HEADER_CHARSET="Charset"
HASH_SHA224="SHA224"
HASH_SHA256="SHA256"
HASH_SHA384="SHA384"
HASH_SHA512="SHA512"
PART_BEGIN="BEGIN"
PART_PGP="PGP"
PART_MESSAGE="MESSAGE"
PART_BLOCK="BLOCK"
PART_PUBLIC="PUBLIC"
PART_PRIVATE="PRIVATE"
PART_KEY="KEY"

View file

@ -0,0 +1,34 @@
#
# AFL Dictionary for OpenPGP (RFC9580)
# ------------------------------------------
#
# Created by Paul Schaub <info@pgpainless.org>
#
# Packet Type IDs
#
RESERVED="\x00"
PKESK="\x01"
SIG="\x02"
SKESK="\0x03"
OPS="\x04"
SECKEY="\x05"
PUBKEY="\x06"
SECSUBKEY="\x07"
COMP="\x08"
SED="\x09"
MARKER="\x0A"
LIT="\x0B"
TRUST="\x0C"
UID="\x0D"
PUBSUBKEY="\x0E"
UAT="\x11"
SEIPD="\x12"
MOD="\x13"
RES20="\x14"
PADDING="\x15"
#
# Entire Packets
#
MARKER_PACKET="\xCA\x03\x50\x47\x50"

View file

@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: 2025 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.sop.fuzzing;
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.junit.FuzzTest;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.api.KeyPassphraseProvider;
import org.bouncycastle.openpgp.api.OpenPGPKey;
import org.bouncycastle.openpgp.api.OpenPGPKeyReader;
import org.bouncycastle.openpgp.api.exception.KeyPassphraseException;
import org.bouncycastle.util.encoders.Hex;
import org.junit.jupiter.api.Test;
import org.pgpainless.sop.SOPImpl;
import sop.SOP;
import sop.exception.SOPGPException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class GenerateKeyFuzzTest {
private final SOP sop = new SOPImpl();
@FuzzTest(maxDuration = "5m")
public void generateKeyWithFuzzedUserId(FuzzedDataProvider provider) throws IOException {
String userId = provider.consumeRemainingAsString();
try {
byte[] keyBytes = sop.generateKey()
.userId(userId)
.generate()
.getBytes();
OpenPGPKey key = new OpenPGPKeyReader().parseKey(keyBytes);
assertNotNull(key.getUserId(userId), "Cannot fetch user-id for '" + userId + "' (" + Hex.toHexString(userId.getBytes(StandardCharsets.UTF_8)) + ")\n" + new String(keyBytes));
} catch (IllegalArgumentException e) {
// expected.
}
}
@FuzzTest
public void generateKeyWithFuzzedPassphrase(FuzzedDataProvider provider) throws IOException, KeyPassphraseException {
byte[] passphrase = provider.consumeRemainingAsBytes();
try {
byte[] keyBytes = sop.generateKey()
.withKeyPassword(passphrase)
.generate()
.getBytes();
OpenPGPKey key = new OpenPGPKeyReader().parseKey(keyBytes);
OpenPGPKey.OpenPGPPrivateKey pk = key.getPrimarySecretKey().unlock(new String(passphrase).toCharArray());
assertNotNull(pk, "Got null result unlocking key that was generated with passphrase 0x'" + Hex.toHexString(passphrase) + "'");
}
catch (SOPGPException.PasswordNotHumanReadable e) {
// expected.
}
catch (PGPException e) {
throw new RuntimeException("Cannot unlock key that was generated with passphrase 0x'" + Hex.toHexString(passphrase) + "'", e);
}
}
}