summary refs log tree commit diff
path: root/crypto/src/openpgp
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2023-02-28 18:49:21 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2023-02-28 18:49:21 +0700
commit5ac39b19f346e72005f41f13ff956f4fa7c97f86 (patch)
tree0ca4585a968a8b11044267b0268d3e65f29fa559 /crypto/src/openpgp
parentUpdate Asn1Tags (diff)
downloadBouncyCastle.NET-ed25519-5ac39b19f346e72005f41f13ff956f4fa7c97f86.tar.xz
OpenPGP updates from bc-java
Diffstat (limited to 'crypto/src/openpgp')
-rw-r--r--crypto/src/openpgp/PGPKeyRing.cs11
-rw-r--r--crypto/src/openpgp/PgpPublicKey.cs377
-rw-r--r--crypto/src/openpgp/PgpPublicKeyRing.cs74
-rw-r--r--crypto/src/openpgp/PgpSecretKey.cs12
-rw-r--r--crypto/src/openpgp/PgpSignature.cs105
-rw-r--r--crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs205
-rw-r--r--crypto/src/openpgp/PgpSignatureSubpacketVector.cs282
-rw-r--r--crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs17
8 files changed, 767 insertions, 316 deletions
diff --git a/crypto/src/openpgp/PGPKeyRing.cs b/crypto/src/openpgp/PGPKeyRing.cs
index 52db80209..63069350e 100644
--- a/crypto/src/openpgp/PGPKeyRing.cs
+++ b/crypto/src/openpgp/PGPKeyRing.cs
@@ -42,20 +42,19 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			}
 		}
 
-		internal static void ReadUserIDs(BcpgInputStream pIn, out IList<object> ids, out IList<TrustPacket> idTrusts,
-			out IList<IList<PgpSignature>> idSigs)
+		internal static void ReadUserIDs(BcpgInputStream pIn, out IList<IUserDataPacket> ids,
+			out IList<TrustPacket> idTrusts, out IList<IList<PgpSignature>> idSigs)
 		{
-			ids = new List<object>();
+			ids = new List<IUserDataPacket>();
 			idTrusts = new List<TrustPacket>();
 			idSigs = new List<IList<PgpSignature>>();
 
             while (IsUserTag(pIn.SkipMarkerPackets()))
 			{
 				Packet obj = pIn.ReadPacket();
-				if (obj is UserIdPacket)
+				if (obj is UserIdPacket id)
 				{
-					UserIdPacket id = (UserIdPacket)obj;
-					ids.Add(id.GetId());
+					ids.Add(id);
 				}
 				else
 				{
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;
         }
     }
 }
diff --git a/crypto/src/openpgp/PgpPublicKeyRing.cs b/crypto/src/openpgp/PgpPublicKeyRing.cs
index ebbb95634..e1a2a83f2 100644
--- a/crypto/src/openpgp/PgpPublicKeyRing.cs
+++ b/crypto/src/openpgp/PgpPublicKeyRing.cs
@@ -50,10 +50,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             // direct signatures and revocations
             var keySigs = ReadSignaturesAndTrust(bcpgInput);
 
-            IList<object> ids;
-            IList<TrustPacket> idTrusts;
-            IList<IList<PgpSignature>> idSigs;
-            ReadUserIDs(bcpgInput, out ids, out idTrusts, out idSigs);
+            ReadUserIDs(bcpgInput, out var ids, out var idTrusts, out var idSigs);
 
             keys.Add(new PgpPublicKey(pubPk, trustPk, keySigs, ids, idTrusts, idSigs));
 
@@ -204,5 +201,74 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
             return new PgpPublicKey(pk, kTrust, sigList);
         }
+
+        /**
+         * Join two copies of the same certificate.
+         * The certificates must have the same primary key, but may carry different subkeys, user-ids and signatures.
+         * The resulting certificate will carry the sum of both certificates subkeys, user-ids and signatures.
+         * <p>
+         * This method will ignore trust packets on the second copy of the certificate and instead
+         * copy the local certificate's trust packets to the joined certificate.
+         *
+         * @param first  local copy of the certificate
+         * @param second remote copy of the certificate (e.g. from a key server)
+         * @return joined key ring
+         * @throws PGPException
+         */
+        public static PgpPublicKeyRing Join(PgpPublicKeyRing first, PgpPublicKeyRing second)
+        {
+            return Join(first, second, false, false);
+        }
+
+        /**
+         * Join two copies of the same certificate.
+         * The certificates must have the same primary key, but may carry different subkeys, user-ids and signatures.
+         * The resulting certificate will carry the sum of both certificates subkeys, user-ids and signatures.
+         * <p>
+         * For each subkey holds: If joinTrustPackets is set to true and the second key is carrying a trust packet,
+         * the trust packet will be copied to the joined key.
+         * Otherwise, the joined key will carry the trust packet of the local copy.
+         *
+         * @param first                      local copy of the certificate
+         * @param second                     remote copy of the certificate (e.g. from a key server)
+         * @param joinTrustPackets           if true, trust packets from the second certificate copy will be carried over into the joined certificate
+         * @param allowSubkeySigsOnNonSubkey if true, the resulting joined certificate may carry subkey signatures on its primary key
+         * @return joined certificate
+         * @throws PGPException
+         */
+        public static PgpPublicKeyRing Join(PgpPublicKeyRing first, PgpPublicKeyRing second, bool joinTrustPackets,
+            bool allowSubkeySigsOnNonSubkey)
+        {
+            if (!Arrays.AreEqual(first.GetPublicKey().GetFingerprint(), second.GetPublicKey().GetFingerprint()))
+                throw new ArgumentException("Cannot merge certificates with differing primary keys.");
+
+            var secondKeys = new HashSet<long>();
+            foreach (var key in second.GetPublicKeys())
+            {
+                secondKeys.Add(key.KeyId);
+            }
+
+            var merged = new List<PgpPublicKey>();
+            foreach (var key in first.GetPublicKeys())
+            {
+                var copy = second.GetPublicKey(key.KeyId);
+                if (copy != null)
+                {
+                    merged.Add(PgpPublicKey.Join(key, copy, joinTrustPackets, allowSubkeySigsOnNonSubkey));
+                    secondKeys.Remove(key.KeyId);
+                }
+                else
+                {
+                    merged.Add(key);
+                }
+            }
+
+            foreach (var additionalKeyId in secondKeys)
+            {
+                merged.Add(second.GetPublicKey(additionalKeyId));
+            }
+
+            return new PgpPublicKeyRing(merged);
+        }
     }
 }
diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs
index 0103cc187..a120f97d8 100644
--- a/crypto/src/openpgp/PgpSecretKey.cs
+++ b/crypto/src/openpgp/PgpSecretKey.cs
@@ -479,6 +479,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             get { return pub.KeyId; }
         }
 
+        /// <summary>The fingerprint of the public key associated with this key.</summary>
+        public byte[] GetFingerprint()
+        {
+            return pub.GetFingerprint();
+        }
+
         /// <summary>Return the S2K usage associated with this key.</summary>
         public int S2kUsage
         {
@@ -832,10 +838,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
                 for (int i = 0; i != pub.ids.Count; i++)
                 {
-                    object pubID = pub.ids[i];
-                    if (pubID is string id)
+                    var pubID = pub.ids[i];
+                    if (pubID is UserIdPacket id)
                     {
-                        bcpgOut.WritePacket(new UserIdPacket(id));
+                        bcpgOut.WritePacket(id);
                     }
                     else if (pubID is PgpUserAttributeSubpacketVector v)
                     {
diff --git a/crypto/src/openpgp/PgpSignature.cs b/crypto/src/openpgp/PgpSignature.cs
index 9b596f279..d1146183a 100644
--- a/crypto/src/openpgp/PgpSignature.cs
+++ b/crypto/src/openpgp/PgpSignature.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.IO;
 
 using Org.BouncyCastle.Asn1;
@@ -38,6 +39,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         public const int SubkeyRevocation = 0x28;
         public const int CertificationRevocation = 0x30;
         public const int Timestamp = 0x40;
+        public const int ThirdPartyConfirmation = 0x50;
 
         private readonly SignaturePacket	sigPck;
         private readonly int				signatureType;
@@ -52,8 +54,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
         }
 
-		internal PgpSignature(
-            SignaturePacket sigPacket)
+		internal PgpSignature(SignaturePacket sigPacket)
 			: this(sigPacket, null)
         {
         }
@@ -83,6 +84,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			get { return sigPck.HashAlgorithm; }
 		}
 
+        /// <summary>Return the digest prefix of the signature.</summary>
+        public byte[] GetDigestPrefix()
+        {
+            return sigPck.GetFingerprint();
+        }
+
         /// <summary>Return true if this signature represents a certification.</summary>
         public bool IsCertification()
         {
@@ -194,14 +201,13 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         public bool Verify()
         {
             byte[] trailer = GetSignatureTrailer();
+
             sig.BlockUpdate(trailer, 0, trailer.Length);
 
 			return sig.VerifySignature(GetSignature());
         }
 
-		private void UpdateWithIdData(
-			int		header,
-			byte[]	idBytes)
+		private void UpdateWithIdData(int header, byte[] idBytes)
 		{
 			this.Update(
 				(byte) header,
@@ -212,8 +218,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			this.Update(idBytes);
 		}
 
-		private void UpdateWithPublicKey(
-			PgpPublicKey key)
+		private void UpdateWithPublicKey(PgpPublicKey key)
 		{
 			byte[] keyBytes = GetEncodedPublicKey(key);
 
@@ -231,9 +236,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// <param name="userAttributes">User attributes the key was stored under.</param>
 		/// <param name="key">The key to be verified.</param>
 		/// <returns>True, if the signature matches, false otherwise.</returns>
-		public bool VerifyCertification(
-			PgpUserAttributeSubpacketVector	userAttributes,
-			PgpPublicKey					key)
+		public bool VerifyCertification(PgpUserAttributeSubpacketVector	userAttributes, PgpPublicKey key)
 		{
 			UpdateWithPublicKey(key);
 
@@ -254,9 +257,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				throw new PgpException("cannot encode subpacket array", e);
 			}
 
-			this.Update(sigPck.GetSignatureTrailer());
-
-			return sig.VerifySignature(GetSignature());
+			return Verify();
 		}
 
 		/// <summary>
@@ -266,9 +267,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// <param name="id">ID the key was stored under.</param>
 		/// <param name="key">The key to be verified.</param>
 		/// <returns>True, if the signature matches, false otherwise.</returns>
-        public bool VerifyCertification(
-            string			id,
-            PgpPublicKey	key)
+        public bool VerifyCertification(string id, PgpPublicKey key)
         {
 			UpdateWithPublicKey(key);
 
@@ -277,9 +276,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             //
             UpdateWithIdData(0xb4, Strings.ToUtf8ByteArray(id));
 
-			Update(sigPck.GetSignatureTrailer());
-
-			return sig.VerifySignature(GetSignature());
+            return Verify();
         }
 
 		/// <summary>Verify a certification for the passed in key against the passed in master key.</summary>
@@ -293,9 +290,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			UpdateWithPublicKey(masterKey);
 			UpdateWithPublicKey(pubKey);
 
-			Update(sigPck.GetSignatureTrailer());
-
-			return sig.VerifySignature(GetSignature());
+			return Verify();
         }
 
 		/// <summary>Verify a key certification, such as revocation, for the passed in key.</summary>
@@ -312,9 +307,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 			UpdateWithPublicKey(pubKey);
 
-            Update(sigPck.GetSignatureTrailer());
-
-			return sig.VerifySignature(GetSignature());
+			return Verify();
         }
 
 		public int SignatureType
@@ -438,13 +431,28 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			return bOut.ToArray();
         }
 
