summary refs log tree commit diff
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
parentUpdate Asn1Tags (diff)
downloadBouncyCastle.NET-ed25519-5ac39b19f346e72005f41f13ff956f4fa7c97f86.tar.xz
OpenPGP updates from bc-java
-rw-r--r--crypto/src/bcpg/AeadAlgorithmTag.cs11
-rw-r--r--crypto/src/bcpg/AeadEncDataPacket.cs77
-rw-r--r--crypto/src/bcpg/ArmoredInputStream.cs2
-rw-r--r--crypto/src/bcpg/BcpgOutputStream.cs87
-rw-r--r--crypto/src/bcpg/ExperimentalPacket.cs30
-rw-r--r--crypto/src/bcpg/HashAlgorithmTags.cs10
-rw-r--r--crypto/src/bcpg/IBcpgKey.cs1
-rw-r--r--crypto/src/bcpg/IUserDataPacket.cs6
-rw-r--r--crypto/src/bcpg/InputStreamPacket.cs3
-rw-r--r--crypto/src/bcpg/MarkerPacket.cs10
-rw-r--r--crypto/src/bcpg/OnePassSignaturePacket.cs2
-rw-r--r--crypto/src/bcpg/PublicKeyEncSessionPacket.cs2
-rw-r--r--crypto/src/bcpg/PublicKeyPacket.cs5
-rw-r--r--crypto/src/bcpg/PublicSubkeyPacket.cs6
-rw-r--r--crypto/src/bcpg/SecretKeyPacket.cs5
-rw-r--r--crypto/src/bcpg/SecretSubkeyPacket.cs5
-rw-r--r--crypto/src/bcpg/SignaturePacket.cs2
-rw-r--r--crypto/src/bcpg/SignatureSubpacket.cs3
-rw-r--r--crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs2
-rw-r--r--crypto/src/bcpg/TrustPacket.cs13
-rw-r--r--crypto/src/bcpg/UserAttributePacket.cs3
-rw-r--r--crypto/src/bcpg/UserIdPacket.cs37
-rw-r--r--crypto/src/bcpg/sig/SignerUserId.cs38
-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
-rw-r--r--crypto/test/src/openpgp/test/PolicyUrlTest.cs58
32 files changed, 1035 insertions, 466 deletions
diff --git a/crypto/src/bcpg/AeadAlgorithmTag.cs b/crypto/src/bcpg/AeadAlgorithmTag.cs
new file mode 100644
index 000000000..632f88838
--- /dev/null
+++ b/crypto/src/bcpg/AeadAlgorithmTag.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Org.BouncyCastle.Bcpg
+{
+    public enum AeadAlgorithmTag : byte
+    {
+        Eax = 1,    // EAX (IV len: 16 octets, Tag len: 16 octets)
+        Ocb = 2,    // OCB (IV len: 15 octets, Tag len: 16 octets)
+        Gcm = 3,    // GCM (IV len: 12 octets, Tag len: 16 octets)
+    }
+}
diff --git a/crypto/src/bcpg/AeadEncDataPacket.cs b/crypto/src/bcpg/AeadEncDataPacket.cs
new file mode 100644
index 000000000..2c7ec97f6
--- /dev/null
+++ b/crypto/src/bcpg/AeadEncDataPacket.cs
@@ -0,0 +1,77 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Bcpg
+{
+    /**
+     * Packet representing AEAD encrypted data. At the moment this appears to exist in the following
+     * expired draft only, but it's appearing despite this.
+     *
+     * @ref https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-rfc4880bis-04#section-5.16
+     */
+    public class AeadEncDataPacket
+        : InputStreamPacket
+    {
+        private readonly byte m_version;
+        private readonly SymmetricKeyAlgorithmTag m_algorithm;
+        private readonly AeadAlgorithmTag m_aeadAlgorithm;
+        private readonly byte m_chunkSize;
+        private readonly byte[] m_iv;
+
+        public AeadEncDataPacket(BcpgInputStream bcpgIn)
+            : base(bcpgIn)
+        {
+            m_version = (byte)bcpgIn.ReadByte();
+            if (m_version != 1)
+                throw new ArgumentException("wrong AEAD packet version: " + m_version);
+
+            m_algorithm = (SymmetricKeyAlgorithmTag)bcpgIn.ReadByte();
+            m_aeadAlgorithm = (AeadAlgorithmTag)bcpgIn.ReadByte();
+            m_chunkSize = (byte)bcpgIn.ReadByte();
+
+            m_iv = new byte[GetIVLength(m_aeadAlgorithm)];
+            bcpgIn.ReadFully(m_iv);
+        }
+
+        public AeadEncDataPacket(SymmetricKeyAlgorithmTag algorithm, AeadAlgorithmTag aeadAlgorithm, int chunkSize,
+            byte[] iv)
+            : base(null)
+        {
+            m_version = 1;
+            m_algorithm = algorithm;
+            m_aeadAlgorithm = aeadAlgorithm;
+            m_chunkSize = (byte)chunkSize;
+            m_iv = Arrays.Clone(iv);
+        }
+
+        public byte Version => m_version;
+
+        public SymmetricKeyAlgorithmTag Algorithm => m_algorithm;
+
+        public AeadAlgorithmTag AeadAlgorithm => m_aeadAlgorithm;
+
+        public int ChunkSize => m_chunkSize;
+
+        public byte[] GetIV()
+        {
+            return m_iv;
+        }
+
+        public static int GetIVLength(AeadAlgorithmTag aeadAlgorithm)
+        {
+            switch (aeadAlgorithm)
+            {
+            case AeadAlgorithmTag.Eax:
+                return 16;
+            case AeadAlgorithmTag.Ocb:
+                return 15;
+            case AeadAlgorithmTag.Gcm:
+                return 12;
+            default:
+                throw new ArgumentException("unknown mode: " + aeadAlgorithm);
+            }
+        }
+    }
+}
diff --git a/crypto/src/bcpg/ArmoredInputStream.cs b/crypto/src/bcpg/ArmoredInputStream.cs
index b493d04fc..eacba17dd 100644
--- a/crypto/src/bcpg/ArmoredInputStream.cs
+++ b/crypto/src/bcpg/ArmoredInputStream.cs
@@ -304,7 +304,7 @@ namespace Org.BouncyCastle.Bcpg
             {
                 c = input.ReadByte();
             }
