using System; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities.Date; namespace Org.BouncyCastle.Bcpg.OpenPgp { /// Generator for old style PGP V3 Signatures. public class PgpV3SignatureGenerator { private readonly PublicKeyAlgorithmTag keyAlgorithm; private readonly HashAlgorithmTag hashAlgorithm; private PgpPrivateKey privKey; private ISigner sig; private IDigest dig; private int signatureType; private byte lastb; /// Create a generator for the passed in keyAlgorithm and hashAlgorithm codes. public PgpV3SignatureGenerator( PublicKeyAlgorithmTag keyAlgorithm, HashAlgorithmTag hashAlgorithm) { if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa) throw new ArgumentException("Invalid algorithm for V3 signature", nameof(keyAlgorithm)); this.keyAlgorithm = keyAlgorithm; this.hashAlgorithm = hashAlgorithm; dig = PgpUtilities.CreateDigest(hashAlgorithm); } /// Initialise the generator for signing. public void InitSign(int sigType, PgpPrivateKey privKey) { InitSign(sigType, privKey, null); } /// Initialise the generator for signing. public void InitSign(int sigType, PgpPrivateKey privKey, SecureRandom random) { this.privKey = privKey; this.signatureType = sigType; AsymmetricKeyParameter key = privKey.Key; this.sig = PgpUtilities.CreateSigner(keyAlgorithm, hashAlgorithm, key); try { ICipherParameters cp = key; // TODO Ask SignerUtilities whether random is permitted? if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa) { // EdDSA signers don't expect a SecureRandom } else { cp = ParameterUtilities.WithRandom(cp, 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 NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER Update(b.AsSpan(off, len)); #else 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); } #endif } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER public void Update(ReadOnlySpan input) { if (signatureType == PgpSignature.CanonicalTextDocument) { for (int i = 0; i < input.Length; ++i) { DoCanonicalUpdateByte(input[i]); } } else { sig.BlockUpdate(input); dig.BlockUpdate(input); } } #endif /// 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 V3 signature object containing the current signature state. public PgpSignature Generate() { long creationTime = DateTimeUtilities.CurrentUnixMs() / 1000L; byte[] hData = new byte[] { (byte) signatureType, (byte)(creationTime >> 24), (byte)(creationTime >> 16), (byte)(creationTime >> 8), (byte) creationTime }; 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(3, signatureType, privKey.KeyId, keyAlgorithm, hashAlgorithm, creationTime * 1000L, fingerPrint, sigValues)); } } }