Skip to content
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

Implement OpenPGP v6 & LibrePGP v5 signature verification #1725

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
131 changes: 86 additions & 45 deletions pg/src/main/java/org/bouncycastle/bcpg/PublicKeyPacket.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class PublicKeyPacket
{
public static final int VERSION_3 = 3;
public static final int VERSION_4 = 4;
public static final int LIBREPGP_5 = 5;
public static final int VERSION_6 = 6;

private int version;
Expand Down Expand Up @@ -43,66 +44,106 @@ public class PublicKeyPacket
this(keyTag, in, false);
}

/**
* Parse a {@link PublicKeyPacket} or {@link PublicSubkeyPacket} from an OpenPGP {@link BCPGInputStream}.
* If <pre>packetTypeID</pre> is {@link #PUBLIC_KEY}, the packet is a primary key.
* If instead it is {@link #PUBLIC_SUBKEY}, it is a subkey packet.
* If <pre>newPacketFormat</pre> is true, the packet format is remembered as {@link PacketFormat#CURRENT},
* otherwise as {@link PacketFormat#LEGACY}.
* @param packetTypeID packet type ID
* @param in packet input stream
* @param newPacketFormat packet format
* @throws IOException if the key packet cannot be parsed
*
* @see <a href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-3-public-keys">
* C-R - Version 3 Public Keys</a>
* @see <a href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-4-public-keys">
* C-R - Version 4 Public Keys</a>
* @see <a href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-version-6-public-keys">
* C-R - Version 6 Public Keys</a>
* @see <a href="https://www.ietf.org/archive/id/draft-koch-librepgp-01.html#name-public-key-packet-formats">
* LibrePGP - Public-Key Packet Formats</a>
*/
PublicKeyPacket(
int keyTag,
int packetTypeID,
BCPGInputStream in,
boolean newPacketFormat)
throws IOException
{
super(keyTag, newPacketFormat);
super(packetTypeID, newPacketFormat);

version = in.read();
time = ((long)in.read() << 24) | (in.read() << 16) | (in.read() << 8) | in.read();

if (version <= VERSION_3)
if (version < 2 || version > VERSION_6)
{
throw new UnsupportedPacketVersionException("Unsupported Public Key Packet version encountered: " + version);
}

time = ((long) in.read() << 24) | ((long) in.read() << 16) | ((long) in.read() << 8) | in.read();

if (version == 2 || version == VERSION_3)
{
validDays = (in.read() << 8) | in.read();
}

algorithm = (byte)in.read();
if (version == VERSION_6)
algorithm = (byte) in.read();

long keyOctetCount = -1;
if (version == LIBREPGP_5 || version == VERSION_6)
{
// TODO: Use keyOctets to be able to parse unknown keys
long keyOctets = ((long)in.read() << 24) | ((long)in.read() << 16) | ((long)in.read() << 8) | in.read();
keyOctetCount = ((long) in.read() << 24) | ((long) in.read() << 16) | ((long) in.read() << 8) | in.read();
}

switch (algorithm)
parseKey(in, algorithm, keyOctetCount);
}

/**
* Parse algorithm-specific public key material.
* @param in input stream which read just up to the public key material
* @param algorithmId public key algorithm ID
* @param optLen optional: Length of the public key material. -1 if not present.
* @throws IOException if the pk material cannot be parsed
*/
private void parseKey(BCPGInputStream in, int algorithmId, long optLen)
throws IOException
{
switch (algorithmId)
{
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:
throw new IOException("unknown PGP public key algorithm encountered: " + algorithm);
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:
throw new IOException("unknown PGP public key algorithm encountered: " + algorithm);
}
}

Expand Down Expand Up @@ -184,7 +225,7 @@ public byte[] getEncodedContents()

pOut.write(algorithm);

if (version == VERSION_6)
if (version == VERSION_6 || version == LIBREPGP_5)
{
int keyOctets = key.getEncoded().length;
pOut.write(keyOctets >> 24);
Expand Down
112 changes: 87 additions & 25 deletions pg/src/main/java/org/bouncycastle/bcpg/SecretKeyPacket.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.bouncycastle.bcpg;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

Expand Down Expand Up @@ -96,16 +97,30 @@ public class SecretKeyPacket
}