-            while (c == ' ' || c == '\t' || c == '\f' || c == '\u000B'); // \u000B ~ \v
+            while (c == ' ' || c == '\t' || c == '\f' || c == '\v');
 
             if (c >= 128)
                 throw new IOException("invalid armor");
diff --git a/crypto/src/bcpg/BcpgOutputStream.cs b/crypto/src/bcpg/BcpgOutputStream.cs
index 3bae7e383..29ed9e22a 100644
--- a/crypto/src/bcpg/BcpgOutputStream.cs
+++ b/crypto/src/bcpg/BcpgOutputStream.cs
@@ -19,6 +19,7 @@ namespace Org.BouncyCastle.Bcpg
 		}
 
 		private Stream outStr;
+        private bool useOldFormat;
         private byte[] partialBuffer;
         private int partialBufferLength;
         private int partialPower;
@@ -27,26 +28,28 @@ namespace Org.BouncyCastle.Bcpg
 
 		/// <summary>Create a stream representing a general packet.</summary>
 		/// <param name="outStr">Output stream to write to.</param>
-		public BcpgOutputStream(
-            Stream outStr)
+		public BcpgOutputStream(Stream outStr)
+            : this(outStr, false)
         {
-			if (outStr == null)
-				throw new ArgumentNullException("outStr");
-
-			this.outStr = outStr;
         }
 
-		/// <summary>Create a stream representing an old style partial object.</summary>
+        /// <summary>Base constructor specifying whether or not to use packets in the new format wherever possible.
+        /// </summary>
 		/// <param name="outStr">Output stream to write to.</param>
-		/// <param name="tag">The packet tag for the object.</param>
-        public BcpgOutputStream(
-            Stream		outStr,
-            PacketTag	tag)
+        /// <param name="newFormatOnly"><c>true</c> if use new format packets, <c>false</c> if backwards compatible
+        /// preferred.</param>
+        public BcpgOutputStream(Stream outStr, bool newFormatOnly)
         {
-			if (outStr == null)
-				throw new ArgumentNullException("outStr");
+            this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr));
+            this.useOldFormat = !newFormatOnly;
+        }
 