-		public void Encode(Stream outStream)
+        public void Encode(Stream outStream)
         {
-            var bcpgOut = BcpgOutputStream.Wrap(outStream);
+            Encode(outStream, false);
+        }
 
-			bcpgOut.WritePacket(sigPck);
+        /**
+         * Encode the signature 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 outStream, bool forTransfer)
+        {
+            // Exportable signatures MUST NOT be exported if forTransfer==true
+            if (forTransfer && (!GetHashedSubPackets().IsExportable() || !GetUnhashedSubPackets().IsExportable()))
+                return;
 
-			if (trustPck != null)
+            var bcpgOut = BcpgOutputStream.Wrap(outStream);
+
+            bcpgOut.WritePacket(sigPck);
+            if (!forTransfer && trustPck != null)
             {
                 bcpgOut.WritePacket(trustPck);
             }
@@ -480,5 +488,44 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                 return false;
             }
         }
+
+        public static bool IsSignatureEncodingEqual(PgpSignature sig1, PgpSignature sig2)
+        {
+            return Arrays.AreEqual(sig1.sigPck.GetSignatureBytes(), sig2.sigPck.GetSignatureBytes());
+        }
+
+        public static PgpSignature Join(PgpSignature sig1, PgpSignature sig2)
+        {
+            if (!IsSignatureEncodingEqual(sig1, sig2))
+                throw new ArgumentException("These are different signatures.");
+
+            // merge unhashed subpackets
+            SignatureSubpacket[] sig1Unhashed = sig1.GetUnhashedSubPackets().ToSubpacketArray();
+            SignatureSubpacket[] sig2Unhashed = sig2.GetUnhashedSubPackets().ToSubpacketArray();
+
+            var merged = new List<SignatureSubpacket>(sig1Unhashed);
+
+            foreach (var subpacket in sig2Unhashed)
+            {
+                if (!merged.Contains(subpacket))
+                {
+                    merged.Add(subpacket);
+                }
+            }
+
+            SignatureSubpacket[] unhashed = merged.ToArray();
+            return new PgpSignature(
+                new SignaturePacket(
+                    sig1.SignatureType,
+                    sig1.KeyId,
+                    sig1.KeyAlgorithm,
+                    sig1.HashAlgorithm,
+                    sig1.GetHashedSubPackets().ToSubpacketArray(),
+                    unhashed,
+                    sig1.GetDigestPrefix(),
+                    sig1.sigPck.GetSignature()
+                )
+            );
+        }
     }
 }
diff --git a/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs b/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs
index 07b9fee17..010e7e052 100644
--- a/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs
+++ b/crypto/src/openpgp/PgpSignatureSubpacketGenerator.cs
@@ -5,7 +5,7 @@ using Org.BouncyCastle.Bcpg.Sig;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
-	/// <remarks>Generator for signature subpackets.</remarks>
+    /// <remarks>Generator for signature subpackets.</remarks>
     public class PgpSignatureSubpacketGenerator
     {
         private readonly List<SignatureSubpacket> list = new List<SignatureSubpacket>();
@@ -35,23 +35,17 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             }
         }
 
-        public void SetRevocable(
-            bool	isCritical,
-            bool	isRevocable)
+        public void SetRevocable(bool isCritical, bool isRevocable)
         {
             list.Add(new Revocable(isCritical, isRevocable));
         }
 
-		public void SetExportable(
-            bool	isCritical,
-            bool	isExportable)
+		public void SetExportable(bool isCritical, bool isExportable)
         {
             list.Add(new Exportable(isCritical, isExportable));
         }
 
-        public void SetFeature(
-            bool    isCritical,
-            byte    feature)
+        public void SetFeature(bool isCritical, byte feature)
         {
             list.Add(new Features(isCritical, feature));
         }
@@ -63,10 +57,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// <param name="isCritical">true if the packet is critical.</param>
 		/// <param name="depth">depth level.</param>
 		/// <param name="trustAmount">trust amount.</param>
-		public void SetTrust(
-            bool	isCritical,
-            int		depth,
-            int		trustAmount)
+		public void SetTrust(bool isCritical, int depth, int trustAmount)
         {
             list.Add(new TrustSignature(isCritical, depth, trustAmount));
         }
@@ -77,9 +68,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// </summary>
 		/// <param name="isCritical">True, if should be treated as critical, false otherwise.</param>
 		/// <param name="seconds">The number of seconds the key is valid, or zero if no expiry.</param>
-        public void SetKeyExpirationTime(
-            bool	isCritical,
-            long	seconds)
+        public void SetKeyExpirationTime(bool isCritical, long seconds)
         {
             list.Add(new KeyExpirationTime(isCritical, seconds));
         }
@@ -90,9 +79,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// </summary>
 		/// <param name="isCritical">True, if should be treated as critical, false otherwise.</param>
 		/// <param name="seconds">The number of seconds the signature is valid, or zero if no expiry.</param>
-        public void SetSignatureExpirationTime(
-            bool	isCritical,
-            long	seconds)
+        public void SetSignatureExpirationTime(bool isCritical, long seconds)
         {
             list.Add(new SignatureExpirationTime(isCritical, seconds));
         }
@@ -103,54 +90,56 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		/// Note: this overrides the generation of a creation time when the signature
 		/// is generated.</p>
 		/// </summary>
-		public void SetSignatureCreationTime(
-			bool		isCritical,
-			DateTime	date)
+		public void SetSignatureCreationTime(bool isCritical, DateTime date)
 		{
 			list.Add(new SignatureCreationTime(isCritical, date));
 		}
 
-		public void SetPreferredHashAlgorithms(
-            bool	isCritical,
-            int[]	algorithms)
+		public void SetPreferredHashAlgorithms(bool	isCritical, int[] algorithms)
         {
             list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredHashAlgorithms, isCritical, algorithms));
         }
 
-		public void SetPreferredSymmetricAlgorithms(
-            bool	isCritical,
-            int[]	algorithms)
+		public void SetPreferredSymmetricAlgorithms(bool isCritical, int[] algorithms)
         {
             list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredSymmetricAlgorithms, isCritical, algorithms));
         }
 
-		public void SetPreferredCompressionAlgorithms(
-            bool	isCritical,
-            int[]	algorithms)
+		public void SetPreferredCompressionAlgorithms(bool isCritical, int[] algorithms)
         {
             list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredCompressionAlgorithms, isCritical, algorithms));
         }
 
-		public void SetKeyFlags(
-            bool	isCritical,
-            int		flags)
+        public void SetPreferredAeadAlgorithms(bool isCritical, int[] algorithms)
+        {
+            list.Add(new PreferredAlgorithms(SignatureSubpacketTag.PreferredAeadAlgorithms, isCritical, algorithms));
+        }
+
+        public void AddPolicyUrl(bool isCritical, string policyUrl)
+        {
+            list.Add(new PolicyUrl(isCritical, policyUrl));
+        }
+
+        public void SetKeyFlags(bool isCritical, int flags)
         {
             list.Add(new KeyFlags(isCritical, flags));
         }
 
-		public void SetSignerUserId(
-            bool	isCritical,
-            string	userId)
+        [Obsolete("Use 'AddSignerUserId' instead")]
+		public void SetSignerUserId(bool isCritical, string userId)
+        {
+            AddSignerUserId(isCritical, userId);
+        }
+
+        public void AddSignerUserId(bool isCritical, string userId)
         {
             if (userId == null)
                 throw new ArgumentNullException("userId");
 
-			list.Add(new SignerUserId(isCritical, userId));
+            list.Add(new SignerUserId(isCritical, userId));
         }
 
-        public void SetSignerUserId(
-            bool    isCritical,
-            byte[]  rawUserId)
+        public void SetSignerUserId(bool isCritical, byte[] rawUserId)
         {
             if (rawUserId == null)
                 throw new ArgumentNullException("rawUserId");
@@ -158,70 +147,116 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             list.Add(new SignerUserId(isCritical, false, rawUserId));
         }
 
-        public void SetEmbeddedSignature(
-			bool			isCritical,
-			PgpSignature	pgpSignature)
+        [Obsolete("Use 'AddEmbeddedSignature' instead")]
+        public void SetEmbeddedSignature(bool isCritical, PgpSignature pgpSignature)
 		{
-			byte[] sig = pgpSignature.GetEncoded();
-			byte[] data;
-
-			// TODO Should be >= ?
-			if (sig.Length - 1 > 256)
-			{
-				data = new byte[sig.Length - 3];
-			}
-			else
-			{
-				data = new byte[sig.Length - 2];
-			}
-
-			Array.Copy(sig, sig.Length - data.Length, data, 0, data.Length);
-
-			list.Add(new EmbeddedSignature(isCritical, false, data));
+            AddEmbeddedSignature(isCritical, pgpSignature);
 		}
 
-		public void SetPrimaryUserId(
-            bool	isCritical,
-            bool	isPrimaryUserId)
+        public void AddEmbeddedSignature(bool isCritical, PgpSignature pgpSignature)
+        {
+            byte[] sig = pgpSignature.GetEncoded();
+            byte[] data;
+
+            // TODO Should be >= ?
+            if (sig.Length - 1 > 256)
+            {
+                data = new byte[sig.Length - 3];
+            }
+            else
+            {
+                data = new byte[sig.Length - 2];
+            }
+
+            Array.Copy(sig, sig.Length - data.Length, data, 0, data.Length);
+
+            list.Add(new EmbeddedSignature(isCritical, false, data));
+        }
+
+        public void SetPrimaryUserId(bool isCritical, bool isPrimaryUserId)
         {
             list.Add(new PrimaryUserId(isCritical, isPrimaryUserId));
         }
 
-		public void SetNotationData(
-			bool	isCritical,
-			bool	isHumanReadable,
-			string	notationName,
-			string	notationValue)
+        [Obsolete("Use 'AddNotationData' instead")]
+		public void SetNotationData(bool isCritical, bool isHumanReadable, string notationName, string notationValue)
 		{
-			list.Add(new NotationData(isCritical, isHumanReadable, notationName, notationValue));
+            AddNotationData(isCritical, isHumanReadable, notationName, notationValue);
 		}
 
-		/// <summary>
-		/// Sets revocation reason sub packet
-		/// </summary>	    
-		public void SetRevocationReason(bool isCritical, RevocationReasonTag reason,
-			string description)
+        public void AddNotationData(bool isCritical, bool isHumanReadable, string notationName, string notationValue)
+        {
+            list.Add(new NotationData(isCritical, isHumanReadable, notationName, notationValue));
+        }
+
+        /// <summary>
+        /// Sets revocation reason sub packet
+        /// </summary>	    
+        public void SetRevocationReason(bool isCritical, RevocationReasonTag reason, string description)
 		{
 			list.Add(new RevocationReason(isCritical, reason, description));
 		}
 
-		/// <summary>
-		/// Sets revocation key sub packet
-		/// </summary>	
+        [Obsolete("Use 'AddRevocationKey' instead")]
 		public void SetRevocationKey(bool isCritical, PublicKeyAlgorithmTag keyAlgorithm, byte[] fingerprint)
 		{
-			list.Add(new RevocationKey(isCritical, RevocationKeyTag.ClassDefault, keyAlgorithm, fingerprint));
+            AddRevocationKey(isCritical, keyAlgorithm, fingerprint);
 		}
 
-		/// <summary>
-		/// Sets issuer key sub packet
-		/// </summary>	
-		public void SetIssuerKeyID(bool isCritical, long keyID)
+        public void AddRevocationKey(bool isCritical, PublicKeyAlgorithmTag keyAlgorithm, byte[] fingerprint)
+        {
+            list.Add(new RevocationKey(isCritical, RevocationKeyTag.ClassDefault, keyAlgorithm, fingerprint));
+        }
+
+        /// <summary>
+        /// Sets issuer key sub packet
+        /// </summary>	
+        public void SetIssuerKeyID(bool isCritical, long keyID)
 		{
 			list.Add(new IssuerKeyId(isCritical, keyID));
-		}    
+		}
+
+        public void SetSignatureTarget(bool isCritical, int publicKeyAlgorithm, int hashAlgorithm, byte[] hashData)
+        {
+            list.Add(new SignatureTarget(isCritical, publicKeyAlgorithm, hashAlgorithm, hashData));
+        }
+
+        public void SetIssuerFingerprint(bool isCritical, PgpSecretKey secretKey)
+        {
+            SetIssuerFingerprint(isCritical, secretKey.PublicKey);
+        }
+
+        public void SetIssuerFingerprint(bool isCritical, PgpPublicKey publicKey)
+        {
+            list.Add(new IssuerFingerprint(isCritical, publicKey.Version, publicKey.GetFingerprint()));
+        }
+
+        public void AddIntendedRecipientFingerprint(bool isCritical, PgpPublicKey publicKey)
+        {
+            list.Add(new IntendedRecipientFingerprint(isCritical, publicKey.Version, publicKey.GetFingerprint()));
+        }
+
+        public void AddCustomSubpacket(SignatureSubpacket subpacket)
+        {
+            list.Add(subpacket);
+        }
+
+        public bool RemovePacket(SignatureSubpacket packet)
+        {
+            return list.Remove(packet);
+        }
+
+        public bool HasSubpacket(SignatureSubpacketTag type)
+        {
+            return null != list.Find(subpacket => subpacket.SubpacketType == type);
+        }
+
+        public SignatureSubpacket[] GetSubpackets(SignatureSubpacketTag type)
+        {
+            return list.FindAll(subpacket => subpacket.SubpacketType == type).ToArray();
+        }
 
-		public PgpSignatureSubpacketVector Generate()
+        public PgpSignatureSubpacketVector Generate()
         {
             return new PgpSignatureSubpacketVector(list.ToArray());
         }
diff --git a/crypto/src/openpgp/PgpSignatureSubpacketVector.cs b/crypto/src/openpgp/PgpSignatureSubpacketVector.cs
index 9417b14fa..ae387198c 100644
--- a/crypto/src/openpgp/PgpSignatureSubpacketVector.cs
+++ b/crypto/src/openpgp/PgpSignatureSubpacketVector.cs
@@ -1,39 +1,32 @@
 using System;
+using System.Collections.Generic;
 using System.IO;
 
 using Org.BouncyCastle.Bcpg.Sig;
 
 namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
-	/// <remarks>Container for a list of signature subpackets.</remarks>
+    /// <remarks>Container for a list of signature subpackets.</remarks>
     public class PgpSignatureSubpacketVector
     {
         public static PgpSignatureSubpacketVector FromSubpackets(SignatureSubpacket[] packets)
         {
-            if (packets == null)
-            {
-                packets = new SignatureSubpacket[0];
-            }
-            return new PgpSignatureSubpacketVector(packets);
+            return new PgpSignatureSubpacketVector(packets ?? new SignatureSubpacket[0]);
         }
 
         private readonly SignatureSubpacket[] packets;
 
-		internal PgpSignatureSubpacketVector(
-            SignatureSubpacket[] packets)
+		internal PgpSignatureSubpacketVector(SignatureSubpacket[] packets)
         {
             this.packets = packets;
         }
 
-		public SignatureSubpacket GetSubpacket(
-            SignatureSubpacketTag type)
+		public SignatureSubpacket GetSubpacket(SignatureSubpacketTag type)
         {
             for (int i = 0; i != packets.Length; i++)
             {
                 if (packets[i].SubpacketType == type)
-                {
                     return packets[i];
-                }
             }
 
 			return null;
@@ -45,8 +38,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		 * @param type type to look for.
 		 * @return true if present, false otherwise.
 		 */
