diff options
-rw-r--r-- | crypto/crypto.csproj | 1 | ||||
-rw-r--r-- | crypto/src/util/io/pem/PemHeader.cs | 7 | ||||
-rw-r--r-- | crypto/src/util/io/pem/PemReader.cs | 355 | ||||
-rw-r--r-- | crypto/test/src/crypto/io/test/PemReaderTest.cs | 157 | ||||
-rw-r--r-- | crypto/test/src/util/io/pem/test/AllTests.cs | 12 |
5 files changed, 492 insertions, 40 deletions
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj index 9c07654d4..a6ddea38e 100644 --- a/crypto/crypto.csproj +++ b/crypto/crypto.csproj @@ -6078,6 +6078,7 @@ <Compile Include="test\src\crypto\io\test\CipherStreamTest.cs"> <SubType>Code</SubType> </Compile> + <Compile Include="test\src\crypto\io\test\PemReaderTest.cs" /> <Compile Include="test\src\crypto\prng\test\CtrDrbgTest.cs"> <SubType>Code</SubType> </Compile> diff --git a/crypto/src/util/io/pem/PemHeader.cs b/crypto/src/util/io/pem/PemHeader.cs index 72da8a4f7..c6236f534 100644 --- a/crypto/src/util/io/pem/PemHeader.cs +++ b/crypto/src/util/io/pem/PemHeader.cs @@ -51,5 +51,10 @@ namespace Org.BouncyCastle.Utilities.IO.Pem return s.GetHashCode(); } - } + + public override string ToString() + { + return name + ":" + val; + } + } } diff --git a/crypto/src/util/io/pem/PemReader.cs b/crypto/src/util/io/pem/PemReader.cs index 7e6252b9b..008a03524 100644 --- a/crypto/src/util/io/pem/PemReader.cs +++ b/crypto/src/util/io/pem/PemReader.cs @@ -1,24 +1,32 @@ using System; using System.Collections; using System.IO; -using System.Text; + using Org.BouncyCastle.Utilities.Encoders; namespace Org.BouncyCastle.Utilities.IO.Pem { - public class PemReader - { - private const string BeginString = "-----BEGIN "; - private const string EndString = "-----END "; + public class PemReader + { private readonly TextReader reader; + private readonly MemoryStream buffer; + private readonly StreamWriter textBuffer; + private readonly IList pushback = Platform.CreateArrayList(); + int c = 0; + + public PemReader(TextReader reader) { if (reader == null) throw new ArgumentNullException("reader"); + + buffer = new MemoryStream(); + textBuffer = new StreamWriter(buffer); + this.reader = reader; } @@ -27,64 +35,337 @@ namespace Org.BouncyCastle.Utilities.IO.Pem get { return reader; } } + /// <returns> /// A <see cref="PemObject"/> /// </returns> - /// <exception cref="IOException"></exception> + /// <exception cref="IOException"></exception> public PemObject ReadPemObject() - { - string line = reader.ReadLine(); + { - while (line != null && !Platform.StartsWith(line, BeginString)) + // + // Look for BEGIN + // + + for (; ; ) + { + + // Seek a leading dash, ignore anything up to that point. + if (!seekDash()) + { + // There are no pem objects here. + return null; + } + + + // consume dash [-----]BEGIN ... + if (!consumeDash()) + { + throw new IOException("no data after consuming leading dashes"); + } + + + skipWhiteSpace(); + + + if (!expect("BEGIN")) + { + continue; + } + + break; + + } + + + skipWhiteSpace(); + + // + // Consume type, accepting whitespace + // + + if (!bufferUntilStopChar('-',false) ) { - line = reader.ReadLine(); - } + throw new IOException("ran out of data before consuming type"); + } + + string type = bufferedString().Trim(); - if (line != null) + + // Consume dashes after type. + + if (!consumeDash()) + { + throw new IOException("ran out of data consuming header"); + } + + skipWhiteSpace(); + + + // + // Read ahead looking for headers. + // Look for a colon for up to 64 characters, as an indication there might be a header. + // + + IList headers = Platform.CreateArrayList(); + + while (seekColon(64)) { - line = line.Substring(BeginString.Length); - int index = line.IndexOf('-'); - if (index > 0 && Platform.EndsWith(line, "-----") && (line.Length - index) == 5) + if (!bufferUntilStopChar(':',false)) { - string type = line.Substring(0, index); + throw new IOException("ran out of data reading header key value"); + } + + string key = bufferedString().Trim(); + - return LoadObject(type); + c = Read(); + if (c != ':') + { + throw new IOException("expected colon"); } - } + + + // + // We are going to look for well formed headers, if they do not end with a "LF" we cannot + // discern where they end. + // + + if (!bufferUntilStopChar('\n', false)) // Now read to the end of the line. + { + throw new IOException("ran out of data before consuming header value"); + } + + skipWhiteSpace(); + + string value = bufferedString().Trim(); + headers.Add(new PemHeader(key,value)); + } + + + // + // Consume payload, ignoring all white space until we encounter a '-' + // + + skipWhiteSpace(); + + if (!bufferUntilStopChar('-',true)) + { + throw new IOException("ran out of data before consuming payload"); + } + + string payload = bufferedString(); + + // Seek the start of the end. + if (!seekDash()) + { + throw new IOException("did not find leading '-'"); + } + + if (!consumeDash()) + { + throw new IOException("no data after consuming trailing dashes"); + } + + if (!expect("END "+type)) + { + throw new IOException("END "+type+" was not found."); + } + + + + if (!seekDash()) + { + throw new IOException("did not find ending '-'"); + } + + + // consume trailing dashes. + consumeDash(); + + + return new PemObject(type, headers, Base64.Decode(payload)); - return null; } - private PemObject LoadObject(string type) + + + private string bufferedString() + { + textBuffer.Flush(); + string value = Strings.FromUtf8ByteArray(buffer.ToArray()); + buffer.Position = 0; + buffer.SetLength(0); + return value; + } + + + private bool seekDash() + { + c = 0; + while((c = Read()) >=0) + { + if (c == '-') + { + break; + } + } + + PushBack(c); + + return c == '-'; + } + + + /// <summary> + /// Seek ':" up to the limit. + /// </summary> + /// <param name="upTo"></param> + /// <returns></returns> + private bool seekColon(int upTo) { - string endMarker = EndString + type; - IList headers = Platform.CreateArrayList(); - StringBuilder buf = new StringBuilder(); + c = 0; + bool colonFound = false; + IList read = Platform.CreateArrayList(); + + for (; upTo>=0 && c >=0; upTo--) + { + c = Read(); + read.Add(c); + if (c == ':') + { + colonFound = true; + break; + } + } - string line; - while ((line = reader.ReadLine()) != null) + while(read.Count>0) + { + PushBack((int)read[read.Count-1]); + read.RemoveAt(read.Count-1); + } + + return colonFound; + } + + + + /// <summary> + /// Consume the dashes + /// </summary> + /// <returns></returns> + private bool consumeDash() + { + c = 0; + while ((c = Read()) >= 0) { - int colonPos = line.IndexOf(':'); - if (colonPos >= 0) + if (c != '-') { - string hdr = line.Substring(0, colonPos); - string val = line.Substring(colonPos + 1).Trim(); + break; + } + } - headers.Add(new PemHeader(hdr, val)); - continue; + PushBack(c); + + return c != -1; + } + + /// <summary> + /// Skip white space leave char in stream. + /// </summary> + private void skipWhiteSpace() + { + while ((c = Read()) >= 0) + { + if (c > ' ') + { + break; } + } + PushBack(c); + } - if (Platform.IndexOf(line, endMarker) >= 0) - break; + /// <summary> + /// Read forward consuming the expected string. + /// </summary> + /// <param name="value">expected string</param> + /// <returns>false if not consumed</returns> - buf.Append(line.Trim()); + private bool expect(String value) + { + for (int t=0; t<value.Length; t++) + { + c = Read(); + if (c == value[t]) + { + continue; + } else + { + return false; + } } - if (line == null) - throw new IOException(endMarker + " not found"); + return true; + } + + + /// <summary> + /// Consume until dash. + /// </summary> + /// <returns>true if stream end not met</returns> + private bool bufferUntilStopChar(char stopChar, bool skipWhiteSpace) + { + while ((c = Read()) >= 0) + { + if (skipWhiteSpace && c <=' ') + { + continue; + } - return new PemObject(type, headers, Base64.Decode(buf.ToString())); + if (c != stopChar) + { + textBuffer.Write((char)c); + textBuffer.Flush(); + + } else + { + PushBack(c); + break; + } + } + + return c > -1; } + + + + private void PushBack(int value) + { + if (pushback.Count == 0) + { + pushback.Add(value); + } else + { + pushback.Insert(0, value); + } + } + + + private int Read() + { + if (pushback.Count>0) + { + int i = (int)pushback[0]; + pushback.RemoveAt(0); + return i; + } + + return reader.Read(); + } + + + + } } diff --git a/crypto/test/src/crypto/io/test/PemReaderTest.cs b/crypto/test/src/crypto/io/test/PemReaderTest.cs new file mode 100644 index 000000000..27b7a1f69 --- /dev/null +++ b/crypto/test/src/crypto/io/test/PemReaderTest.cs @@ -0,0 +1,157 @@ +using NUnit.Framework; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Utilities.IO.Pem; +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.IO.Tests +{ + [TestFixture] + public class PemReaderTest + { + + [Test] + public void TestMalformedInput() + { + string raw = "-----BEGIN CERTIFICATE REQUEST----- MIIBkTCB+wIBADAUMRIwEAYDVQQDDAlUZXN0MlNBTnMwgZ8wDQYJKoZIhvcNAQEB BQADgY0AMIGJAoGBAPPPH7W8LqBMCwSu/MsmCeSCfBzMEp4k+aZmeKw8EQD1R3FK WtPy/LcaUyQhyIeNPFAH8JEz0dJRJjleFL8G5pv7c2YXjBmIfbF/W2eETBIohMDP pWOqKYiT1mqzw25rP1VuXGXaSfN22RReomUd9O2GuEkaqz5x5iTRD6aLmDoJAgMB AAGgPjA8BgkqhkiG9w0BCQ4xLzAtMCsGA1UdEQQkMCKCD3NhbjEudGVzdC5sb2Nh bIIPc2FuMi50ZXN0LmxvY2FsMA0GCSqGSIb3DQEBCwUAA4GBAOacp+9s7/jpmSTA ORvx4nsDwBsY4VLeuPUc2gYmHqfVgrCCSHKPQtQge0P5atudbo+q8Fn+/5JnJR6/ JaooICY3M+/QVrvzvV30i5W8aEIERfXsEIcFyVxv24p6SbrGAcSjwpqvgAf0z82F D3f1qdFATb9HAFsuD/J0HexTFDvB -----END CERTIFICATE REQUEST-----"; + + PemReader pemReader = new PemReader(new StringReader(raw)); + var item = pemReader.ReadPemObject(); + + var pkcs10 = Pkcs10CertificationRequest.GetInstance(Asn1Sequence.GetInstance(item.Content)); + var subject = pkcs10.GetCertificationRequestInfo().Subject.ToString(); + + Assert.AreEqual("CERTIFICATE REQUEST", item.Type); + Assert.AreEqual("CN=Test2SANs", subject); + } + + + + [Test] + public void TestSaneInput() + { + + String test = "Certificate:\n" + + " Data:\n" + + " Version: 3 (0x2)\n" + + " Serial Number: 865 (0x361)\n" + + " Signature Algorithm: ecdsa-with-SHA1\n" + + " Issuer: CN=estExampleCA\n" + + " Validity\n" + + " Not Before: Sep 29 12:41:31 2014 GMT\n" + + " Not After : Dec 16 12:41:31 2022 GMT\n" + + " Subject: CN=*.cisco.com\n" + + " Subject Public Key Info:\n" + + " Public Key Algorithm: rsaEncryption\n" + + " Public-Key: (1024 bit)\n" + + " Modulus:\n" + + " 00:b7:08:e6:18:f2:32:d7:07:44:4b:f3:b1:83:01:\n" + + " 59:f8:bc:ec:26:71:92:9a:53:70:f2:c0:be:2a:d6:\n" + + " 26:6f:45:11:86:d7:ee:37:9d:d3:2f:22:b2:8b:9b:\n" + + " c5:96:00:36:73:97:c3:4c:f2:7a:0b:2c:e0:cc:d9:\n" + + " f0:ec:ba:1b:75:8c:66:b1:86:10:fd:be:df:6b:67:\n" + + " 9c:0e:6b:2a:0e:d0:80:a8:dc:7a:d4:df:6e:79:28:\n" + + " a7:60:1a:11:b7:ae:40:94:bb:b4:11:ed:1b:6f:a7:\n" + + " 91:ae:33:ec:bf:9c:30:f3:dc:91:2c:b4:3e:8c:c9:\n" + + " bd:f1:d1:aa:f6:c2:1d:6a:cd\n" + + " Exponent: 65537 (0x10001)\n" + + " X509v3 extensions:\n" + + " X509v3 Basic Constraints: \n" + + " CA:FALSE\n" + + " X509v3 Key Usage: \n" + + " Digital Signature, Non Repudiation, Key Encipherment\n" + + " Signature Algorithm: ecdsa-with-SHA1\n" + + " 30:44:02:20:76:4f:3a:6c:b4:99:cb:1e:37:f4:0d:6e:e1:74:\n" + + " 4b:99:bb:f5:c4:b6:3d:c1:61:df:8c:d7:1f:9f:e7:d3:64:d6:\n" + + " 02:20:64:38:8f:6f:32:37:2b:7d:cf:28:93:e5:e6:e7:70:c5:\n" + + " a9:12:04:b0:4b:a5:29:7b:23:df:85:f2:18:44:8b:d2\n" + + "-----BEGIN CERTIFICATE-----\n" + + "MIIBezCCASOgAwIBAgICA2EwCQYHKoZIzj0EATAXMRUwEwYDVQQDEwxlc3RFeGFt\n" + + "cGxlQ0EwHhcNMTQwOTI5MTI0MTMxWhcNMjIxMjE2MTI0MTMxWjAWMRQwEgYDVQQD\n" + + "DAsqLmNpc2NvLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtwjmGPIy\n" + + "1wdES/OxgwFZ+LzsJnGSmlNw8sC+KtYmb0URhtfuN53TLyKyi5vFlgA2c5fDTPJ6\n" + + "CyzgzNnw7LobdYxmsYYQ/b7fa2ecDmsqDtCAqNx61N9ueSinYBoRt65AlLu0Ee0b\n" + + "b6eRrjPsv5ww89yRLLQ+jMm98dGq9sIdas0CAwEAAaMaMBgwCQYDVR0TBAIwADAL\n" + + "BgNVHQ8EBAMCBeAwCQYHKoZIzj0EAQNHADBEAiB2TzpstJnLHjf0DW7hdEuZu/XE\n" + + "tj3BYd+M1x+f59Nk1gIgZDiPbzI3K33PKJPl5udwxakSBLBLpSl7I9+F8hhEi9I=\n" + + "-----END CERTIFICATE-----\n"; + + PemReader pemReader = new PemReader(new StringReader(test)); + var item = pemReader.ReadPemObject(); + Assert.AreEqual("CERTIFICATE", item.Type); + X509CertificateStructure cert = X509CertificateStructure.GetInstance(Asn1Sequence.GetInstance(item.Content)); + Assert.AreEqual("CN=estExampleCA", cert.Issuer.ToString()); + } + + + [Test] + public void TestWithHeaders() + { + String hdr = "Proc-Type: 4,CRL\n"; + String hdr2 = "CRL: CRL Header\n"; + String hdr3 = "Originator-Certificate: originator certificate\n"; + String hdr4 = "CRL: crl header\n"; + String hdr5 = "Originator-Certificate: next originator certificate\n"; + + String test = "-----BEGIN CERTIFICATE-----\n" + hdr + hdr2 + " \t\r\0" + hdr3 + hdr4 + hdr5 + + "MIIBezCCASOgAwIBAgICA2EwCQYHKoZIzj0EATAXMRUwEwYDVQQDEwxlc3RFeGFt\n" + + "cGxlQ0EwHhcNMTQwOTI5MTI0MTMxWhcNMjIxMjE2MTI0MTMxWjAWMRQwEgYDVQQD\n" + + "DAsqLmNpc2NvLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtwjmGPIy\n" + + "1wdES/OxgwFZ+LzsJnGSmlNw8sC+KtYmb0URhtfuN53TLyKyi5vFlgA2c5fDTPJ6\n" + + "CyzgzNnw7LobdYxmsYYQ/b7fa2ecDmsqDtCAqNx61N9ueSinYBoRt65AlLu0Ee0b\n" + + "b6eRrjPsv5ww89yRLLQ+jMm98dGq9sIdas0CAwEAAaMaMBgwCQYDVR0TBAIwADAL\n" + + "BgNVHQ8EBAMCBeAwCQYHKoZIzj0EAQNHADBEAiB2TzpstJnLHjf0DW7hdEuZu/XE\n" + + "tj3BYd+M1x+f59Nk1gIgZDiPbzI3K33PKJPl5udwxakSBLBLpSl7I9+F8hhEi9I=\n" + + "-----END CERTIFICATE-----\n"; + + PemReader pemReader = new PemReader(new StringReader(test)); + var item = pemReader.ReadPemObject(); + Assert.AreEqual("CERTIFICATE", item.Type); + X509CertificateStructure cert = X509CertificateStructure.GetInstance(Asn1Sequence.GetInstance(item.Content)); + Assert.AreEqual("CN=estExampleCA", cert.Issuer.ToString()); + + + int t = 0; + foreach(string[] items in new String[][] { + new string[] { "Proc-Type", "4,CRL" }, + new string[] { "CRL", "CRL Header" }, + new string[] { "Originator-Certificate", "originator certificate" }, + new string[] { "CRL", "crl header" }, + new string[] { "Originator-Certificate", "next originator certificate" }, + + }) + { + Assert.AreEqual(items[0], ((PemHeader)item.Headers[t]).Name); + Assert.AreEqual(items[1], ((PemHeader)item.Headers[t]).Value); + t++; + } + + } + + [Test] + public void TestNoWhiteSpace() + { + + + String test = "-----BEGIN CERTIFICATE-----"+ + "MIIBezCCASOgAwIBAgICA2EwCQYHKoZIzj0EATAXMRUwEwYDVQQDEwxlc3RFeGFt" + + "cGxlQ0EwHhcNMTQwOTI5MTI0MTMxWhcNMjIxMjE2MTI0MTMxWjAWMRQwEgYDVQQD" + + "DAsqLmNpc2NvLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtwjmGPIy" + + "1wdES/OxgwFZ+LzsJnGSmlNw8sC+KtYmb0URhtfuN53TLyKyi5vFlgA2c5fDTPJ6" + + "CyzgzNnw7LobdYxmsYYQ/b7fa2ecDmsqDtCAqNx61N9ueSinYBoRt65AlLu0Ee0b" + + "b6eRrjPsv5ww89yRLLQ+jMm98dGq9sIdas0CAwEAAaMaMBgwCQYDVR0TBAIwADAL" + + "BgNVHQ8EBAMCBeAwCQYHKoZIzj0EAQNHADBEAiB2TzpstJnLHjf0DW7hdEuZu/XE" + + "tj3BYd+M1x+f59Nk1gIgZDiPbzI3K33PKJPl5udwxakSBLBLpSl7I9+F8hhEi9I=" + + "-----END CERTIFICATE-----"; + + PemReader pemReader = new PemReader(new StringReader(test)); + var item = pemReader.ReadPemObject(); + Assert.AreEqual("CERTIFICATE", item.Type); + X509CertificateStructure cert = X509CertificateStructure.GetInstance(Asn1Sequence.GetInstance(item.Content)); + Assert.AreEqual("CN=estExampleCA", cert.Issuer.ToString()); + } + + } +} diff --git a/crypto/test/src/util/io/pem/test/AllTests.cs b/crypto/test/src/util/io/pem/test/AllTests.cs index c0ca667f5..2eea7221c 100644 --- a/crypto/test/src/util/io/pem/test/AllTests.cs +++ b/crypto/test/src/util/io/pem/test/AllTests.cs @@ -66,9 +66,17 @@ namespace Org.BouncyCastle.Utilities.IO.Pem.Tests [Test] public void TestMalformed() { - PemReader rd = new PemReader(new StringReader("-----BEGIN \n")); + try + { + PemReader rd = new PemReader(new StringReader("-----BEGIN \n")); + rd.ReadPemObject(); + Assert.Fail("must fail on malformed"); + } catch (IOException ioex) + { + Assert.AreEqual("ran out of data before consuming type", ioex.Message); + } - Assert.IsNull(rd.ReadPemObject()); + } private void lengthTest(string type, IList headers, byte[] data) |