Skip to content

Commit

Permalink
Merge branch '1787-v6-signature' into 'main'
Browse files Browse the repository at this point in the history
#1787 Implement generation of OpenPGP v6 Signatures

See merge request root/bc-java!21
  • Loading branch information
dghgit committed Sep 11, 2024
2 parents 4472df4 + 19ecb09 commit e76d843
Show file tree
Hide file tree
Showing 7 changed files with 574 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ public SignatureCreationTime(
super(SignatureSubpacketTags.CREATION_TIME, critical, false, timeToBytes(date));
}

public SignatureCreationTime(
Date date)
{
this(true, date);
}

public Date getTime()
{
long time = Utils.timeFromBytes(data);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ public int getKeyAlgorithm()
}

/**
* Return true, if the signature is contains any signatures that follow.
* An bracketing OPS is followed by additional OPS packets and is calculated over all the data between itself
* Return true, if the signature contains any signatures that follow.
* A bracketing OPS is followed by additional OPS packets and is calculated over all the data between itself
* and its corresponding signature (it is an attestation for contained signatures).
*
* @return true if containing, false otherwise
Expand Down
12 changes: 12 additions & 0 deletions pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.bouncycastle.bcpg.MPInteger;
import org.bouncycastle.bcpg.Packet;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyPacket;
import org.bouncycastle.bcpg.SignaturePacket;
import org.bouncycastle.bcpg.SignatureSubpacket;
import org.bouncycastle.bcpg.TrustPacket;
Expand Down Expand Up @@ -310,6 +311,17 @@ public void init(PGPContentVerifierBuilderProvider verifierBuilderProvider, PGPP
{
throw new PGPException("Illegal signature type 0xFF provided.");
}

if (getVersion() == SignaturePacket.VERSION_6 && pubKey.getVersion() != PublicKeyPacket.VERSION_6)
{
throw new PGPException("MUST NOT verify v6 signature with non-v6 key.");
}

if (getVersion() == SignaturePacket.VERSION_4 && pubKey.getVersion() != PublicKeyPacket.VERSION_4)
{
throw new PGPException("MUST NOT verify v4 signature with non-v4 key.");
}

PGPContentVerifierBuilder verifierBuilder = createVerifierProvider(verifierBuilderProvider);

init(verifierBuilder.build(pubKey));
Expand Down
177 changes: 131 additions & 46 deletions pg/src/main/java/org/bouncycastle/openpgp/PGPSignatureGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
import java.math.BigInteger;
import java.util.Date;

import org.bouncycastle.bcpg.HashUtils;
import org.bouncycastle.bcpg.MPInteger;
import org.bouncycastle.bcpg.OnePassSignaturePacket;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.SignaturePacket;
import org.bouncycastle.bcpg.SignatureSubpacket;
import org.bouncycastle.bcpg.SignatureSubpacketTags;
import org.bouncycastle.bcpg.sig.IssuerFingerprint;
import org.bouncycastle.bcpg.sig.IssuerKeyID;
import org.bouncycastle.bcpg.sig.SignatureCreationTime;
import org.bouncycastle.crypto.CryptoServicesRegistrar;
import org.bouncycastle.openpgp.operator.PGPContentSigner;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.util.Arrays;
Expand All @@ -31,6 +34,7 @@ public class PGPSignatureGenerator
//private int providedKeyAlgorithm = -1;
private int providedKeyAlgorithm = -1;
private PGPPublicKey signingPubKey;
private byte[] salt;

/**
* Create a version 4 signature generator built on the passed in contentSignerBuilder.
Expand Down Expand Up @@ -88,8 +92,8 @@ public PGPSignatureGenerator(
/**
* Initialise the generator for signing.
*
* @param signatureType
* @param key
* @param signatureType type of signature
* @param key private signing key
* @throws PGPException
*/
public void init(
Expand All @@ -106,12 +110,37 @@ public void init(
sigType = contentSigner.getType();
lastb = 0;

// if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm())
// {
// throw new PGPException("key algorithm mismatch");
// }
if (providedKeyAlgorithm >= 0 && providedKeyAlgorithm != contentSigner.getKeyAlgorithm())
{
throw new PGPException("key algorithm mismatch");
}

if (key.getPublicKeyPacket().getVersion() != version)
{
throw new PGPException("Key version mismatch.");
}

if (version == SignaturePacket.VERSION_6)
{
int saltSize = HashUtils.getV6SignatureSaltSizeInBytes(contentSigner.getHashAlgorithm());
salt = new byte[saltSize];
CryptoServicesRegistrar.getSecureRandom().nextBytes(salt);
try
{
sigOut.write(salt);
}
catch (IOException e)
{
throw new PGPException("Cannot update signature with salt.");
}
}
}

/**
* Set the hashed signature subpackets.
* Hashed signature subpackets are covered by the signature.
* @param hashedPcks hashed signature subpackets
*/
public void setHashedSubpackets(
PGPSignatureSubpacketVector hashedPcks)
{
Expand All @@ -124,6 +153,11 @@ public void setHashedSubpackets(
hashed = hashedPcks.toSubpacketArray();
}

/**
* Set the unhashed signature subpackets.
* Unhashed signature subpackets are not covered by the signature.
* @param unhashedPcks unhashed signature subpackets
*/
public void setUnhashedSubpackets(
PGPSignatureSubpacketVector unhashedPcks)
{
Expand All @@ -147,7 +181,26 @@ public PGPOnePassSignature generateOnePassVersion(
boolean isNested)
throws PGPException
{
return new PGPOnePassSignature(new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(), contentSigner.getKeyID(), isNested));
if (version == SignaturePacket.VERSION_6)
{
return new PGPOnePassSignature(v6OPSPacket(isNested));
}
else
{
return new PGPOnePassSignature(v3OPSPacket(isNested));
}
}

private OnePassSignaturePacket v3OPSPacket(boolean isNested)
{
return new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(),
contentSigner.getKeyID(), isNested);
}

private OnePassSignaturePacket v6OPSPacket(boolean isNested)
{
return new OnePassSignaturePacket(sigType, contentSigner.getHashAlgorithm(), contentSigner.getKeyAlgorithm(),
salt, signingPubKey.getFingerprint(), isNested);
}

/**
Expand All @@ -159,66 +212,51 @@ public PGPOnePassSignature generateOnePassVersion(
public PGPSignature generate()
throws PGPException
{
MPInteger[] sigValues;
int version = 4;
ByteArrayOutputStream sOut = new ByteArrayOutputStream();
SignatureSubpacket[] hPkts, unhPkts;

if (packetNotPresent(hashed, SignatureSubpacketTags.CREATION_TIME))
{
hPkts = insertSubpacket(hashed, new SignatureCreationTime(false, new Date()));
}
else
{
hPkts = hashed;
}

if (packetNotPresent(hashed, SignatureSubpacketTags.ISSUER_KEY_ID) && packetNotPresent(unhashed, SignatureSubpacketTags.ISSUER_KEY_ID))
{
unhPkts = insertSubpacket(unhashed, new IssuerKeyID(false, contentSigner.getKeyID()));
}
else
{
unhPkts = unhashed;
}
prepareSignatureSubpackets();

ByteArrayOutputStream sOut = new ByteArrayOutputStream();
try
{
// hash the "header"
sOut.write((byte)version);
sOut.write((byte)sigType);
sOut.write((byte)contentSigner.getKeyAlgorithm());
sOut.write((byte)contentSigner.getHashAlgorithm());

// hash signature subpackets
ByteArrayOutputStream hOut = new ByteArrayOutputStream();

for (int i = 0; i != hPkts.length; i++)
for (int i = 0; i != hashed.length; i++)
{
hPkts[i].encode(hOut);
hashed[i].encode(hOut);
}

byte[] data = hOut.toByteArray();

if (version == SignaturePacket.VERSION_6)
{
sOut.write((byte) (data.length >> 24));
sOut.write((byte) (data.length >> 16));
}
sOut.write((byte)(data.length >> 8));
sOut.write((byte)data.length);
sOut.write(data);
byte[] hData = sOut.toByteArray();

// hash the "footer"
int dataLen = sOut.toByteArray().length;
sOut.write((byte)version);
sOut.write((byte)0xff);
sOut.write((byte)(hData.length >> 24));
sOut.write((byte)(hData.length >> 16));
sOut.write((byte)(hData.length >> 8));
sOut.write((byte)(hData.length));
sOut.write((byte)(dataLen >> 24));
sOut.write((byte)(dataLen >> 16));
sOut.write((byte)(dataLen >> 8));
sOut.write((byte)(dataLen));
}
catch (IOException e)
{
throw new PGPException("exception encoding hashed data.", e);
}


byte[] trailer = sOut.toByteArray();

blockUpdate(trailer, 0, trailer.length);
MPInteger[] sigValues;
switch (contentSigner.getKeyAlgorithm())
{
case PublicKeyAlgorithmTags.RSA_SIGN:
Expand Down Expand Up @@ -253,16 +291,63 @@ public PGPSignature generate()
fingerPrint[0] = digest[0];
fingerPrint[1] = digest[1];

if (sigValues != null)
SignaturePacket sigPckt;
if (sigValues != null) // MPI encoding
{
return new PGPSignature(new SignaturePacket(sigType, contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(),
contentSigner.getHashAlgorithm(), hPkts, unhPkts, fingerPrint, sigValues));
sigPckt = new SignaturePacket(version, sigType, contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(),
contentSigner.getHashAlgorithm(), hashed, unhashed, fingerPrint, sigValues, salt);
}
else
else // native encoding
{
// Ed25519, Ed448 use raw encoding instead of MPI
return new PGPSignature(new SignaturePacket(4, sigType, contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(),
contentSigner.getHashAlgorithm(), hPkts, unhPkts, fingerPrint, contentSigner.getSignature(), null));

sigPckt = new SignaturePacket(version, sigType, contentSigner.getKeyID(), contentSigner.getKeyAlgorithm(),
contentSigner.getHashAlgorithm(), hashed, unhashed, fingerPrint, contentSigner.getSignature(), salt);
}
return new PGPSignature(sigPckt);
}

protected void prepareSignatureSubpackets()
throws PGPException
{
switch (version)
{
case SignaturePacket.VERSION_4:
case SignaturePacket.VERSION_5:
{
// Insert hashed signature creation time if missing
if (packetNotPresent(hashed, SignatureSubpacketTags.CREATION_TIME))
{
hashed = insertSubpacket(hashed, new SignatureCreationTime(true, new Date()));
}

// Insert unhashed issuer key-ID if missing
if (packetNotPresent(hashed, SignatureSubpacketTags.ISSUER_KEY_ID) && packetNotPresent(unhashed, SignatureSubpacketTags.ISSUER_KEY_ID))
{
unhashed = insertSubpacket(unhashed, new IssuerKeyID(false, contentSigner.getKeyID()));
}

break;
}

case SignaturePacket.VERSION_6:
{
// Insert hashed signature creation time if missing
if (packetNotPresent(hashed, SignatureSubpacketTags.CREATION_TIME))
{
hashed = insertSubpacket(hashed, new SignatureCreationTime(true, new Date()));
}

// Insert hashed issuer fingerprint subpacket if missing
if (packetNotPresent(hashed, SignatureSubpacketTags.ISSUER_FINGERPRINT) &&
packetNotPresent(unhashed, SignatureSubpacketTags.ISSUER_FINGERPRINT) &&
signingPubKey != null)
{
hashed = insertSubpacket(hashed, new IssuerFingerprint(true, version, signingPubKey.getFingerprint()));
}

break;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -551,12 +551,13 @@ public void performTest()

int[] criticalHashed = hashedPcks.getCriticalTags();

if (criticalHashed.length != 1)
// SignerUserID and SignatureCreationTime are critical.
if (criticalHashed.length != 2)
{
fail("wrong number of critical packets found.");
}

if (criticalHashed[0] != SignatureSubpacketTags.SIGNER_USER_ID)
if (criticalHashed[1] != SignatureSubpacketTags.SIGNER_USER_ID)
{
fail("wrong critical packet found in tag list.");
}
Expand Down
Loading

0 comments on commit e76d843

Please sign in to comment.