-			this.outStr = outStr;
+        /// <summary>Create a stream representing an old style partial object.</summary>
+        /// <param name="outStr">Output stream to write to.</param>
+        /// <param name="tag">The packet tag for the object.</param>
+        public BcpgOutputStream(Stream outStr, PacketTag tag)
+        {
+            this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr));
             this.WriteHeader(tag, true, true, 0);
         }
 
@@ -55,16 +58,9 @@ namespace Org.BouncyCastle.Bcpg
 		/// <param name="tag">Packet tag.</param>
 		/// <param name="length">Size of chunks making up the packet.</param>
 		/// <param name="oldFormat">If true, the header is written out in old format.</param>
-		public BcpgOutputStream(
-            Stream		outStr,
-            PacketTag	tag,
-            long		length,
-            bool		oldFormat)
+		public BcpgOutputStream(Stream outStr, PacketTag tag, long length, bool oldFormat)
         {
-			if (outStr == null)
-				throw new ArgumentNullException("outStr");
-
-			this.outStr = outStr;
+            this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr));
 
             if (length > 0xFFFFFFFFL)
             {
@@ -84,15 +80,9 @@ namespace Org.BouncyCastle.Bcpg
 		/// <param name="outStr">Output stream to write to.</param>
 		/// <param name="tag">Packet tag.</param>
 		/// <param name="length">Size of chunks making up the packet.</param>
-		public BcpgOutputStream(
-            Stream		outStr,
-            PacketTag	tag,
-            long		length)
+		public BcpgOutputStream(Stream outStr, PacketTag tag, long length)
         {
-			if (outStr == null)
-				throw new ArgumentNullException("outStr");
-
-            this.outStr = outStr;
+            this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr));
             this.WriteHeader(tag, false, false, length);
         }
 
@@ -100,15 +90,9 @@ namespace Org.BouncyCastle.Bcpg
 		/// <param name="outStr">Output stream to write to.</param>
 		/// <param name="tag">Packet tag.</param>
 		/// <param name="buffer">Buffer to use for collecting chunks.</param>
-        public BcpgOutputStream(
-            Stream		outStr,
-            PacketTag	tag,
-            byte[]		buffer)
+        public BcpgOutputStream(Stream outStr, PacketTag tag, byte[] buffer)
         {
-			if (outStr == null)
-				throw new ArgumentNullException("outStr");
-
-            this.outStr = outStr;
+            this.outStr = outStr ?? throw new ArgumentNullException(nameof(outStr));
             this.WriteHeader(tag, false, true, 0);
 
 			this.partialBuffer = buffer;
@@ -120,15 +104,13 @@ namespace Org.BouncyCastle.Bcpg
             }
 
 			if (partialPower > 30)
-            {
                 throw new IOException("Buffer cannot be greater than 2^30 in length.");
-            }
+
             this.partialBufferLength = 1 << partialPower;
             this.partialOffset = 0;
         }
 
-		private void WriteNewPacketLength(
-            long bodyLen)
+		private void WriteNewPacketLength(long bodyLen)
         {
             if (bodyLen < 192)
             {
@@ -378,29 +360,28 @@ namespace Org.BouncyCastle.Bcpg
 				(byte)n);
 		}
 
-		public void WritePacket(
-            ContainedPacket p)
+		public void WritePacket(ContainedPacket p)
         {
             p.Encode(this);
         }
 
-        internal void WritePacket(
-            PacketTag	tag,
-            byte[]		body,
-            bool		oldFormat)
+        internal void WritePacket(PacketTag tag, byte[] body)
+        {
+            WritePacket(tag, body, useOldFormat);
+        }
+
+        internal void WritePacket(PacketTag tag, byte[] body, bool oldFormat)
         {
             this.WriteHeader(tag, oldFormat, false, body.Length);
             this.Write(body);
         }
 
-		public void WriteObject(
-            BcpgObject bcpgObject)
+		public void WriteObject(BcpgObject bcpgObject)
         {
             bcpgObject.Encode(this);
         }
 
