summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2021-10-13 01:00:45 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2021-10-13 01:00:45 +0700
commit460d7a09866dfb6baca98a14c754a5624cca7352 (patch)
tree8d1ac9195ff5158d126dfc8d2273067bb8bbe3ca
parentTest fixes (diff)
downloadBouncyCastle.NET-ed25519-460d7a09866dfb6baca98a14c754a5624cca7352.tar.xz
Latest ArmoredInputStream stuff from bc-java
-rw-r--r--crypto/crypto.csproj5
-rw-r--r--crypto/src/bcpg/ArmoredInputStream.cs371
-rw-r--r--crypto/test/UnitTests.csproj1
-rw-r--r--crypto/test/src/openpgp/test/ArmoredInputStreamTest.cs419
-rw-r--r--crypto/test/src/openpgp/test/RegressionTest.cs23
5 files changed, 631 insertions, 188 deletions
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index e95ee2f82..c7f5d4c22 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -14889,6 +14889,11 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "test\src\openpgp\test\ArmoredInputStreamTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "test\src\openpgp\test\DSA2Test.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
diff --git a/crypto/src/bcpg/ArmoredInputStream.cs b/crypto/src/bcpg/ArmoredInputStream.cs
index d5d9f7ffb..2895c379a 100644
--- a/crypto/src/bcpg/ArmoredInputStream.cs
+++ b/crypto/src/bcpg/ArmoredInputStream.cs
@@ -9,10 +9,18 @@ using Org.BouncyCastle.Utilities.IO;
 namespace Org.BouncyCastle.Bcpg
 {
     /**
-    * reader for Base64 armored objects - read the headers and then start returning
-    * bytes when the data is reached. An IOException is thrown if the CRC check
-    * fails.
-    */
+     * reader for Base64 armored objects - read the headers and then start returning
+     * bytes when the data is reached. An IOException is thrown if the CRC check
+     * is detected and fails.
+     * <p>
+     * By default a missing CRC will not cause an exception. To force CRC detection use:
+     * <pre>
+     *     ArmoredInputStream aIn = ...
+     *
+     *     aIn.setDetectMissingCRC(true);
+     * </pre>
+     * </p>
+     */
     public class ArmoredInputStream
         : BaseInputStream
     {
@@ -23,6 +31,7 @@ namespace Org.BouncyCastle.Bcpg
         static ArmoredInputStream()
         {
             decodingTable = new byte[128];
+            Arrays.Fill(decodingTable, 0xff);
             for (int i = 'A'; i <= 'Z'; i++)
             {
                 decodingTable[i] = (byte)(i - 'A');
@@ -44,23 +53,19 @@ namespace Org.BouncyCastle.Bcpg
         *
         * @return the offset the data starts in out.
         */
-        private int Decode(
-            int      in0,
-            int      in1,
-            int      in2,
-            int      in3,
-            int[]    result)
+        private static int Decode(int in0, int in1, int in2, int in3, int[] result)
         {
             if (in3 < 0)
-            {
                 throw new EndOfStreamException("unexpected end of file in armored stream.");
-            }
 
-            int    b1, b2, b3, b4;
+            int b1, b2, b3, b4;
             if (in2 == '=')
             {
-                b1 = decodingTable[in0] &0xff;
-                b2 = decodingTable[in1] & 0xff;
+                b1 = decodingTable[in0];
+                b2 = decodingTable[in1];
+                if ((b1 | b2) >= 128)
+                    throw new IOException("invalid armor");
+
                 result[2] = ((b1 << 2) | (b2 >> 4)) & 0xff;
                 return 2;
             }
@@ -69,6 +74,9 @@ namespace Org.BouncyCastle.Bcpg
                 b1 = decodingTable[in0];
                 b2 = decodingTable[in1];
                 b3 = decodingTable[in2];
+                if ((b1 | b2 | b3) >= 128)
+                    throw new IOException("invalid armor");
+
                 result[1] = ((b1 << 2) | (b2 >> 4)) & 0xff;
                 result[2] = ((b2 << 4) | (b3 >> 2)) & 0xff;
                 return 1;
@@ -79,6 +87,9 @@ namespace Org.BouncyCastle.Bcpg
                 b2 = decodingTable[in1];
                 b3 = decodingTable[in2];
                 b4 = decodingTable[in3];
+                if ((b1 | b2 | b3 | b4) >= 128)
+                    throw new IOException("invalid armor");
+
                 result[0] = ((b1 << 2) | (b2 >> 4)) & 0xff;
                 result[1] = ((b2 << 4) | (b3 >> 2)) & 0xff;
                 result[2] = ((b3 << 6) | b4) & 0xff;
@@ -86,6 +97,12 @@ namespace Org.BouncyCastle.Bcpg
             }
         }
 
+        /*
+         * Ignore missing CRC checksums.
+         * https://tests.sequoia-pgp.org/#ASCII_Armor suggests that missing CRC sums do not invalidate the message.
+         */
+        private bool detectMissingChecksum = false;
+
         Stream      input;
         bool        start = true;
         int[]       outBuf = new int[3];
@@ -97,7 +114,7 @@ namespace Org.BouncyCastle.Bcpg
         bool        newLineFound = false;
         bool        clearText = false;
         bool        restart = false;
-        IList       headerList= Platform.CreateArrayList();
+        IList       headerList = Platform.CreateArrayList();
         int         lastC = 0;
 		bool		isEndOfStream;
 
@@ -107,8 +124,7 @@ namespace Org.BouncyCastle.Bcpg
         *
         * @param input
         */
-        public ArmoredInputStream(
-            Stream input)
+        public ArmoredInputStream(Stream input)
 			: this(input, true)
         {
         }
@@ -121,9 +137,7 @@ namespace Org.BouncyCastle.Bcpg
         * @param input
         * @param hasHeaders true if headers are to be looked for, false otherwise.
         */
-        public ArmoredInputStream(
-            Stream	input,
-            bool	hasHeaders)
+        public ArmoredInputStream(Stream input, bool hasHeaders)
         {
             this.input = input;
             this.hasHeaders = hasHeaders;
@@ -169,13 +183,13 @@ namespace Org.BouncyCastle.Bcpg
 
 			if (headerFound)
             {
-                StringBuilder    Buffer = new StringBuilder("-");
+                StringBuilder    buf = new StringBuilder("-");
                 bool             eolReached = false;
                 bool             crLf = false;
 
 				if (restart)    // we've had to look ahead two '-'
                 {
-                    Buffer.Append('-');
+                    buf.Append('-');
                 }
 
 				while ((c = input.ReadByte()) >= 0)
@@ -194,16 +208,20 @@ namespace Org.BouncyCastle.Bcpg
                     }
                     if (c == '\r' || (last != '\r' && c == '\n'))
                     {
-						string line = Buffer.ToString();
+						string line = buf.ToString();
 						if (line.Trim().Length < 1)
 							break;
+
+                        if (headerList.Count > 0 && line.IndexOf(':') < 0)
+                            throw new IOException("invalid armor header");
+
                         headerList.Add(line);
-                        Buffer.Length = 0;
+                        buf.Length = 0;
                     }
 
                     if (c != '\n' && c != '\r')
                     {
-                        Buffer.Append((char)c);
+                        buf.Append((char)c);
                         eolReached = false;
                     }
                     else
@@ -225,7 +243,7 @@ namespace Org.BouncyCastle.Bcpg
 
 			if (headerList.Count > 0)
             {
-                header = (string) headerList[0];
+                header = (string)headerList[0];
             }
 
 			clearText = "-----BEGIN PGP SIGNED MESSAGE-----".Equals(header);
@@ -267,14 +285,12 @@ namespace Org.BouncyCastle.Bcpg
         public string[] GetArmorHeaders()
         {
             if (headerList.Count <= 1)
-            {
                 return null;
-            }
 
 			string[] hdrs = new string[headerList.Count - 1];
             for (int i = 0; i != hdrs.Length; i++)
             {
-                hdrs[i] = (string) headerList[i + 1];
+                hdrs[i] = (string)headerList[i + 1];
             }
 
 			return hdrs;
@@ -287,120 +303,115 @@ namespace Org.BouncyCastle.Bcpg
             {
                 c = input.ReadByte();
             }
-            while (c == ' ' || c == '\t');
+            while (c == ' ' || c == '\t' || c == '\f' || c == '\u000B') ; // \u000B ~ \v
 
-			return c;
-        }
-
-		private int ReadIgnoreWhitespace()
-        {
-            int c;
-            do
-            {
-                c = input.ReadByte();
-            }
-            while (c == ' ' || c == '\t' || c == '\r' || c == '\n');
+            if (c >= 128)
+                throw new IOException("invalid armor");
 
             return c;
         }
 
-		private int ReadByteClearText()
+        public override int ReadByte()
         {
-            int c = input.ReadByte();
-
-            if (c == '\r' || (c == '\n' && lastC != '\r'))
+            if (start)
             {
-                newLineFound = true;
+                if (hasHeaders)
+                {
+                    ParseHeaders();
+                }
+
+                crc.Reset();
+                start = false;
             }
-            else if (newLineFound && c == '-')
+
+            int c;
+
+            if (clearText)
             {
                 c = input.ReadByte();
-                if (c == '-')            // a header, not dash escaped
+
+                if (c == '\r' || (c == '\n' && lastC != '\r'))
                 {
-                    clearText = false;
-                    start = true;
-                    restart = true;
+                    newLineFound = true;
                 }
-                else                   // a space - must be a dash escape
+                else if (newLineFound && c == '-')
                 {
                     c = input.ReadByte();
-                }
-                newLineFound = false;
-            }
-            else
-            {
-                if (c != '\n' && lastC != '\r')
-                {
+                    if (c == '-')            // a header, not dash escaped
+                    {
+                        clearText = false;
+                        start = true;
+                        restart = true;
+                    }
+                    else                   // a space - must be a dash escape
+                    {
+                        c = input.ReadByte();
+                    }
                     newLineFound = false;
                 }
-            }
-
-            lastC = c;
-
-			if (c < 0)
-			{
-				isEndOfStream = true;
-			}
-
-			return c;
-        }
-
-        private int ReadClearText(byte[] buffer, int offset, int count)
-        {
-            int pos = offset;
-            try
-            {
-                int end = offset + count;
-                while (pos < end)
+                else
                 {
-                    int c = ReadByteClearText();
-                    if (c == -1)
+                    if (c != '\n' && lastC != '\r')
                     {
-                        break;
+                        newLineFound = false;
                     }
-                    buffer[pos++] = (byte) c;
                 }
-            }
-            catch (IOException ioe)
-            {
-                if (pos == offset) throw ioe;
-            }
+            
+                lastC = c;
 
-			return pos - offset;
-        }
+                if (c < 0)
+                {
+                    isEndOfStream = true;
+                }
+            
+                return c;
+            }
 
-        private int DoReadByte()
-        {
             if (bufPtr > 2 || crcFound)
             {
-                int c = ReadIgnoreSpace();
-                if (c == '\n' || c == '\r')
+                c = ReadIgnoreSpace();
+            
+                if (c == '\r' || c == '\n')
                 {
-                    c = ReadIgnoreWhitespace();
+                    c = ReadIgnoreSpace();
+                
+                    while (c == '\n' || c == '\r')
+                    {
+                        c = ReadIgnoreSpace();
+                    }
+
+                    if (c < 0)                // EOF
+                    {
+                        isEndOfStream = true;
+                        return -1;
+                    }
+
                     if (c == '=')            // crc reached
                     {
                         bufPtr = Decode(ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf);
-
-                        if (bufPtr != 0)
+                        if (bufPtr == 0)
                         {
-                            throw new IOException("no crc found in armored message.");
-                        }
+                            int i = ((outBuf[0] & 0xff) << 16)
+                                    | ((outBuf[1] & 0xff) << 8)
+                                    | (outBuf[2] & 0xff);
 
-                        crcFound = true;
+                            crcFound = true;
 
-                        int i = ((outBuf[0] & 0xff) << 16)
-                            | ((outBuf[1] & 0xff) << 8)
-                            | (outBuf[2] & 0xff);
-
-                        if (i != crc.Value)
+                            if (i != crc.Value)
+                            {
+                                throw new IOException("crc check failed in armored message.");
+                            }
+                            return ReadByte();
+                        }
+                        else
                         {
-                            throw new IOException("crc check failed in armored message.");
+                            if (detectMissingChecksum)
+                            {
+                                throw new IOException("no crc found in armored message");
+                            }
                         }
-
-						return ReadByte();
                     }
-
-                    if (c == '-')        // end of record reached
+                    else if (c == '-')        // end of record reached
                     {
                         while ((c = input.ReadByte()) >= 0)
                         {
@@ -410,98 +421,92 @@ namespace Org.BouncyCastle.Bcpg
                             }
                         }
 
-                        if (!crcFound)
+                        if (!crcFound && detectMissingChecksum)
                         {
-                            throw new IOException("crc check not found.");
+                            throw new IOException("crc check not found");
                         }
 
                         crcFound = false;
                         start = true;
                         bufPtr = 3;
 
-						if (c < 0)
-						{
-							isEndOfStream = true;
-						}
+                        if (c < 0)
+                        {
+                            isEndOfStream = true;
+                        }
 
-						return -1;
+                        return -1;
+                    }
+                    else                   // data
+                    {
+                        bufPtr = Decode(c, ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf);
                     }
                 }
-
-                if (c < 0)
-                {
-					isEndOfStream = true;
-					return -1;
-                }
-
-                bufPtr = Decode(c, ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf);
-            }
-
-            return outBuf[bufPtr++];
-        }
-
-        public override int ReadByte()
-        {
-            if (start)
-            {
-                if (hasHeaders)
+                else
                 {
-                    ParseHeaders();
+                    if (c >= 0)
+                    {
+                        bufPtr = Decode(c, ReadIgnoreSpace(), ReadIgnoreSpace(), ReadIgnoreSpace(), outBuf);
+                    }
+                    else
+                    {
+                        isEndOfStream = true;
+                        return -1;
+                    }
                 }
-
-				crc.Reset();
-				start = false;
             }
 
-			if (clearText)
-            {
-                return ReadByteClearText();
-            }
-
-            int c = DoReadByte();
+            c = outBuf[bufPtr++];
 
             crc.Update(c);
 
             return c;
         }
 
-        public override int Read(byte[] buffer, int offset, int count)
+        /**
+         * Reads up to <code>len</code> bytes of data from the input stream into
+         * an array of bytes.  An attempt is made to read as many as
+         * <code>len</code> bytes, but a smaller number may be read.
+         * The number of bytes actually read is returned as an integer.
+         *
+         * The first byte read is stored into element <code>b[off]</code>, the
+         * next one into <code>b[off+1]</code>, and so on. The number of bytes read
+         * is, at most, equal to <code>len</code>.
+         *
+         * NOTE: We need to override the custom behavior of Java's {@link InputStream#read(byte[], int, int)},
+         * as the upstream method silently swallows {@link IOException IOExceptions}.
+         * This would cause CRC checksum errors to go unnoticed.
+         *
+         * @see <a href="https://github.com/bcgit/bc-java/issues/998">Related BC bug report</a>
+         * @param b byte array
+         * @param off offset at which we start writing data to the array
+         * @param len number of bytes we write into the array
+         * @return total number of bytes read into the buffer
+         *
+         * @throws IOException if an exception happens AT ANY POINT
+         */
+        public override int Read(byte[] b, int off, int len)
         {
-            if (start && count > 0)
-            {
-                if (hasHeaders)
-                {
-                    ParseHeaders();
-                }
-                start = false;
-            }
+            CheckIndexSize(b.Length, off, len);
 
-            if (clearText)
+            int pos = 0;
+            while (pos < len)
             {
-                return ReadClearText(buffer, offset, count);
-            }
+                int c = ReadByte();
+                if (c < 0)
+                    break;
 
-            int pos = offset;
-            try
-            {
-                int end = offset + count;
-                while (pos < end)
-                {
-                    int c = DoReadByte();
-                    crc.Update(c);
-                    if (c == -1)
-                    {
-                        break;
-                    }
-                    buffer[pos++] = (byte) c;
-                }
-            }
-            catch (IOException ioe)
-            {
-                if (pos == offset) throw ioe;
+                b[off + pos++] = (byte)c;
             }
+            return pos;
+        }
 
-            return pos - offset;
+        private void CheckIndexSize(int size, int off, int len)
+        {
+            if (off < 0 || len < 0)
+                throw new IndexOutOfRangeException("Offset and length cannot be negative.");
+            if (off > size - len)
+                throw new IndexOutOfRangeException("Invalid offset and length.");
         }
 
 #if PORTABLE
@@ -514,11 +519,23 @@ namespace Org.BouncyCastle.Bcpg
             base.Dispose(disposing);
         }
 #else
