using System; using System.Collections; using System.IO; using Org.BouncyCastle.Asn1.X9; 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; } else if (key is ECPublicBcpgKey) { this.keyStrength = ECNamedCurveTable.GetByOid(((ECPublicBcpgKey)key).CurveOid).Curve.FieldSize; } } } /// /// 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. /// WARNING: This method will return 1 for keys with version > 3 that expire in less than 1 day [Obsolete("Use 'GetValidSeconds' instead")] public int ValidDays { get { if (publicPk.Version <= 3) { return publicPk.ValidDays; } long expSecs = GetValidSeconds(); if (expSecs <= 0) return 0; int days = (int)(expSecs / (24 * 60 * 60)); return System.Math.Max(1, days); } } /// 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 Arrays.Clone(trustPk.GetLevelAndTrustAmount()); } /// The number of valid seconds from creation time - zero means no expiry. public long GetValidSeconds() { if (publicPk.Version <= 3) { return (long)publicPk.ValidDays * (24 * 60 * 60); } 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; } 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.ECDH: 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 = subSigs; if (sigs == null) { sigs = Platform.CreateArrayList(keySigs); foreach (ICollection extraSigs in idSigs) { CollectionUtilities.AddRange(sigs, extraSigs); } } return new EnumerableProxy(sigs); } /** * Return all signatures/certifications directly associated with this key (ie, not to a user id). * * @return an iterator (possibly empty) with all signatures/certifications. */ public IEnumerable GetKeySignatures() { IList sigs = subSigs; if (sigs == null) { sigs = Platform.CreateArrayList(keySigs); } return new EnumerableProxy(sigs); } public PublicKeyPacket PublicKeyPacket { get { return publicPk; } } 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; } } }