-		public bool HasSubpacket(
-			SignatureSubpacketTag type)
+		public bool HasSubpacket(SignatureSubpacketTag type)
 		{
 			return GetSubpacket(type) != null;
 		}
@@ -56,8 +48,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 		 * @param type subpacket type code
 		 * @return an array of zero or more matching subpackets.
 		 */
-		public SignatureSubpacket[] GetSubpackets(
-			SignatureSubpacketTag type)
+		public SignatureSubpacket[] GetSubpackets(SignatureSubpacketTag type)
 		{
             int count = 0;
             for (int i = 0; i < packets.Length; ++i)
@@ -82,6 +73,27 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             return result;
         }
 
+        /// <exception cref="PgpException"/>
+        public PgpSignatureList GetEmbeddedSignatures()
+        {
+            SignatureSubpacket[] sigs = GetSubpackets(SignatureSubpacketTag.EmbeddedSignature);
+            PgpSignature[] l = new PgpSignature[sigs.Length];
+
+            for (int i = 0; i < sigs.Length; i++)
+            {
+                try
+                {
+                    l[i] = new PgpSignature(SignaturePacket.FromByteArray(sigs[i].GetData()));
+                }
+                catch (IOException e)
+                {
+                    throw new PgpException("Unable to parse signature packet: " + e.Message, e);
+                }
+            }
+
+            return new PgpSignatureList(l);
+        }
+
         public NotationData[] GetNotationDataOccurrences()
 		{
 			SignatureSubpacket[] notations = GetSubpackets(SignatureSubpacketTag.NotationData);
@@ -95,11 +107,26 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			return vals;
 		}
 
