410 lines
9.9 KiB
C#
410 lines
9.9 KiB
C#
using System;
|
|
using System.IO;
|
|
using Org.BouncyCastle.Asn1;
|
|
|
|
using Org.BouncyCastle.Crypto;
|
|
using Org.BouncyCastle.Security;
|
|
using Org.BouncyCastle.Utilities;
|
|
using Org.BouncyCastle.Utilities.Date;
|
|
|
|
namespace Org.BouncyCastle.Bcpg.OpenPgp
|
|
{
|
|
/// <remarks>A PGP signature object.</remarks>
|
|
public class PgpSignature
|
|
{
|
|
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 DirectKey = 0x1f;
|
|
public const int KeyRevocation = 0x20;
|
|
public const int SubkeyRevocation = 0x28;
|
|
public const int CertificationRevocation = 0x30;
|
|
public const int Timestamp = 0x40;
|
|
|
|
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((SignaturePacket)bcpgInput.ReadPacket())
|
|
{
|
|
}
|
|
|
|
internal PgpSignature(
|
|
SignaturePacket sigPacket)
|
|
: this(sigPacket, null)
|
|
{
|
|
}
|
|
|
|
internal PgpSignature(
|
|
SignaturePacket sigPacket,
|
|
TrustPacket trustPacket)
|
|
{
|
|
if (sigPacket == null)
|
|
throw new ArgumentNullException("sigPacket");
|
|
|
|
this.sigPck = sigPacket;
|
|
this.signatureType = sigPck.SignatureType;
|
|
this.trustPck = trustPacket;
|
|
}
|
|
|
|
private void GetSig()
|
|
{
|
|
this.sig = SignerUtilities.GetSigner(
|
|
PgpUtilities.GetSignatureName(sigPck.KeyAlgorithm, sigPck.HashAlgorithm));
|
|
}
|
|
|
|
/// <summary>The OpenPGP version number for this signature.</summary>
|
|
public int Version
|
|
{
|
|
get { return sigPck.Version; }
|
|
}
|
|
|
|
/// <summary>The key algorithm associated with this signature.</summary>
|
|
public PublicKeyAlgorithmTag KeyAlgorithm
|
|
{
|
|
get { return sigPck.KeyAlgorithm; }
|
|
}
|
|
|
|
/// <summary>The hash algorithm associated with this signature.</summary>
|
|
public HashAlgorithmTag HashAlgorithm
|
|
{
|
|
get { return sigPck.HashAlgorithm; }
|
|
}
|
|
|
|
public void InitVerify(
|
|
PgpPublicKey pubKey)
|
|
{
|
|
lastb = 0;
|
|
if (sig == null)
|
|
{
|
|
GetSig();
|
|
}
|
|
try
|
|
{
|
|
sig.Init(false, pubKey.GetKey());
|
|
}
|
|
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 (signatureType == CanonicalTextDocument)
|
|
{
|
|
int finish = off + length;
|
|
|
|
for (int i = off; i != finish; i++)
|
|
{
|
|
doCanonicalUpdateByte(bytes[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sig.BlockUpdate(bytes, off, length);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify the signature as certifying the passed in public key as associated
|
|
/// with the passed in user attributes.
|
|
/// </summary>
|
|
/// <param name="userAttributes">User attributes the key was stored under.</param>
|
|
/// <param name="key">The key to be verified.</param>
|
|
/// <returns>True, if the signature matches, false otherwise.</returns>
|
|
public bool VerifyCertification(
|
|
PgpUserAttributeSubpacketVector userAttributes,
|
|
PgpPublicKey key)
|
|
{
|
|
UpdateWithPublicKey(key);
|
|
|
|
//
|
|
// hash in the userAttributes
|
|
//
|
|
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);
|
|
}
|
|
|
|
this.Update(sigPck.GetSignatureTrailer());
|
|
|
|
return sig.VerifySignature(this.GetSignature());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify the signature as certifying the passed in public key as associated
|
|
/// with the passed in ID.
|
|
/// </summary>
|
|
/// <param name="id">ID the key was stored under.</param>
|
|
/// <param name="key">The key to be verified.</param>
|
|
/// <returns>True, if the signature matches, false otherwise.</returns>
|
|
public bool VerifyCertification(
|
|
string id,
|
|
PgpPublicKey key)
|
|
{
|
|
UpdateWithPublicKey(key);
|
|
|
|
//
|
|
// hash in the id
|
|
//
|
|
UpdateWithIdData(0xb4, Strings.ToByteArray(id));
|
|
|
|
Update(sigPck.GetSignatureTrailer());
|
|
|
|
return sig.VerifySignature(GetSignature());
|
|
}
|
|
|
|
/// <summary>Verify a certification for the passed in key against the passed in master key.</summary>
|
|
/// <param name="masterKey">The key we are verifying against.</param>
|
|
/// <param name="pubKey">The key we are verifying.</param>
|
|
/// <returns>True, if the certification is valid, false otherwise.</returns>
|
|
public bool VerifyCertification(
|
|
PgpPublicKey masterKey,
|
|
PgpPublicKey pubKey)
|
|
{
|
|
UpdateWithPublicKey(masterKey);
|
|
UpdateWithPublicKey(pubKey);
|
|
|
|
Update(sigPck.GetSignatureTrailer());
|
|
|
|
return sig.VerifySignature(GetSignature());
|
|
}
|
|
|
|
/// <summary>Verify a key certification, such as revocation, for the passed in key.</summary>
|
|
/// <param name="pubKey">The key we are checking.</param>
|
|
/// <returns>True, if the certification is valid, false otherwise.</returns>
|
|
public bool VerifyCertification(
|
|
PgpPublicKey pubKey)
|
|
{
|
|
if (SignatureType != KeyRevocation
|
|
&& SignatureType != SubkeyRevocation)
|
|
{
|
|
throw new InvalidOperationException("signature is not a key signature");
|
|
}
|
|
|
|
UpdateWithPublicKey(pubKey);
|
|
|
|
Update(sigPck.GetSignatureTrailer());
|
|
|
|
return sig.VerifySignature(GetSignature());
|
|
}
|
|
|
|
public int SignatureType
|
|
{
|
|
get { return sigPck.SignatureType; }
|
|
}
|
|
|
|
/// <summary>The ID of the key that created the signature.</summary>
|
|
public long KeyId
|
|
{
|
|
get { return sigPck.KeyId; }
|
|
}
|
|
|
|
[Obsolete("Use 'CreationTime' property instead")]
|
|
public DateTime GetCreationTime()
|
|
{
|
|
return CreationTime;
|
|
}
|
|
|
|
/// <summary>The creation time of this signature.</summary>
|
|
public DateTime CreationTime
|
|
{
|
|
get { return DateTimeUtilities.UnixMsToDateTime(sigPck.CreationTime); }
|
|
}
|
|
|
|
public byte[] GetSignatureTrailer()
|
|
{
|
|
return sigPck.GetSignatureTrailer();
|
|
}
|
|
|
|
public PgpSignatureSubpacketVector GetHashedSubPackets()
|
|
{
|
|
return createSubpacketVector(sigPck.GetHashedSubPackets());
|
|
}
|
|
|
|
public PgpSignatureSubpacketVector GetUnhashedSubPackets()
|
|
{
|
|
return createSubpacketVector(sigPck.GetUnhashedSubPackets());
|
|
}
|
|
|
|
private 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
|
|
{
|
|
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()
|
|
{
|
|
MemoryStream bOut = new MemoryStream();
|
|
|
|
Encode(bOut);
|
|
|
|
return bOut.ToArray();
|
|
}
|
|
|
|
public void Encode(
|
|
Stream outStream)
|
|
{
|
|
BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStream);
|
|
|
|
bcpgOut.WritePacket(sigPck);
|
|
|
|
if (trustPck != null)
|
|
{
|
|
bcpgOut.WritePacket(trustPck);
|
|
}
|
|
}
|
|
|
|
private byte[] GetEncodedPublicKey(
|
|
PgpPublicKey pubKey)
|
|
{
|
|
try
|
|
{
|
|
return pubKey.publicPk.GetEncodedContents();
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
throw new PgpException("exception preparing key.", e);
|
|
}
|
|
}
|
|
}
|
|
}
|