using System; using System.Collections.Generic; using System.Globalization; using System.IO; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Collections; namespace Org.BouncyCastle.Bcpg.OpenPgp { /// /// Often a PGP key ring file is made up of a succession of master/sub-key key rings. /// If you want to read an entire public key file in one hit this is the class for you. /// public class PgpPublicKeyRingBundle { private readonly IDictionary m_pubRings; private readonly IList m_order; private PgpPublicKeyRingBundle(IDictionary pubRings, IList order) { m_pubRings = pubRings; m_order = order; } public PgpPublicKeyRingBundle(byte[] encoding) : this(new MemoryStream(encoding, false)) { } /// Build a PgpPublicKeyRingBundle from the passed in input stream. /// Input stream containing data. /// If a problem parsing the stream occurs. /// If an object is encountered which isn't a PgpPublicKeyRing. public PgpPublicKeyRingBundle(Stream inputStream) : this(new PgpObjectFactory(inputStream).AllPgpObjects()) { } public PgpPublicKeyRingBundle(IEnumerable e) { m_pubRings = new Dictionary(); m_order = new List(); foreach (var obj in e) { // Marker packets must be ignored if (obj is PgpMarker) continue; if (!(obj is PgpPublicKeyRing pgpPub)) throw new PgpException(Platform.GetTypeName(obj) + " found where PgpPublicKeyRing expected"); long key = pgpPub.GetPublicKey().KeyId; m_pubRings.Add(key, pgpPub); m_order.Add(key); } } /// Return the number of key rings in this collection. public int Count { get { return m_order.Count; } } /// Allow enumeration of the public key rings making up this collection. public IEnumerable GetKeyRings() { return CollectionUtilities.Proxy(KeyRings); } /// Allow enumeration of the key rings associated with the passed in userId. /// The user ID to be matched. /// An IEnumerable of key rings which matched (possibly none). public IEnumerable GetKeyRings(string userId) { return GetKeyRings(userId, false, false); } /// Allow enumeration of the key rings associated with the passed in userId. /// The user ID to be matched. /// If true, userId need only be a substring of an actual ID string to match. /// An IEnumerable of key rings which matched (possibly none). public IEnumerable GetKeyRings(string userId, bool matchPartial) { return GetKeyRings(userId, matchPartial, false); } /// Allow enumeration of the key rings associated with the passed in userId. /// The user ID to be matched. /// If true, userId need only be a substring of an actual ID string to match. /// If true, case is ignored in user ID comparisons. /// An IEnumerable of key rings which matched (possibly none). public IEnumerable GetKeyRings(string userID, bool matchPartial, bool ignoreCase) { var compareInfo = CultureInfo.InvariantCulture.CompareInfo; var compareOptions = ignoreCase ? CompareOptions.OrdinalIgnoreCase : CompareOptions.Ordinal; foreach (PgpPublicKeyRing pubRing in KeyRings) { foreach (string nextUserID in pubRing.GetPublicKey().GetUserIds()) { if (matchPartial) { if (compareInfo.IndexOf(nextUserID, userID, compareOptions) >= 0) yield return pubRing; } else { if (compareInfo.Compare(nextUserID, userID, compareOptions) == 0) yield return pubRing; } } } } /// Return the PGP public key associated with the given key id. /// The ID of the public key to return. public PgpPublicKey GetPublicKey(long keyId) { foreach (PgpPublicKeyRing pubRing in KeyRings) { PgpPublicKey pub = pubRing.GetPublicKey(keyId); if (pub != null) return pub; } return null; } /// Return the public key ring which contains the key referred to by keyId /// key ID to match against public PgpPublicKeyRing GetPublicKeyRing(long keyId) { if (m_pubRings.TryGetValue(keyId, out var keyRing)) return keyRing; foreach (PgpPublicKeyRing pubRing in KeyRings) { if (pubRing.GetPublicKey(keyId) != null) return pubRing; } return null; } /// Return the PGP public key associated with the given key fingerprint. /// the public key fingerprint to match against. public PgpPublicKey GetPublicKey(byte[] fingerprint) { foreach (PgpPublicKeyRing pubRing in KeyRings) { PgpPublicKey pub = pubRing.GetPublicKey(fingerprint); if (pub != null) return pub; } return null; } /// Return the public key ring which contains the key associated with the given key fingerprint. /// /// the public key fingerprint to match against. public PgpPublicKeyRing GetPublicKeyRing(byte[] fingerprint) { foreach (PgpPublicKeyRing pubRing in KeyRings) { if (pubRing.GetPublicKey(fingerprint) != null) return pubRing; } return null; } /// /// Return true if a key matching the passed in key ID is present, false otherwise. /// /// key ID to look for. public bool Contains(long keyID) { return GetPublicKey(keyID) != null; } public byte[] GetEncoded() { MemoryStream bOut = new MemoryStream(); Encode(bOut); return bOut.ToArray(); } public void Encode(Stream outStr) { BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr); foreach (long key in m_order) { m_pubRings[key].Encode(bcpgOut); } } private ICollection KeyRings => m_pubRings.Values; /// /// Return a new bundle containing the contents of the passed in bundle and /// the passed in public key ring. /// /// The PgpPublicKeyRingBundle the key ring is to be added to. /// The key ring to be added. /// A new PgpPublicKeyRingBundle merging the current one with the passed in key ring. /// If the keyId for the passed in key ring is already present. public static PgpPublicKeyRingBundle AddPublicKeyRing(PgpPublicKeyRingBundle bundle, PgpPublicKeyRing publicKeyRing) { long key = publicKeyRing.GetPublicKey().KeyId; if (bundle.m_pubRings.ContainsKey(key)) throw new ArgumentException("Bundle already contains a key with a keyId for the passed in ring."); var newPubRings = new Dictionary(bundle.m_pubRings); var newOrder = new List(bundle.m_order); newPubRings[key] = publicKeyRing; newOrder.Add(key); return new PgpPublicKeyRingBundle(newPubRings, newOrder); } /// /// Return a new bundle containing the contents of the passed in bundle with /// the passed in public key ring removed. /// /// The PgpPublicKeyRingBundle the key ring is to be removed from. /// The key ring to be removed. /// A new PgpPublicKeyRingBundle not containing the passed in key ring. /// If the keyId for the passed in key ring is not present. public static PgpPublicKeyRingBundle RemovePublicKeyRing(PgpPublicKeyRingBundle bundle, PgpPublicKeyRing publicKeyRing) { long key = publicKeyRing.GetPublicKey().KeyId; if (!bundle.m_pubRings.ContainsKey(key)) throw new ArgumentException("Bundle does not contain a key with a keyId for the passed in ring."); var newPubRings = new Dictionary(bundle.m_pubRings); var newOrder = new List(bundle.m_order); newPubRings.Remove(key); newOrder.Remove(key); return new PgpPublicKeyRingBundle(newPubRings, newOrder); } } }