-		public long GetIssuerKeyId()
+        public NotationData[] GetNotationDataOccurrences(string notationName)
+        {
+            NotationData[] notations = GetNotationDataOccurrences();
+            var notationsWithName = new List<NotationData>();
+            for (int i = 0; i != notations.Length; i++)
+            {
+                NotationData notation = notations[i];
+                if (notation.GetNotationName().Equals(notationName))
+                {
+                    notationsWithName.Add(notation);
+                }
+            }
+            return notationsWithName.ToArray();
+        }
+
+        public long GetIssuerKeyId()
         {
             SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.IssuerKeyId);
 
-            return p == null ? 0 : ((IssuerKeyId) p).KeyId;
+            return p == null ? 0 : ((IssuerKeyId)p).KeyId;
         }
 
 		public bool HasSignatureCreationTime()
@@ -109,26 +136,27 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 		public DateTime GetSignatureCreationTime()
         {
-            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.CreationTime);
-
-            if (p == null)
-            {
-                throw new PgpException("SignatureCreationTime not available");
-            }
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.CreationTime)
+                ?? throw new PgpException("SignatureCreationTime not available");
 
             return ((SignatureCreationTime)p).GetTime();
         }
 
-		/// <summary>
-		/// Return the number of seconds a signature is valid for after its creation date.
-		/// A value of zero means the signature never expires.
-		/// </summary>
-		/// <returns>Seconds a signature is valid for.</returns>
+        public bool HasSignatureExpirationTime()
+        {
+            return GetSubpacket(SignatureSubpacketTag.ExpireTime) != null;
+        }
+
+        /// <summary>
+        /// Return the number of seconds a signature is valid for after its creation date.
+        /// A value of zero means the signature never expires.
+        /// </summary>
+        /// <returns>Seconds a signature is valid for.</returns>
         public long GetSignatureExpirationTime()
         {
             SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.ExpireTime);
 
