using System;
using System.Collections;
using System.IO;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.IO;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.Collections;
namespace Org.BouncyCastle.Bcpg.OpenPgp
{
/// General class to handle a PGP public key object.
public class PgpPublicKey
{
private static readonly int[] MasterKeyCertificationTypes = new int[]
{
PgpSignature.PositiveCertification,
PgpSignature.CasualCertification,
PgpSignature.NoCertification,
PgpSignature.DefaultCertification
};
private long keyId;
private byte[] fingerprint;
private int keyStrength;
internal PublicKeyPacket publicPk;
internal TrustPacket trustPk;
internal IList keySigs = Platform.CreateArrayList();
internal IList ids = Platform.CreateArrayList();
internal IList idTrusts = Platform.CreateArrayList();
internal IList idSigs = Platform.CreateArrayList();
internal IList subSigs;
private void Init()
{
IBcpgKey key = publicPk.Key;
if (publicPk.Version <= 3)
{
RsaPublicBcpgKey rK = (RsaPublicBcpgKey) key;
this.keyId = rK.Modulus.LongValue;
try
{
IDigest digest = DigestUtilities.GetDigest("MD5");
byte[] bytes = rK.Modulus.ToByteArrayUnsigned();
digest.BlockUpdate(bytes, 0, bytes.Length);
bytes = rK.PublicExponent.ToByteArrayUnsigned();
digest.BlockUpdate(bytes, 0, bytes.Length);
this.fingerprint = DigestUtilities.DoFinal(digest);
}
//catch (NoSuchAlgorithmException)
catch (Exception e)
{
throw new IOException("can't find MD5", e);
}
this.keyStrength = rK.Modulus.BitLength;
}
else
{
byte[] kBytes = publicPk.GetEncodedContents();
try
{
IDigest digest = DigestUtilities.GetDigest("SHA1");
digest.Update(0x99);
digest.Update((byte)(kBytes.Length >> 8));
digest.Update((byte)kBytes.Length);
digest.BlockUpdate(kBytes, 0, kBytes.Length);
this.fingerprint = DigestUtilities.DoFinal(digest);
}
catch (Exception e)
{
throw new IOException("can't find SHA1", e);
}
this.keyId = (long)(((ulong)fingerprint[fingerprint.Length - 8] << 56)
| ((ulong)fingerprint[fingerprint.Length - 7] << 48)
| ((ulong)fingerprint[fingerprint.Length - 6] << 40)
| ((ulong)fingerprint[fingerprint.Length - 5] << 32)
| ((ulong)fingerprint[fingerprint.Length - 4] << 24)
| ((ulong)fingerprint[fingerprint.Length - 3] << 16)
| ((ulong)fingerprint[fingerprint.Length - 2] << 8)
| (ulong)fingerprint[fingerprint.Length - 1]);
if (key is RsaPublicBcpgKey)
{
this.keyStrength = ((RsaPublicBcpgKey)key).Modulus.BitLength;
}
else if (key is DsaPublicBcpgKey)
{
this.keyStrength = ((DsaPublicBcpgKey)key).P.BitLength;
}
else if (key is ElGamalPublicBcpgKey)
{
this.keyStrength = ((ElGamalPublicBcpgKey)key).P.BitLength;
}
}
}
///
/// Create a PgpPublicKey from the passed in lightweight one.
///
///
/// Note: the time passed in affects the value of the key's keyId, so you probably only want
/// to do this once for a lightweight key, or make sure you keep track of the time you used.
///
/// Asymmetric algorithm type representing the public key.
/// Actual public key to associate.
/// Date of creation.
/// If pubKey is not public.
/// On key creation problem.
public PgpPublicKey(
PublicKeyAlgorithmTag algorithm,
AsymmetricKeyParameter pubKey,
DateTime time)
{
if (pubKey.IsPrivate)
throw new ArgumentException("Expected a public key", "pubKey");
IBcpgKey bcpgKey;
if (pubKey is RsaKeyParameters)
{
RsaKeyParameters rK = (RsaKeyParameters) pubKey;
bcpgKey = new RsaPublicBcpgKey(rK.Modulus, rK.Exponent);
}
else if (pubKey is DsaPublicKeyParameters)
{
DsaPublicKeyParameters dK = (DsaPublicKeyParameters) pubKey;
DsaParameters dP = dK.Parameters;
bcpgKey = new DsaPublicBcpgKey(dP.P, dP.Q, dP.G, dK.Y);
}
else if (pubKey is ElGamalPublicKeyParameters)
{
ElGamalPublicKeyParameters eK = (ElGamalPublicKeyParameters) pubKey;
ElGamalParameters eS = eK.Parameters;
bcpgKey = new ElGamalPublicBcpgKey(eS.P, eS.G, eK.Y);
}
else
{
throw new PgpException("unknown key class");
}
this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey);
this.ids = Platform.CreateArrayList();
this.idSigs = Platform.CreateArrayList();
try
{
Init();
}
catch (IOException e)
{
throw new PgpException("exception calculating keyId", e);
}
}
/// Constructor for a sub-key.
internal PgpPublicKey(
PublicKeyPacket publicPk,
TrustPacket trustPk,
IList sigs)
{
this.publicPk = publicPk;
this.trustPk = trustPk;
this.subSigs = sigs;
Init();
}
internal PgpPublicKey(
PgpPublicKey key,
TrustPacket trust,
IList subSigs)
{
this.publicPk = key.publicPk;
this.trustPk = trust;
this.subSigs = subSigs;
this.fingerprint = key.fingerprint;
this.keyId = key.keyId;
this.keyStrength = key.keyStrength;
}
/// Copy constructor.
/// The public key to copy.
internal PgpPublicKey(
PgpPublicKey pubKey)
{
this.publicPk = pubKey.publicPk;
this.keySigs = Platform.CreateArrayList(pubKey.keySigs);
this.ids = Platform.CreateArrayList(pubKey.ids);
this.idTrusts = Platform.CreateArrayList(pubKey.idTrusts);
this.idSigs = Platform.CreateArrayList(pubKey.idSigs.Count);
for (int i = 0; i != pubKey.idSigs.Count; i++)
{
this.idSigs.Add(Platform.CreateArrayList((IList)pubKey.idSigs[i]));
}
if (pubKey.subSigs != null)
{
this.subSigs = Platform.CreateArrayList(pubKey.subSigs.Count);
for (int i = 0; i != pubKey.subSigs.Count; i++)
{
this.subSigs.Add(pubKey.subSigs[i]);
}
}
this.fingerprint = pubKey.fingerprint;
this.keyId = pubKey.keyId;
this.keyStrength = pubKey.keyStrength;
}
internal PgpPublicKey(
PublicKeyPacket publicPk,
TrustPacket trustPk,
IList keySigs,
IList ids,
IList idTrusts,
IList idSigs)
{
this.publicPk = publicPk;
this.trustPk = trustPk;
this.keySigs = keySigs;
this.ids = ids;
this.idTrusts = idTrusts;
this.idSigs = idSigs;
Init();
}
internal PgpPublicKey(
PublicKeyPacket publicPk,
IList ids,
IList idSigs)
{
this.publicPk = publicPk;
this.ids = ids;
this.idSigs = idSigs;
Init();
}
/// The version of this key.
public int Version
{
get { return publicPk.Version; }
}
/// The creation time of this key.
public DateTime CreationTime
{
get { return publicPk.GetTime(); }
}
/// The number of valid days from creation time - zero means no expiry.
public int ValidDays
{
get
{
if (publicPk.Version > 3)
{
return (int)(GetValidSeconds() / (24 * 60 * 60));
}
return publicPk.ValidDays;
}
}
/// Return the trust data associated with the public key, if present.
/// A byte array with trust data, null otherwise.
public byte[] GetTrustData()
{
if (trustPk == null)
{
return null;
}
return trustPk.GetLevelAndTrustAmount();
}
/// The number of valid seconds from creation time - zero means no expiry.
public long GetValidSeconds()
{
if (publicPk.Version > 3)
{
if (IsMasterKey)
{
for (int i = 0; i != MasterKeyCertificationTypes.Length; i++)
{
long seconds = GetExpirationTimeFromSig(true, MasterKeyCertificationTypes[i]);
if (seconds >= 0)
{
return seconds;
}
}
}
else
{
long seconds = GetExpirationTimeFromSig(false, PgpSignature.SubkeyBinding);
if (seconds >= 0)
{
return seconds;
}
}
return 0;
}
return (long) publicPk.ValidDays * 24 * 60 * 60;
}
private long GetExpirationTimeFromSig(
bool selfSigned,
int signatureType)
{
foreach (PgpSignature sig in GetSignaturesOfType(signatureType))
{
if (!selfSigned || sig.KeyId == KeyId)
{
PgpSignatureSubpacketVector hashed = sig.GetHashedSubPackets();
if (hashed != null)
{
return hashed.GetKeyExpirationTime();
}
return 0;
}
}
return -1;
}
/// The keyId associated with the public key.
public long KeyId
{
get { return keyId; }
}
/// The fingerprint of the key
public byte[] GetFingerprint()
{
return (byte[]) fingerprint.Clone();
}
///
/// Check if this key has an algorithm type that makes it suitable to use for encryption.
///
///
/// Note: with version 4 keys KeyFlags subpackets should also be considered when present for
/// determining the preferred use of the key.
///
///
/// true if this key algorithm is suitable for encryption.
///
public bool IsEncryptionKey
{
get
{
switch (publicPk.Algorithm)
{
case PublicKeyAlgorithmTag.ElGamalEncrypt:
case PublicKeyAlgorithmTag.ElGamalGeneral:
case PublicKeyAlgorithmTag.RsaEncrypt:
case PublicKeyAlgorithmTag.RsaGeneral:
return true;
default:
return false;
}
}
}
/// True, if this is a master key.
public bool IsMasterKey
{
get { return subSigs == null; }
}
/// The algorithm code associated with the public key.
public PublicKeyAlgorithmTag Algorithm
{
get { return publicPk.Algorithm; }
}
/// The strength of the key in bits.
public int BitStrength
{
get { return keyStrength; }
}
/// The public key contained in the object.
/// A lightweight public key.
/// If the key algorithm is not recognised.
public AsymmetricKeyParameter GetKey()
{
try
{
switch (publicPk.Algorithm)
{
case PublicKeyAlgorithmTag.RsaEncrypt:
case PublicKeyAlgorithmTag.RsaGeneral:
case PublicKeyAlgorithmTag.RsaSign:
RsaPublicBcpgKey rsaK = (RsaPublicBcpgKey) publicPk.Key;
return new RsaKeyParameters(false, rsaK.Modulus, rsaK.PublicExponent);
case PublicKeyAlgorithmTag.Dsa:
DsaPublicBcpgKey dsaK = (DsaPublicBcpgKey) publicPk.Key;
return new DsaPublicKeyParameters(dsaK.Y, new DsaParameters(dsaK.P, dsaK.Q, dsaK.G));
case PublicKeyAlgorithmTag.ElGamalEncrypt:
case PublicKeyAlgorithmTag.ElGamalGeneral:
ElGamalPublicBcpgKey elK = (ElGamalPublicBcpgKey) publicPk.Key;
return new ElGamalPublicKeyParameters(elK.Y, new ElGamalParameters(elK.P, elK.G));
default:
throw new PgpException("unknown public key algorithm encountered");
}
}
catch (PgpException e)
{
throw e;
}
catch (Exception e)
{
throw new PgpException("exception constructing public key", e);
}
}
/// Allows enumeration of any user IDs associated with the key.
/// An IEnumerable of string objects.
public IEnumerable GetUserIds()
{
IList temp = Platform.CreateArrayList();
foreach (object o in ids)
{
if (o is string)
{
temp.Add(o);
}
}
return new EnumerableProxy(temp);
}
/// Allows enumeration of any user attribute vectors associated with the key.
/// An IEnumerable of PgpUserAttributeSubpacketVector objects.
public IEnumerable GetUserAttributes()
{
IList temp = Platform.CreateArrayList();
foreach (object o in ids)
{
if (o is PgpUserAttributeSubpacketVector)
{
temp.Add(o);
}
}
return new EnumerableProxy(temp);
}
/// Allows enumeration of any signatures associated with the passed in id.
/// The ID to be matched.
/// An IEnumerable of PgpSignature objects.
public IEnumerable GetSignaturesForId(
string id)
{
if (id == null)
throw new ArgumentNullException("id");
for (int i = 0; i != ids.Count; i++)
{
if (id.Equals(ids[i]))
{
return new EnumerableProxy((IList)idSigs[i]);
}
}
return null;
}
/// Allows enumeration of signatures associated with the passed in user attributes.
/// The vector of user attributes to be matched.
/// An IEnumerable of PgpSignature objects.
public IEnumerable GetSignaturesForUserAttribute(
PgpUserAttributeSubpacketVector userAttributes)
{
for (int i = 0; i != ids.Count; i++)
{
if (userAttributes.Equals(ids[i]))
{
return new EnumerableProxy((IList) idSigs[i]);
}
}
return null;
}
/// Allows enumeration of signatures of the passed in type that are on this key.
/// The type of the signature to be returned.
/// An IEnumerable of PgpSignature objects.
public IEnumerable GetSignaturesOfType(
int signatureType)
{
IList temp = Platform.CreateArrayList();
foreach (PgpSignature sig in GetSignatures())
{
if (sig.SignatureType == signatureType)
{
temp.Add(sig);
}
}
return new EnumerableProxy(temp);
}
/// Allows enumeration of all signatures/certifications associated with this key.
/// An IEnumerable with all signatures/certifications.
public IEnumerable GetSignatures()
{
IList sigs;
if (subSigs != null)
{
sigs = subSigs;
}
else
{
sigs = Platform.CreateArrayList(keySigs);
foreach (ICollection extraSigs in idSigs)
{
CollectionUtilities.AddRange(sigs, extraSigs);
}
}
return new EnumerableProxy(sigs);
}
public byte[] GetEncoded()
{
MemoryStream bOut = new MemoryStream();
Encode(bOut);
return bOut.ToArray();
}
public void Encode(
Stream outStr)
{
BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr);
bcpgOut.WritePacket(publicPk);
if (trustPk != null)
{
bcpgOut.WritePacket(trustPk);
}
if (subSigs == null) // not a sub-key
{
foreach (PgpSignature keySig in keySigs)
{
keySig.Encode(bcpgOut);
}
for (int i = 0; i != ids.Count; i++)
{
if (ids[i] is string)
{
string id = (string) ids[i];
bcpgOut.WritePacket(new UserIdPacket(id));
}
else
{
PgpUserAttributeSubpacketVector v = (PgpUserAttributeSubpacketVector)ids[i];
bcpgOut.WritePacket(new UserAttributePacket(v.ToSubpacketArray()));
}
if (idTrusts[i] != null)
{
bcpgOut.WritePacket((ContainedPacket)idTrusts[i]);
}
foreach (PgpSignature sig in (IList) idSigs[i])
{
sig.Encode(bcpgOut);
}
}
}
else
{
foreach (PgpSignature subSig in subSigs)
{
subSig.Encode(bcpgOut);
}
}
}
/// Check whether this (sub)key has a revocation signature on it.
/// True, if this (sub)key has been revoked.
public bool IsRevoked()
{
int ns = 0;
bool revoked = false;
if (IsMasterKey) // Master key
{
while (!revoked && (ns < keySigs.Count))
{
if (((PgpSignature)keySigs[ns++]).SignatureType == PgpSignature.KeyRevocation)
{
revoked = true;
}
}
}
else // Sub-key
{
while (!revoked && (ns < subSigs.Count))
{
if (((PgpSignature)subSigs[ns++]).SignatureType == PgpSignature.SubkeyRevocation)
{
revoked = true;
}
}
}
return revoked;
}
/// Add a certification for an id to the given public key.
/// The key the certification is to be added to.
/// The ID the certification is associated with.
/// The new certification.
/// The re-certified key.
public static PgpPublicKey AddCertification(
PgpPublicKey key,
string id,
PgpSignature certification)
{
return AddCert(key, id, certification);
}
/// Add a certification for the given UserAttributeSubpackets to the given public key.
/// The key the certification is to be added to.
/// The attributes the certification is associated with.
/// The new certification.
/// The re-certified key.
public static PgpPublicKey AddCertification(
PgpPublicKey key,
PgpUserAttributeSubpacketVector userAttributes,
PgpSignature certification)
{
return AddCert(key, userAttributes, certification);
}
private static PgpPublicKey AddCert(
PgpPublicKey key,
object id,
PgpSignature certification)
{
PgpPublicKey returnKey = new PgpPublicKey(key);
IList sigList = null;
for (int i = 0; i != returnKey.ids.Count; i++)
{
if (id.Equals(returnKey.ids[i]))
{
sigList = (IList) returnKey.idSigs[i];
}
}
if (sigList != null)
{
sigList.Add(certification);
}
else
{
sigList = Platform.CreateArrayList();
sigList.Add(certification);
returnKey.ids.Add(id);
returnKey.idTrusts.Add(null);
returnKey.idSigs.Add(sigList);
}
return returnKey;
}
///
/// Remove any certifications associated with a user attribute subpacket on a key.
///
/// The key the certifications are to be removed from.
/// The attributes to be removed.
///
/// The re-certified key, or null if the user attribute subpacket was not found on the key.
///
public static PgpPublicKey RemoveCertification(
PgpPublicKey key,
PgpUserAttributeSubpacketVector userAttributes)
{
return RemoveCert(key, userAttributes);
}
/// Remove any certifications associated with a given ID on a key.
/// The key the certifications are to be removed from.
/// The ID that is to be removed.
/// The re-certified key, or null if the ID was not found on the key.
public static PgpPublicKey RemoveCertification(
PgpPublicKey key,
string id)
{
return RemoveCert(key, id);
}
private static PgpPublicKey RemoveCert(
PgpPublicKey key,
object id)
{
PgpPublicKey returnKey = new PgpPublicKey(key);
bool found = false;
for (int i = 0; i < returnKey.ids.Count; i++)
{
if (id.Equals(returnKey.ids[i]))
{
found = true;
returnKey.ids.RemoveAt(i);
returnKey.idTrusts.RemoveAt(i);
returnKey.idSigs.RemoveAt(i);
}
}
return found ? returnKey : null;
}
/// Remove a certification associated with a given ID on a key.
/// The key the certifications are to be removed from.
/// The ID that the certfication is to be removed from.
/// The certfication to be removed.
/// The re-certified key, or null if the certification was not found.
public static PgpPublicKey RemoveCertification(
PgpPublicKey key,
string id,
PgpSignature certification)
{
return RemoveCert(key, id, certification);
}
/// Remove a certification associated with a given user attributes on a key.
/// The key the certifications are to be removed from.
/// The user attributes that the certfication is to be removed from.
/// The certification to be removed.
/// The re-certified key, or null if the certification was not found.
public static PgpPublicKey RemoveCertification(
PgpPublicKey key,
PgpUserAttributeSubpacketVector userAttributes,
PgpSignature certification)
{
return RemoveCert(key, userAttributes, certification);
}
private static PgpPublicKey RemoveCert(
PgpPublicKey key,
object id,
PgpSignature certification)
{
PgpPublicKey returnKey = new PgpPublicKey(key);
bool found = false;
for (int i = 0; i < returnKey.ids.Count; i++)
{
if (id.Equals(returnKey.ids[i]))
{
IList certs = (IList) returnKey.idSigs[i];
found = certs.Contains(certification);
if (found)
{
certs.Remove(certification);
}
}
}
return found ? returnKey : null;
}
/// Add a revocation or some other key certification to a key.
/// The key the revocation is to be added to.
/// The key signature to be added.
/// The new changed public key object.
public static PgpPublicKey AddCertification(
PgpPublicKey key,
PgpSignature certification)
{
if (key.IsMasterKey)
{
if (certification.SignatureType == PgpSignature.SubkeyRevocation)
{
throw new ArgumentException("signature type incorrect for master key revocation.");
}
}
else
{
if (certification.SignatureType == PgpSignature.KeyRevocation)
{
throw new ArgumentException("signature type incorrect for sub-key revocation.");
}
}
PgpPublicKey returnKey = new PgpPublicKey(key);
if (returnKey.subSigs != null)
{
returnKey.subSigs.Add(certification);
}
else
{
returnKey.keySigs.Add(certification);
}
return returnKey;
}
/// Remove a certification from the key.
/// The key the certifications are to be removed from.
/// The certfication to be removed.
/// The modified key, null if the certification was not found.
public static PgpPublicKey RemoveCertification(
PgpPublicKey key,
PgpSignature certification)
{
PgpPublicKey returnKey = new PgpPublicKey(key);
IList sigs = returnKey.subSigs != null
? returnKey.subSigs
: returnKey.keySigs;
// bool found = sigs.Remove(certification);
int pos = sigs.IndexOf(certification);
bool found = pos >= 0;
if (found)
{
sigs.RemoveAt(pos);
}
else
{
foreach (String id in key.GetUserIds())
{
foreach (object sig in key.GetSignaturesForId(id))
{
// TODO Is this the right type of equality test?
if (certification == sig)
{
found = true;
returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
}
}
}
if (!found)
{
foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes())
{
foreach (object sig in key.GetSignaturesForUserAttribute(id))
{
// TODO Is this the right type of equality test?
if (certification == sig)
{
found = true;
returnKey = PgpPublicKey.RemoveCertification(returnKey, id, certification);
}
}
}
}
}
return returnKey;
}
}
}