using System; using System.Collections.Generic; using System.IO; using Org.BouncyCastle.Bcpg.Sig; using Org.BouncyCastle.Crypto.Utilities; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Date; using Org.BouncyCastle.Utilities.IO; namespace Org.BouncyCastle.Bcpg { /// Generic signature packet. public class SignaturePacket : ContainedPacket { private int version; private int signatureType; private long creationTime; private long keyId; private PublicKeyAlgorithmTag keyAlgorithm; private HashAlgorithmTag hashAlgorithm; private MPInteger[] signature; private byte[] fingerprint; private SignatureSubpacket[] hashedData; private SignatureSubpacket[] unhashedData; private byte[] signatureEncoding; internal SignaturePacket(BcpgInputStream bcpgIn) { version = bcpgIn.ReadByte(); if (version == 3 || version == 2) { // int l = bcpgIn.ReadByte(); signatureType = bcpgIn.ReadByte(); creationTime = (((long)bcpgIn.ReadByte() << 24) | ((long)bcpgIn.ReadByte() << 16) | ((long)bcpgIn.ReadByte() << 8) | (uint)bcpgIn.ReadByte()) * 1000L; keyId |= (long)bcpgIn.ReadByte() << 56; keyId |= (long)bcpgIn.ReadByte() << 48; keyId |= (long)bcpgIn.ReadByte() << 40; keyId |= (long)bcpgIn.ReadByte() << 32; keyId |= (long)bcpgIn.ReadByte() << 24; keyId |= (long)bcpgIn.ReadByte() << 16; keyId |= (long)bcpgIn.ReadByte() << 8; keyId |= (uint)bcpgIn.ReadByte(); keyAlgorithm = (PublicKeyAlgorithmTag) bcpgIn.ReadByte(); hashAlgorithm = (HashAlgorithmTag) bcpgIn.ReadByte(); } else if (version == 4) { signatureType = bcpgIn.ReadByte(); keyAlgorithm = (PublicKeyAlgorithmTag) bcpgIn.ReadByte(); hashAlgorithm = (HashAlgorithmTag) bcpgIn.ReadByte(); int hashedLength = (bcpgIn.ReadByte() << 8) | bcpgIn.ReadByte(); byte[] hashed = new byte[hashedLength]; bcpgIn.ReadFully(hashed); // // read the signature sub packet data. // SignatureSubpacketsParser sIn = new SignatureSubpacketsParser( new MemoryStream(hashed, false)); var v = new List(); SignatureSubpacket sub; while ((sub = sIn.ReadPacket()) != null) { v.Add(sub); } hashedData = v.ToArray(); foreach (var p in hashedData) { if (p is IssuerKeyId issuerKeyId) { keyId = issuerKeyId.KeyId; } else if (p is SignatureCreationTime sigCreationTime) { creationTime = DateTimeUtilities.DateTimeToUnixMs(sigCreationTime.GetTime()); } } int unhashedLength = (bcpgIn.ReadByte() << 8) | bcpgIn.ReadByte(); byte[] unhashed = new byte[unhashedLength]; bcpgIn.ReadFully(unhashed); sIn = new SignatureSubpacketsParser(new MemoryStream(unhashed, false)); v.Clear(); while ((sub = sIn.ReadPacket()) != null) { v.Add(sub); } unhashedData = v.ToArray(); foreach (var p in unhashedData) { if (p is IssuerKeyId issuerKeyId) { keyId = issuerKeyId.KeyId; } } } else { Streams.Drain(bcpgIn); throw new UnsupportedPacketVersionException("unsupported version: " + version); } fingerprint = new byte[2]; bcpgIn.ReadFully(fingerprint); switch (keyAlgorithm) { case PublicKeyAlgorithmTag.RsaGeneral: case PublicKeyAlgorithmTag.RsaSign: MPInteger v = new MPInteger(bcpgIn); signature = new MPInteger[1]{ v }; break; case PublicKeyAlgorithmTag.Dsa: MPInteger r = new MPInteger(bcpgIn); MPInteger s = new MPInteger(bcpgIn); signature = new MPInteger[2]{ r, s }; break; case PublicKeyAlgorithmTag.ElGamalEncrypt: // yep, this really does happen sometimes. case PublicKeyAlgorithmTag.ElGamalGeneral: MPInteger p = new MPInteger(bcpgIn); MPInteger g = new MPInteger(bcpgIn); MPInteger y = new MPInteger(bcpgIn); signature = new MPInteger[3]{ p, g, y }; break; case PublicKeyAlgorithmTag.ECDsa: case PublicKeyAlgorithmTag.EdDsa_Legacy: MPInteger ecR = new MPInteger(bcpgIn); MPInteger ecS = new MPInteger(bcpgIn); signature = new MPInteger[2]{ ecR, ecS }; break; default: if (keyAlgorithm < PublicKeyAlgorithmTag.Experimental_1 || keyAlgorithm > PublicKeyAlgorithmTag.Experimental_11) throw new IOException("unknown signature key algorithm: " + keyAlgorithm); signature = null; signatureEncoding = Streams.ReadAll(bcpgIn); break; } } /** * 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, PublicKeyAlgorithmTag keyAlgorithm, HashAlgorithmTag 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, PublicKeyAlgorithmTag keyAlgorithm, HashAlgorithmTag 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, PublicKeyAlgorithmTag keyAlgorithm, HashAlgorithmTag hashAlgorithm, SignatureSubpacket[] hashedData, SignatureSubpacket[] unhashedData, byte[] fingerprint, MPInteger[] 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 int Version => version; public int SignatureType => signatureType; /** * return the keyId * @return the keyId that created the signature. */ public long KeyId => keyId; /** * Return the signatures fingerprint. * @return fingerprint (digest prefix) of the signature */ public byte[] GetFingerprint() { return Arrays.Clone(fingerprint); } /** * return the signature trailer that must be included with the data * to reconstruct the signature * * @return byte[] */ public byte[] GetSignatureTrailer() { if (version == 3) { long time = creationTime / 1000L; byte[] trailer = new byte[5]; trailer[0] = (byte)signatureType; Pack.UInt32_To_BE((uint)time, trailer, 1); return trailer; } MemoryStream sOut = new MemoryStream(); sOut.WriteByte((byte)Version); sOut.WriteByte((byte)SignatureType); sOut.WriteByte((byte)KeyAlgorithm); sOut.WriteByte((byte)HashAlgorithm); // Mark position an reserve two bytes for length long lengthPosition = sOut.Position; sOut.WriteByte(0x00); sOut.WriteByte(0x00); SignatureSubpacket[] hashed = GetHashedSubPackets(); for (int i = 0; i != hashed.Length; i++) { hashed[i].Encode(sOut); } ushort dataLength = Convert.ToUInt16(sOut.Position - lengthPosition - 2); uint hDataLength = Convert.ToUInt32(sOut.Position); sOut.WriteByte((byte)Version); sOut.WriteByte(0xff); sOut.WriteByte((byte)(hDataLength >> 24)); sOut.WriteByte((byte)(hDataLength >> 16)); sOut.WriteByte((byte)(hDataLength >> 8)); sOut.WriteByte((byte)(hDataLength )); // Reset position and fill in length sOut.Position = lengthPosition; sOut.WriteByte((byte)(dataLength >> 8)); sOut.WriteByte((byte)(dataLength )); return sOut.ToArray(); } public PublicKeyAlgorithmTag KeyAlgorithm => keyAlgorithm; public HashAlgorithmTag HashAlgorithm => 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. */ public MPInteger[] GetSignature() => signature; /** * Return the byte encoding of the signature section. * @return uninterpreted signature bytes. */ public byte[] GetSignatureBytes() { if (signatureEncoding != null) return (byte[])signatureEncoding.Clone(); MemoryStream bOut = new MemoryStream(); using (var pOut = new BcpgOutputStream(bOut)) { foreach (MPInteger sigObj in signature) { try { pOut.WriteObject(sigObj); } catch (IOException e) { throw new Exception("internal error: " + e); } } } return bOut.ToArray(); } public SignatureSubpacket[] GetHashedSubPackets() => hashedData; public SignatureSubpacket[] GetUnhashedSubPackets() => unhashedData; /// Return the creation time in milliseconds since 1 Jan., 1970 UTC. public long CreationTime => creationTime; public override void Encode(BcpgOutputStream bcpgOut) { MemoryStream bOut = new MemoryStream(); using (var pOut = new BcpgOutputStream(bOut)) { pOut.WriteByte((byte)version); if (version == 3 || version == 2) { byte nextBlockLength = 5; pOut.Write(nextBlockLength, (byte)signatureType); pOut.WriteInt((int)(creationTime / 1000L)); pOut.WriteLong(keyId); pOut.Write((byte)keyAlgorithm, (byte)hashAlgorithm); } else if (version == 4) { pOut.Write((byte)signatureType, (byte)keyAlgorithm, (byte)hashAlgorithm); EncodeLengthAndData(pOut, GetEncodedSubpackets(hashedData)); EncodeLengthAndData(pOut, GetEncodedSubpackets(unhashedData)); } else { throw new IOException("unknown version: " + version); } pOut.Write(fingerprint); if (signature != null) { pOut.WriteObjects(signature); } else { pOut.Write(signatureEncoding); } } bcpgOut.WritePacket(PacketTag.Signature, bOut.ToArray()); } private static void EncodeLengthAndData( BcpgOutputStream pOut, byte[] data) { pOut.WriteShort((short) data.Length); pOut.Write(data); } private static byte[] GetEncodedSubpackets( SignatureSubpacket[] ps) { MemoryStream sOut = new MemoryStream(); foreach (SignatureSubpacket p in ps) { p.Encode(sOut); } return sOut.ToArray(); } private void SetCreationTime() { foreach (SignatureSubpacket p in hashedData) { if (p is SignatureCreationTime signatureCreationTime) { creationTime = DateTimeUtilities.DateTimeToUnixMs(signatureCreationTime.GetTime()); break; } } } public static SignaturePacket FromByteArray(byte[] data) { BcpgInputStream input = BcpgInputStream.Wrap(new MemoryStream(data)); return new SignaturePacket(input); } } }