From 2ee089e648a2aefe6d84244855b5c923b1496881 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Mon, 22 Nov 2021 15:22:28 +0700 Subject: Add BitString parsers --- crypto/BouncyCastle.Android.csproj | 4 + crypto/BouncyCastle.csproj | 4 + crypto/BouncyCastle.iOS.csproj | 4 + crypto/crypto.csproj | 20 +++++ crypto/src/asn1/ASN1StreamParser.cs | 16 ++-- crypto/src/asn1/Asn1InputStream.cs | 12 ++- crypto/src/asn1/BerBitStringParser.cs | 56 ++++++++++++ crypto/src/asn1/ConstructedBitStream.cs | 134 +++++++++++++++++++++++++++++ crypto/src/asn1/DLBitString.cs | 55 ++++++++++++ crypto/src/asn1/DLBitStringParser.cs | 65 ++++++++++++++ crypto/src/asn1/DLSequence.cs | 11 +++ crypto/src/asn1/DerBitString.cs | 2 +- crypto/src/asn1/util/Asn1Dump.cs | 105 +++++++++++++--------- crypto/test/src/asn1/test/BitStringTest.cs | 31 +++---- 14 files changed, 442 insertions(+), 77 deletions(-) create mode 100644 crypto/src/asn1/BerBitStringParser.cs create mode 100644 crypto/src/asn1/ConstructedBitStream.cs create mode 100644 crypto/src/asn1/DLBitString.cs create mode 100644 crypto/src/asn1/DLBitStringParser.cs diff --git a/crypto/BouncyCastle.Android.csproj b/crypto/BouncyCastle.Android.csproj index 198ec093e..875c99cfe 100644 --- a/crypto/BouncyCastle.Android.csproj +++ b/crypto/BouncyCastle.Android.csproj @@ -95,12 +95,14 @@ + + @@ -141,6 +143,8 @@ + + diff --git a/crypto/BouncyCastle.csproj b/crypto/BouncyCastle.csproj index 849dd3202..56be079db 100644 --- a/crypto/BouncyCastle.csproj +++ b/crypto/BouncyCastle.csproj @@ -89,12 +89,14 @@ + + @@ -135,6 +137,8 @@ + + diff --git a/crypto/BouncyCastle.iOS.csproj b/crypto/BouncyCastle.iOS.csproj index 456a15b19..4490f0f07 100644 --- a/crypto/BouncyCastle.iOS.csproj +++ b/crypto/BouncyCastle.iOS.csproj @@ -90,12 +90,14 @@ + + @@ -136,6 +138,8 @@ + + diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj index a588a961a..d3666bfcf 100644 --- a/crypto/crypto.csproj +++ b/crypto/crypto.csproj @@ -293,6 +293,11 @@ SubType = "Code" BuildAction = "Compile" /> + + + + A parser for indefinite-length BIT STRINGs. + internal class BerBitStringParser + : Asn1BitStringParser + { + private readonly Asn1StreamParser m_parser; + + private ConstructedBitStream m_bitStream; + + internal BerBitStringParser(Asn1StreamParser parser) + { + m_parser = parser; + } + + public Stream GetOctetStream() + { + return m_bitStream = new ConstructedBitStream(m_parser, true); + } + + public Stream GetBitStream() + { + return m_bitStream = new ConstructedBitStream(m_parser, false); + } + + public int PadBits + { + get { return m_bitStream.PadBits; } + } + + public Asn1Object ToAsn1Object() + { + try + { + return Parse(m_parser); + } + catch (IOException e) + { + throw new Asn1ParsingException("IOException converting stream to byte array: " + e.Message, e); + } + } + + internal static BerBitString Parse(Asn1StreamParser sp) + { + ConstructedBitStream bitStream = new ConstructedBitStream(sp, false); + byte[] data = Streams.ReadAll(bitStream); + int padBits = bitStream.PadBits; + return new BerBitString(data, padBits); + } + } +} diff --git a/crypto/src/asn1/ConstructedBitStream.cs b/crypto/src/asn1/ConstructedBitStream.cs new file mode 100644 index 000000000..7c9e7c9e4 --- /dev/null +++ b/crypto/src/asn1/ConstructedBitStream.cs @@ -0,0 +1,134 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Asn1 +{ + internal class ConstructedBitStream + : BaseInputStream + { + private readonly Asn1StreamParser m_parser; + private readonly bool m_octetAligned; + + private bool m_first = true; + private int m_padBits = 0; + + private Asn1BitStringParser m_currentParser; + private Stream m_currentStream; + + internal ConstructedBitStream(Asn1StreamParser parser, bool octetAligned) + { + m_parser = parser; + m_octetAligned = octetAligned; + } + + internal int PadBits + { + get { return m_padBits; } + } + + public override int Read(byte[] buf, int off, int len) + { + if (len < 1) + return 0; + + if (m_currentStream == null) + { + if (!m_first) + return 0; + + m_currentParser = GetNextParser(); + if (m_currentParser == null) + return 0; + + m_first = false; + m_currentStream = m_currentParser.GetBitStream(); + } + + int totalRead = 0; + + for (;;) + { + int numRead = m_currentStream.Read(buf, off + totalRead, len - totalRead); + + if (numRead > 0) + { + totalRead += numRead; + + if (totalRead == len) + return totalRead; + } + else + { + m_padBits = m_currentParser.PadBits; + m_currentParser = GetNextParser(); + if (m_currentParser == null) + { + m_currentStream = null; + return totalRead; + } + + m_currentStream = m_currentParser.GetBitStream(); + } + } + } + + public override int ReadByte() + { + if (m_currentStream == null) + { + if (!m_first) + return -1; + + m_currentParser = GetNextParser(); + if (m_currentParser == null) + return -1; + + m_first = false; + m_currentStream = m_currentParser.GetBitStream(); + } + + for (;;) + { + int b = m_currentStream.ReadByte(); + + if (b >= 0) + return b; + + m_padBits = m_currentParser.PadBits; + m_currentParser = GetNextParser(); + if (m_currentParser == null) + { + m_currentStream = null; + return -1; + } + + m_currentStream = m_currentParser.GetBitStream(); + } + } + + private Asn1BitStringParser GetNextParser() + { + IAsn1Convertible asn1Obj = m_parser.ReadObject(); + if (asn1Obj == null) + { + if (m_octetAligned && m_padBits != 0) + throw new IOException("expected octet-aligned bitstring, but found padBits: " + m_padBits); + + return null; + } + + if (asn1Obj is Asn1BitStringParser) + { + if (m_padBits != 0) + throw new IOException("only the last nested bitstring can have padding"); + + return (Asn1BitStringParser)asn1Obj; + } + + throw new IOException("unknown object encountered: " + Platform.GetTypeName(asn1Obj)); + } + } +} diff --git a/crypto/src/asn1/DLBitString.cs b/crypto/src/asn1/DLBitString.cs new file mode 100644 index 000000000..5d8f3ac5e --- /dev/null +++ b/crypto/src/asn1/DLBitString.cs @@ -0,0 +1,55 @@ +using System; + +namespace Org.BouncyCastle.Asn1 +{ + /// A Definite length BIT STRING + public class DLBitString + : DerBitString + { + public DLBitString(byte data, int padBits) + : base(data, padBits) + { + } + + public DLBitString(byte[] data) + : this(data, 0) + { + } + + public DLBitString(byte[] data, int padBits) + : base(data, padBits) + { + } + + public DLBitString(int namedBits) + : base(namedBits) + { + } + + public DLBitString(Asn1Encodable obj) + : this(obj.GetDerEncoded(), 0) + { + } + + internal DLBitString(byte[] contents, bool check) + : base(contents, check) + { + } + + internal override IAsn1Encoding GetEncoding(int encoding) + { + if (Asn1OutputStream.EncodingDer == encoding) + return base.GetEncoding(encoding); + + return new PrimitiveEncoding(Asn1Tags.Universal, Asn1Tags.BitString, contents); + } + + internal override IAsn1Encoding GetEncodingImplicit(int encoding, int tagClass, int tagNo) + { + if (Asn1OutputStream.EncodingDer == encoding) + return base.GetEncodingImplicit(encoding, tagClass, tagNo); + + return new PrimitiveEncoding(tagClass, tagNo, contents); + } + } +} diff --git a/crypto/src/asn1/DLBitStringParser.cs b/crypto/src/asn1/DLBitStringParser.cs new file mode 100644 index 000000000..643361e64 --- /dev/null +++ b/crypto/src/asn1/DLBitStringParser.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Asn1 +{ + /// Parser for a DL encoded BIT STRING. + internal class DLBitStringParser + : Asn1BitStringParser + { + private readonly DefiniteLengthInputStream m_stream; + private int m_padBits = 0; + + internal DLBitStringParser(DefiniteLengthInputStream stream) + { + m_stream = stream; + } + + public Stream GetBitStream() + { + return GetBitStream(false); + } + + public Stream GetOctetStream() + { + return GetBitStream(true); + } + + public int PadBits + { + get { return m_padBits; } + } + + public Asn1Object ToAsn1Object() + { + try + { + return DerBitString.CreatePrimitive(m_stream.ToArray()); + } + catch (IOException e) + { + throw new Asn1ParsingException("IOException converting stream to byte array: " + e.Message, e); + } + } + + private Stream GetBitStream(bool octetAligned) + { + int length = m_stream.Remaining; + if (length < 1) + throw new InvalidOperationException("content octets cannot be empty"); + + m_padBits = m_stream.ReadByte(); + if (m_padBits > 0) + { + if (length < 2) + throw new InvalidOperationException("zero length data with non-zero pad bits"); + if (m_padBits > 7) + throw new InvalidOperationException("pad bits cannot be greater than 7 or less than 0"); + if (octetAligned) + throw new IOException("expected octet-aligned bitstring, but found padBits: " + m_padBits); + } + + return m_stream; + } + } +} diff --git a/crypto/src/asn1/DLSequence.cs b/crypto/src/asn1/DLSequence.cs index 8cf8d5bcb..b3c21ece5 100644 --- a/crypto/src/asn1/DLSequence.cs +++ b/crypto/src/asn1/DLSequence.cs @@ -64,6 +64,17 @@ namespace Org.BouncyCastle.Asn1 Asn1OutputStream.GetContentsEncodings(encoding, elements)); } + internal override DerBitString ToAsn1BitString() + { + return new DLBitString(BerBitString.FlattenBitStrings(GetConstructedBitStrings()), false); + } + + // TODO[asn1] DLExternal + //internal override DerExternal ToAsn1External() + //{ + // return new DLExternal(this); + //} + internal override Asn1Set ToAsn1Set() { return new DLSet(false, elements); diff --git a/crypto/src/asn1/DerBitString.cs b/crypto/src/asn1/DerBitString.cs index d3836b740..2b1ff3354 100644 --- a/crypto/src/asn1/DerBitString.cs +++ b/crypto/src/asn1/DerBitString.cs @@ -359,7 +359,7 @@ namespace Org.BouncyCastle.Asn1 byte finalOctet = contents[length - 1]; if (finalOctet != (byte)(finalOctet & (0xFF << padBits))) - return new BerBitString(contents, false); + return new DLBitString(contents, false); } return new DerBitString(contents, false); diff --git a/crypto/src/asn1/util/Asn1Dump.cs b/crypto/src/asn1/util/Asn1Dump.cs index f573d3663..0ba5c394f 100644 --- a/crypto/src/asn1/util/Asn1Dump.cs +++ b/crypto/src/asn1/util/Asn1Dump.cs @@ -132,64 +132,88 @@ namespace Org.BouncyCastle.Asn1.Utilities { buf.Append(indent + "Integer(" + ((DerInteger)obj).Value + ")" + NewLine); } - else if (obj is BerOctetString) + else if (obj is Asn1OctetString) { - byte[] octets = ((Asn1OctetString)obj).GetOctets(); - string extra = verbose ? DumpBinaryDataAsString(indent, octets) : ""; - buf.Append(indent + "BER Octet String" + "[" + octets.Length + "] " + extra + NewLine); - } - else if (obj is DerOctetString) - { - byte[] octets = ((Asn1OctetString)obj).GetOctets(); - string extra = verbose ? DumpBinaryDataAsString(indent, octets) : ""; - buf.Append(indent + "DER Octet String" + "[" + octets.Length + "] " + extra + NewLine); + Asn1OctetString oct = (Asn1OctetString)obj; + byte[] octets = oct.GetOctets(); + + if (obj is BerOctetString) + { + buf.Append(indent + "BER Octet String[" + octets.Length + "]" + NewLine); + } + else + { + buf.Append(indent + "DER Octet String[" + octets.Length + "]" + NewLine); + } + + if (verbose) + { + buf.Append(DumpBinaryDataAsString(indent, octets)); + } } else if (obj is DerBitString) { - DerBitString bt = (DerBitString)obj; - byte[] bytes = bt.GetBytes(); - string extra = verbose ? DumpBinaryDataAsString(indent, bytes) : ""; - buf.Append(indent + "DER Bit String" + "[" + bytes.Length + ", " + bt.PadBits + "] " + extra + NewLine); + DerBitString bitString = (DerBitString)obj; + byte[] bytes = bitString.GetBytes(); + int padBits = bitString.PadBits; + + if (bitString is BerBitString) + { + buf.Append(indent + "BER Bit String[" + bytes.Length + ", " + padBits + "]" + NewLine); + } + else if (bitString is DLBitString) + { + buf.Append(indent + "DL Bit String[" + bytes.Length + ", " + padBits + "]" + NewLine); + } + else + { + buf.Append(indent + "DER Bit String[" + bytes.Length + ", " + padBits + "]" + NewLine); + } + + if (verbose) + { + buf.Append(DumpBinaryDataAsString(indent, bytes)); + } } else if (obj is DerIA5String) { - buf.Append(indent + "IA5String(" + ((DerIA5String)obj).GetString() + ") " + NewLine); + buf.Append(indent + "IA5String(" + ((DerIA5String)obj).GetString() + ")" + NewLine); } else if (obj is DerUtf8String) { - buf.Append(indent + "UTF8String(" + ((DerUtf8String)obj).GetString() + ") " + NewLine); + buf.Append(indent + "UTF8String(" + ((DerUtf8String)obj).GetString() + ")" + NewLine); } else if (obj is DerPrintableString) { - buf.Append(indent + "PrintableString(" + ((DerPrintableString)obj).GetString() + ") " + NewLine); + buf.Append(indent + "PrintableString(" + ((DerPrintableString)obj).GetString() + ")" + NewLine); } else if (obj is DerVisibleString) { - buf.Append(indent + "VisibleString(" + ((DerVisibleString)obj).GetString() + ") " + NewLine); + buf.Append(indent + "VisibleString(" + ((DerVisibleString)obj).GetString() + ")" + NewLine); } else if (obj is DerBmpString) { - buf.Append(indent + "BMPString(" + ((DerBmpString)obj).GetString() + ") " + NewLine); + buf.Append(indent + "BMPString(" + ((DerBmpString)obj).GetString() + ")" + NewLine); } else if (obj is DerT61String) { - buf.Append(indent + "T61String(" + ((DerT61String)obj).GetString() + ") " + NewLine); + buf.Append(indent + "T61String(" + ((DerT61String)obj).GetString() + ")" + NewLine); } else if (obj is DerGraphicString) { - buf.Append(indent + "GraphicString(" + ((DerGraphicString)obj).GetString() + ") " + NewLine); + buf.Append(indent + "GraphicString(" + ((DerGraphicString)obj).GetString() + ")" + NewLine); } else if (obj is DerVideotexString) { - buf.Append(indent + "VideotexString(" + ((DerVideotexString)obj).GetString() + ") " + NewLine); + buf.Append(indent + "VideotexString(" + ((DerVideotexString)obj).GetString() + ")" + NewLine); } else if (obj is DerUtcTime) { - buf.Append(indent + "UTCTime(" + ((DerUtcTime)obj).TimeString + ") " + NewLine); + buf.Append(indent + "UTCTime(" + ((DerUtcTime)obj).TimeString + ")" + NewLine); } else if (obj is DerGeneralizedTime) { - buf.Append(indent + "GeneralizedTime(" + ((DerGeneralizedTime)obj).GetTime() + ") " + NewLine); + buf.Append(indent + "GeneralizedTime(" + ((DerGeneralizedTime)obj).GetTime() + ")" + NewLine); } else if (obj is DerEnumerated) { @@ -250,32 +274,27 @@ namespace Org.BouncyCastle.Asn1.Utilities private static string DumpBinaryDataAsString(string indent, byte[] bytes) { + if (bytes.Length < 1) + return ""; + indent += Tab; - StringBuilder buf = new StringBuilder(NewLine); + StringBuilder buf = new StringBuilder(); for (int i = 0; i < bytes.Length; i += SampleSize) { - if (bytes.Length - i > SampleSize) - { - buf.Append(indent); - buf.Append(Hex.ToHexString(bytes, i, SampleSize)); - buf.Append(Tab); - buf.Append(CalculateAscString(bytes, i, SampleSize)); - buf.Append(NewLine); - } - else + int remaining = bytes.Length - i; + int chunk = System.Math.Min(remaining, SampleSize); + + buf.Append(indent); + buf.Append(Hex.ToHexString(bytes, i, chunk)); + for (int j = chunk; j < SampleSize; ++j) { - buf.Append(indent); - buf.Append(Hex.ToHexString(bytes, i, bytes.Length - i)); - for (int j = bytes.Length - i; j != SampleSize; j++) - { - buf.Append(" "); - } - buf.Append(Tab); - buf.Append(CalculateAscString(bytes, i, bytes.Length - i)); - buf.Append(NewLine); + buf.Append(" "); } + buf.Append(Tab); + buf.Append(CalculateAscString(bytes, i, chunk)); + buf.Append(NewLine); } return buf.ToString(); diff --git a/crypto/test/src/asn1/test/BitStringTest.cs b/crypto/test/src/asn1/test/BitStringTest.cs index 35b7811bc..af893fe9f 100644 --- a/crypto/test/src/asn1/test/BitStringTest.cs +++ b/crypto/test/src/asn1/test/BitStringTest.cs @@ -83,29 +83,26 @@ namespace Org.BouncyCastle.Asn1.Tests { if (Arrays.AreEqual(derData, Asn1Object.FromByteArray(dlData).GetEncoded())) { - //Fail("failed DL check"); - Fail("failed BER check"); + Fail("failed DL check"); } - IAsn1String dl = BerBitString.GetInstance(dlData); + DerBitString dl = DerBitString.GetInstance(dlData); - //IsTrue("DL test failed", dl is DLBitString); - IsTrue("BER test failed", dl is BerBitString); + IsTrue("DL test failed", dl is DLBitString); if (!Arrays.AreEqual(derData, Asn1Object.FromByteArray(dlData).GetDerEncoded())) { Fail("failed DER check"); } - // TODO This test isn't applicable until we get the DL variants - //try - //{ - // DerBitString.GetInstance(dlData); - // Fail("no exception"); - //} - //catch (ArgumentException e) - //{ - // // ignore - //} - IAsn1String der = DerBitString.GetInstance(derData); - IsTrue("DER test failed", der is DerBitString); + try + { + // GetInstance should work for "an object that can be converted into [a DerBitString]". + DerBitString.GetInstance(dlData); + } + catch (ArgumentException) + { + Fail("failed DL encoding conversion"); + } + DerBitString der = DerBitString.GetInstance(derData); + IsTrue("DER test failed", typeof(DerBitString) == der.GetType()); } public override void PerformTest() -- cgit 1.4.1