-			return p == null ? 0 : ((SignatureExpirationTime) p).Time;
+			return p == null ? 0 : ((SignatureExpirationTime)p).Time;
         }
 
 		/// <summary>
@@ -140,77 +168,58 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
         {
             SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.KeyExpireTime);
 
-			return p == null ? 0 : ((KeyExpirationTime) p).Time;
+			return p == null ? 0 : ((KeyExpirationTime)p).Time;
         }
 
 		public int[] GetPreferredHashAlgorithms()
         {
             SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredHashAlgorithms);
 
-			return p == null ? null : ((PreferredAlgorithms) p).GetPreferences();
+			return p == null ? null : ((PreferredAlgorithms)p).GetPreferences();
         }
 
 		public int[] GetPreferredSymmetricAlgorithms()
         {
             SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredSymmetricAlgorithms);
 
-            return p == null ? null : ((PreferredAlgorithms) p).GetPreferences();
+            return p == null ? null : ((PreferredAlgorithms)p).GetPreferences();
         }
 
 		public int[] GetPreferredCompressionAlgorithms()
         {
             SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredCompressionAlgorithms);
 
-            return p == null ? null : ((PreferredAlgorithms) p).GetPreferences();
+            return p == null ? null : ((PreferredAlgorithms)p).GetPreferences();
+        }
+
+        public int[] GetPreferredAeadAlgorithms()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PreferredAeadAlgorithms);
+
+            return p == null ? null : ((PreferredAlgorithms)p).GetPreferences();
         }
 
