Skip to content

OpenPGP: Mitigate some RuntimeExceptions uncovered using Fuzzing #2123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3cac5d8
RegularExpression packet: Catch IndexOutOfBounds (-1) for malformed r…
vanitasvitae Jul 10, 2025
1e50bb6
PGPSignature: Prevent IndexOutOfBoundsExceptions for malformed Ed2551…
vanitasvitae Jul 10, 2025
322a1e8
Introduce MalformedPacketException
vanitasvitae Jul 10, 2025
eb8bcc4
AEADEncDataPacket: Rethrow IllegalArgumentException for unknown AEAD …
vanitasvitae Jul 10, 2025
86c723a
ECDHPGPPublicKey: Prevent IndexOutOfBoundsException and throw Malform…
vanitasvitae Jul 10, 2025
a5a0ab9
LiteralDataPacket: Throw MalformedPacketException for negative body l…
vanitasvitae Jul 10, 2025
e139e2f
AEADEncDataPacket: Fix error message
vanitasvitae Jul 10, 2025
af17961
OctetArrayBCPGKey: Prevent OutOfMemoryError by limiting encoding leng…
vanitasvitae Jul 10, 2025
26875db
PublicKeyEncSessionPacket: Prevent negative keyInfoLen octet
vanitasvitae Jul 10, 2025
3359b93
PublicKeyPacket: Prevent negative keyLen octet
vanitasvitae Jul 10, 2025
bf833a1
SignaturePacket: Prevent large or negative subpacket encoding lengths
vanitasvitae Jul 10, 2025
1d1b5d2
SymmetricKeyEncSessionPacket: Rethrow IllegalArgumentException for un…
vanitasvitae Jul 10, 2025
4c83c07
SignatureExpirationTime: Sanitize date encoding length
vanitasvitae Jul 10, 2025
7caacb3
NotationData: Sanitize encoding length
vanitasvitae Jul 10, 2025
8c4dc73
IssuerFingerprint, IntendedRecipientFingerprint: Sanitize minimal enc…
vanitasvitae Jul 10, 2025
1388777
ImageAttribute: Sanitize encoding length
vanitasvitae Jul 10, 2025
7271e08
UserAttributeSubpacketInputStream: Sanitize encoding length and limit…
vanitasvitae Jul 10, 2025
08cbdb7
SignaturePacket: Rethrow IllegalArgumentExceptions in subpackets as M…
vanitasvitae Jul 10, 2025
ae92b59
SignatureSubpacketInputStream: Rethrow IllegalArgumentExceptions duri…
vanitasvitae Jul 10, 2025
85bcddb
PublicKeyEncSessionPacket: Rethrow IllegalArgumentException for malfo…
vanitasvitae Jul 11, 2025
509a0a3
SymmetricKeyEncSessionPacket: Prevent NegativeArraySizeException for …
vanitasvitae Jul 11, 2025
2ea1758
SymmetricKeyEncSessionPacket: Rethrow IllegalArgumentException for un…
vanitasvitae Jul 11, 2025
c47c742
SignaturePacket: Prevent negative salt size
vanitasvitae Jul 11, 2025
4213302
SymmetricKeyEncSessionPacket: Prevent auth tag length exceeding sessi…
vanitasvitae Jul 11, 2025
dbc397c
PublicKeyPacket: Rethrow IllegalStateExceptions (e.g. invalid hash fu…
vanitasvitae Jul 11, 2025
7b32d2b
Move MAX_LEN variable from OctetArrayBCPGKey to PublicKeyPacket
vanitasvitae Jul 11, 2025
a1c8996
Add Packet.sanitizeLength() utility method
vanitasvitae Jul 11, 2025
1ed2a6b
SecretKeyPacket: Sanitize s2k length octet
vanitasvitae Jul 11, 2025
bc6a827
SecretKeyPacket: Sanitize key octet count
vanitasvitae Jul 11, 2025
5218813
SecretKeyPacket: Rethrow IllegalArgumentException for unknown AEAD al…
vanitasvitae Jul 11, 2025
9567351
PGPObjectFactory: Prevent ClassCastException when parsing dangling Mo…
vanitasvitae Jul 12, 2025
d3b1462
UserAttributeSubpacketInputStream: Increase MAX_LEN to 2^30
vanitasvitae Jul 14, 2025
196569e
PGPObjectFactory: Return dangling TrustPackets as UnknownPacket
vanitasvitae Jul 14, 2025
07d9e84
UserAttributeSubpacketInputStream: Adopt dynamic read limits from Sig…
vanitasvitae Jul 15, 2025
20b5879
DO NOT MERGE: Bump BC version
vanitasvitae Jun 26, 2025
ed5fed1
Rethrow IllegalArgumentException for invalid unhashed IssuerKeyID sub…
vanitasvitae Jul 23, 2025
77e9074
Prevent ClassCastException for dangling UserID / UserAttribute packets
vanitasvitae Jul 23, 2025
0692caa
Prevent ClassCastException for v3 OpenPGP keys with non-RSA public keys
vanitasvitae Jul 23, 2025
cafe4e3
PGPObjectFactory: Catch IllegalArgumentException for illegal PKESK/SK…
vanitasvitae Jul 23, 2025
ade1c0c
SignaturePacket: Move try-catch blocks into separate methods
vanitasvitae Jul 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
org.gradle.jvmargs=-Xmx2g
version=1.81
version=1.82-SNAPSHOT
maxVersion=1.82
org.gradle.java.installations.auto-detect=false
org.gradle.java.installations.auto-download=false
Expand Down
12 changes: 10 additions & 2 deletions pg/src/main/java/org/bouncycastle/bcpg/AEADEncDataPacket.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,22 @@ public AEADEncDataPacket(BCPGInputStream in,
version = (byte)in.read();
if (version != VERSION_1)
{
throw new UnsupportedPacketVersionException("wrong AEAD packet version: " + version);
throw new UnsupportedPacketVersionException("Unknown AEAD packet version: " + version);
}

algorithm = (byte)in.read();
aeadAlgorithm = (byte)in.read();
chunkSize = (byte)in.read();

iv = new byte[AEADUtils.getIVLength(aeadAlgorithm)];
try
{
int ivLen = AEADUtils.getIVLength(aeadAlgorithm);
iv = new byte[ivLen];
}
catch (IllegalArgumentException e)
{
throw new MalformedPacketException("Unknown AEAD algorithm ID: " + aeadAlgorithm, e);
}
in.readFully(iv);
}

Expand Down
7 changes: 3 additions & 4 deletions pg/src/main/java/org/bouncycastle/bcpg/ECDHPublicBCPGKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,11 @@ public ECDHPublicBCPGKey(
super(in);

int length = in.read();
byte[] kdfParameters = new byte[length];
if (kdfParameters.length != 3)
if (length != 3)
{
throw new IllegalStateException("kdf parameters size of 3 expected.");
throw new MalformedPacketException("KDF parameters size of 3 expected.");
}

byte[] kdfParameters = new byte[length];
in.readFully(kdfParameters);

reserved = kdfParameters[0];
Expand Down
4 changes: 4 additions & 0 deletions pg/src/main/java/org/bouncycastle/bcpg/LiteralDataPacket.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ public class LiteralDataPacket

format = in.read();
int l = in.read();
if (l < 0)
{
throw new MalformedPacketException("File name size cannot be negative.");
}

fileName = new byte[l];
for (int i = 0; i != fileName.length; i++)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.bouncycastle.bcpg;

import java.io.IOException;

public class MalformedPacketException
extends IOException
{

public MalformedPacketException(String message)
{
super(message);
}

public MalformedPacketException(Throwable cause)
{
super(cause);
}

public MalformedPacketException(String message, Throwable cause)
{
super(message, cause);
}
}
6 changes: 6 additions & 0 deletions pg/src/main/java/org/bouncycastle/bcpg/OctetArrayBCPGKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.io.IOException;
import org.bouncycastle.util.Arrays;

import static org.bouncycastle.bcpg.PublicKeyPacket.MAX_LEN;

/**
* Public/Secret BCPGKey which is encoded as an array of octets rather than an MPI.
*/
Expand All @@ -15,6 +17,10 @@ public abstract class OctetArrayBCPGKey
OctetArrayBCPGKey(int length, BCPGInputStream in)
throws IOException
{
if (length > MAX_LEN)
{
throw new IOException("Max key length (" + MAX_LEN + ") exceeded (" + length + ")");
}
key = new byte[length];
in.readFully(key);
}
Expand Down
14 changes: 14 additions & 0 deletions pg/src/main/java/org/bouncycastle/bcpg/Packet.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,18 @@ public boolean isCritical()
{
return getPacketTag() <= 39;
}

static int sanitizeLength(int len, int max, String variableName)
throws MalformedPacketException
{
if (len < 0)
{
throw new MalformedPacketException(variableName + " cannot be negative.");
}
if (len > max)
{
throw new MalformedPacketException(variableName + " (" + len + ") exceeds limit (" + max + ").");
}
return len;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ public class PublicKeyEncSessionPacket
else if (version == VERSION_6)
{
int keyInfoLen = in.read();
if (keyInfoLen < 0)
{
throw new MalformedPacketException("Key Info Length cannot be negative: " + keyInfoLen);
}
if (keyInfoLen == 0)
{
// anon recipient
Expand All @@ -69,13 +73,20 @@ else if (version == VERSION_6)
in.readFully(keyFingerprint);
// Derived key-ID from fingerprint
// TODO: Replace with getKeyIdentifier
if (keyVersion == PublicKeyPacket.VERSION_4)
try
{
keyID = FingerprintUtil.keyIdFromV4Fingerprint(keyFingerprint);
if (keyVersion == PublicKeyPacket.VERSION_4)
{
keyID = FingerprintUtil.keyIdFromV4Fingerprint(keyFingerprint);
}
else
{
keyID = FingerprintUtil.keyIdFromV6Fingerprint(keyFingerprint);
}
}
else
catch (IllegalArgumentException e)
{
keyID = FingerprintUtil.keyIdFromV6Fingerprint(keyFingerprint);
throw new MalformedPacketException("Malformed fingerprint encoding.", e);
}
}
}
Expand Down
92 changes: 52 additions & 40 deletions pg/src/main/java/org/bouncycastle/bcpg/PublicKeyPacket.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class PublicKeyPacket
extends ContainedPacket
implements PublicKeyAlgorithmTags
{
public static int MAX_LEN = 2 * 1024 * 1024; // 2mb; McEliece keys can get ~1mb in size, so allow some margin

/**
* OpenPGP v3 keys are deprecated.
* They can only be used with RSA.
Expand Down Expand Up @@ -150,6 +152,10 @@ public class PublicKeyPacket
{
// TODO: Use keyOctets to be able to parse unknown keys
keyOctets = StreamUtil.read4OctetLength(in);
if (keyOctets < 0)
{
throw new MalformedPacketException("Octet length cannot be negative.");
}
}

parseKey(in, algorithm, keyOctets);
Expand All @@ -165,50 +171,56 @@ public class PublicKeyPacket
private void parseKey(BCPGInputStream in, int algorithmId, long optLen)
throws IOException
{

switch (algorithmId)
try
{
case RSA_ENCRYPT:
case RSA_GENERAL:
case RSA_SIGN:
key = new RSAPublicBCPGKey(in);
break;
case DSA:
key = new DSAPublicBCPGKey(in);
break;
case ELGAMAL_ENCRYPT:
case ELGAMAL_GENERAL:
key = new ElGamalPublicBCPGKey(in);
break;
case ECDH:
key = new ECDHPublicBCPGKey(in);
break;
case X25519:
key = new X25519PublicBCPGKey(in);
break;
case X448:
key = new X448PublicBCPGKey(in);
break;
case ECDSA:
key = new ECDSAPublicBCPGKey(in);
break;
case EDDSA_LEGACY:
key = new EdDSAPublicBCPGKey(in);
break;
case Ed25519:
key = new Ed25519PublicBCPGKey(in);
break;
case Ed448:
key = new Ed448PublicBCPGKey(in);
break;
default:
if (version == VERSION_6 || version == LIBREPGP_5)
switch (algorithmId)
{
// with version 5 & 6, we can gracefully handle unknown key types, as the length is known.
key = new UnknownBCPGKey((int) optLen, in);
case RSA_ENCRYPT:
case RSA_GENERAL:
case RSA_SIGN:
key = new RSAPublicBCPGKey(in);
break;
case DSA:
key = new DSAPublicBCPGKey(in);
break;
case ELGAMAL_ENCRYPT:
case ELGAMAL_GENERAL:
key = new ElGamalPublicBCPGKey(in);
break;
case ECDH:
key = new ECDHPublicBCPGKey(in);
break;
case X25519:
key = new X25519PublicBCPGKey(in);
break;
case X448:
key = new X448PublicBCPGKey(in);
break;
case ECDSA:
key = new ECDSAPublicBCPGKey(in);
break;
case EDDSA_LEGACY:
key = new EdDSAPublicBCPGKey(in);
break;
case Ed25519:
key = new Ed25519PublicBCPGKey(in);
break;
case Ed448:
key = new Ed448PublicBCPGKey(in);
break;
default:
if (version == VERSION_6 || version == LIBREPGP_5)
{
// with version 5 & 6, we can gracefully handle unknown key types, as the length is known.
key = new UnknownBCPGKey((int) optLen, in);
break;
}
throw new IOException("unknown PGP public key algorithm encountered: " + algorithm);
}
throw new IOException("unknown PGP public key algorithm encountered: " + algorithm);
}
catch (IllegalStateException e)
{
throw new MalformedPacketException("Malformed PGP key.", e);
}
}

Expand Down
15 changes: 14 additions & 1 deletion pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.io.Streams;

import static org.bouncycastle.bcpg.PublicKeyPacket.MAX_LEN;

/**
* Base class for OpenPGP secret (primary) keys.
*/
public class SecretKeyPacket
extends ContainedPacket
implements PublicKeyAlgorithmTags
{
public static int MAX_S2K_ENCODING_LEN = 8192; // arbitrary

/**
* S2K-usage octet indicating that the secret key material is unprotected.
*/
Expand Down Expand Up @@ -179,6 +183,7 @@ public class SecretKeyPacket
if (version == PublicKeyPacket.VERSION_6 && (s2kUsage == USAGE_SHA1 || s2kUsage == USAGE_AEAD))
{
int s2KLen = in.read();
s2KLen = sanitizeLength(s2KLen, MAX_S2K_ENCODING_LEN, "S2K length octet");
byte[] s2kBytes = new byte[s2KLen];
in.readFully(s2kBytes);

Expand All @@ -194,7 +199,14 @@ public class SecretKeyPacket
}
if (s2kUsage == USAGE_AEAD)
{
iv = new byte[AEADUtils.getIVLength(aeadAlgorithm)];
try
{
iv = new byte[AEADUtils.getIVLength(aeadAlgorithm)];
}
catch (IllegalArgumentException e)
{
throw new MalformedPacketException("Unknown AEAD algorithm", e);
}
Streams.readFully(in, iv);
}
else
Expand Down Expand Up @@ -227,6 +239,7 @@ public class SecretKeyPacket
// encoded keyOctetCount does not contain checksum
keyOctetCount += 2;
}
keyOctetCount = sanitizeLength((int) keyOctetCount, MAX_LEN, "Key octet count");
this.secKeyData = new byte[(int) keyOctetCount];
in.readFully(secKeyData);
}
Expand Down
Loading