mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-09-09 18:29:39 +02:00
WIP
This commit is contained in:
parent
8f41fb0f27
commit
880b0720db
23 changed files with 226 additions and 6 deletions
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
|
@ -0,0 +1 @@
|
||||||
|
»ЏОхТО---Q(K
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,5 @@
|
||||||
|
-----BEGIN PGP MESSAGÚ-----
|
||||||
|
|
||||||
|
ywtiAAECAwTA/+66vg==
|
||||||
|
=pAS2
|
||||||
|
-----END PGP MESSAGE-----
|
|
@ -0,0 +1,5 @@
|
||||||
|
-----BEGIN PGP MESSAGE-----
|
||||||
|
|
||||||
|
ywtiAAECAwTA/+66vg==
|
||||||
|
=pAS2
|
||||||
|
-----END PGP MESSAGE-----
|
|
@ -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"
|
|
@ -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"
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
/
|
|
@ -0,0 +1 @@
|
||||||
|
]
M
|
|
@ -0,0 +1 @@
|
||||||
|
牋<EFBFBD>
|
|
@ -0,0 +1 @@
|
||||||
|
<EFBFBD><EFBFBD>
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
]
|
|
@ -0,0 +1 @@
|
||||||
|
<EFBFBD>
|
Loading…
Add table
Add a link
Reference in a new issue