-		public void WriteObjects(
-			params BcpgObject[] v)
+		public void WriteObjects(params BcpgObject[] v)
 		{
 			foreach (BcpgObject o in v)
 			{
diff --git a/crypto/src/bcpg/ExperimentalPacket.cs b/crypto/src/bcpg/ExperimentalPacket.cs
index 36a254be1..3d413db09 100644
--- a/crypto/src/bcpg/ExperimentalPacket.cs
+++ b/crypto/src/bcpg/ExperimentalPacket.cs
@@ -5,34 +5,24 @@ namespace Org.BouncyCastle.Bcpg
 {
 	/// <remarks>Basic packet for an experimental packet.</remarks>
     public class ExperimentalPacket
-        : ContainedPacket //, PublicKeyAlgorithmTag
+        : ContainedPacket
     {
-        private readonly PacketTag	tag;
-        private readonly byte[]		contents;
+        private readonly PacketTag m_tag;
+        private readonly byte[] m_contents;
 
-		internal ExperimentalPacket(
-            PacketTag		tag,
-            BcpgInputStream	bcpgIn)
+		internal ExperimentalPacket(PacketTag tag, BcpgInputStream bcpgIn)
         {
-            this.tag = tag;
-
-			this.contents = bcpgIn.ReadAll();
+            m_tag = tag;
+			m_contents = bcpgIn.ReadAll();
         }
 
-		public PacketTag Tag
-        {
-			get { return tag; }
-        }
+		public PacketTag Tag => m_tag;
 
-		public byte[] GetContents()
-        {
-			return (byte[]) contents.Clone();
-        }
+		public byte[] GetContents() => (byte[])m_contents.Clone();
 
-		public override void Encode(
-            BcpgOutputStream bcpgOut)
+		public override void Encode(BcpgOutputStream bcpgOut)
         {
-            bcpgOut.WritePacket(tag, contents, true);
+            bcpgOut.WritePacket(m_tag, m_contents);
         }
     }
 }
diff --git a/crypto/src/bcpg/HashAlgorithmTags.cs b/crypto/src/bcpg/HashAlgorithmTags.cs
index 96c009153..a13725de6 100644
--- a/crypto/src/bcpg/HashAlgorithmTags.cs
+++ b/crypto/src/bcpg/HashAlgorithmTags.cs
@@ -15,5 +15,15 @@ namespace Org.BouncyCastle.Bcpg
 		Sha384 = 9,			// SHA-384
 		Sha512 = 10,		// SHA-512
 		Sha224 = 11,		// SHA-224
+		Sha3_256 = 12,		// SHA3-256
+		Sha3_512 = 14,		// SHA3-512
+
+		MD4 = 301,
+		Sha3_224 = 312,		// SHA3-224
+		Sha3_256_Old = 313,	//SHA3-256
+		Sha3_384 = 314,		// SHA3-384
+		Sha3_512_Old = 315, // SHA3-512
+
+		SM3 = 326,			// SM3
 	}
 }
