All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bouncycastle.bcpg.SignaturePacket Maven / Gradle / Ivy

Go to download

The Bouncy Castle Java APIs for the OpenPGP Protocol. The APIs are designed primarily to be used in conjunction with the BC LTS provider but may also be used with other providers providing cryptographic services.

The newest version!
package org.bouncycastle.bcpg;

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

import org.bouncycastle.bcpg.sig.IssuerFingerprint;
import org.bouncycastle.bcpg.sig.IssuerKeyID;
import org.bouncycastle.bcpg.sig.SignatureCreationTime;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Pack;
import org.bouncycastle.util.io.Streams;

/**
 * generic signature packet
 */
public class SignaturePacket
    extends ContainedPacket implements PublicKeyAlgorithmTags
{
    public static final int VERSION_2 = 2;
    public static final int VERSION_3 = 3;
    public static final int VERSION_4 = 4;  // https://datatracker.ietf.org/doc/rfc4880/
    public static final int VERSION_5 = 5;  // https://datatracker.ietf.org/doc/draft-koch-librepgp/
    public static final int VERSION_6 = 6;  // https://www.rfc-editor.org/rfc/rfc9580.html

    private int                    version;
    private int                    signatureType;
    private long                   creationTime;
    private long                   keyID;
    private int                    keyAlgorithm;
    private int                    hashAlgorithm;
    private MPInteger[]            signature;
    private byte[]                 fingerPrint;
    private SignatureSubpacket[]   hashedData;
    private SignatureSubpacket[]   unhashedData;
    private byte[]                 signatureEncoding;
    private byte[]                 salt; // v6 only

    SignaturePacket(
            BCPGInputStream    in)
            throws IOException
    {
        this(in, false);
    }

    SignaturePacket(
        BCPGInputStream    in,
        boolean newPacketFormat)
        throws IOException
    {
        super(SIGNATURE, newPacketFormat);

        version = in.read();
        switch (version)
        {
            case VERSION_2:
            case VERSION_3:
                parseV2_V3(in);
                break;
            case VERSION_4:
            case VERSION_5:
                parseV4_V5(in);
                break;
            case VERSION_6:
                parseV6(in);
                break;
            default:
                Streams.drain(in);
                throw new UnsupportedPacketVersionException("unsupported version: " + version);
        }
    }

    /**
     * Parse a version 2 or version 3 signature.
     * @param in input stream which already skipped over the version number
     * @throws IOException if the packet is malformed
     *
     * @see 
     *     Version 3 packet format
     */
    private void parseV2_V3(BCPGInputStream in)
        throws IOException
    {
        int    l = in.read(); // length l MUST be 5

        signatureType = in.read();
        creationTime = StreamUtil.readTime(in);

        keyID = StreamUtil.readKeyID(in);
        keyAlgorithm = in.read();
        hashAlgorithm = in.read();

        // left 16 bits of the signed hash value
        fingerPrint = new byte[2];
        in.readFully(fingerPrint);

        parseSignature(in);
    }

    /**
     * Parse a version 4 or version 5 signature.
     * The difference between version 4 and 5 is that a version 5 signature contains additional metadata.
     * @param in input stream which already skipped over the version number
     * @throws IOException if the packet is malformed
     *
     * @see 
     *     Version 4 packet format
     * @see 
     *     Version 5 packet format
     */
    private void parseV4_V5(BCPGInputStream in)
            throws IOException
    {
        signatureType = in.read();
        keyAlgorithm = in.read();
        hashAlgorithm = in.read();

        parseSubpackets(in);

        // left 16 bits of the signed hash value
        fingerPrint = new byte[2];
        in.readFully(fingerPrint);

        parseSignature(in);
    }

    /**
     * Parse a version 6 signature.
     * Version 6 signatures do use 4 octet subpacket area length descriptors and contain an additional salt value
     * (which may or may not be of size 0, LibrePGP and OpenPGP are in disagreement here).
     * @param in input stream which already skipped over the version number
     * @throws IOException if the packet is malformed
     *
     * @see 
     *     OpenPGP - Version 6 packet format
     */
    private void parseV6(BCPGInputStream in)
            throws IOException
    {
        signatureType = in.read();
        keyAlgorithm = in.read();
        hashAlgorithm = in.read();

        parseSubpackets(in);

        // left 16 bits of the signed hash value
        fingerPrint = new byte[2];
        in.readFully(fingerPrint);

        int saltSize = in.read();
        salt = new byte[saltSize];
        in.readFully(salt);

        parseSignature(in);
    }

    /**
     * Parse the hashed and unhashed signature subpacket areas of the signature.
     * Version 4 and 5 signature encode the area length using 2 octets, while version 6 uses 4 octet lengths instead.
     *
     * @param in input stream which skipped to after the hash algorithm octet
     * @throws IOException if the packet is malformed
     */
    private void parseSubpackets(BCPGInputStream in)
            throws IOException
    {

        Vector vec = readSignatureSubpacketVector(in);
        hashedData = new SignatureSubpacket[vec.size()];

        for (int i = 0; i != hashedData.length; i++)
        {
            SignatureSubpacket p = (SignatureSubpacket)vec.elementAt(i);
            if (p instanceof IssuerKeyID)
            {
                keyID = ((IssuerKeyID)p).getKeyID();
            }
            else if (p instanceof SignatureCreationTime)
            {
                creationTime = ((SignatureCreationTime)p).getTime().getTime();
            }

            hashedData[i] = p;
        }

        vec = readSignatureSubpacketVector(in);
        unhashedData = new SignatureSubpacket[vec.size()];

        for (int i = 0; i != unhashedData.length; i++)
        {
            SignatureSubpacket p = (SignatureSubpacket)vec.elementAt(i);
            if (p instanceof IssuerKeyID)
            {
                keyID = ((IssuerKeyID)p).getKeyID();
            }

            unhashedData[i] = p;
        }

        setIssuerKeyId();
        setCreationTime();
    }

    private Vector readSignatureSubpacketVector(BCPGInputStream in)
        throws IOException
    {
        int hashedLength;
        if (version == 6)
        {
            hashedLength = StreamUtil.read4OctetLength(in);
        }
        else
        {
            hashedLength = StreamUtil.read2OctetLength(in);
        }
        byte[] hashed = new byte[hashedLength];

        in.readFully(hashed);

        //
        // read the signature sub packet data.
        //
        SignatureSubpacket sub;
        SignatureSubpacketInputStream sIn = new SignatureSubpacketInputStream(
            new ByteArrayInputStream(hashed));

        Vector vec = new Vector();
        while ((sub = sIn.readPacket()) != null)
        {
            vec.addElement(sub);
        }
        return vec;
    }

    /**
     * Parse the algorithm-specific signature encoding.
     * Ed25519 and Ed448 do not populate the signature MPInteger field, but instead read the raw signature to
     * signatureEncoding directly.
     *
     * @param in input stream which skipped the head of the signature
     * @throws IOException if the packet is malformed
     */
    private void parseSignature(BCPGInputStream in)
            throws IOException
    {
        switch (keyAlgorithm)
        {
            case RSA_GENERAL:
            case RSA_SIGN:
                MPInteger    v = new MPInteger(in);

                signature = new MPInteger[1];
                signature[0] = v;
                break;
            case DSA:
                MPInteger    r = new MPInteger(in);
                MPInteger    s = new MPInteger(in);

                signature = new MPInteger[2];
                signature[0] = r;
                signature[1] = s;
                break;
            case ELGAMAL_ENCRYPT: // yep, this really does happen sometimes.
            case ELGAMAL_GENERAL:
                MPInteger       p = new MPInteger(in);
                MPInteger       g = new MPInteger(in);
                MPInteger       y = new MPInteger(in);

                signature = new MPInteger[3];
                signature[0] = p;
                signature[1] = g;
                signature[2] = y;
                break;
            case Ed448:
                signatureEncoding = new byte[org.bouncycastle.math.ec.rfc8032.Ed448.SIGNATURE_SIZE];
                in.readFully(signatureEncoding);
                break;
            case Ed25519:
                signatureEncoding = new byte[org.bouncycastle.math.ec.rfc8032.Ed25519.SIGNATURE_SIZE];
                in.readFully(signatureEncoding);
                break;
            case ECDSA:
            case EDDSA_LEGACY:

                MPInteger    ecR = new MPInteger(in);
                MPInteger    ecS = new MPInteger(in);

                signature = new MPInteger[2];
                signature[0] = ecR;
                signature[1] = ecS;
                break;
            default:
                if (keyAlgorithm >= PublicKeyAlgorithmTags.EXPERIMENTAL_1 && keyAlgorithm <= PublicKeyAlgorithmTags.EXPERIMENTAL_11)
                {
                    signature = null;
                    signatureEncoding = Streams.readAll(in);
                }
                else
                {
                    throw new IOException("unknown signature key algorithm: " + keyAlgorithm);
                }
        }
    }

    /**
     * Generate a version 4 signature packet.
     *
     * @param signatureType
     * @param keyAlgorithm
     * @param hashAlgorithm
     * @param hashedData
     * @param unhashedData
     * @param fingerPrint
     * @param signature
     */
    public SignaturePacket(
        int                     signatureType,
        long                    keyID,
        int                     keyAlgorithm,
        int                     hashAlgorithm,
        SignatureSubpacket[]    hashedData,
        SignatureSubpacket[]    unhashedData,
        byte[]                  fingerPrint,
        MPInteger[]             signature)
    {
        this(4, signatureType, keyID, keyAlgorithm, hashAlgorithm, hashedData, unhashedData, fingerPrint, signature);
    }

    /**
     * Generate a version 2/3 signature packet.
     *
     * @param signatureType
     * @param keyAlgorithm
     * @param hashAlgorithm
     * @param fingerPrint
     * @param signature
     */
    public SignaturePacket(
        int                     version,
        int                     signatureType,
        long                    keyID,
        int                     keyAlgorithm,
        int                     hashAlgorithm,
        long                    creationTime,
        byte[]                  fingerPrint,
        MPInteger[]             signature)
    {
        this(version, signatureType, keyID, keyAlgorithm, hashAlgorithm, null, null, fingerPrint, signature);

        this.creationTime = creationTime;
    }

    public SignaturePacket(
        int                     version,
        int                     signatureType,
        long                    keyID,
        int                     keyAlgorithm,
        int                     hashAlgorithm,
        SignatureSubpacket[]    hashedData,
        SignatureSubpacket[]    unhashedData,
        byte[]                  fingerPrint,
        MPInteger[]             signature)
    {
        super(SIGNATURE);

        this.version = version;
        this.signatureType = signatureType;
        this.keyID = keyID;
        this.keyAlgorithm = keyAlgorithm;
        this.hashAlgorithm = hashAlgorithm;
        this.hashedData = hashedData;
        this.unhashedData = unhashedData;
        this.fingerPrint = fingerPrint;
        this.signature = signature;

        if (hashedData != null)
        {
            setCreationTime();
        }
    }

    public SignaturePacket(
            int                     version,
            int                     signatureType,
            long                    keyID,
            int                     keyAlgorithm,
            int                     hashAlgorithm,
            SignatureSubpacket[]    hashedData,
            SignatureSubpacket[]    unhashedData,
            byte[]                  fingerPrint,
            byte[]                  signatureEncoding,
            byte[]                  salt)
    {
        super(SIGNATURE);

        this.version = version;
        this.signatureType = signatureType;
        this.keyID = keyID;
        this.keyAlgorithm = keyAlgorithm;
        this.hashAlgorithm = hashAlgorithm;
        this.hashedData = hashedData;
        this.unhashedData = unhashedData;
        this.fingerPrint = fingerPrint;
        this.signatureEncoding = Arrays.clone(signatureEncoding);
        this.salt = Arrays.clone(salt);
        if (hashedData != null)
        {
            setCreationTime();
        }
    }

    public SignaturePacket(
        int version,
        int signatureType,
        long keyID,
        int keyAlgorithm,
        int hashAlgorithm,
        SignatureSubpacket[] hashedData,
        SignatureSubpacket[] unhashedData,
        byte[] fingerPrint,
        MPInteger[] signature,
        byte[] salt)
    {
        super(SIGNATURE);

        this.version = version;
        this.signatureType = signatureType;
        this.keyID = keyID;
        this.keyAlgorithm = keyAlgorithm;
        this.hashAlgorithm = hashAlgorithm;
        this.hashedData = hashedData;
        this.unhashedData = unhashedData;
        this.fingerPrint = fingerPrint;
        this.signature = signature;
        this.salt = Arrays.clone(salt);
        if (hashedData != null)
        {
            setCreationTime();
        }
    }

    /**
     * get the version number
     */
    public int getVersion()
    {
        return version;
    }

    /**
     * return the signature type.
     */
    public int getSignatureType()
    {
        return signatureType;
    }

    /**
     * return the keyID
     * @return the keyID that created the signature.
     */
    public long getKeyID()
    {
        return keyID;
    }

    /**
     * Return the signature's fingerprint.
     * @return fingerprint (digest prefix) of the signature
     */
    public byte[] getFingerPrint()
    {
        return Arrays.clone(fingerPrint);
    }

    /**
     * Return the signature's salt.
     * Only for v6 signatures.
     * @return salt
     */
    public byte[] getSalt()
    {
        return salt;
    }

    /**
     * return the signature trailer that must be included with the data
     * to reconstruct the signature
     *
     * @return byte[]
     */
    public byte[] getSignatureTrailer()
    {
        byte[]    trailer = null;

        if (version == VERSION_3 || version == VERSION_2)
        {
            trailer = new byte[5];

            long    time = creationTime / 1000;

            trailer[0] = (byte)signatureType;
            Pack.intToBigEndian((int)time, trailer, 1);
        }
        else if (version == VERSION_4 || version == VERSION_5 || version == VERSION_6)
        {
            ByteArrayOutputStream    sOut = new ByteArrayOutputStream();
            SignatureSubpacket[]     hashed = this.getHashedSubPackets();
            try
            {
                sOut.write((byte)this.getVersion());
                sOut.write((byte)this.getSignatureType());
                sOut.write((byte)this.getKeyAlgorithm());
                sOut.write((byte)this.getHashAlgorithm());

                ByteArrayOutputStream    hOut = new ByteArrayOutputStream();


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

                byte[]                   data = hOut.toByteArray();
                if (version != VERSION_6)
                {
                    StreamUtil.write2OctetLength(sOut, data.length);
                }
                else
                {
                    StreamUtil.write4OctetLength(sOut, data.length);
                }
                sOut.write(data);

                byte[]    hData = sOut.toByteArray();

                sOut.write((byte)this.getVersion());
                sOut.write((byte)0xff);
                if (version == VERSION_5)
                {
                    StreamUtil.write8OctetLength(sOut, hData.length);
                }
                else
                {
                    StreamUtil.write4OctetLength(sOut, hData.length);
                }
            }
            catch (IOException e)
            {
                throw new RuntimeException("exception generating trailer: " + e);
            }

            trailer = sOut.toByteArray();
        }

        return trailer;
    }

    /**
     * return the encryption algorithm tag
     */
    public int getKeyAlgorithm()
    {
        return keyAlgorithm;
    }

    /**
     * return the hashAlgorithm tag
     */
    public int getHashAlgorithm()
    {
        return hashAlgorithm;
    }

    /**
     * return the signature as a set of integers - note this is normalised to be the
     * ASN.1 encoding of what appears in the signature packet.
     * Note, that Ed25519 and Ed448 returns null, as the raw signature is stored in signatureEncoding only.
     * For those, use {@link #getSignatureBytes()} instead.
     */
    public MPInteger[] getSignature()
    {
        return signature;
    }

    /**
     * Return the byte encoding of the signature section.
     * @return uninterpreted signature bytes.
     */
    public byte[] getSignatureBytes()
    {
        if (signatureEncoding != null)
        {
            return Arrays.clone(signatureEncoding);
        }

        ByteArrayOutputStream bOut = new ByteArrayOutputStream();

        try
        {
            BCPGOutputStream bcOut = new BCPGOutputStream(bOut);
            for (int i = 0; i != signature.length; i++)
            {
                bcOut.writeObject(signature[i]);
            }
            bcOut.close();
        }
        catch (IOException e)
        {
            throw new RuntimeException("internal error: " + e);
        }

        return bOut.toByteArray();
    }

    public SignatureSubpacket[] getHashedSubPackets()
    {
        return hashedData;
    }

    public SignatureSubpacket[] getUnhashedSubPackets()
    {
        return unhashedData;
    }

    /**
     * Return the creation time of the signature in milli-seconds.
     *
     * @return the creation time in millis
     */
    public long getCreationTime()
    {
        return creationTime;
    }

    public void encode(
        BCPGOutputStream    out)
        throws IOException
    {
        ByteArrayOutputStream    bOut = new ByteArrayOutputStream();
        BCPGOutputStream         pOut = new BCPGOutputStream(bOut);

        pOut.write(version);

        if (version == VERSION_3 || version == VERSION_2)
        {
            pOut.write(5); // the length of the next block

            long    time = creationTime / 1000;

            pOut.write(signatureType);
            StreamUtil.writeTime(pOut, time);

            StreamUtil.writeKeyID(pOut, keyID);

            pOut.write(keyAlgorithm);
            pOut.write(hashAlgorithm);
        }
        else if (version == VERSION_4 || version == VERSION_5 || version == VERSION_6)
        {
            pOut.write(signatureType);
            pOut.write(keyAlgorithm);
            pOut.write(hashAlgorithm);

            ByteArrayOutputStream sOut = new ByteArrayOutputStream();
            writeSignatureSubpacketArray(sOut, pOut, hashedData);
            sOut.reset();
            writeSignatureSubpacketArray(sOut, pOut, unhashedData);
        }
        else
        {
            throw new IOException("unknown version: " + version);
        }

        pOut.write(fingerPrint);

        if (version == VERSION_6)
        {
            pOut.write(salt.length);
            pOut.write(salt);
        }

        if (signature != null)
        {
            for (int i = 0; i != signature.length; i++)
            {
                pOut.writeObject(signature[i]);
            }
        }
        else
        {
            pOut.write(signatureEncoding);
        }

        pOut.close();

        out.writePacket(hasNewPacketFormat(), SIGNATURE, bOut.toByteArray());
    }

    private void writeSignatureSubpacketArray(ByteArrayOutputStream sOut, BCPGOutputStream pOut, SignatureSubpacket[] array)
        throws IOException
    {
        for (int i = 0; i != array.length; i++)
        {
            array[i].encode(sOut);
        }

        byte[] data = sOut.toByteArray();

        if (version == VERSION_6)
        {
            StreamUtil.write4OctetLength(pOut, data.length);
        }
        else
        {
            StreamUtil.write2OctetLength(pOut, data.length);
        }
        pOut.write(data);
    }

    private void setCreationTime()
    {
        for (int i = 0; i != hashedData.length; i++)
        {
            if (hashedData[i] instanceof SignatureCreationTime)
            {
                creationTime = ((SignatureCreationTime)hashedData[i]).getTime().getTime();
                break;
            }
        }
    }

    /**
     * Iterate over the hashed and unhashed signature subpackets to identify either a {@link IssuerKeyID} or
     * {@link IssuerFingerprint} subpacket to derive the issuer key-ID from.
     * The issuer {@link IssuerKeyID} and {@link IssuerFingerprint} subpacket information is "self-authenticating",
     * as its authenticity can be verified by checking the signature with the corresponding key.
     * Therefore, we can also check the unhashed signature subpacket area.
     */
    private void setIssuerKeyId()
    {
        if (keyID != 0L)
        {
            return;
        }

        for (int idx = 0; idx != hashedData.length; idx++)
        {
            SignatureSubpacket p  = hashedData[idx];
            if (p instanceof IssuerKeyID)
            {
                keyID = ((IssuerKeyID) p).getKeyID();
                return;
            }
            if (p instanceof IssuerFingerprint)
            {
                keyID = ((IssuerFingerprint) p).getKeyID();
                return;
            }
        }

        for (int idx = 0; idx != unhashedData.length; idx++)
        {
            SignatureSubpacket p = unhashedData[idx];
            if (p instanceof IssuerKeyID)
            {
                keyID = ((IssuerKeyID) p).getKeyID();
                return;
            }
            if (p instanceof IssuerFingerprint)
            {
                keyID = ((IssuerFingerprint) p).getKeyID();
                return;
            }
        }
    }

    public static SignaturePacket fromByteArray(byte[] data)
        throws IOException
    {
        BCPGInputStream in = new BCPGInputStream(new ByteArrayInputStream(data));

        return new SignaturePacket(in);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy