diff options
Diffstat (limited to 'crypto/src/openpgp/PgpPublicKey.cs')
-rw-r--r-- | crypto/src/openpgp/PgpPublicKey.cs | 377 |
1 files changed, 274 insertions, 103 deletions
diff --git a/crypto/src/openpgp/PgpPublicKey.cs b/crypto/src/openpgp/PgpPublicKey.cs index 6fa7cfae8..b4ae92838 100644 --- a/crypto/src/openpgp/PgpPublicKey.cs +++ b/crypto/src/openpgp/PgpPublicKey.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.IO; using Org.BouncyCastle.Asn1.Cryptlib; @@ -86,17 +87,18 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp PgpSignature.DirectKey, }; - private long keyId; - private byte[] fingerprint; - private int keyStrength; - internal PublicKeyPacket publicPk; internal TrustPacket trustPk; internal IList<PgpSignature> keySigs = new List<PgpSignature>(); - internal IList<object> ids = new List<object>(); + internal IList<IUserDataPacket> ids = new List<IUserDataPacket>(); internal IList<TrustPacket> idTrusts = new List<TrustPacket>(); internal IList<IList<PgpSignature>> idSigs = new List<IList<PgpSignature>>(); - internal IList<PgpSignature> subSigs; + + internal IList<PgpSignature> subSigs = null; + + private long keyId; + private byte[] fingerprint; + private int keyStrength; private void Init() { @@ -261,7 +263,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } this.publicPk = new PublicKeyPacket(algorithm, time, bcpgKey); - this.ids = new List<object>(); + this.ids = new List<IUserDataPacket>(); this.idSigs = new List<IList<PgpSignature>>(); try @@ -275,7 +277,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } public PgpPublicKey(PublicKeyPacket publicPk) - : this(publicPk, new List<object>(), new List<IList<PgpSignature>>()) + : this(publicPk, new List<IUserDataPacket>(), new List<IList<PgpSignature>>()) { } @@ -311,7 +313,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp this.publicPk = pubKey.publicPk; this.keySigs = new List<PgpSignature>(pubKey.keySigs); - this.ids = new List<object>(pubKey.ids); + this.ids = new List<IUserDataPacket>(pubKey.ids); this.idTrusts = new List<TrustPacket>(pubKey.idTrusts); this.idSigs = new List<IList<PgpSignature>>(pubKey.idSigs.Count); @@ -334,7 +336,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp PublicKeyPacket publicPk, TrustPacket trustPk, IList<PgpSignature> keySigs, - IList<object> ids, + IList<IUserDataPacket> ids, IList<TrustPacket> idTrusts, IList<IList<PgpSignature>> idSigs) { @@ -350,7 +352,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp internal PgpPublicKey( PublicKeyPacket publicPk, - IList<object> ids, + IList<IUserDataPacket> ids, IList<IList<PgpSignature>> idSigs) { this.publicPk = publicPk; @@ -359,6 +361,26 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp Init(); } + internal PgpPublicKey( + PgpPublicKey original, + TrustPacket trustPk, + List<PgpSignature> keySigs, + List<IUserDataPacket> ids, + List<TrustPacket> idTrusts, + IList<IList<PgpSignature>> idSigs) + { + this.publicPk = original.publicPk; + this.fingerprint = original.fingerprint; + this.keyStrength = original.keyStrength; + this.keyId = original.keyId; + + this.trustPk = trustPk; + this.keySigs = keySigs; + this.ids = ids; + this.idTrusts = idTrusts; + this.idSigs = idSigs; + } + /// <summary>The version of this key.</summary> public int Version { @@ -456,13 +478,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return expiryTime; } - /// <summary>The keyId associated with the public key.</summary> + /// <summary>The key ID associated with the public key.</summary> public long KeyId { get { return keyId; } } - /// <summary>The fingerprint of the key</summary> + /// <summary>The fingerprint of the public key</summary> public byte[] GetFingerprint() { return (byte[]) fingerprint.Clone(); @@ -648,11 +670,29 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { var result = new List<string>(); - foreach (object id in ids) + foreach (var id in ids) { - if (id is string s) + if (id is UserIdPacket userId) { - result.Add(s); + result.Add(userId.GetId()); + } + } + + return CollectionUtilities.Proxy(result); + } + + /// <summary>Return any userIDs associated with the key in raw byte form.</summary> + /// <remarks>No attempt is made to convert the IDs into strings.</remarks> + /// <returns>An <c>IEnumerable</c> of <c>byte[]</c>.</returns> + public IEnumerable<byte[]> GetRawUserIds() + { + var result = new List<byte[]>(); + + foreach (var id in ids) + { + if (id is UserIdPacket userId) + { + result.Add(userId.GetRawId()); } } @@ -665,9 +705,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { var result = new List<PgpUserAttributeSubpacketVector>(); - foreach (object o in ids) + foreach (var id in ids) { - if (o is PgpUserAttributeSubpacketVector v) + if (id is PgpUserAttributeSubpacketVector v) { result.Add(v); } @@ -684,22 +724,18 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp if (id == null) throw new ArgumentNullException(nameof(id)); - var result = new List<PgpSignature>(); - bool userIdFound = false; + return GetSignaturesForId(new UserIdPacket(id)); + } - for (int i = 0; i != ids.Count; i++) - { - if (id.Equals(ids[i])) - { - userIdFound = true; - result.AddRange(idSigs[i]); - } - } + public IEnumerable<PgpSignature> GetSignaturesForId(byte[] rawId) + { + if (rawId == null) + throw new ArgumentNullException(nameof(rawId)); - return userIdFound ? CollectionUtilities.Proxy(result) : null; + return GetSignaturesForId(new UserIdPacket(rawId)); } - private IEnumerable<PgpSignature> GetSignaturesForID(UserIdPacket id) + private IEnumerable<PgpSignature> GetSignaturesForId(UserIdPacket id) { var signatures = new List<PgpSignature>(); bool userIdFound = false; @@ -819,13 +855,24 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return bOut.ToArray(); } - public void Encode( - Stream outStr) + public void Encode(Stream outStr) + { + Encode(outStr, false); + } + + /** + * Encode the key to outStream, with trust packets stripped out if forTransfer is true. + * + * @param outStream stream to write the key encoding to. + * @param forTransfer if the purpose of encoding is to send key to other users. + * @throws IOException in case of encoding error. + */ + public void Encode(Stream outStr, bool forTransfer) { BcpgOutputStream bcpgOut = BcpgOutputStream.Wrap(outStr); bcpgOut.WritePacket(publicPk); - if (trustPk != null) + if (!forTransfer && trustPk != null) { bcpgOut.WritePacket(trustPk); } @@ -839,11 +886,9 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp for (int i = 0; i != ids.Count; i++) { - if (ids[i] is string) + if (ids[i] is UserIdPacket id) { - string id = (string) ids[i]; - - bcpgOut.WritePacket(new UserIdPacket(id)); + bcpgOut.WritePacket(id); } else { @@ -851,14 +896,14 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp bcpgOut.WritePacket(new UserAttributePacket(v.ToSubpacketArray())); } - if (idTrusts[i] != null) + if (!forTransfer && idTrusts[i] != null) { - bcpgOut.WritePacket((ContainedPacket)idTrusts[i]); + bcpgOut.WritePacket((TrustPacket)idTrusts[i]); } foreach (PgpSignature sig in idSigs[i]) { - sig.Encode(bcpgOut); + sig.Encode(bcpgOut, forTransfer); } } } @@ -910,7 +955,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp string id, PgpSignature certification) { - return AddCert(key, id, certification); + return AddCert(key, new UserIdPacket(id), certification); } /// <summary>Add a certification for the given UserAttributeSubpackets to the given public key.</summary> @@ -928,7 +973,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp private static PgpPublicKey AddCert( PgpPublicKey key, - object id, + IUserDataPacket id, PgpSignature certification) { PgpPublicKey returnKey = new PgpPublicKey(key); @@ -966,8 +1011,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <returns> /// The re-certified key, or null if the user attribute subpacket was not found on the key. /// </returns> - public static PgpPublicKey RemoveCertification( - PgpPublicKey key, + public static PgpPublicKey RemoveCertification(PgpPublicKey key, PgpUserAttributeSubpacketVector userAttributes) { return RemoveCert(key, userAttributes); @@ -977,16 +1021,21 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <param name="key">The key the certifications are to be removed from.</param> /// <param name="id">The ID that is to be removed.</param> /// <returns>The re-certified key, or null if the ID was not found on the key.</returns> - public static PgpPublicKey RemoveCertification( - PgpPublicKey key, - string id) + public static PgpPublicKey RemoveCertification(PgpPublicKey key, string id) { - return RemoveCert(key, id); + return RemoveCert(key, new UserIdPacket(id)); } - private static PgpPublicKey RemoveCert( - PgpPublicKey key, - object id) + /// <summary>Remove any certifications associated with a given ID on a key.</summary> + /// <param name="key">The key the certifications are to be removed from.</param> + /// <param name="rawId">The ID that is to be removed in raw byte form.</param> + /// <returns>The re-certified key, or null if the ID was not found on the key.</returns> + public static PgpPublicKey RemoveCertification(PgpPublicKey key, byte[] rawId) + { + return RemoveCert(key, new UserIdPacket(rawId)); + } + + private static PgpPublicKey RemoveCert(PgpPublicKey key, IUserDataPacket id) { PgpPublicKey returnKey = new PgpPublicKey(key); bool found = false; @@ -1007,15 +1056,22 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <summary>Remove a certification associated with a given ID on a key.</summary> /// <param name="key">The key the certifications are to be removed from.</param> + /// <param name="id">The ID that the certfication is to be removed from (in its raw byte form).</param> + /// <param name="certification">The certfication to be removed.</param> + /// <returns>The re-certified key, or null if the certification was not found.</returns> + public static PgpPublicKey RemoveCertification(PgpPublicKey key, byte[] id, PgpSignature certification) + { + return RemoveCert(key, new UserIdPacket(id), certification); + } + + /// <summary>Remove a certification associated with a given ID on a key.</summary> + /// <param name="key">The key the certifications are to be removed from.</param> /// <param name="id">The ID that the certfication is to be removed from.</param> /// <param name="certification">The certfication to be removed.</param> /// <returns>The re-certified key, or null if the certification was not found.</returns> - public static PgpPublicKey RemoveCertification( - PgpPublicKey key, - string id, - PgpSignature certification) + public static PgpPublicKey RemoveCertification(PgpPublicKey key, string id, PgpSignature certification) { - return RemoveCert(key, id, certification); + return RemoveCert(key, new UserIdPacket(id), certification); } /// <summary>Remove a certification associated with a given user attributes on a key.</summary> @@ -1023,18 +1079,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <param name="userAttributes">The user attributes that the certfication is to be removed from.</param> /// <param name="certification">The certification to be removed.</param> /// <returns>The re-certified key, or null if the certification was not found.</returns> - public static PgpPublicKey RemoveCertification( - PgpPublicKey key, - PgpUserAttributeSubpacketVector userAttributes, - PgpSignature certification) + 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) + private static PgpPublicKey RemoveCert(PgpPublicKey key, IUserDataPacket id, PgpSignature certification) { PgpPublicKey returnKey = new PgpPublicKey(key); bool found = false; @@ -1043,13 +1094,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { if (id.Equals(returnKey.ids[i])) { - var certs = returnKey.idSigs[i]; - found = certs.Contains(certification); - - if (found) - { - certs.Remove(certification); - } + found |= returnKey.idSigs[i].Remove(certification); } } @@ -1060,35 +1105,23 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// <param name="key">The key the revocation is to be added to.</param> /// <param name="certification">The key signature to be added.</param> /// <returns>The new changed public key object.</returns> - public static PgpPublicKey AddCertification( - PgpPublicKey key, - PgpSignature certification) + 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); + var sigs = returnKey.subSigs ?? returnKey.keySigs; - if (returnKey.subSigs != null) - { - returnKey.subSigs.Add(certification); - } - else - { - returnKey.keySigs.Add(certification); - } + sigs.Add(certification); return returnKey; } @@ -1102,33 +1135,171 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp var returnKey = new PgpPublicKey(key); var sigs = returnKey.subSigs ?? returnKey.keySigs; - if (sigs.Remove(certification)) - return returnKey; + bool found = sigs.Remove(certification); - // TODO Java uses getRawUserIDs - foreach (string id in key.GetUserIds()) + foreach (var idSigs in returnKey.idSigs) { - if (ContainsSignature(key.GetSignaturesForId(id), certification)) - return RemoveCertification(returnKey, id, certification); + found |= idSigs.Remove(certification); } - foreach (PgpUserAttributeSubpacketVector id in key.GetUserAttributes()) + return found ? returnKey : null; + } + + /** + * Merge this the given local public key with another, potentially fresher copy. + * The resulting {@link PGPPublicKey} contains the sum of both keys user-ids and signatures. + * <p> + * If joinTrustPackets is set to true and the copy carries a trust packet, + * the joined key will copy the trust-packet from the copy. + * Otherwise, it will carry the trust packet of the local key. + * + * @param key local public key + * @param copy copy of the public key (e.g. from a key server) + * @param joinTrustPackets if true, trust packets from the copy are copied over into the resulting key + * @param allowSubkeySigsOnNonSubkey if true, subkey signatures on the copy will be present in the merged key, even if key was not a subkey before. + * @return joined key + * @throws PGPException + */ + public static PgpPublicKey Join(PgpPublicKey key, PgpPublicKey copy, bool joinTrustPackets, + bool allowSubkeySigsOnNonSubkey) + { + if (key.KeyId != copy.keyId) + throw new ArgumentException("Key-ID mismatch."); + + TrustPacket trustPk = key.trustPk; + List<PgpSignature> keySigs = new List<PgpSignature>(key.keySigs); + List<IUserDataPacket> ids = new List<IUserDataPacket>(key.ids); + List<TrustPacket> idTrusts = new List<TrustPacket>(key.idTrusts); + List<IList<PgpSignature>> idSigs = new List<IList<PgpSignature>>(key.idSigs); + List<PgpSignature> subSigs = key.subSigs == null ? null : new List<PgpSignature>(key.subSigs); + + if (joinTrustPackets) { - if (ContainsSignature(key.GetSignaturesForUserAttribute(id), certification)) - return RemoveCertification(returnKey, id, certification); + if (copy.trustPk != null) + { + trustPk = copy.trustPk; + } } - return returnKey; - } + // key signatures + foreach (PgpSignature keySig in copy.keySigs) + { + bool found = false; + for (int i = 0; i < keySigs.Count; i++) + { + PgpSignature existingKeySig = keySigs[i]; + if (PgpSignature.IsSignatureEncodingEqual(existingKeySig, keySig)) + { + found = true; + // join existing sig with copy to apply modifications in unhashed subpackets + existingKeySig = PgpSignature.Join(existingKeySig, keySig); + keySigs[i] = existingKeySig; + break; + } + } + if (found) + break; - private static bool ContainsSignature(IEnumerable<PgpSignature> signatures, PgpSignature signature) - { - foreach (PgpSignature candidate in signatures) + keySigs.Add(keySig); + } + + // user-ids and id sigs + for (int idIdx = 0; idIdx < copy.ids.Count; idIdx++) + { + IUserDataPacket copyId = copy.ids[idIdx]; + List<PgpSignature> copyIdSigs = new List<PgpSignature>(copy.idSigs[idIdx]); + TrustPacket copyTrust = copy.idTrusts[idIdx]; + + int existingIdIndex = -1; + for (int i = 0; i < ids.Count; i++) + { + IUserDataPacket existingId = ids[i]; + if (existingId.Equals(copyId)) + { + existingIdIndex = i; + break; + } + } + + // new user-id + if (existingIdIndex == -1) + { + ids.Add(copyId); + idSigs.Add(copyIdSigs); + idTrusts.Add(joinTrustPackets ? copyTrust : null); + continue; + } + + // existing user-id + if (joinTrustPackets && copyTrust != null) + { + TrustPacket existingTrust = idTrusts[existingIdIndex]; + if (existingTrust == null || + Arrays.AreEqual(copyTrust.GetLevelAndTrustAmount(), existingTrust.GetLevelAndTrustAmount())) + { + idTrusts[existingIdIndex] = copyTrust; + } + } + + var existingIdSigs = idSigs[existingIdIndex]; + foreach (PgpSignature newSig in copyIdSigs) + { + bool found = false; + for (int i = 0; i < existingIdSigs.Count; i++) + { + PgpSignature existingSig = existingIdSigs[i]; + if (PgpSignature.IsSignatureEncodingEqual(newSig, existingSig)) + { + found = true; + // join existing sig with copy to apply modifications in unhashed subpackets + existingSig = PgpSignature.Join(existingSig, newSig); + existingIdSigs[i] = existingSig; + break; + } + } + if (!found) + { + existingIdSigs.Add(newSig); + } + } + } + + // subSigs + if (copy.subSigs != null) { - if (signature == candidate) - return true; + if (subSigs == null && allowSubkeySigsOnNonSubkey) + { + subSigs = new List<PgpSignature>(copy.subSigs); + } + else + { + foreach (PgpSignature copySubSig in copy.subSigs) + { + bool found = false; + for (int i = 0; subSigs != null && i < subSigs.Count; i++) + { + PgpSignature existingSubSig = subSigs[i]; + if (PgpSignature.IsSignatureEncodingEqual(existingSubSig, copySubSig)) + { + found = true; + // join existing sig with copy to apply modifications in unhashed subpackets + existingSubSig = PgpSignature.Join(existingSubSig, copySubSig); + subSigs[i] = existingSubSig; + break; + } + } + if (!found && subSigs != null) + { + subSigs.Add(copySubSig); + } + } + } } - return false; + + PgpPublicKey merged = new PgpPublicKey(key, trustPk, keySigs, ids, idTrusts, idSigs); + merged.subSigs = subSigs; + + return merged; } } } |