summary refs log tree commit diff
path: root/crypto/src/openpgp/PgpPublicKey.cs
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/src/openpgp/PgpPublicKey.cs')
-rw-r--r--crypto/src/openpgp/PgpPublicKey.cs377
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;
         }
     }
 }