diff --git a/crypto/src/bcpg/IBcpgKey.cs b/crypto/src/bcpg/IBcpgKey.cs
index 275461772..86832eb8b 100644
--- a/crypto/src/bcpg/IBcpgKey.cs
+++ b/crypto/src/bcpg/IBcpgKey.cs
@@ -2,6 +2,7 @@ using System;
 
 namespace Org.BouncyCastle.Bcpg
 {
+	// TODO[api] Extend IEncodable
 	/// <remarks>Base interface for a PGP key.</remarks>
     public interface IBcpgKey
     {
diff --git a/crypto/src/bcpg/IUserDataPacket.cs b/crypto/src/bcpg/IUserDataPacket.cs
new file mode 100644
index 000000000..1519b5007
--- /dev/null
+++ b/crypto/src/bcpg/IUserDataPacket.cs
@@ -0,0 +1,6 @@
+namespace Org.BouncyCastle.Bcpg
+{
+    public interface IUserDataPacket
+    {
+    }
+}
diff --git a/crypto/src/bcpg/InputStreamPacket.cs b/crypto/src/bcpg/InputStreamPacket.cs
index c45efab7b..d4946ebf2 100644
--- a/crypto/src/bcpg/InputStreamPacket.cs
+++ b/crypto/src/bcpg/InputStreamPacket.cs
@@ -5,8 +5,7 @@ namespace Org.BouncyCastle.Bcpg
     {
         private readonly BcpgInputStream bcpgIn;
 
-		public InputStreamPacket(
-            BcpgInputStream bcpgIn)
+		public InputStreamPacket(BcpgInputStream bcpgIn)
         {
             this.bcpgIn = bcpgIn;
         }
diff --git a/crypto/src/bcpg/MarkerPacket.cs b/crypto/src/bcpg/MarkerPacket.cs
index 4dc4b5a83..f2c450793 100644
--- a/crypto/src/bcpg/MarkerPacket.cs
+++ b/crypto/src/bcpg/MarkerPacket.cs
@@ -7,18 +7,16 @@ namespace Org.BouncyCastle.Bcpg
         : ContainedPacket
     {
         // "PGP"
-        byte[] marker = { (byte)0x50, (byte)0x47, (byte)0x50 };
+        private readonly byte[] marker = { (byte)0x50, (byte)0x47, (byte)0x50 };
 
-        public MarkerPacket(
-            BcpgInputStream bcpgIn)
+        public MarkerPacket(BcpgInputStream bcpgIn)
         {
             bcpgIn.ReadFully(marker);
         }
 
-        public override void Encode(
-            BcpgOutputStream bcpgOut)
+        public override void Encode(BcpgOutputStream bcpgOut)
         {
-            bcpgOut.WritePacket(PacketTag.Marker, marker, true);
+            bcpgOut.WritePacket(PacketTag.Marker, marker);
         }
     }
 }
diff --git a/crypto/src/bcpg/OnePassSignaturePacket.cs b/crypto/src/bcpg/OnePassSignaturePacket.cs
index 70f0a2207..4efae1d63 100644
--- a/crypto/src/bcpg/OnePassSignaturePacket.cs
+++ b/crypto/src/bcpg/OnePassSignaturePacket.cs
@@ -81,7 +81,7 @@ namespace Org.BouncyCastle.Bcpg
 				pOut.WriteByte((byte)nested);
 			}
 
-			bcpgOut.WritePacket(PacketTag.OnePassSignature, bOut.ToArray(), true);
+			bcpgOut.WritePacket(PacketTag.OnePassSignature, bOut.ToArray());
 		}
 	}
 }
diff --git a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
index f0b1577ec..25d297890 100644
--- a/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
+++ b/crypto/src/bcpg/PublicKeyEncSessionPacket.cs
@@ -105,7 +105,7 @@ namespace Org.BouncyCastle.Bcpg
 				}
 			}
 
-			bcpgOut.WritePacket(PacketTag.PublicKeyEncryptedSession, bOut.ToArray(), true);
+			bcpgOut.WritePacket(PacketTag.PublicKeyEncryptedSession, bOut.ToArray());
 		}
 	}
 }
diff --git a/crypto/src/bcpg/PublicKeyPacket.cs b/crypto/src/bcpg/PublicKeyPacket.cs
index 40c696a37..b3b5d1600 100644
--- a/crypto/src/bcpg/PublicKeyPacket.cs
+++ b/crypto/src/bcpg/PublicKeyPacket.cs
@@ -115,10 +115,9 @@ namespace Org.BouncyCastle.Bcpg
             return bOut.ToArray();
         }
 
-        public override void Encode(
-            BcpgOutputStream bcpgOut)
+        public override void Encode(BcpgOutputStream bcpgOut)
         {
-            bcpgOut.WritePacket(PacketTag.PublicKey, GetEncodedContents(), true);
+            bcpgOut.WritePacket(PacketTag.PublicKey, GetEncodedContents());
         }
     }
 }
diff --git a/crypto/src/bcpg/PublicSubkeyPacket.cs b/crypto/src/bcpg/PublicSubkeyPacket.cs
index 6e1aeda98..0e1065b72 100644
--- a/crypto/src/bcpg/PublicSubkeyPacket.cs
+++ b/crypto/src/bcpg/PublicSubkeyPacket.cs
@@ -1,5 +1,6 @@
 using System;
 using System.IO;
+
 namespace Org.BouncyCastle.Bcpg
 {
 	/// <remarks>Basic packet for a PGP public subkey</remarks>
@@ -21,10 +22,9 @@ namespace Org.BouncyCastle.Bcpg
         {
         }
 
-		public override void Encode(
-            BcpgOutputStream bcpgOut)
+		public override void Encode(BcpgOutputStream bcpgOut)
         {
-            bcpgOut.WritePacket(PacketTag.PublicSubkey, GetEncodedContents(), true);
+            bcpgOut.WritePacket(PacketTag.PublicSubkey, GetEncodedContents());
         }
     }
 }
