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 secret key file in one hit this is the class for you. /// public class PgpSecretKeyRingBundle { private readonly IDictionary m_secretRings; private readonly IList m_order; private PgpSecretKeyRingBundle(IDictionary secretRings, IList order) { m_secretRings = secretRings; m_order = order; } public PgpSecretKeyRingBundle(byte[] encoding) : this(new MemoryStream(encoding, false)) { } /// Build a PgpSecretKeyRingBundle 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 PgpSecretKeyRing. public PgpSecretKeyRingBundle(Stream inputStream) : this(new PgpObjectFactory(inputStream).AllPgpObjects()) { } public PgpSecretKeyRingBundle(IEnumerable e) { m_secretRings = new Dictionary(); m_order = new List(); foreach (var obj in e) { // Marker packets must be ignored if (obj is PgpMarker) continue; if (!(obj is PgpSecretKeyRing pgpSecret)) throw new PgpException(Platform.GetTypeName(obj) + " found where PgpSecretKeyRing expected"); long key = pgpSecret.GetPublicKey().KeyId; m_secretRings.Add(key, pgpSecret); m_order.Add(key); } } /// Return the number of rings in this collection. public int Count { get { return m_order.Count; } } /// Allow enumeration of the secret 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 (PgpSecretKeyRing secRing in KeyRings) { foreach (string nextUserID in secRing.GetSecretKey().UserIds) { if (matchPartial) { if (compareInfo.IndexOf(nextUserID, userID, compareOptions) >= 0) yield return secRing; } else { if (compareInfo.Compare(nextUserID, userID, compareOptions) == 0) yield return secRing; } } } } /// Return the PGP secret key associated with the given key id. /// The ID of the secret key to return. public PgpSecretKey GetSecretKey(long keyId) { foreach (PgpSecretKeyRing secRing in KeyRings) { PgpSecretKey sec = secRing.GetSecretKey(keyId); if (sec != null) return sec; } return null; } /// Return the secret key ring which contains the key referred to by keyId /// The ID of the secret key public PgpSecretKeyRing GetSecretKeyRing(long keyId) { if (m_secretRings.TryGetValue(keyId, out var keyRing)) return keyRing; foreach (PgpSecretKeyRing secretRing in KeyRings) { if (secretRing.GetSecretKey(keyId) != null) return secretRing; } 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 GetSecretKey(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_secretRings[key].Encode(bcpgOut); } } private ICollection KeyRings => m_secretRings.Values; /// /// Return a new bundle containing the contents of the passed in bundle and /// the passed in secret key ring. /// /// The PgpSecretKeyRingBundle the key ring is to be added to. /// The key ring to be added. /// A new PgpSecretKeyRingBundle merging the current one with the passed in key ring. /// If the keyId for the passed in key ring is already present. public static PgpSecretKeyRingBundle AddSecretKeyRing(PgpSecretKeyRingBundle bundle, PgpSecretKeyRing secretKeyRing) { long key = secretKeyRing.GetPublicKey().KeyId; if (bundle.m_secretRings.ContainsKey(key)) throw new ArgumentException("Collection already contains a key with a keyId for the passed in ring."); var newSecretRings = new Dictionary(bundle.m_secretRings); var newOrder = new List(bundle.m_order); newSecretRings[key] = secretKeyRing; newOrder.Add(key); return new PgpSecretKeyRingBundle(newSecretRings, newOrder); } /// /// Return a new bundle containing the contents of the passed in bundle with /// the passed in secret key ring removed. /// /// The PgpSecretKeyRingBundle the key ring is to be removed from. /// The key ring to be removed. /// A new PgpSecretKeyRingBundle not containing the passed in key ring. /// If the keyId for the passed in key ring is not present. public static PgpSecretKeyRingBundle RemoveSecretKeyRing(PgpSecretKeyRingBundle bundle, PgpSecretKeyRing secretKeyRing) { long key = secretKeyRing.GetPublicKey().KeyId; if (!bundle.m_secretRings.ContainsKey(key)) throw new ArgumentException("Collection does not contain a key with a keyId for the passed in ring."); var newSecretRings = new Dictionary(bundle.m_secretRings); var newOrder = new List(bundle.m_order); newSecretRings.Remove(key); newOrder.Remove(key); return new PgpSecretKeyRingBundle(newSecretRings, newOrder); } } }