/**
* @param in
* @throws IOException
* Parse a {@link SecretKeyPacket} or {@link SecretSubkeyPacket} from an OpenPGP {@link BCPGInputStream}.
* The return type depends on the <pre>packetTypeID</pre>:
* {@link PacketTags#SECRET_KEY} means the result is a {@link SecretKeyPacket}.
* {@link PacketTags#SECRET_SUBKEY} results in a {@link SecretSubkeyPacket}.
*
* @param packetTypeID packet type ID
* @param in packet input stream
* @param newPacketFormat packet format
* @throws IOException if the secret key packet cannot be parsed
*
* @see <a href="https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-secret-key-packet-formats">
* C-R - Secret-Key Packet Formats</a>
* @see <a href="https://www.ietf.org/archive/id/draft-koch-librepgp-01.html#name-secret-key-packet-formats">
* LibrePGP - Secret-Key Packet Formats</a>
* @see <a href="https://datatracker.ietf.org/doc/draft-dkg-openpgp-hardware-secrets/">
* Hardware-Backed Secret Keys</a>
*/
SecretKeyPacket(
int keyTag,
int packetTypeID,
BCPGInputStream in,
boolean newPacketFormat)
throws IOException
{
super(keyTag, newPacketFormat);
super(packetTypeID, newPacketFormat);

if (this instanceof SecretSubkeyPacket)
{
Expand All @@ -119,12 +134,15 @@ public class SecretKeyPacket
int version = pubKeyPacket.getVersion();
s2kUsage = in.read();

if (version == 6 && s2kUsage != USAGE_NONE)
int conditionalParameterLength = -1;
if (version == PublicKeyPacket.LIBREPGP_5 ||
(version == PublicKeyPacket.VERSION_6 && s2kUsage != USAGE_NONE))
{
// TODO: Use length to parse unknown parameters
int conditionalParameterLength = in.read();
conditionalParameterLength = in.read();
}

// 255, 254, 253
if (s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_SHA1 || s2kUsage == USAGE_AEAD)
{
encAlgorithm = in.read();
Expand All @@ -133,44 +151,74 @@ public class SecretKeyPacket
{
encAlgorithm = s2kUsage;
}

// 253
if (s2kUsage == USAGE_AEAD)
{
aeadAlgorithm = in.read();
}
if (s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_SHA1 || s2kUsage == USAGE_AEAD)

// version = 6 && 254 || 253
if (version == PublicKeyPacket.VERSION_6 && (s2kUsage == USAGE_SHA1 || s2kUsage == USAGE_AEAD))
{
if (version == PublicKeyPacket.VERSION_6)
int s2KLen = in.read();
byte[] s2kBytes = new byte[s2KLen];
in.readFully(s2kBytes);

// TODO: catch UnsupportedPacketVersionException gracefully
s2k = new S2K(new ByteArrayInputStream(s2kBytes));
}
else
{
// 255, 254, 253
if (s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_SHA1 || s2kUsage == USAGE_AEAD)
{
// TODO: Use length to parse unknown S2Ks
int s2kLen = in.read();
s2k = new S2K(in);
}
s2k = new S2K(in);
}

if (s2kUsage == USAGE_AEAD)
{
iv = new byte[AEADUtils.getIVLength(aeadAlgorithm)];
Streams.readFully(in, iv);
}
boolean isGNUDummyNoPrivateKey = s2k != null
else
{
boolean isGNUDummyNoPrivateKey = s2k != null
&& s2k.getType() == S2K.GNU_DUMMY_S2K
&& s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY;
if (!(isGNUDummyNoPrivateKey))
{
if (s2kUsage != 0 && iv == null)
if (!(isGNUDummyNoPrivateKey))
{
if (encAlgorithm < 7)
if (s2kUsage != USAGE_NONE && iv == null)
{
iv = new byte[8];
if (encAlgorithm < 7)
{
iv = new byte[8];
}
else
{
iv = new byte[16];
}
in.readFully(iv, 0, iv.length);
}
else
{
iv = new byte[16];
}
in.readFully(iv, 0, iv.length);
}
}

this.secKeyData = in.readAll();
if (version == PublicKeyPacket.LIBREPGP_5)
{
long keyOctetCount = ((long) in.read() << 24) | ((long) in.read() << 16) | ((long) in.read() << 8) | in.read();
if (s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_NONE)
{
// encoded keyOctetCount does not contain checksum
keyOctetCount += 2;
}
this.secKeyData = new byte[(int) keyOctetCount];
in.readFully(secKeyData);
}
else
{
this.secKeyData = in.readAll();
}
}

/**
Expand Down Expand Up @@ -291,9 +339,11 @@ public byte[] getEncodedContents()

pOut.write(s2kUsage);


// conditional parameters
byte[] conditionalParameters = encodeConditionalParameters();
if (pubKeyPacket.getVersion() == PublicKeyPacket.VERSION_6 && s2kUsage != USAGE_NONE)
if (pubKeyPacket.getVersion() == PublicKeyPacket.LIBREPGP_5 ||
(pubKeyPacket.getVersion() == PublicKeyPacket.VERSION_6 && s2kUsage != USAGE_NONE))
{
pOut.write(conditionalParameters.length);
}
Expand All @@ -302,9 +352,21 @@ public byte[] getEncodedContents()
// encrypted secret key
if (secKeyData != null && secKeyData.length > 0)
{
if (pubKeyPacket.getVersion() == PublicKeyPacket.LIBREPGP_5)
{
int keyOctetCount = secKeyData.length;
// v5 keyOctetCount does not include checksum octets
if (s2kUsage == USAGE_CHECKSUM || s2kUsage == USAGE_NONE)
{
keyOctetCount -= 2;
}
pOut.write(keyOctetCount >> 24);
pOut.write(keyOctetCount >> 16);
pOut.write(keyOctetCount >> 8);
pOut.write(keyOctetCount);
}
pOut.write(secKeyData);
}

pOut.close();

return bOut.toByteArray();
Expand Down
Loading