diff --git a/crypto/src/bcpg/SecretKeyPacket.cs b/crypto/src/bcpg/SecretKeyPacket.cs
index d9ceab4f1..1bc684a69 100644
--- a/crypto/src/bcpg/SecretKeyPacket.cs
+++ b/crypto/src/bcpg/SecretKeyPacket.cs
@@ -161,10 +161,9 @@ namespace Org.BouncyCastle.Bcpg
             return bOut.ToArray();
         }
 
-        public override void Encode(
-            BcpgOutputStream bcpgOut)
+        public override void Encode(BcpgOutputStream bcpgOut)
         {
-            bcpgOut.WritePacket(PacketTag.SecretKey, GetEncodedContents(), true);
+            bcpgOut.WritePacket(PacketTag.SecretKey, GetEncodedContents());
         }
     }
 }
diff --git a/crypto/src/bcpg/SecretSubkeyPacket.cs b/crypto/src/bcpg/SecretSubkeyPacket.cs
index 8f1746942..2d405aec2 100644
--- a/crypto/src/bcpg/SecretSubkeyPacket.cs
+++ b/crypto/src/bcpg/SecretSubkeyPacket.cs
@@ -34,10 +34,9 @@ namespace Org.BouncyCastle.Bcpg
 		{
 		}
 
-		public override void Encode(
-			BcpgOutputStream bcpgOut)
+		public override void Encode(BcpgOutputStream bcpgOut)
         {
-            bcpgOut.WritePacket(PacketTag.SecretSubkey, GetEncodedContents(), true);
+            bcpgOut.WritePacket(PacketTag.SecretSubkey, GetEncodedContents());
         }
     }
 }
diff --git a/crypto/src/bcpg/SignaturePacket.cs b/crypto/src/bcpg/SignaturePacket.cs
index 09e6b059d..99ca7df40 100644
--- a/crypto/src/bcpg/SignaturePacket.cs
+++ b/crypto/src/bcpg/SignaturePacket.cs
@@ -392,7 +392,7 @@ namespace Org.BouncyCastle.Bcpg
                 }
             }
 
-			bcpgOut.WritePacket(PacketTag.Signature, bOut.ToArray(), true);
+			bcpgOut.WritePacket(PacketTag.Signature, bOut.ToArray());
         }
 
 		private static void EncodeLengthAndData(
diff --git a/crypto/src/bcpg/SignatureSubpacket.cs b/crypto/src/bcpg/SignatureSubpacket.cs
index c2186f373..f7f82b2a6 100644
--- a/crypto/src/bcpg/SignatureSubpacket.cs
+++ b/crypto/src/bcpg/SignatureSubpacket.cs
@@ -103,8 +103,7 @@ namespace Org.BouncyCastle.Bcpg
             if (obj == this)
                 return true;
 
-            SignatureSubpacket other = obj as SignatureSubpacket;
-            if (null == other)
+            if (!(obj is SignatureSubpacket other))
                 return false;
 
             return this.type == other.type
diff --git a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
index 85bcc2874..901088e33 100644
--- a/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
+++ b/crypto/src/bcpg/SymmetricKeyEncSessionPacket.cs
@@ -82,7 +82,7 @@ namespace Org.BouncyCastle.Bcpg
                 }
             }
 
-			bcpgOut.WritePacket(PacketTag.SymmetricKeyEncryptedSessionKey, bOut.ToArray(), true);
+			bcpgOut.WritePacket(PacketTag.SymmetricKeyEncryptedSessionKey, bOut.ToArray());
         }
     }
 }
diff --git a/crypto/src/bcpg/TrustPacket.cs b/crypto/src/bcpg/TrustPacket.cs
index 6f1969c2a..a2a30177f 100644
--- a/crypto/src/bcpg/TrustPacket.cs
+++ b/crypto/src/bcpg/TrustPacket.cs
@@ -9,8 +9,7 @@ namespace Org.BouncyCastle.Bcpg
     {
         private readonly byte[] levelAndTrustAmount;
 
-		public TrustPacket(
-            BcpgInputStream bcpgIn)
+		public TrustPacket(BcpgInputStream bcpgIn)
         {
             MemoryStream bOut = new MemoryStream();
 
@@ -23,10 +22,9 @@ namespace Org.BouncyCastle.Bcpg
 			levelAndTrustAmount = bOut.ToArray();
         }
 
-		public TrustPacket(
-            int trustCode)
+		public TrustPacket(int trustCode)
         {
-			this.levelAndTrustAmount = new byte[]{ (byte) trustCode };
+			this.levelAndTrustAmount = new byte[]{ (byte)trustCode };
         }
 
 		public byte[] GetLevelAndTrustAmount()
@@ -34,10 +32,9 @@ namespace Org.BouncyCastle.Bcpg
 			return (byte[]) levelAndTrustAmount.Clone();
 		}
 
