using System; using System.IO; using Org.BouncyCastle.Bcpg.Sig; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Bcpg.OpenPgp { /// Generator for PGP signatures. // TODO Should be able to implement ISigner? public class PgpSignatureGenerator { private static readonly SignatureSubpacket[] EmptySignatureSubpackets = new SignatureSubpacket[0]; private PublicKeyAlgorithmTag keyAlgorithm; private HashAlgorithmTag hashAlgorithm; private PgpPrivateKey privKey; private ISigner sig; private IDigest dig; private int signatureType; private byte lastb; private SignatureSubpacket[] unhashed = EmptySignatureSubpackets; private SignatureSubpacket[] hashed = EmptySignatureSubpackets; /// Create a generator for the passed in keyAlgorithm and hashAlgorithm codes. public PgpSignatureGenerator( PublicKeyAlgorithmTag keyAlgorithm, HashAlgorithmTag hashAlgorithm) { this.keyAlgorithm = keyAlgorithm; this.hashAlgorithm = hashAlgorithm; dig = DigestUtilities.GetDigest(PgpUtilities.GetDigestName(hashAlgorithm)); sig = SignerUtilities.GetSigner(PgpUtilities.GetSignatureName(keyAlgorithm, hashAlgorithm)); } /// Initialise the generator for signing. public void InitSign( int sigType, PgpPrivateKey key) { InitSign(sigType, key, null); } /// Initialise the generator for signing. public void InitSign( int sigType, PgpPrivateKey key, SecureRandom random) { this.privKey = key; this.signatureType = sigType; try { ICipherParameters cp = key.Key; if (random != null) { cp = new ParametersWithRandom(key.Key, random); } sig.Init(true, cp); } catch (InvalidKeyException e) { throw new PgpException("invalid key.", e); } dig.Reset(); lastb = 0; } public void Update( byte b) { if (signatureType == PgpSignature.CanonicalTextDocument) { doCanonicalUpdateByte(b); } else { doUpdateByte(b); } } private void doCanonicalUpdateByte( byte b) { if (b == '\r') { doUpdateCRLF(); } else if (b == '\n') { if (lastb != '\r') { doUpdateCRLF(); } } else { doUpdateByte(b); } lastb = b; } private void doUpdateCRLF() { doUpdateByte((byte)'\r'); doUpdateByte((byte)'\n'); } private void doUpdateByte( byte b) { sig.Update(b); dig.Update(b); } public void Update( params byte[] b) { Update(b, 0, b.Length); } public void Update( byte[] b, int off, int len) { if (signatureType == PgpSignature.CanonicalTextDocument) { int finish = off + len; for (int i = off; i != finish; i++) { doCanonicalUpdateByte(b[i]); } } else { sig.BlockUpdate(b, off, len); dig.BlockUpdate(b, off, len); } } public void SetHashedSubpackets( PgpSignatureSubpacketVector hashedPackets) { hashed = hashedPackets == null ? EmptySignatureSubpackets : hashedPackets.ToSubpacketArray(); } public void SetUnhashedSubpackets( PgpSignatureSubpacketVector unhashedPackets) { unhashed = unhashedPackets == null ? EmptySignatureSubpackets : unhashedPackets.ToSubpacketArray(); } /// Return the one pass header associated with the current signature. public PgpOnePassSignature GenerateOnePassVersion( bool isNested) { return new PgpOnePassSignature( new OnePassSignaturePacket( signatureType, hashAlgorithm, keyAlgorithm, privKey.KeyId, isNested)); } /// Return a signature object containing the current signature state. public PgpSignature Generate() { SignatureSubpacket[] hPkts = hashed, unhPkts = unhashed; if (!packetPresent(hashed, SignatureSubpacketTag.CreationTime)) { hPkts = insertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow)); } if (!packetPresent(hashed, SignatureSubpacketTag.IssuerKeyId) && !packetPresent(unhashed, SignatureSubpacketTag.IssuerKeyId)) { unhPkts = insertSubpacket(unhPkts, new IssuerKeyId(false, privKey.KeyId)); } int version = 4; byte[] hData; try { MemoryStream hOut = new MemoryStream(); for (int i = 0; i != hPkts.Length; i++) { hPkts[i].Encode(hOut); } byte[] data = hOut.ToArray(); MemoryStream sOut = new MemoryStream(data.Length + 6); sOut.WriteByte((byte)version); sOut.WriteByte((byte)signatureType); sOut.WriteByte((byte)keyAlgorithm); sOut.WriteByte((byte)hashAlgorithm); sOut.WriteByte((byte)(data.Length >> 8)); sOut.WriteByte((byte)data.Length); sOut.Write(data, 0, data.Length); hData = sOut.ToArray(); } catch (IOException e) { throw new PgpException("exception encoding hashed data.", e); } sig.BlockUpdate(hData, 0, hData.Length); dig.BlockUpdate(hData, 0, hData.Length); hData = new byte[] { (byte) version, 0xff, (byte)(hData.Length >> 24), (byte)(hData.Length >> 16), (byte)(hData.Length >> 8), (byte) hData.Length }; sig.BlockUpdate(hData, 0, hData.Length); dig.BlockUpdate(hData, 0, hData.Length); byte[] sigBytes = sig.GenerateSignature(); byte[] digest = DigestUtilities.DoFinal(dig); byte[] fingerPrint = new byte[] { digest[0], digest[1] }; // an RSA signature bool isRsa = keyAlgorithm == PublicKeyAlgorithmTag.RsaSign || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral; MPInteger[] sigValues = isRsa ? PgpUtilities.RsaSigToMpi(sigBytes) : PgpUtilities.DsaSigToMpi(sigBytes); return new PgpSignature( new SignaturePacket(signatureType, privKey.KeyId, keyAlgorithm, hashAlgorithm, hPkts, unhPkts, fingerPrint, sigValues)); } /// Generate a certification for the passed in ID and key. /// The ID we are certifying against the public key. /// The key we are certifying against the ID. /// The certification. public PgpSignature GenerateCertification( string id, PgpPublicKey pubKey) { UpdateWithPublicKey(pubKey); // // hash in the id // UpdateWithIdData(0xb4, Strings.ToUtf8ByteArray(id)); return Generate(); } /// Generate a certification for the passed in userAttributes. /// The ID we are certifying against the public key. /// The key we are certifying against the ID. /// The certification. public PgpSignature GenerateCertification( PgpUserAttributeSubpacketVector userAttributes, PgpPublicKey pubKey) { UpdateWithPublicKey(pubKey); // // hash in the attributes // try { MemoryStream bOut = new MemoryStream(); foreach (UserAttributeSubpacket packet in userAttributes.ToSubpacketArray()) { packet.Encode(bOut); } UpdateWithIdData(0xd1, bOut.ToArray()); } catch (IOException e) { throw new PgpException("cannot encode subpacket array", e); } return this.Generate(); } /// Generate a certification for the passed in key against the passed in master key. /// The key we are certifying against. /// The key we are certifying. /// The certification. public PgpSignature GenerateCertification( PgpPublicKey masterKey, PgpPublicKey pubKey) { UpdateWithPublicKey(masterKey); UpdateWithPublicKey(pubKey); return Generate(); } /// Generate a certification, such as a revocation, for the passed in key. /// The key we are certifying. /// The certification. public PgpSignature GenerateCertification( PgpPublicKey pubKey) { UpdateWithPublicKey(pubKey); return Generate(); } private byte[] GetEncodedPublicKey( PgpPublicKey pubKey) { try { return pubKey.publicPk.GetEncodedContents(); } catch (IOException e) { throw new PgpException("exception preparing key.", e); } } private bool packetPresent( SignatureSubpacket[] packets, SignatureSubpacketTag type) { for (int i = 0; i != packets.Length; i++) { if (packets[i].SubpacketType == type) { return true; } } return false; } private SignatureSubpacket[] insertSubpacket( SignatureSubpacket[] packets, SignatureSubpacket subpacket) { SignatureSubpacket[] tmp = new SignatureSubpacket[packets.Length + 1]; tmp[0] = subpacket; packets.CopyTo(tmp, 1); return tmp; } private void UpdateWithIdData( int header, byte[] idBytes) { this.Update( (byte) header, (byte)(idBytes.Length >> 24), (byte)(idBytes.Length >> 16), (byte)(idBytes.Length >> 8), (byte)(idBytes.Length)); this.Update(idBytes); } private void UpdateWithPublicKey( PgpPublicKey key) { byte[] keyBytes = GetEncodedPublicKey(key); this.Update( (byte) 0x99, (byte)(keyBytes.Length >> 8), (byte)(keyBytes.Length)); this.Update(keyBytes); } } }