using System;
using System.Collections.Generic;
using System.IO;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC.Rfc8032;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.Date;
namespace Org.BouncyCastle.Bcpg.OpenPgp
{
/// A PGP signature object.
public class PgpSignature
{
private static SignaturePacket Cast(Packet packet)
{
if (packet is SignaturePacket signaturePacket)
return signaturePacket;
throw new IOException("unexpected packet in stream: " + packet);
}
public const int BinaryDocument = 0x00;
public const int CanonicalTextDocument = 0x01;
public const int StandAlone = 0x02;
public const int DefaultCertification = 0x10;
public const int NoCertification = 0x11;
public const int CasualCertification = 0x12;
public const int PositiveCertification = 0x13;
public const int SubkeyBinding = 0x18;
public const int PrimaryKeyBinding = 0x19;
public const int DirectKey = 0x1f;
public const int KeyRevocation = 0x20;
public const int SubkeyRevocation = 0x28;
public const int CertificationRevocation = 0x30;
public const int Timestamp = 0x40;
public const int ThirdPartyConfirmation = 0x50;
private readonly SignaturePacket sigPck;
private readonly int signatureType;
private readonly TrustPacket trustPck;
private ISigner sig;
private byte lastb; // Initial value anything but '\r'
internal PgpSignature(
BcpgInputStream bcpgInput)
: this(Cast(bcpgInput.ReadPacket()))
{
}
internal PgpSignature(SignaturePacket sigPacket)
: this(sigPacket, null)
{
}
internal PgpSignature(SignaturePacket sigPacket, TrustPacket trustPacket)
{
this.sigPck = sigPacket ?? throw new ArgumentNullException(nameof(sigPacket));
this.signatureType = sigPck.SignatureType;
this.trustPck = trustPacket;
}
/// The OpenPGP version number for this signature.
public int Version
{
get { return sigPck.Version; }
}
/// The key algorithm associated with this signature.
public PublicKeyAlgorithmTag KeyAlgorithm
{
get { return sigPck.KeyAlgorithm; }
}
/// The hash algorithm associated with this signature.
public HashAlgorithmTag HashAlgorithm
{
get { return sigPck.HashAlgorithm; }
}
/// Return the digest prefix of the signature.
public byte[] GetDigestPrefix()
{
return sigPck.GetFingerprint();
}
/// Return true if this signature represents a certification.
public bool IsCertification()
{
return IsCertification(SignatureType);
}
public void InitVerify(PgpPublicKey pubKey)
{
lastb = 0;
AsymmetricKeyParameter key = pubKey.GetKey();
if (sig == null)
{
this.sig = PgpUtilities.CreateSigner(sigPck.KeyAlgorithm, sigPck.HashAlgorithm, key);
}
try
{
sig.Init(false, key);
}
catch (InvalidKeyException e)
{
throw new PgpException("invalid key.", e);
}
}
public void Update(byte b)
{
if (signatureType == CanonicalTextDocument)
{
DoCanonicalUpdateByte(b);
}
else
{
sig.Update(b);
}
}
private void DoCanonicalUpdateByte(byte b)
{
if (b == '\r')
{
DoUpdateCRLF();
}
else if (b == '\n')
{
if (lastb != '\r')
{
DoUpdateCRLF();
}
}
else
{
sig.Update(b);
}
lastb = b;
}
private void DoUpdateCRLF()
{
sig.Update((byte)'\r');
sig.Update((byte)'\n');
}
public void Update(params byte[] bytes)
{
Update(bytes, 0, bytes.Length);
}
public void Update(byte[] bytes, int off, int length)
{
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
Update(bytes.AsSpan(off, length));
#else
if (signatureType == CanonicalTextDocument)
{
int finish = off + length;
for (int i = off; i != finish; i++)
{
DoCanonicalUpdateByte(bytes[i]);
}
}
else
{
sig.BlockUpdate(bytes, off, length);
}
#endif
}
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
public void Update(ReadOnlySpan input)
{
if (signatureType == CanonicalTextDocument)
{
for (int i = 0; i < input.Length; ++i)
{
DoCanonicalUpdateByte(input[i]);
}
}
else
{
sig.BlockUpdate(input);
}
}
#endif
public bool Verify()
{
byte[] trailer = GetSignatureTrailer();
sig.BlockUpdate(trailer, 0, trailer.Length);
return sig.VerifySignature(GetSignature());
}
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);
}
///
/// Verify the signature as certifying the passed in public key as associated
/// with the passed in user attributes.
///
/// User attributes the key was stored under.
/// The key to be verified.
/// True, if the signature matches, false otherwise.
public bool VerifyCertification(PgpUserAttributeSubpacketVector userAttributes, PgpPublicKey key)
{
UpdateWithPublicKey(key);
//
// hash in the userAttributes
//
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 Verify();
}
///
/// Verify the signature as certifying the passed in public key as associated
/// with the passed in ID.
///
/// ID the key was stored under.
/// The key to be verified.
/// True, if the signature matches, false otherwise.
public bool VerifyCertification(string id, PgpPublicKey key)
{
UpdateWithPublicKey(key);
//
// hash in the id
//
UpdateWithIdData(0xb4, Strings.ToUtf8ByteArray(id));
return Verify();
}
/// Verify a certification for the passed in key against the passed in master key.
/// The key we are verifying against.
/// The key we are verifying.
/// True, if the certification is valid, false otherwise.
public bool VerifyCertification(
PgpPublicKey masterKey,
PgpPublicKey pubKey)
{
UpdateWithPublicKey(masterKey);
UpdateWithPublicKey(pubKey);
return Verify();
}
/// Verify a key certification, such as revocation, for the passed in key.
/// The key we are checking.
/// True, if the certification is valid, false otherwise.
public bool VerifyCertification(
PgpPublicKey pubKey)
{
if (SignatureType != KeyRevocation
&& SignatureType != SubkeyRevocation)
{
throw new InvalidOperationException("signature is not a key signature");
}
UpdateWithPublicKey(pubKey);
return Verify();
}
public int SignatureType
{
get { return sigPck.SignatureType; }
}
/// The ID of the key that created the signature.
public long KeyId
{
get { return sigPck.KeyId; }
}
/// The creation time of this signature.
public DateTime CreationTime
{
get { return DateTimeUtilities.UnixMsToDateTime(sigPck.CreationTime); }
}
public byte[] GetSignatureTrailer()
{
return sigPck.GetSignatureTrailer();
}
///
/// Return true if the signature has either hashed or unhashed subpackets.
///
public bool HasSubpackets
{
get
{
return sigPck.GetHashedSubPackets() != null
|| sigPck.GetUnhashedSubPackets() != null;
}
}
public PgpSignatureSubpacketVector GetHashedSubPackets()
{
return CreateSubpacketVector(sigPck.GetHashedSubPackets());
}
public PgpSignatureSubpacketVector GetUnhashedSubPackets()
{
return CreateSubpacketVector(sigPck.GetUnhashedSubPackets());
}
private static PgpSignatureSubpacketVector CreateSubpacketVector(SignatureSubpacket[] pcks)
{
return pcks == null ? null : new PgpSignatureSubpacketVector(pcks);
}
public byte[] GetSignature()
{
MPInteger[] sigValues = sigPck.GetSignature();
byte[] signature;
if (sigValues != null)
{
if (sigValues.Length == 1) // an RSA signature
{
signature = sigValues[0].Value.ToByteArrayUnsigned();
}
else if (KeyAlgorithm == PublicKeyAlgorithmTag.EdDsa)
{
if (sigValues.Length != 2)
throw new InvalidOperationException();
BigInteger v0 = sigValues[0].Value;
BigInteger v1 = sigValues[1].Value;
if (v0.BitLength == 918 &&
v1.Equals(BigInteger.Zero) &&
v0.ShiftRight(912).Equals(BigInteger.ValueOf(0x40)))
{
signature = new byte[Ed448.SignatureSize];
BigIntegers.AsUnsignedByteArray(v0.ClearBit(918), signature, 0, signature.Length);
}
else if (v0.BitLength <= 256 && v1.BitLength <= 256)
{
signature = new byte[Ed25519.SignatureSize];
BigIntegers.AsUnsignedByteArray(sigValues[0].Value, signature, 0, 32);
BigIntegers.AsUnsignedByteArray(sigValues[1].Value, signature, 32, 32);
}
else
{
throw new InvalidOperationException();
}
}
else
{
if (sigValues.Length != 2)
throw new InvalidOperationException();
try
{
signature = new DerSequence(
new DerInteger(sigValues[0].Value),
new DerInteger(sigValues[1].Value)).GetEncoded();
}
catch (IOException e)
{
throw new PgpException("exception encoding DSA sig.", e);
}
}
}
else
{
signature = sigPck.GetSignatureBytes();
}
return signature;
}
// TODO Handle the encoding stuff by subclassing BcpgObject?
public byte[] GetEncoded()
{
var bOut = new MemoryStream();
Encode(bOut);
return bOut.ToArray();
}
public void Encode(Stream outStream)
{
Encode(outStream, false);
}
/**
* Encode the signature to outStream, with trust packets stripped out if forTransfer is true.
*
* @param outStream stream to write the key encoding to.
* @param forTransfer if the purpose of encoding is to send key to other users.
* @throws IOException in case of encoding error.
*/
public void Encode(Stream outStream, bool forTransfer)
{
// Exportable signatures MUST NOT be exported if forTransfer==true
if (forTransfer && (!GetHashedSubPackets().IsExportable() || !GetUnhashedSubPackets().IsExportable()))
return;
var bcpgOut = BcpgOutputStream.Wrap(outStream);
bcpgOut.WritePacket(sigPck);
if (!forTransfer && trustPck != null)
{
bcpgOut.WritePacket(trustPck);
}
}
private static byte[] GetEncodedPublicKey(PgpPublicKey pubKey)
{
try
{
return pubKey.publicPk.GetEncodedContents();
}
catch (IOException e)
{
throw new PgpException("exception preparing key.", e);
}
}
///
/// Return true if the passed in signature type represents a certification, false if the signature type is not.
///
///
/// true if signatureType is a certification, false otherwise.
public static bool IsCertification(int signatureType)
{
switch (signatureType)
{
case DefaultCertification:
case NoCertification:
case CasualCertification:
case PositiveCertification:
return true;
default:
return false;
}
}
public static bool IsSignatureEncodingEqual(PgpSignature sig1, PgpSignature sig2)
{
return Arrays.AreEqual(sig1.sigPck.GetSignatureBytes(), sig2.sigPck.GetSignatureBytes());
}
public static PgpSignature Join(PgpSignature sig1, PgpSignature sig2)
{
if (!IsSignatureEncodingEqual(sig1, sig2))
throw new ArgumentException("These are different signatures.");
// merge unhashed subpackets
SignatureSubpacket[] sig1Unhashed = sig1.GetUnhashedSubPackets().ToSubpacketArray();
SignatureSubpacket[] sig2Unhashed = sig2.GetUnhashedSubPackets().ToSubpacketArray();
var merged = new List(sig1Unhashed);
foreach (var subpacket in sig2Unhashed)
{
if (!merged.Contains(subpacket))
{
merged.Add(subpacket);
}
}
SignatureSubpacket[] unhashed = merged.ToArray();
return new PgpSignature(
new SignaturePacket(
sig1.SignatureType,
sig1.KeyId,
sig1.KeyAlgorithm,
sig1.HashAlgorithm,
sig1.GetHashedSubPackets().ToSubpacketArray(),
unhashed,
sig1.GetDigestPrefix(),
sig1.sigPck.GetSignature()
)
);
}
}
}