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(m_pubRings.Values);
}
/// 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 GetKeyRings())
{
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 GetKeyRings())
{
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 GetKeyRings())
{
if (pubRing.GetPublicKey(keyId) != 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);
}
}
///
/// 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);
}
}
}