-		public override void Encode(
-            BcpgOutputStream bcpgOut)
+		public override void Encode(BcpgOutputStream bcpgOut)
         {
-            bcpgOut.WritePacket(PacketTag.Trust, levelAndTrustAmount, true);
+            bcpgOut.WritePacket(PacketTag.Trust, levelAndTrustAmount);
         }
     }
 }
diff --git a/crypto/src/bcpg/UserAttributePacket.cs b/crypto/src/bcpg/UserAttributePacket.cs
index 861f62f5d..0be24e006 100644
--- a/crypto/src/bcpg/UserAttributePacket.cs
+++ b/crypto/src/bcpg/UserAttributePacket.cs
@@ -37,8 +37,7 @@ namespace Org.BouncyCastle.Bcpg
             return subpackets;
         }
 
-        public override void Encode(
-            BcpgOutputStream bcpgOut)
+        public override void Encode(BcpgOutputStream bcpgOut)
         {
             MemoryStream bOut = new MemoryStream();
 
diff --git a/crypto/src/bcpg/UserIdPacket.cs b/crypto/src/bcpg/UserIdPacket.cs
index a175e74a6..7d3d3a846 100644
--- a/crypto/src/bcpg/UserIdPacket.cs
+++ b/crypto/src/bcpg/UserIdPacket.cs
@@ -1,37 +1,56 @@
 using System;
 using System.Text;
 
+using Org.BouncyCastle.Utilities;
+
 namespace Org.BouncyCastle.Bcpg
 {
     /**
     * Basic type for a user ID packet.
     */
     public class UserIdPacket
-        : ContainedPacket
+        : ContainedPacket, IUserDataPacket
     {
         private readonly byte[] idData;
 
-        public UserIdPacket(
-            BcpgInputStream bcpgIn)
+        public UserIdPacket(BcpgInputStream bcpgIn)
         {
             this.idData = bcpgIn.ReadAll();
         }
 
-		public UserIdPacket(
-			string id)
+		public UserIdPacket(string id)
         {
             this.idData = Encoding.UTF8.GetBytes(id);
         }
 
-		public string GetId()
+        public UserIdPacket(byte[] rawId)
+        {
+            this.idData = Arrays.Clone(rawId);
+        }
+
+        public string GetId()
         {
 			return Encoding.UTF8.GetString(idData, 0, idData.Length);
         }
 
-		public override void Encode(
-            BcpgOutputStream bcpgOut)
+        public byte[] GetRawId() => Arrays.Clone(idData);
+
+        public override bool Equals(object obj)
+        {
+            if (!(obj is UserIdPacket other))
+                return false;
+
+            return Arrays.AreEqual(this.idData, other.idData);
+        }
+
+        public override int GetHashCode()
+        {
+            return Arrays.GetHashCode(this.idData);
+        }
+
+        public override void Encode(BcpgOutputStream bcpgOut)
         {
-            bcpgOut.WritePacket(PacketTag.UserId, idData, true);
+            bcpgOut.WritePacket(PacketTag.UserId, idData);
         }
     }
 }
diff --git a/crypto/src/bcpg/sig/SignerUserId.cs b/crypto/src/bcpg/sig/SignerUserId.cs
index 6f812e210..bfa00d078 100644
--- a/crypto/src/bcpg/sig/SignerUserId.cs
+++ b/crypto/src/bcpg/sig/SignerUserId.cs
@@ -1,3 +1,5 @@
+using Org.BouncyCastle.Utilities;
+
 namespace Org.BouncyCastle.Bcpg.Sig
 {
     /**
@@ -6,44 +8,18 @@ namespace Org.BouncyCastle.Bcpg.Sig
     public class SignerUserId
         : SignatureSubpacket
     {
-        private static byte[] UserIdToBytes(
-            string id)
-        {
-            byte[] idData = new byte[id.Length];
-
-            for (int i = 0; i != id.Length; i++)
-            {
-                idData[i] = (byte)id[i];
-            }
-
-			return idData;
-        }
-
-        public SignerUserId(
-            bool    critical,
-            bool    isLongLength,
-            byte[]  data)
+        public SignerUserId(bool critical, bool isLongLength, byte[] data)
             : base(SignatureSubpacketTag.SignerUserId, critical, isLongLength, data)
 		{
 		}
 
-        public SignerUserId(
-            bool    critical,
-            string  userId)
-            : base(SignatureSubpacketTag.SignerUserId, critical, false, UserIdToBytes(userId))
+        public SignerUserId(bool critical, string userId)
+            : base(SignatureSubpacketTag.SignerUserId, critical, false, Strings.ToUtf8ByteArray(userId))
 		{
         }
 
-        public string GetId()
-        {
-            char[] chars = new char[data.Length];
-
-			for (int i = 0; i != chars.Length; i++)
-            {
-                chars[i] = (char)(data[i] & 0xff);
-            }
+        public string GetId() => Strings.FromUtf8ByteArray(data);
 
-			return new string(chars);
-        }
+        public byte[] GetRawId() => Arrays.Clone(data);
     }
 }
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;
diff --git a/crypto/test/src/openpgp/test/PolicyUrlTest.cs b/crypto/test/src/openpgp/test/PolicyUrlTest.cs
new file mode 100644
index 000000000..d98397f39
--- /dev/null
+++ b/crypto/test/src/openpgp/test/PolicyUrlTest.cs
@@ -0,0 +1,58 @@
+using System.IO;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Bcpg.Sig;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
+{
+    [TestFixture]
+    public class PolicyUrlTest
+    {
+        [Test]
+        public void TestGetUrl()
+        {
+            PolicyUrl policyUrl = new PolicyUrl(true, "https://bouncycastle.org/policy/alice.txt");
+            Assert.IsTrue(policyUrl.IsCritical());
+            Assert.AreEqual("https://bouncycastle.org/policy/alice.txt", policyUrl.Url);
+
+            policyUrl = new PolicyUrl(false, "https://bouncycastle.org/policy/bob.txt");
+            Assert.IsFalse(policyUrl.IsCritical());
+            Assert.AreEqual("https://bouncycastle.org/policy/bob.txt", policyUrl.Url);
+        }
+
+        [Test]
+        public void TestParsingFromSignature()
+        {
+            string signatureWithPolicyUrl = "-----BEGIN PGP SIGNATURE-----\n" +
+                "\n" +
+                "iKQEHxYKAFYFAmIRIAgJEDXXpSQjWzWvFiEEVSc3S9X9kRTsyfjqNdelJCNbNa8u\n" +
+                "Gmh0dHBzOi8vZXhhbXBsZS5vcmcvfmFsaWNlL3NpZ25pbmctcG9saWN5LnR4dAAA\n" +
+                "NnwBAImA2KdiS/7kLWoQpwc+A6N2PtAvLxG0gkZmGzYgRWvGAP9g4GLAA/GQ0plr\n" +
+                "Xn7uLnOG49S1fFA9P+R1Dd8Qoa4+Dg==\n" +
+                "=OPUu\n" +
+                "-----END PGP SIGNATURE-----\n";
+
+            MemoryStream byteIn = new MemoryStream(Strings.ToByteArray(signatureWithPolicyUrl), false);
+            ArmoredInputStream armorIn = new ArmoredInputStream(byteIn);
+            PgpObjectFactory objectFactory = new PgpObjectFactory(armorIn);
+
+            PgpSignatureList signatures = (PgpSignatureList)objectFactory.NextPgpObject();
+            PgpSignature signature = signatures[0];
+
+            PolicyUrl policyUrl = signature.GetHashedSubPackets().GetPolicyUrl();
+            Assert.AreEqual("https://example.org/~alice/signing-policy.txt", policyUrl.Url);
+
+            PolicyUrl other = new PolicyUrl(false, "https://example.org/~alice/signing-policy.txt");
+
+            MemoryStream first = new MemoryStream();
+            policyUrl.Encode(first);
+
+            MemoryStream second = new MemoryStream();
+            other.Encode(second);
+
+            Assert.IsTrue(Arrays.AreEqual(first.ToArray(), second.ToArray()));
+        }
+    }
+}