-		public override void Close()
+        public override void Close()
 		{
             Platform.Dispose(input);
 			base.Close();
 		}
 #endif
+
+        /**
+         * Change how the stream should react if it encounters missing CRC checksum.
+         * The default value is false (ignore missing CRC checksums). If the behavior is set to true,
+         * an {@link IOException} will be thrown if a missing CRC checksum is encountered.
+         *
+         * @param detectMissing ignore missing CRC sums
+         */
+        public virtual void SetDetectMissingCrc(bool detectMissing)
+        {
+            this.detectMissingChecksum = detectMissing;
+        }
     }
 }
diff --git a/crypto/test/UnitTests.csproj b/crypto/test/UnitTests.csproj
index 398135599..64505fb15 100644
--- a/crypto/test/UnitTests.csproj
+++ b/crypto/test/UnitTests.csproj
@@ -382,6 +382,7 @@
     <Compile Include="src\openpgp\examples\RsaKeyRingGenerator.cs" />
     <Compile Include="src\openpgp\examples\SignedFileProcessor.cs" />
     <Compile Include="src\openpgp\examples\test\AllTests.cs" />
+    <Compile Include="src\openpgp\test\ArmoredInputStreamTest.cs" />
     <Compile Include="src\openpgp\test\DSA2Test.cs" />
     <Compile Include="src\openpgp\test\IgnoreMarkerPacketInCertificatesTest.cs" />
     <Compile Include="src\openpgp\test\PGPArmoredTest.cs" />
