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 generation of OpenPGP v6 Signatures #1787

Closed
wants to merge 12 commits into from
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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 @@ -200,8 +200,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
22 changes: 21 additions & 1 deletion 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 @@ -156,6 +157,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 Expand Up @@ -195,10 +207,18 @@ private void checkSaltSize()
}

private void updateWithSalt()
throws PGPException
{
if (getVersion() == SignaturePacket.VERSION_6)
{
update(sigPck.getSalt());
try
{
sigOut.write(sigPck.getSalt());
}
catch (IOException e)
{
throw new PGPException("Could not update with salt.", e);
}
}
}

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 @@ -542,12 +542,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