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.Math.EC.Rfc8032;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
namespace Org.BouncyCastle.Bcpg.OpenPgp
{
/// Generator for PGP signatures.
public class PgpSignatureGenerator
{
private static readonly SignatureSubpacket[] EmptySignatureSubpackets = new SignatureSubpacket[0];
private readonly PublicKeyAlgorithmTag keyAlgorithm;
private readonly 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 = 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_Legacy)
{
// 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
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 (!IsPacketPresent(hashed, SignatureSubpacketTag.CreationTime))
{
hPkts = InsertSubpacket(hPkts, new SignatureCreationTime(false, DateTime.UtcNow));
}
if (!IsPacketPresent(hashed, SignatureSubpacketTag.IssuerKeyId)
&& !IsPacketPresent(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[2]{ digest[0], digest[1] };
MPInteger[] sigValues;
if (keyAlgorithm == PublicKeyAlgorithmTag.EdDsa_Legacy)
{
int sigLen = sigBytes.Length;
if (sigLen == Ed25519.SignatureSize)
{
sigValues = new MPInteger[2]{
new MPInteger(new BigInteger(1, sigBytes, 0, 32)),
new MPInteger(new BigInteger(1, sigBytes, 32, 32))
};
}
else if (sigLen == Ed448.SignatureSize)
{
sigValues = new MPInteger[2]{
new MPInteger(new BigInteger(1, Arrays.Prepend(sigBytes, 0x40))),
new MPInteger(BigInteger.Zero)
};
}
else
{
throw new InvalidOperationException();
}
}
else if (keyAlgorithm == PublicKeyAlgorithmTag.RsaSign || keyAlgorithm == PublicKeyAlgorithmTag.RsaGeneral)
{
sigValues = PgpUtilities.RsaSigToMpi(sigBytes);
}
else
{
sigValues = 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
{
var 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 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 static byte[] GetEncodedPublicKey(PgpPublicKey pubKey)
{
try
{
return pubKey.publicPk.GetEncodedContents();
}
catch (IOException e)
{
throw new PgpException("exception preparing key.", e);
}
}
private static bool IsPacketPresent(SignatureSubpacket[] packets, SignatureSubpacketTag type)
{
for (int i = 0; i != packets.Length; i++)
{
if (packets[i].SubpacketType == type)
return true;
}
return false;
}
private static SignatureSubpacket[] InsertSubpacket(SignatureSubpacket[] packets, SignatureSubpacket subpacket)
{
return Arrays.Prepend(packets, subpacket);
}
private void UpdateWithIdData(int header, byte[] idBytes)
{
Update(
(byte) header,
(byte)(idBytes.Length >> 24),
(byte)(idBytes.Length >> 16),
(byte)(idBytes.Length >> 8),
(byte)(idBytes.Length));
Update(idBytes);
}
private void UpdateWithPublicKey(
PgpPublicKey key)
{
byte[] keyBytes = GetEncodedPublicKey(key);
Update(
0x99,
(byte)(keyBytes.Length >> 8),
(byte)(keyBytes.Length));
Update(keyBytes);
}
}
}