diff --git a/crypto/test/src/openpgp/test/ArmoredInputStreamTest.cs b/crypto/test/src/openpgp/test/ArmoredInputStreamTest.cs
new file mode 100644
index 000000000..375d4ef1c
--- /dev/null
+++ b/crypto/test/src/openpgp/test/ArmoredInputStreamTest.cs
@@ -0,0 +1,419 @@
+using System;
+using System.IO;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.IO;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
+{
+    [TestFixture]
+    public class ArmoredInputStreamTest
+        : SimpleTest
+    {
+        private static readonly byte[] bogusData = Hex.Decode(
+            "ed864b3c622d5d71d43e5bd77876e81bfaaba8522f64cb494dc897daa3494f7da598f5907b758b72394fbefea77b86a16865e7bf" +
+            "b8f5bb46bb0d2db4a99a6a4542b9040a0e4f74b8e202c4eb255e8a81a59be9c0d5d2c593b8b512c9bdc75a243cb0992b5a885889" +
+            "a4a3d3d70e1fcb415d4f718e8230b11895e3706314912554d7c19dafb733df7d02e9a2f42492139648618b1943af9e2941bd0e42" +
+            "73b58de9b734d15a793a6d7673b3e90dedebc2a479965680de61880dddea25c0168237a6e52846e6a5aa9fb9161ac7a3996315cf" +
+            "7d391cb86e86cce44e2a353b68cf84d3ac49eddde9a040180533af4aade92de7a03c1982020a2591141aeffd2ad07ffbc0d0a303" +
+            "710763f47317a5c468d18e69e4094945060f707778cd65b4e94c2e27b5e5a8f4d510e01c9f84b22e1c59486a9b72552682833a07" +
+            "23995febde56a59d31b60b2cdac00efbf693ac27607c8c4a502749bc7bde65ec80c661aadada4611e3093607d8c7927bf4b29ea9" +
+            "0481a4616952abb88cb2f4ad78c2e94ba9193f0d0dac17d972ed0b6a738a11a6f27a3a5857e9a0746c5acb2a33ea05491e23db39" +
+            "657549b5b1a341f2088232b5c72c1a3fcce6dd8b1ce8ade51977521e473b6b208458ce513606daf47689c9a239e161cc592f070f" +
+            "395a2b964547954516651fc122a781b336d3ddc529c37c16022e6882ba52a6ff9ffd1e362971096997053e916953ca6146eb9973" +
+            "a28663074692fa3d216b4f169d20e32655602461dce525db1d94b9c43620e05a9e2d3465bb7ed0c07493738434bcf058a73b22ab" +
+            "0fd6ade1b995b3129d791e3f3a9446d35d9fe521ba5f196f98e7b637132aaa2df424ddb4e372cb70ede1eacdf0b454de91ae279f" +
+            "ec3c16e268f262a169a2d9ecb27ac1ae177fa6f31f63991179b35e5a48d5cd0e369f50f92cf0327bde1cbd8125201b0c0e4c9242" +
+            "d416cf48a9ac9c367ab424999fdf3cf5faac259b302b68c417f1461380a57d4f6a5bad8e60ac2140af53f3b44b2e4a44383e597e" +
+            "f0d7594d2f8c74346a7759b13364bf7abe08663ed4d2ab92bd60231d71e63c125739c446c048e76b7157c644ad6e136f3078c79a" +
+            "27d505af22b25706c4cf1aec5cb9578e0d0f0471fabdbfc4504a4f971b2c68a4af75ff9a438b1e4c3507dd93c38dc8caa43e87c3" +
+            "95e27d9b23402671e1a8a6f9563ba8e9d00d2f99d77d25bdda2ac4fe363db138a6eeea4ec1a5ae104cc30b9e4f468799335157da" +
+            "a9f4c310ef0806ef1803d81db84f58b45639a1749c705594cf5dbbe6109af4711eb080af4edd0d0386c09676b705d3a0ccad5cc7" +
+            "b5f289a884ce649b5b00b46ad33ee43b0db8c0202cf1fdde4c3b61d5fec99e3024016ccdb0ff2d321f08781d08e4312de38245eb" +
+            "bc2af032d2a59e36be6467bc23456b4ac178d36cf9f45df5e833a1981ed1a1032679ea0a");
+
+        private static readonly string badHeaderData1 =
+              "-----BEGIN PGP MESSAGE-----\n"
+            + "Version: BCPG v1.32\n"
+            + "Comment: A dummy message\n"
+            + "Comment actually not really as there is no colon"
+            + " \t \t\n"
+            + "SGVsbG8gV29ybGQh\n"
+            + "=d9Xi\n"
+            + "-----END PGP MESSAGE-----\n";
+
+        private static readonly string badHeaderData2 =
+              "-----BEGIN PGP MESSAGE-----\n"
+            + "Comment actually not really as there is no colon"
+            + " \t \t\n"
+            + "SGVsbG8gV29ybGQh\n"
+            + "=d9Xi\n"
+            + "-----END PGP MESSAGE-----\n";
+
+        // See https://tests.sequoia-pgp.org/#Mangled_ASCII_Armored_Key (Blank line with '\t\r\v\f')
+        public static readonly string ARMOR_WITH_BACKSLASH_T_R_V_F = "" +
+            "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
+            "Comment: Bob's OpenPGP Transferable Secret Key\n" +
+            " \t\n" +
+            "\u000B\f\n" +
+            "lQVYBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
+            "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" +
+            "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" +
+            "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" +
+            "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" +
+            "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" +
+            "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" +
+            "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" +
+            "vLIwa3T4CyshfT0AEQEAAQAL/RZqbJW2IqQDCnJi4Ozm++gPqBPiX1RhTWSjwxfM\n" +
+            "cJKUZfzLj414rMKm6Jh1cwwGY9jekROhB9WmwaaKT8HtcIgrZNAlYzANGRCM4TLK\n" +
+            "3VskxfSwKKna8l+s+mZglqbAjUg3wmFuf9Tj2xcUZYmyRm1DEmcN2ZzpvRtHgX7z\n" +
+            "Wn1mAKUlSDJZSQks0zjuMNbupcpyJokdlkUg2+wBznBOTKzgMxVNC9b2g5/tMPUs\n" +
+            "hGGWmF1UH+7AHMTaS6dlmr2ZBIyogdnfUqdNg5sZwsxSNrbglKP4sqe7X61uEAIQ\n" +
+            "bD7rT3LonLbhkrj3I8wilUD8usIwt5IecoHhd9HziqZjRCc1BUBkboUEoyedbDV4\n" +
+            "i4qfsFZ6CEWoLuD5pW7dEp0M+WeuHXO164Rc+LnH6i1VQrpb1Okl4qO6ejIpIjBI\n" +
+            "1t3GshtUu/mwGBBxs60KBX5g77mFQ9lLCRj8lSYqOsHRKBhUp4qM869VA+fD0BRP\n" +
+            "fqPT0I9IH4Oa/A3jYJcg622GwQYA1LhnP208Waf6PkQSJ6kyr8ymY1yVh9VBE/g6\n" +
+            "fRDYA+pkqKnw9wfH2Qho3ysAA+OmVOX8Hldg+Pc0Zs0e5pCavb0En8iFLvTA0Q2E\n" +
+            "LR5rLue9uD7aFuKFU/VdcddY9Ww/vo4k5p/tVGp7F8RYCFn9rSjIWbfvvZi1q5Tx\n" +
+            "+akoZbga+4qQ4WYzB/obdX6SCmi6BndcQ1QdjCCQU6gpYx0MddVERbIp9+2SXDyL\n" +
+            "hpxjSyz+RGsZi/9UAshT4txP4+MZBgDfK3ZqtW+h2/eMRxkANqOJpxSjMyLO/FXN\n" +
+            "WxzTDYeWtHNYiAlOwlQZEPOydZFty9IVzzNFQCIUCGjQ/nNyhw7adSgUk3+BXEx/\n" +
+            "MyJPYY0BYuhLxLYcrfQ9nrhaVKxRJj25SVHj2ASsiwGJRZW4CC3uw40OYxfKEvNC\n" +
+            "mer/VxM3kg8qqGf9KUzJ1dVdAvjyx2Hz6jY2qWCyRQ6IMjWHyd43C4r3jxooYKUC\n" +
+            "YnstRQyb/gCSKahveSEjo07CiXMr88UGALwzEr3npFAsPW3osGaFLj49y1oRe11E\n" +
+            "he9gCHFm+fuzbXrWmdPjYU5/ZdqdojzDqfu4ThfnipknpVUM1o6MQqkjM896FHm8\n" +
+            "zbKVFSMhEP6DPHSCexMFrrSgN03PdwHTO6iBaIBBFqmGY01tmJ03SxvSpiBPON9P\n" +
+            "NVvy/6UZFedTq8A07OUAxO62YUSNtT5pmK2vzs3SAZJmbFbMh+NN204TRI72GlqT\n" +
+            "t5hcfkuv8hrmwPS/ZR6q312mKQ6w/1pqO9qitCFCb2IgQmFiYmFnZSA8Ym9iQG9w\n" +
+            "ZW5wZ3AuZXhhbXBsZT6JAc4EEwEKADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\n" +
+            "F4AWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAUCXaWe+gAKCRD7/MgqAV5zMG9sC/9U\n" +
+            "2T3RrqEbw533FPNfEflhEVRIZ8gDXKM8hU6cqqEzCmzZT6xYTe6sv4y+PJBGXJFX\n" +
+            "yhj0g6FDkSyboM5litOcTupURObVqMgA/Y4UKERznm4fzzH9qek85c4ljtLyNufe\n" +
+            "doL2pp3vkGtn7eD0QFRaLLmnxPKQ/TlZKdLE1G3u8Uot8QHicaR6GnAdc5UXQJE3\n" +
+            "BiV7jZuDyWmZ1cUNwJkKL6oRtp+ZNDOQCrLNLecKHcgCqrpjSQG5oouba1I1Q6Vl\n" +
+            "sP44dhA1nkmLHtxlTOzpeHj4jnk1FaXmyasurrrI5CgU/L2Oi39DGKTH/A/cywDN\n" +
+            "4ZplIQ9zR8enkbXquUZvFDe+Xz+6xRXtb5MwQyWODB3nHw85HocLwRoIN9WdQEI+\n" +
+            "L8a/56AuOwhs8llkSuiITjR7r9SgKJC2WlAHl7E8lhJ3VDW3ELC56KH308d6mwOG\n" +
+            "ZRAqIAKzM1T5FGjMBhq7ZV0eqdEntBh3EcOIfj2M8rg1MzJv+0mHZOIjByawikad\n" +
+            "BVgEXaWc8gEMANYwv1xsYyunXYK0X1vY/rP1NNPvhLyLIE7NpK90YNBj+xS1ldGD\n" +
+            "bUdZqZeef2xJe8gMQg05DoD1DF3GipZ0Ies65beh+d5hegb7N4pzh0LzrBrVNHar\n" +
+            "29b5ExdI7i4iYD5TO6Vr/qTUOiAN/byqELEzAb+L+b2DVz/RoCm4PIp1DU9ewcc2\n" +
+            "WB38Ofqut3nLYA5tqJ9XvAiEQme+qAVcM3ZFcaMt4I4dXhDZZNg+D9LiTWcxdUPB\n" +
+            "leu8iwDRjAgyAhPzpFp+nWoqWA81uIiULWD1Fj+IVoY3ZvgivoYOiEFBJ9lbb4te\n" +
+            "g9m5UT/AaVDTWuHzbspVlbiVe+qyB77C2daWzNyx6UYBPLOo4r0t0c91kbNE5lgj\n" +
+            "Z7xz6los0N1U8vq91EFSeQJoSQ62XWavYmlCLmdNT6BNfgh4icLsT7Vr1QMX9jzn\n" +
+            "JtTPxdXytSdHvpSpULsqJ016l0dtmONcK3z9mj5N5z0k1tg1AH970TGYOe2aUcSx\n" +
+            "IRDMXDOPyzEfjwARAQABAAv9F2CwsjS+Sjh1M1vegJbZjei4gF1HHpEM0K0PSXsp\n" +
+            "SfVvpR4AoSJ4He6CXSMWg0ot8XKtDuZoV9jnJaES5UL9pMAD7JwIOqZm/DYVJM5h\n" +
+            "OASCh1c356/wSbFbzRHPtUdZO9Q30WFNJM5pHbCJPjtNoRmRGkf71RxtvHBzy7np\n" +
+            "Ga+W6U/NVKHw0i0CYwMI0YlKDakYW3Pm+QL+gHZFvngGweTod0f9l2VLLAmeQR/c\n" +
+            "+EZs7lNumhuZ8mXcwhUc9JQIhOkpO+wreDysEFkAcsKbkQP3UDUsA1gFx9pbMzT0\n" +
+            "tr1oZq2a4QBtxShHzP/ph7KLpN+6qtjks3xB/yjTgaGmtrwM8tSe0wD1RwXS+/1o\n" +
+            "BHpXTnQ7TfeOGUAu4KCoOQLv6ELpKWbRBLWuiPwMdbGpvVFALO8+kvKAg9/r+/ny\n" +
+            "zM2GQHY+J3Jh5JxPiJnHfXNZjIKLbFbIPdSKNyJBuazXW8xIa//mEHMI5OcvsZBK\n" +
+            "clAIp7LXzjEjKXIwHwDcTn9pBgDpdOKTHOtJ3JUKx0rWVsDH6wq6iKV/FTVSY5jl\n" +
+            "zN+puOEsskF1Lfxn9JsJihAVO3yNsp6RvkKtyNlFazaCVKtDAmkjoh60XNxcNRqr\n" +
+            "gCnwdpbgdHP6v/hvZY54ZaJjz6L2e8unNEkYLxDt8cmAyGPgH2XgL7giHIp9jrsQ\n" +
+            "aS381gnYwNX6wE1aEikgtY91nqJjwPlibF9avSyYQoMtEqM/1UjTjB2KdD/MitK5\n" +
+            "fP0VpvuXpNYZedmyq4UOMwdkiNMGAOrfmOeT0olgLrTMT5H97Cn3Yxbk13uXHNu/\n" +
+            "ZUZZNe8s+QtuLfUlKAJtLEUutN33TlWQY522FV0m17S+b80xJib3yZVJteVurrh5\n" +
+            "HSWHAM+zghQAvCesg5CLXa2dNMkTCmZKgCBvfDLZuZbjFwnwCI6u/NhOY9egKuUf\n" +
+            "SA/je/RXaT8m5VxLYMxwqQXKApzD87fv0tLPlVIEvjEsaf992tFEFSNPcG1l/jpd\n" +
+            "5AVXw6kKuf85UkJtYR1x2MkQDrqY1QX/XMw00kt8y9kMZUre19aCArcmor+hDhRJ\n" +
+            "E3Gt4QJrD9z/bICESw4b4z2DbgD/Xz9IXsA/r9cKiM1h5QMtXvuhyfVeM01enhxM\n" +
+            "GbOH3gjqqGNKysx0UODGEwr6AV9hAd8RWXMchJLaExK9J5SRawSg671ObAU24SdY\n" +
+            "vMQ9Z4kAQ2+1ReUZzf3ogSMRZtMT+d18gT6L90/y+APZIaoArLPhebIAGq39HLmJ\n" +
+            "26x3z0WAgrpA1kNsjXEXkoiZGPLKIGoe3hqJAbYEGAEKACAWIQTRpm4aI7GCyZgP\n" +
+            "eIz7/MgqAV5zMAUCXaWc8gIbDAAKCRD7/MgqAV5zMOn/C/9ugt+HZIwX308zI+QX\n" +
+            "c5vDLReuzmJ3ieE0DMO/uNSC+K1XEioSIZP91HeZJ2kbT9nn9fuReuoff0T0Dief\n" +
+            "rbwcIQQHFFkrqSp1K3VWmUGp2JrUsXFVdjy/fkBIjTd7c5boWljv/6wAsSfiv2V0\n" +
+            "JSM8EFU6TYXxswGjFVfc6X97tJNeIrXL+mpSmPPqy2bztcCCHkWS5lNLWQw+R7Vg\n" +
+            "71Fe6yBSNVrqC2/imYG2J9zlowjx1XU63Wdgqp2Wxt0l8OmsB/W80S1fRF5G4SDH\n" +
+            "s9HXglXXqPsBRZJYfP+VStm9L5P/sKjCcX6WtZR7yS6G8zj/X767MLK/djANvpPd\n" +
+            "NVniEke6hM3CNBXYPAMhQBMWhCulcoz+0lxi8L34rMN+Dsbma96psdUrn7uLaB91\n" +
+            "6we0CTfF8qqm7BsVAgalon/UUiuMY80U3ueoj3okiSTiHIjD/YtpXSPioC8nMng7\n" +
+            "xqAY9Bwizt4FWgXuLm1a4+So4V9j1TRCXd12Uc2l2RNmgDE=\n" +
+            "=miES\n" +
+            "-----END PGP PRIVATE KEY BLOCK-----";
+
+        private static readonly string ASCII_ARMOR_CRC_MISMATCH = "" +
+            "-----BEGIN PGP MESSAGE-----\n" +
+            "Version: FlowCrypt 5.0.4 Gmail Encryption flowcrypt.com\n" +
+            "Comment: Seamlessly send, receive and search encrypted email\n" +
+            "\n" +
+            "wcFMA+ADv/5v4RgKAQ/+K2rrAqhjMe9FLCfklI9Y30Woktg0Q/xe71EVw6WO\n" +
+            "tVD/VK+xv4CHzi+HojtE0U2F+vqoPSO0q5TN9giKPMTiK25PnCzfd7Q+zXiF\n" +
+            "j+5RSHTVJxC62qLHhtKsAQtC4asub8cQIFXbZz3Ns4+7jKtSWPcRqhKTurWv\n" +
+            "XVH0YAFJDsFYo26r2V9c+Ie0uoQPx8graEGpKO9GtoQjXMKK32oApuBSSlmS\n" +
+            "Q+nxyxMx1V+gxP4qgGBCxqkBFRYB/Ve6ygNHL1KxxCVTEw9pgnxJscn89Iio\n" +
+            "dO6qZ9EgIV0PVQN0Yw033MTgAhCHunlE/qXvDxib4tdihoNsLN0q5kdOeiMW\n" +
+            "+ntm3kphjMpQ6TMCUGtdS7UmvnadZ+dh5s785M8S9oY64mQd6QuYA2iy1IQv\n" +
+            "q3zpW4/ba2gqL36qCCw/OaruXpQ4NeBr3hMaJQjWgeSuMsQnNGYUn5Nn1+9X\n" +
+            "wtlithO8eLi3M1dg19dpDky8CacWfGgHD7SNsZ2zqFqyd1qtdFcit5ynQUHS\n" +
+            "IiJKeUknGv1dQAnPPJ1FdXyyqC/VDBZG6CNdnxjonmQDRh1YlqNwSnmrR/Sy\n" +
+            "X7n+nGra+/0EHJW6ohaSdep2jAwJDelq/DI1lqiN16ZXJ2/WH6pItA9tmkLU\n" +
+            "61QUz6qwPAnd0t6iy/YkOi2/s1+dwC0DwOcZoUPF8bTBwUwDS1ov/OYtlQEB\n" +
+            "D/46rCPRZrX34ipseTkZxtw3YPhbNkNHo95Mzh9lpeaaZIqtUg2yiFUnhwLi\n" +
+            "tYwyBCkXCb92l1GXXxGSmvSLDSKfQfIpZ0rV5j50MYKIpjSeJZyH/3qP+JXv\n" +
+            "Z47GsTp0z5/oNau5XQwuhLhUtRoZd1WS9ahSJ1akiKeYJroLbTg10fjL25yp\n" +
+            "iaoV16SqKA1H/JOuj6lT5z1nuez35JjeSpUc7ksdot60ZovMfWC+OGRnkYKb\n" +
+            "7KxFd7uaxL6uOBOFyvRxYeohKd73aVkiKpcWd4orI18FhlftFNAwIdsmfzNc\n" +
+            "mzTHZaUl89iYxEKR6ae6AKws1wzLq0noarsf2eKBVbTSfmK3S3xFqduKINnc\n" +
+            "e5Yb3F5adSj1dUjm1BZ4aqzsgKyBb+J8keG9ESsnFOyxOIUXDM1nIo1IOgzC\n" +
+            "M928Jb9GVa+uhdXRrb5cLjTihTusJN0I8oJrwKkwIpCJVgPMdDLkeubrMBQ4\n" +
+            "fbpl4V76sOU2Nx+6nG2FnFBFBFohOL+0nTK5/6Ns9ateN7K9VP++QcoeqfPk\n" +
+            "IUO3+lCZW+trTSvvFId3ziUVsPTeuAS+7nxSMfWZ/K9Ci6QV/Xnx3F/qSmuS\n" +
+            "AUm4zPQ1EjZf1N/5K+vhcCTN4MMx406VlqtedkXL2KPwZ6jDS/ww8RfcmPnD\n" +
+            "s94ct0WCZZtNlnQq+5h0ybwTJNLC2QFyrhhPqztVY95n9La2Mw5WITCWzg/d\n" +
+            "IBUceW/OwHYtePyaSQkCnegDw/2mN2/GC8d0OlwULcTYG6uVenGv2UOUbCr3\n" +
+            "Pfy/Eb/VqUEZK00PdvVQV7FWYAshuTFPTqidph04CgQvBpi3SDEEo8SkEIFS\n" +
+            "/iEeRQaWjFEXKUI3FwKXPJQWvFpbrXBOAjnxXXbAFYOLxdydmq1GVl9Mm3GU\n" +
+            "Clc9g6t9vaYDBPx2gN562/CM/nT8Vq45VHe79XkrrcHDwLn7yeHJScNFsib+\n" +
+            "VvwTPoUftlhC/ai21D403TsJpm7ZmPcDjagoIcXrS/lN03z79RBmSKFtYiXW\n" +
+            "4obkKSGow61vMBh2/XLVYKJKpYKm/GnVlJxA0zQVl558x8I/nAMaxSzwx+ZY\n" +
+            "waVU/s5PLZ7Ghg3MOguiRTlflKUQyL0A7NR46OjFgUnHAZRxr4KO3GoxVPy4\n" +
+            "XLeS4+Wl68s7QlV6WF1IKCHWEUMEeRRea2/OvvlS/oLs2MNNWDemlJ4SiXHf\n" +
+            "xINU38Txo84A00NALbKppsSyy9Gwj//rO/FcerupkfeuOm9nHFwIQeeC5bWD\n" +
+            "mmRlC90r2jY8gM/v3Jjy9h8PbXWxh9MUpc7/kAcTwdGlMxiVjE29p065qTRr\n" +
+            "Oi6sJ7pWuYTfWldZqTVmaBjlv0zuXQ8Eo8o/USvoTs+oihYIMcqReqdeqr/N\n" +
+            "e+sDtYKRg/LKp/JJ5nAQzVMP67DxkgwLNxx0ijBLysaQmvRlsiYWayxZB1Xd\n" +
+            "BxA2bjZRvsmww+hgSKNlcsiubJGBqfqvgmlebZuJHHSC1L6mdMYgcihKmYAj\n" +
+            "p+HFLyqgyeRVMdjRHcrEdxNPG4fJmlk1bYiVQQ4XAd72w+AHS/seZ5HzbAK0\n" +
+            "omuHYUD5PTEqZ1K9JObSsh3XMUkJK+z3BnrOxnTOOyG2r+4FxizH6rfz/Pgg\n" +
+            "sPxqxE9ELUlgQe8plcPFge6aN9tUoSe+vMtDaEAqKw9JwofBF7jlxTqMMvQC\n" +
+            "gWbn9x3W5o4VrnpjYGtPl8sh1QREu0A+0PUJAKL4A3GSMYRouGewLSMNJlOg\n" +
+            "/0pPF6qB+Fi4GJ7ju5C07tfr9z9UqRj09kDXJuoJd95NdSiCz6ndugn6gs8B\n" +
+            "Qf/XPxZVefeMLiB6p8pG0iZ/jcJjyYJLtTg6kA+1/ffmJPfH/76ZA9dgEJLj\n" +
+            "/W2u0Lp4NY8cwqcXuGKgl72TVJ34Iawl35Y0yr47k/7Y1vEQ5Q3bT7HP5A==\n" +
+            "=FdCC\n" +
+            "-----END PGP MESSAGE-----";
+
+    public static readonly string KEY_WITH_MISSING_CRC = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
+            "Comment: Bob's OpenPGP Transferable Secret Key\n" +
+            "\n" +
+            "lQVYBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv\n" +
+            "/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz\n" +
+            "/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/\n" +
+            "5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3\n" +
+            "X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv\n" +
+            "9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0\n" +
+            "qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb\n" +
+            "SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb\n" +
+            "vLIwa3T4CyshfT0AEQEAAQAL/RZqbJW2IqQDCnJi4Ozm++gPqBPiX1RhTWSjwxfM\n" +
+            "cJKUZfzLj414rMKm6Jh1cwwGY9jekROhB9WmwaaKT8HtcIgrZNAlYzANGRCM4TLK\n" +
+            "3VskxfSwKKna8l+s+mZglqbAjUg3wmFuf9Tj2xcUZYmyRm1DEmcN2ZzpvRtHgX7z\n" +
+            "Wn1mAKUlSDJZSQks0zjuMNbupcpyJokdlkUg2+wBznBOTKzgMxVNC9b2g5/tMPUs\n" +
+            "hGGWmF1UH+7AHMTaS6dlmr2ZBIyogdnfUqdNg5sZwsxSNrbglKP4sqe7X61uEAIQ\n" +
+            "bD7rT3LonLbhkrj3I8wilUD8usIwt5IecoHhd9HziqZjRCc1BUBkboUEoyedbDV4\n" +
+            "i4qfsFZ6CEWoLuD5pW7dEp0M+WeuHXO164Rc+LnH6i1VQrpb1Okl4qO6ejIpIjBI\n" +
+            "1t3GshtUu/mwGBBxs60KBX5g77mFQ9lLCRj8lSYqOsHRKBhUp4qM869VA+fD0BRP\n" +
+            "fqPT0I9IH4Oa/A3jYJcg622GwQYA1LhnP208Waf6PkQSJ6kyr8ymY1yVh9VBE/g6\n" +
+            "fRDYA+pkqKnw9wfH2Qho3ysAA+OmVOX8Hldg+Pc0Zs0e5pCavb0En8iFLvTA0Q2E\n" +
+            "LR5rLue9uD7aFuKFU/VdcddY9Ww/vo4k5p/tVGp7F8RYCFn9rSjIWbfvvZi1q5Tx\n" +
+            "+akoZbga+4qQ4WYzB/obdX6SCmi6BndcQ1QdjCCQU6gpYx0MddVERbIp9+2SXDyL\n" +
+            "hpxjSyz+RGsZi/9UAshT4txP4+MZBgDfK3ZqtW+h2/eMRxkANqOJpxSjMyLO/FXN\n" +
+            "WxzTDYeWtHNYiAlOwlQZEPOydZFty9IVzzNFQCIUCGjQ/nNyhw7adSgUk3+BXEx/\n" +
+            "MyJPYY0BYuhLxLYcrfQ9nrhaVKxRJj25SVHj2ASsiwGJRZW4CC3uw40OYxfKEvNC\n" +
+            "mer/VxM3kg8qqGf9KUzJ1dVdAvjyx2Hz6jY2qWCyRQ6IMjWHyd43C4r3jxooYKUC\n" +
+            "YnstRQyb/gCSKahveSEjo07CiXMr88UGALwzEr3npFAsPW3osGaFLj49y1oRe11E\n" +
+            "he9gCHFm+fuzbXrWmdPjYU5/ZdqdojzDqfu4ThfnipknpVUM1o6MQqkjM896FHm8\n" +
+            "zbKVFSMhEP6DPHSCexMFrrSgN03PdwHTO6iBaIBBFqmGY01tmJ03SxvSpiBPON9P\n" +
+            "NVvy/6UZFedTq8A07OUAxO62YUSNtT5pmK2vzs3SAZJmbFbMh+NN204TRI72GlqT\n" +
+            "t5hcfkuv8hrmwPS/ZR6q312mKQ6w/1pqO9qitCFCb2IgQmFiYmFnZSA8Ym9iQG9w\n" +
+            "ZW5wZ3AuZXhhbXBsZT6JAc4EEwEKADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC\n" +
+            "F4AWIQTRpm4aI7GCyZgPeIz7/MgqAV5zMAUCXaWe+gAKCRD7/MgqAV5zMG9sC/9U\n" +
+            "2T3RrqEbw533FPNfEflhEVRIZ8gDXKM8hU6cqqEzCmzZT6xYTe6sv4y+PJBGXJFX\n" +
+            "yhj0g6FDkSyboM5litOcTupURObVqMgA/Y4UKERznm4fzzH9qek85c4ljtLyNufe\n" +
+            "doL2pp3vkGtn7eD0QFRaLLmnxPKQ/TlZKdLE1G3u8Uot8QHicaR6GnAdc5UXQJE3\n" +
+            "BiV7jZuDyWmZ1cUNwJkKL6oRtp+ZNDOQCrLNLecKHcgCqrpjSQG5oouba1I1Q6Vl\n" +
+            "sP44dhA1nkmLHtxlTOzpeHj4jnk1FaXmyasurrrI5CgU/L2Oi39DGKTH/A/cywDN\n" +
+            "4ZplIQ9zR8enkbXquUZvFDe+Xz+6xRXtb5MwQyWODB3nHw85HocLwRoIN9WdQEI+\n" +
+            "L8a/56AuOwhs8llkSuiITjR7r9SgKJC2WlAHl7E8lhJ3VDW3ELC56KH308d6mwOG\n" +
+            "ZRAqIAKzM1T5FGjMBhq7ZV0eqdEntBh3EcOIfj2M8rg1MzJv+0mHZOIjByawikad\n" +
+            "BVgEXaWc8gEMANYwv1xsYyunXYK0X1vY/rP1NNPvhLyLIE7NpK90YNBj+xS1ldGD\n" +
+            "bUdZqZeef2xJe8gMQg05DoD1DF3GipZ0Ies65beh+d5hegb7N4pzh0LzrBrVNHar\n" +
+            "29b5ExdI7i4iYD5TO6Vr/qTUOiAN/byqELEzAb+L+b2DVz/RoCm4PIp1DU9ewcc2\n" +
+            "WB38Ofqut3nLYA5tqJ9XvAiEQme+qAVcM3ZFcaMt4I4dXhDZZNg+D9LiTWcxdUPB\n" +
+            "leu8iwDRjAgyAhPzpFp+nWoqWA81uIiULWD1Fj+IVoY3ZvgivoYOiEFBJ9lbb4te\n" +
+            "g9m5UT/AaVDTWuHzbspVlbiVe+qyB77C2daWzNyx6UYBPLOo4r0t0c91kbNE5lgj\n" +
+            "Z7xz6los0N1U8vq91EFSeQJoSQ62XWavYmlCLmdNT6BNfgh4icLsT7Vr1QMX9jzn\n" +
+            "JtTPxdXytSdHvpSpULsqJ016l0dtmONcK3z9mj5N5z0k1tg1AH970TGYOe2aUcSx\n" +
+            "IRDMXDOPyzEfjwARAQABAAv9F2CwsjS+Sjh1M1vegJbZjei4gF1HHpEM0K0PSXsp\n" +
+            "SfVvpR4AoSJ4He6CXSMWg0ot8XKtDuZoV9jnJaES5UL9pMAD7JwIOqZm/DYVJM5h\n" +
+            "OASCh1c356/wSbFbzRHPtUdZO9Q30WFNJM5pHbCJPjtNoRmRGkf71RxtvHBzy7np\n" +
+            "Ga+W6U/NVKHw0i0CYwMI0YlKDakYW3Pm+QL+gHZFvngGweTod0f9l2VLLAmeQR/c\n" +
+            "+EZs7lNumhuZ8mXcwhUc9JQIhOkpO+wreDysEFkAcsKbkQP3UDUsA1gFx9pbMzT0\n" +
+            "tr1oZq2a4QBtxShHzP/ph7KLpN+6qtjks3xB/yjTgaGmtrwM8tSe0wD1RwXS+/1o\n" +
+            "BHpXTnQ7TfeOGUAu4KCoOQLv6ELpKWbRBLWuiPwMdbGpvVFALO8+kvKAg9/r+/ny\n" +
+            "zM2GQHY+J3Jh5JxPiJnHfXNZjIKLbFbIPdSKNyJBuazXW8xIa//mEHMI5OcvsZBK\n" +
+            "clAIp7LXzjEjKXIwHwDcTn9pBgDpdOKTHOtJ3JUKx0rWVsDH6wq6iKV/FTVSY5jl\n" +
+            "zN+puOEsskF1Lfxn9JsJihAVO3yNsp6RvkKtyNlFazaCVKtDAmkjoh60XNxcNRqr\n" +
+            "gCnwdpbgdHP6v/hvZY54ZaJjz6L2e8unNEkYLxDt8cmAyGPgH2XgL7giHIp9jrsQ\n" +
+            "aS381gnYwNX6wE1aEikgtY91nqJjwPlibF9avSyYQoMtEqM/1UjTjB2KdD/MitK5\n" +
+            "fP0VpvuXpNYZedmyq4UOMwdkiNMGAOrfmOeT0olgLrTMT5H97Cn3Yxbk13uXHNu/\n" +
+            "ZUZZNe8s+QtuLfUlKAJtLEUutN33TlWQY522FV0m17S+b80xJib3yZVJteVurrh5\n" +
+            "HSWHAM+zghQAvCesg5CLXa2dNMkTCmZKgCBvfDLZuZbjFwnwCI6u/NhOY9egKuUf\n" +
+            "SA/je/RXaT8m5VxLYMxwqQXKApzD87fv0tLPlVIEvjEsaf992tFEFSNPcG1l/jpd\n" +
+            "5AVXw6kKuf85UkJtYR1x2MkQDrqY1QX/XMw00kt8y9kMZUre19aCArcmor+hDhRJ\n" +
+            "E3Gt4QJrD9z/bICESw4b4z2DbgD/Xz9IXsA/r9cKiM1h5QMtXvuhyfVeM01enhxM\n" +
+            "GbOH3gjqqGNKysx0UODGEwr6AV9hAd8RWXMchJLaExK9J5SRawSg671ObAU24SdY\n" +
+            "vMQ9Z4kAQ2+1ReUZzf3ogSMRZtMT+d18gT6L90/y+APZIaoArLPhebIAGq39HLmJ\n" +
+            "26x3z0WAgrpA1kNsjXEXkoiZGPLKIGoe3hqJAbYEGAEKACAWIQTRpm4aI7GCyZgP\n" +
+            "eIz7/MgqAV5zMAUCXaWc8gIbDAAKCRD7/MgqAV5zMOn/C/9ugt+HZIwX308zI+QX\n" +
+            "c5vDLReuzmJ3ieE0DMO/uNSC+K1XEioSIZP91HeZJ2kbT9nn9fuReuoff0T0Dief\n" +
+            "rbwcIQQHFFkrqSp1K3VWmUGp2JrUsXFVdjy/fkBIjTd7c5boWljv/6wAsSfiv2V0\n" +
+            "JSM8EFU6TYXxswGjFVfc6X97tJNeIrXL+mpSmPPqy2bztcCCHkWS5lNLWQw+R7Vg\n" +
+            "71Fe6yBSNVrqC2/imYG2J9zlowjx1XU63Wdgqp2Wxt0l8OmsB/W80S1fRF5G4SDH\n" +
+            "s9HXglXXqPsBRZJYfP+VStm9L5P/sKjCcX6WtZR7yS6G8zj/X767MLK/djANvpPd\n" +
+            "NVniEke6hM3CNBXYPAMhQBMWhCulcoz+0lxi8L34rMN+Dsbma96psdUrn7uLaB91\n" +
+            "6we0CTfF8qqm7BsVAgalon/UUiuMY80U3ueoj3okiSTiHIjD/YtpXSPioC8nMng7\n" +
+            "xqAY9Bwizt4FWgXuLm1a4+So4V9j1TRCXd12Uc2l2RNmgDE=\n" +
+            "-----END PGP PRIVATE KEY BLOCK-----";
+
+        public override string Name
+        {
+            get { return "ArmoredInputStream"; }
+        }
+
+        public override void PerformTest()
+        {
+            try
+            {
+                PgpObjectFactory pgpObjectFactoryOfTestFile = new PgpObjectFactory(
+                    new ArmoredInputStream(new MemoryStream(
+                        Arrays.Concatenate(
+                            Strings.ToByteArray("-----BEGIN PGP MESSAGE-----\n" + "Version: BCPG v1.32\n\n"),
+                            bogusData),
+                        false)));
+                pgpObjectFactoryOfTestFile.NextPgpObject(); // <-- EXCEPTION HERE
+                Fail("no exception");
+            }
+            catch (IOException e)
+            {
+                IsTrue("invalid armor".Equals(e.Message));
+            }
+
+            try
+            {
+                PgpObjectFactory pgpObjectFactoryOfTestFile = new PgpObjectFactory(
+                    new ArmoredInputStream(new MemoryStream(
+                        Strings.ToByteArray(badHeaderData1),
+                        false)));
+                Fail("no exception");
+            }
+            catch (IOException e)
+            {
+                IsTrue("invalid armor header".Equals(e.Message));
+            }
+
+            try
+            {
+                PgpObjectFactory pgpObjectFactoryOfTestFile = new PgpObjectFactory(
+                    new ArmoredInputStream(new MemoryStream(
+                        Strings.ToByteArray(badHeaderData2),
+                        false)));
+                Fail("no exception");
+            }
+            catch (IOException e)
+            {
+                IsTrue("invalid armor header".Equals(e.Message));
+            }
+
+            DoBackslashTrvfTest();
+            DoCrcErrorGetsThrownTest();
+            DoIgnoreMissingCrcTest();
+        }
+
+        public void DoBackslashTrvfTest()
+        {
+            MemoryStream bIn = new MemoryStream(Strings.ToByteArray(ARMOR_WITH_BACKSLASH_T_R_V_F), false);
+            ArmoredInputStream armor = new ArmoredInputStream(bIn);
+
+            try
+            {
+                PgpSecretKeyRing secretKey = new PgpSecretKeyRing(armor);
+            }
+            catch (IOException e)
+            {
+                Fail("Cannot parse armor containing blank line with '\\t\\r\\v\\f'.", e);
+            }
+        }
+
+        private void DoCrcErrorGetsThrownTest()
+        {
+            MemoryStream bytesIn = new MemoryStream(Strings.ToByteArray(ASCII_ARMOR_CRC_MISMATCH), false);
+            ArmoredInputStream armorIn = new ArmoredInputStream(bytesIn);
+            MemoryStream bytesOut = new MemoryStream();
+
+            try
+            {
+                Streams.PipeAll(armorIn, bytesOut);
+                Fail("Expected IOException to be thrown due to CRC mismatch.");
+            }
+            catch (IOException e)
+            {
+                IsEquals("crc check failed in armored message.", e.Message);
+            }
+        }
+
+        private void DoIgnoreMissingCrcTest()
+        {
+            MemoryStream data = new MemoryStream(Strings.ToByteArray(KEY_WITH_MISSING_CRC), false);
+            ArmoredInputStream armorIn = new ArmoredInputStream(data);
+
+            try
+            {
+                Streams.Drain(armorIn);
+            }
+            catch (IOException e)
+            {
+                Fail("Missing CRC sum must be ignored.", e);
+            }
+
+            data.Position = 0L;
+            armorIn = new ArmoredInputStream(data);
+            armorIn.SetDetectMissingCrc(false);
+
+            try
+            {
+                Streams.Drain(armorIn);
+            }
+            catch (IOException e)
+            {
+                Fail("Missing CRC sum must be ignored.", e);
+            }
+
+            data.Position = 0L;
+            armorIn = new ArmoredInputStream(data);
+            armorIn.SetDetectMissingCrc(true);
+
+            try
+            {
+                Streams.Drain(armorIn);
+                Fail("Missing CRC sum MUST NOT be ignored.");
+            }
+            catch (IOException e)
+            {
+                IsEquals("crc check not found", e.Message);
+                // expected
+            }
+        }
+
+        public static void Main(string[] args)
+        {
+            RunTest(new ArmoredInputStreamTest());
+        }
+
+        [Test]
+        public void TestFunction()
+        {
+            string resultText = Perform().ToString();
+
+            Assert.AreEqual(Name + ": Okay", resultText);
+        }
+    }
+}
diff --git a/crypto/test/src/openpgp/test/RegressionTest.cs b/crypto/test/src/openpgp/test/RegressionTest.cs
index a65173989..3f5bcfcbd 100644
--- a/crypto/test/src/openpgp/test/RegressionTest.cs
+++ b/crypto/test/src/openpgp/test/RegressionTest.cs
@@ -8,24 +8,25 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests
     {
         public static ITest[] tests =
         {
-            new PgpKeyRingTest(),
-            new PgpRsaTest(),
-            new PgpDsaTest(),
-            new PgpDsaElGamalTest(),
-            new PgpPbeTest(),
-            new PgpMarkerTest(),
-            new PgpPacketTest(),
+            new ArmoredInputStreamTest(),
+            new IgnoreMarkerPacketInCertificatesTest(),
             new PgpArmoredTest(),
-            new PgpSignatureTest(),
             new PgpClearSignedSignatureTest(),
             new PgpCompressionTest(),
-            new PgpNoPrivateKeyTest(),
+            new PgpDsaElGamalTest(),
+            new PgpDsaTest(),
             new PgpECDHTest(),
             new PgpECDsaTest(),
             new PgpECMessageTest(),
-            new PgpParsingTest(),
             new PgpFeaturesTest(),
-            new IgnoreMarkerPacketInCertificatesTest(),
+            new PgpKeyRingTest(),
+            new PgpMarkerTest(),
+            new PgpNoPrivateKeyTest(),
+            new PgpPacketTest(),
+            new PgpParsingTest(),
+            new PgpPbeTest(),
+            new PgpRsaTest(),
+            new PgpSignatureTest(),
         };
 
         public static void Main(string[] args)