-		public int GetKeyFlags()
+        public int GetKeyFlags()
         {
             SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.KeyFlags);
 
-            return p == null ? 0 : ((KeyFlags) p).Flags;
+            return p == null ? 0 : ((KeyFlags)p).Flags;
         }
 
 		public string GetSignerUserId()
         {
             SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.SignerUserId);
 
-			return p == null ? null : ((SignerUserId) p).GetId();
+			return p == null ? null : ((SignerUserId)p).GetId();
         }
 
 		public bool IsPrimaryUserId()
 		{
-			PrimaryUserId primaryId = (PrimaryUserId)
-				this.GetSubpacket(SignatureSubpacketTag.PrimaryUserId);
-
-			if (primaryId != null)
-			{
-				return primaryId.IsPrimaryUserId();
-			}
+			PrimaryUserId primaryId = (PrimaryUserId)GetSubpacket(SignatureSubpacketTag.PrimaryUserId);
 
-			return false;
+            return primaryId != null && primaryId.IsPrimaryUserId();
 		}
 
-        public PgpSignatureList GetEmbeddedSignatures()
-        {
-            SignatureSubpacket [] sigs = GetSubpackets(SignatureSubpacketTag.EmbeddedSignature);
-            PgpSignature[] l = new PgpSignature[sigs.Length];
-   
-            for (int i = 0; i < sigs.Length; i++)
-            {
-                try
-                {
-                    l[i] = new PgpSignature(SignaturePacket.FromByteArray(sigs[i].GetData()));
-                }
-                catch (IOException e)
-                {
-                    throw new PgpException("Unable to parse signature packet: " + e.Message, e);
-                }
-            }
-
-            return new PgpSignatureList(l);
-        }
-
 		public SignatureSubpacketTag[] GetCriticalTags()
         {
             int count = 0;
@@ -237,25 +246,152 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			return list;
         }
 
+        public SignatureTarget GetSignatureTarget()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.SignatureTarget);
+
+            return p == null ? null : new SignatureTarget(p.IsCritical(), p.IsLongLength(), p.GetData());
+        }
+
         public Features GetFeatures()
         {
-            SignatureSubpacket p = this.GetSubpacket(SignatureSubpacketTag.Features);
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.Features);
 
-            if (p == null)
-                return null;
+            return p == null ? null : new Features(p.IsCritical(), p.IsLongLength(), p.GetData());
+        }
 
-            return new Features(p.IsCritical(), p.IsLongLength(), p.GetData());
+        public IssuerFingerprint GetIssuerFingerprint()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.IssuerFingerprint);
+
+            return p == null ? null : new IssuerFingerprint(p.IsCritical(), p.IsLongLength(), p.GetData());
+        }
+
+        public IntendedRecipientFingerprint GetIntendedRecipientFingerprint()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.IntendedRecipientFingerprint);
+
+            return p == null ? null : new IntendedRecipientFingerprint(p.IsCritical(), p.IsLongLength(), p.GetData());
+        }
+
+        public IntendedRecipientFingerprint[] GetIntendedRecipientFingerprints()
+        {
+            SignatureSubpacket[] subpackets = GetSubpackets(SignatureSubpacketTag.IntendedRecipientFingerprint);
+            IntendedRecipientFingerprint[] recipients = new IntendedRecipientFingerprint[subpackets.Length];
+            for (int i = 0; i < recipients.Length; i++)
+            {
+                SignatureSubpacket p = subpackets[i];
+                recipients[i] = new IntendedRecipientFingerprint(p.IsCritical(), p.IsLongLength(), p.GetData());
+            }
+            return recipients;
+        }
+
+        public Exportable GetExportable()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.Exportable);
+
+            return p == null ? null : new Exportable(p.IsCritical(), p.IsLongLength(), p.GetData());
+        }
+
+        public bool IsExportable()
+        {
+            Exportable exportable = GetExportable();
+            return exportable == null || exportable.IsExportable();
+        }
+
+        public PolicyUrl GetPolicyUrl()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.PolicyUrl);
+
+            return p == null ? null : new PolicyUrl(p.IsCritical(), p.IsLongLength(), p.GetData());
+        }
+
+        public PolicyUrl[] GetPolicyUrls()
+        {
+            SignatureSubpacket[] subpackets = GetSubpackets(SignatureSubpacketTag.PolicyUrl);
+            PolicyUrl[] policyUrls = new PolicyUrl[subpackets.Length];
+            for (int i = 0; i < subpackets.Length; i++)
+            {
+                SignatureSubpacket p = subpackets[i];
+                policyUrls[i] = new PolicyUrl(p.IsCritical(), p.IsLongLength(), p.GetData());
+            }
+            return policyUrls;
+        }
+
+        public RegularExpression GetRegularExpression()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.RegExp);
+
+            return p == null ? null : new RegularExpression(p.IsCritical(), p.IsLongLength(), p.GetData());
+        }
+
+        public RegularExpression[] GetRegularExpressions()
+        {
+            SignatureSubpacket[] subpackets = GetSubpackets(SignatureSubpacketTag.RegExp);
+            RegularExpression[] regexes = new RegularExpression[subpackets.Length];
+            for (int i = 0; i < regexes.Length; i++)
+            {
+                SignatureSubpacket p = subpackets[i];
+                regexes[i] = new RegularExpression(p.IsCritical(), p.IsLongLength(), p.GetData());
+            }
+            return regexes;
+        }
+
+        public Revocable GetRevocable()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.Revocable);
+
+            return p == null ? null : new Revocable(p.IsCritical(), p.IsLongLength(), p.GetData());
+        }
+
+        public bool IsRevocable()
+        {
+            Revocable revocable = GetRevocable();
+            return revocable == null || revocable.IsRevocable();
+        }
+
+        public RevocationKey[] GetRevocationKeys()
+        {
+            SignatureSubpacket[] subpackets = GetSubpackets(SignatureSubpacketTag.RevocationKey);
+            RevocationKey[] revocationKeys = new RevocationKey[subpackets.Length];
+            for (int i = 0; i < revocationKeys.Length; i++)
+            {
+                SignatureSubpacket p = subpackets[i]; 
+                revocationKeys[i] = new RevocationKey(p.IsCritical(), p.IsLongLength(), p.GetData());
+            }
+            return revocationKeys;
+        }
+
+        public RevocationReason GetRevocationReason()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.RevocationReason);
+
+            return p == null ? null : new RevocationReason(p.IsCritical(), p.IsLongLength(), p.GetData());
+        }
+
+        public TrustSignature GetTrust()
+        {
+            SignatureSubpacket p = GetSubpacket(SignatureSubpacketTag.TrustSig);
+
+            return p == null ? null : new TrustSignature(p.IsCritical(), p.IsLongLength(), p.GetData());
         }
 
 		/// <summary>Return the number of packets this vector contains.</summary>
-		public int Count
-		{
-			get { return packets.Length; }
-		}
+		public int Count => packets.Length;
 
 		internal SignatureSubpacket[] ToSubpacketArray()
         {
             return packets;
         }
+
+        /**
+         * Return a copy of the subpackets in this vector.
+         *
+         * @return an array containing the vector subpackets in order.
+         */
+        public SignatureSubpacket[] ToArray()
+        {
+            return (SignatureSubpacket[])packets.Clone();
+        }
     }
 }
diff --git a/crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs b/crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs
index df5081390..e9a42e4b7 100644
--- a/crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs
+++ b/crypto/src/openpgp/PgpUserAttributeSubpacketVector.cs
@@ -4,6 +4,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 {
 	/// <remarks>Container for a list of user attribute subpackets.</remarks>
     public class PgpUserAttributeSubpacketVector
+        : IUserDataPacket
     {
         public static PgpUserAttributeSubpacketVector FromSubpackets(UserAttributeSubpacket[] packets)
         {
@@ -22,15 +23,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             this.packets = packets;
         }
 
-		public UserAttributeSubpacket GetSubpacket(
-            UserAttributeSubpacketTag type)
+		public UserAttributeSubpacket GetSubpacket(UserAttributeSubpacketTag type)
         {
             for (int i = 0; i != packets.Length; i++)
             {
                 if (packets[i].SubpacketType == type)
-                {
                     return packets[i];
-                }
             }
 
 			return null;
@@ -48,28 +46,21 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             return packets;
         }
 
-		public override bool Equals(
-            object obj)
+		public override bool Equals(object obj)
         {
             if (obj == this)
                 return true;
 
-			PgpUserAttributeSubpacketVector other = obj as PgpUserAttributeSubpacketVector;
-
-			if (other == null)
+            if (!(obj is PgpUserAttributeSubpacketVector other))
 				return false;
 
 			if (other.packets.Length != packets.Length)
-            {
                 return false;
-            }
 
 			for (int i = 0; i != packets.Length; i++)
             {
                 if (!other.packets[i].Equals(packets[i]))
-                {
                     return false;
-                }
             }
 
 			return true;