Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/corefx.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Common/src/System/Security/Cryptography/Asn1V2.cs738
-rw-r--r--src/Common/src/System/Security/Cryptography/AsnReader.cs2910
-rw-r--r--src/Common/src/System/Security/Cryptography/AsnWriter.cs1602
-rw-r--r--src/Common/tests/System/Security/Cryptography/ByteUtils.cs20
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/Asn1ReaderTests.cs26
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ComprehensiveReadTests.cs265
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ParseTag.cs160
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/PeekTests.cs213
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadBMPString.cs755
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadBitString.cs671
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadBoolean.cs220
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadEnumerated.cs760
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadGeneralizedTime.cs522
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadIA5String.cs721
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadInteger.cs498
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadLength.cs162
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadNamedBitList.cs315
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadNull.cs130
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadObjectIdentifier.cs286
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadOctetString.cs559
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadSequence.cs338
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadSetOf.cs304
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadUTF8String.cs701
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadUtcTime.cs238
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReaderStateTests.cs36
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/Asn1WriterTests.cs54
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/ComprehensiveWriteTest.cs272
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/PushPopSequence.cs514
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/PushPopSetOf.cs497
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteBMPString.cs294
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteBitString.cs305
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteBoolean.cs81
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteCharacterString.cs437
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteEnumerated.cs402
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteGeneralizedTime.cs244
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteIA5String.cs288
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteInteger.cs306
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteNamedBitList.cs227
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteNull.cs50
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteObjectIdentifier.cs289
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteOctetString.cs156
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteUtcTime.cs122
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteUtf8String.cs287
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Resources/Strings.resx24
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj48
45 files changed, 18045 insertions, 2 deletions
diff --git a/src/Common/src/System/Security/Cryptography/Asn1V2.cs b/src/Common/src/System/Security/Cryptography/Asn1V2.cs
new file mode 100644
index 0000000000..f897ef11ad
--- /dev/null
+++ b/src/Common/src/System/Security/Cryptography/Asn1V2.cs
@@ -0,0 +1,738 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+
+namespace System.Security.Cryptography.Asn1
+{
+ // ITU-T-REC.X.680-201508 sec 4.
+ internal enum AsnEncodingRules
+ {
+ BER,
+ CER,
+ DER,
+ }
+
+ // Uses a masked overlay of the tag class encoding.
+ // T-REC-X.690-201508 sec 8.1.2.2
+ internal enum TagClass : byte
+ {
+ Universal = 0,
+ Application = 0b0100_0000,
+ ContextSpecific = 0b1000_0000,
+ Private = 0b1100_0000,
+ }
+
+ // ITU-T-REC.X.680-201508 sec 8.6
+ internal enum UniversalTagNumber
+ {
+ EndOfContents = 0,
+ Boolean = 1,
+ Integer = 2,
+ BitString = 3,
+ OctetString = 4,
+ Null = 5,
+ ObjectIdentifier = 6,
+ ObjectDescriptor = 7,
+ External = 8,
+ InstanceOf = External,
+ Real = 9,
+ Enumerated = 10,
+ Embedded = 11,
+ UTF8String = 12,
+ RelativeObjectIdentifier = 13,
+ Time = 14,
+ // 15 is reserved
+ Sequence = 16,
+ SequenceOf = Sequence,
+ Set = 17,
+ SetOf = Set,
+ NumericString = 18,
+ PrintableString = 19,
+ TeletexString = 20,
+ T61String = TeletexString,
+ VideotexString = 21,
+ IA5String = 22,
+ UtcTime = 23,
+ GeneralizedTime = 24,
+ GraphicString = 25,
+ VisibleString = 26,
+ ISO646String = VisibleString,
+ GeneralString = 27,
+ UniversalString = 28,
+ UnrestrictedCharacterString = 29,
+ BMPString = 30,
+ Date = 31,
+ TimeOfDay = 32,
+ DateTime = 33,
+ Duration = 34,
+ ObjectIdentifierIRI = 35,
+ RelativeObjectIdentifierIRI = 36,
+ }
+
+ // Represents a BER-family encoded tag.
+ // T-REC-X.690-201508 sec 8.1.2
+ internal struct Asn1Tag : IEquatable<Asn1Tag>
+ {
+ private const byte ClassMask = 0b1100_0000;
+ private const byte ConstructedMask = 0b0010_0000;
+ private const byte ControlMask = ClassMask | ConstructedMask;
+ private const byte TagNumberMask = 0b0001_1111;
+
+ internal static readonly Asn1Tag EndOfContents = new Asn1Tag(0, (int)UniversalTagNumber.EndOfContents);
+ internal static readonly Asn1Tag Boolean = new Asn1Tag(0, (int)UniversalTagNumber.Boolean);
+ internal static readonly Asn1Tag Integer = new Asn1Tag(0, (int)UniversalTagNumber.Integer);
+ internal static readonly Asn1Tag PrimitiveBitString = new Asn1Tag(0, (int)UniversalTagNumber.BitString);
+ internal static readonly Asn1Tag ConstructedBitString = new Asn1Tag(ConstructedMask, (int)UniversalTagNumber.BitString);
+ internal static readonly Asn1Tag PrimitiveOctetString = new Asn1Tag(0, (int)UniversalTagNumber.OctetString);
+ internal static readonly Asn1Tag ConstructedOctetString = new Asn1Tag(ConstructedMask, (int)UniversalTagNumber.OctetString);
+ internal static readonly Asn1Tag Null = new Asn1Tag(0, (int)UniversalTagNumber.Null);
+ internal static readonly Asn1Tag ObjectIdentifier = new Asn1Tag(0, (int)UniversalTagNumber.ObjectIdentifier);
+ internal static readonly Asn1Tag Enumerated = new Asn1Tag(0, (int)UniversalTagNumber.Enumerated);
+ internal static readonly Asn1Tag Sequence = new Asn1Tag(ConstructedMask, (int)UniversalTagNumber.Sequence);
+ internal static readonly Asn1Tag SetOf = new Asn1Tag(ConstructedMask, (int)UniversalTagNumber.SetOf);
+ internal static readonly Asn1Tag UtcTime = new Asn1Tag(0, (int)UniversalTagNumber.UtcTime);
+ internal static readonly Asn1Tag GeneralizedTime = new Asn1Tag(0, (int)UniversalTagNumber.GeneralizedTime);
+
+ private readonly byte _controlFlags;
+ private readonly int _tagValue;
+
+ public TagClass TagClass => (TagClass)(_controlFlags & ClassMask);
+ public bool IsConstructed => (_controlFlags & ConstructedMask) != 0;
+ public int TagValue => _tagValue;
+
+ private Asn1Tag(byte controlFlags, int tagValue)
+ {
+ _controlFlags = (byte)(controlFlags & ControlMask);
+ _tagValue = tagValue;
+ }
+
+ public Asn1Tag(UniversalTagNumber universalTagNumber, bool isConstructed = false)
+ : this(isConstructed ? ConstructedMask : (byte)0, (int)universalTagNumber)
+ {
+ // T-REC-X.680-201508 sec 8.6 (Table 1)
+ const UniversalTagNumber ReservedIndex = (UniversalTagNumber)15;
+
+ if (universalTagNumber < UniversalTagNumber.EndOfContents ||
+ universalTagNumber > UniversalTagNumber.RelativeObjectIdentifierIRI ||
+ universalTagNumber == ReservedIndex)
+ {
+ throw new ArgumentOutOfRangeException(nameof(universalTagNumber), universalTagNumber, null);
+ }
+ }
+
+ public Asn1Tag(TagClass tagClass, int tagValue, bool isConstructed = false)
+ : this((byte)((byte)tagClass | (isConstructed ? ConstructedMask : 0)), tagValue)
+ {
+ if (tagClass < TagClass.Universal || tagClass > TagClass.Private)
+ {
+ throw new ArgumentOutOfRangeException(nameof(tagClass), tagClass, null);
+ }
+ }
+
+ public Asn1Tag AsConstructed()
+ {
+ return new Asn1Tag((byte)(_controlFlags | ConstructedMask), _tagValue);
+ }
+
+ public Asn1Tag AsPrimitive()
+ {
+ return new Asn1Tag((byte)(_controlFlags & ~ConstructedMask), _tagValue);
+ }
+
+ public static bool TryParse(ReadOnlySpan<byte> source, out Asn1Tag tag, out int bytesRead)
+ {
+ tag = default(Asn1Tag);
+ bytesRead = 0;
+
+ if (source.IsEmpty)
+ {
+ return false;
+ }
+
+ byte first = source[bytesRead];
+ bytesRead++;
+ uint tagValue = (uint)(first & TagNumberMask);
+
+ if (tagValue == TagNumberMask)
+ {
+ // Multi-byte encoding
+ // T-REC-X.690-201508 sec 8.1.2.4
+ const byte ContinuationFlag = 0x80;
+ const byte ValueMask = ContinuationFlag - 1;
+
+ tagValue = 0;
+ byte current;
+
+ do
+ {
+ if (source.Length <= bytesRead)
+ {
+ bytesRead = 0;
+ return false;
+ }
+
+ current = source[bytesRead];
+ byte currentValue = (byte)(current & ValueMask);
+ bytesRead++;
+
+ // If TooBigToShift is shifted left 7, the content bit shifts out.
+ // So any value greater than or equal to this cannot be shifted without loss.
+ const int TooBigToShift = 0b00000010_00000000_00000000_00000000;
+
+ if (tagValue >= TooBigToShift)
+ {
+ bytesRead = 0;
+ return false;
+ }
+
+ tagValue <<= 7;
+ tagValue |= currentValue;
+
+ // The first byte cannot have the value 0 (T-REC-X.690-201508 sec 8.1.2.4.2.c)
+ if (tagValue == 0)
+ {
+ bytesRead = 0;
+ return false;
+ }
+ }
+ while ((current & ContinuationFlag) == ContinuationFlag);
+
+ // This encoding is only valid for tag values greater than 30.
+ // (T-REC-X.690-201508 sec 8.1.2.3, 8.1.2.4)
+ if (tagValue <= 30)
+ {
+ bytesRead = 0;
+ return false;
+ }
+
+ // There's not really any ambiguity, but prevent negative numbers from showing up.
+ if (tagValue > int.MaxValue)
+ {
+ bytesRead = 0;
+ return false;
+ }
+ }
+
+ Debug.Assert(bytesRead > 0);
+ tag = new Asn1Tag(first, (int)tagValue);
+ return true;
+ }
+
+ public int CalculateEncodedSize()
+ {
+ const int SevenBits = 0b0111_1111;
+ const int FourteenBits = 0b0011_1111_1111_1111;
+ const int TwentyOneBits = 0b0001_1111_1111_1111_1111_1111;
+ const int TwentyEightBits = 0b0000_1111_1111_1111_1111_1111_1111_1111;
+
+ if (TagValue < TagNumberMask)
+ return 1;
+ if (TagValue <= SevenBits)
+ return 2;
+ if (TagValue <= FourteenBits)
+ return 3;
+ if (TagValue <= TwentyOneBits)
+ return 4;
+ if (TagValue <= TwentyEightBits)
+ return 5;
+
+ return 6;
+ }
+
+ public bool TryWrite(Span<byte> destination, out int bytesWritten)
+ {
+ int spaceRequired = CalculateEncodedSize();
+
+ if (destination.Length < spaceRequired)
+ {
+ bytesWritten = 0;
+ return false;
+ }
+
+ if (spaceRequired == 1)
+ {
+ byte value = (byte)(_controlFlags | TagValue);
+ destination[0] = value;
+ bytesWritten = 1;
+ return true;
+ }
+
+ byte firstByte = (byte)(_controlFlags | TagNumberMask);
+ destination[0] = firstByte;
+
+ int remaining = TagValue;
+ int idx = spaceRequired - 1;
+
+ while (remaining > 0)
+ {
+ int segment = remaining & 0x7F;
+
+ // The last byte doesn't get the marker, which we write first.
+ if (remaining != TagValue)
+ {
+ segment |= 0x80;
+ }
+
+ Debug.Assert(segment <= byte.MaxValue);
+ destination[idx] = (byte)segment;
+ remaining >>= 7;
+ idx--;
+ }
+
+ Debug.Assert(idx == 0);
+ bytesWritten = spaceRequired;
+ return true;
+ }
+
+ public bool Equals(Asn1Tag other)
+ {
+ return _controlFlags == other._controlFlags && _tagValue == other._tagValue;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ return obj is Asn1Tag && Equals((Asn1Tag)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ // Most TagValue values will be in the 0-30 range,
+ // the GetHashCode value only has collisions when TagValue is
+ // between 2^29 and uint.MaxValue
+ return (_controlFlags << 24) ^ _tagValue;
+ }
+
+ public static bool operator ==(Asn1Tag left, Asn1Tag right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Asn1Tag left, Asn1Tag right)
+ {
+ return !left.Equals(right);
+ }
+
+ public override string ToString()
+ {
+ const string ConstructedPrefix = "Constructed ";
+ string classAndValue;
+
+ if (TagClass == TagClass.Universal)
+ {
+ classAndValue = ((UniversalTagNumber)TagValue).ToString();
+ }
+ else
+ {
+ classAndValue = TagClass + "-" + TagValue;
+ }
+
+ if (IsConstructed)
+ {
+ return ConstructedPrefix + classAndValue;
+ }
+
+ return classAndValue;
+ }
+ }
+
+ internal static class AsnCharacterStringEncodings
+ {
+ private static readonly Text.Encoding s_utf8Encoding = new UTF8Encoding(false, throwOnInvalidBytes: true);
+ private static readonly Text.Encoding s_bmpEncoding = new BMPEncoding();
+ private static readonly Text.Encoding s_ia5Encoding = new IA5Encoding();
+ private static readonly Text.Encoding s_visibleStringEncoding = new VisibleStringEncoding();
+ private static readonly Text.Encoding s_printableStringEncoding = new PrintableStringEncoding();
+
+ internal static Text.Encoding GetEncoding(UniversalTagNumber encodingType)
+ {
+ switch (encodingType)
+ {
+ case UniversalTagNumber.UTF8String:
+ return s_utf8Encoding;
+ case UniversalTagNumber.PrintableString:
+ return s_printableStringEncoding;
+ case UniversalTagNumber.IA5String:
+ return s_ia5Encoding;
+ case UniversalTagNumber.VisibleString:
+ return s_visibleStringEncoding;
+ case UniversalTagNumber.BMPString:
+ return s_bmpEncoding;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(encodingType), encodingType, null);
+ }
+ }
+ }
+
+ internal abstract class SpanBasedEncoding : Text.Encoding
+ {
+ protected SpanBasedEncoding()
+ : base(0, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback)
+ {
+ }
+
+ protected abstract int GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, bool write);
+ protected abstract int GetChars(ReadOnlySpan<byte> bytes, Span<char> chars, bool write);
+
+ public override int GetByteCount(char[] chars, int index, int count)
+ {
+ return GetByteCount(new ReadOnlySpan<char>(chars, index, count));
+ }
+
+ public override unsafe int GetByteCount(char* chars, int count)
+ {
+ return GetByteCount(new ReadOnlySpan<char>(chars, count));
+ }
+
+ public override int GetByteCount(string s)
+ {
+ return GetByteCount(s.AsReadOnlySpan());
+ }
+
+ private int GetByteCount(ReadOnlySpan<char> chars)
+ {
+ return GetBytes(chars, Span<byte>.Empty, write: false);
+ }
+
+ public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
+ {
+ return GetBytes(
+ new ReadOnlySpan<char>(chars, charIndex, charCount),
+ new Span<byte>(bytes, byteIndex, bytes.Length - byteIndex),
+ write: true);
+ }
+
+ public override unsafe int GetBytes(char* chars, int charCount, byte* bytes, int byteCount)
+ {
+ return GetBytes(
+ new ReadOnlySpan<char>(chars, charCount),
+ new Span<byte>(bytes, byteCount),
+ write: true);
+ }
+
+ public override int GetCharCount(byte[] bytes, int index, int count)
+ {
+ return GetCharCount(new ReadOnlySpan<byte>(bytes, index, count));
+ }
+
+ public override unsafe int GetCharCount(byte* bytes, int count)
+ {
+ return GetCharCount(new ReadOnlySpan<byte>(bytes, count));
+ }
+
+ private int GetCharCount(ReadOnlySpan<byte> bytes)
+ {
+ return GetChars(bytes, Span<char>.Empty, write: false);
+ }
+
+ public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
+ {
+ return GetChars(
+ new ReadOnlySpan<byte>(bytes, byteIndex, byteCount),
+ new Span<char>(chars, charIndex, chars.Length - charIndex),
+ write: true);
+ }
+
+ public override unsafe int GetChars(byte* bytes, int byteCount, char* chars, int charCount)
+ {
+ return GetChars(
+ new ReadOnlySpan<byte>(bytes, byteCount),
+ new Span<char>(chars, charCount),
+ write: true);
+ }
+ }
+
+ internal class IA5Encoding : RestrictedAsciiStringEncoding
+ {
+ // T-REC-X.680-201508 sec 41, Table 8.
+ // ISO International Register of Coded Character Sets to be used with Escape Sequences 001
+ // is ASCII 0x00 - 0x1F
+ // ISO International Register of Coded Character Sets to be used with Escape Sequences 006
+ // is ASCII 0x21 - 0x7E
+ // Space is ASCII 0x20, delete is ASCII 0x7F.
+ //
+ // The net result is all of 7-bit ASCII
+ internal IA5Encoding()
+ : base(0x00, 0x7F)
+ {
+ }
+ }
+
+ internal class VisibleStringEncoding : RestrictedAsciiStringEncoding
+ {
+ // T-REC-X.680-201508 sec 41, Table 8.
+ // ISO International Register of Coded Character Sets to be used with Escape Sequences 006
+ // is ASCII 0x21 - 0x7E
+ // Space is ASCII 0x20.
+ internal VisibleStringEncoding()
+ : base(0x20, 0x7E)
+ {
+ }
+ }
+
+ internal class PrintableStringEncoding : RestrictedAsciiStringEncoding
+ {
+ // T-REC-X.680-201508 sec 41.4
+ internal PrintableStringEncoding()
+ : base("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 '()+,-./:=?")
+ {
+ }
+ }
+
+ internal abstract class RestrictedAsciiStringEncoding : SpanBasedEncoding
+ {
+ private readonly bool[] _isAllowed;
+
+ protected RestrictedAsciiStringEncoding(byte minCharAllowed, byte maxCharAllowed)
+ {
+ Debug.Assert(minCharAllowed <= maxCharAllowed);
+ Debug.Assert(maxCharAllowed <= 0x7F);
+
+ bool[] isAllowed = new bool[0x80];
+
+ for (byte charCode = minCharAllowed; charCode <= maxCharAllowed; charCode++)
+ {
+ isAllowed[charCode] = true;
+ }
+
+ _isAllowed = isAllowed;
+ }
+
+ protected RestrictedAsciiStringEncoding(IEnumerable<char> allowedChars)
+ {
+ bool[] isAllowed = new bool[0x7F];
+
+ foreach (char c in allowedChars)
+ {
+ if (c >= isAllowed.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(allowedChars));
+ }
+
+ Debug.Assert(isAllowed[c] == false);
+ isAllowed[c] = true;
+ }
+
+ _isAllowed = isAllowed;
+ }
+
+ public override int GetMaxByteCount(int charCount)
+ {
+ return charCount;
+ }
+
+ public override int GetMaxCharCount(int byteCount)
+ {
+ return byteCount;
+ }
+
+ protected override int GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, bool write)
+ {
+ if (chars.IsEmpty)
+ {
+ return 0;
+ }
+
+ for (int i = 0; i < chars.Length; i++)
+ {
+ char c = chars[i];
+
+ if ((uint)c >= (uint)_isAllowed.Length || !_isAllowed[c])
+ {
+ EncoderFallback.CreateFallbackBuffer().Fallback(c, i);
+
+ Debug.Fail("Fallback should have thrown");
+ throw new CryptographicException();
+ }
+
+ if (write)
+ {
+ bytes[i] = (byte)c;
+ }
+ }
+
+ return chars.Length;
+ }
+
+ protected override int GetChars(ReadOnlySpan<byte> bytes, Span<char> chars, bool write)
+ {
+ if (bytes.IsEmpty)
+ {
+ return 0;
+ }
+
+ for (int i = 0; i < bytes.Length; i++)
+ {
+ byte b = bytes[i];
+
+ if ((uint)b >= (uint)_isAllowed.Length || !_isAllowed[b])
+ {
+ DecoderFallback.CreateFallbackBuffer().Fallback(
+ new[] { b },
+ i);
+
+ Debug.Fail("Fallback should have thrown");
+ throw new CryptographicException();
+ }
+
+ if (write)
+ {
+ chars[i] = (char)b;
+ }
+ }
+
+ return bytes.Length;
+ }
+ }
+
+ /// <summary>
+ /// Big-Endian UCS-2 encoding (the same as UTF-16BE, but disallowing surrogate pairs to leave plane 0)
+ /// </summary>
+ // T-REC-X.690-201508 sec 8.23.8 says to see ISO/IEC 10646:2003 section 13.1.
+ // ISO/IEC 10646:2003 sec 13.1 says each character is represented by "two octets".
+ // ISO/IEC 10646:2003 sec 6.3 says that when serialized as octets to use big endian.
+ internal class BMPEncoding : SpanBasedEncoding
+ {
+ protected override int GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, bool write)
+ {
+ if (chars.IsEmpty)
+ {
+ return 0;
+ }
+
+ int writeIdx = 0;
+
+ for (int i = 0; i < chars.Length; i++)
+ {
+ char c = chars[i];
+
+ if (char.IsSurrogate(c))
+ {
+ EncoderFallback.CreateFallbackBuffer().Fallback(c, i);
+
+ Debug.Fail("Fallback should have thrown");
+ throw new CryptographicException();
+ }
+
+ ushort val16 = c;
+
+ if (write)
+ {
+ bytes[writeIdx + 1] = (byte)val16;
+ bytes[writeIdx] = (byte)(val16 >> 8);
+ }
+
+ writeIdx += 2;
+ }
+
+ return writeIdx;
+ }
+
+ protected override int GetChars(ReadOnlySpan<byte> bytes, Span<char> chars, bool write)
+ {
+ if (bytes.IsEmpty)
+ {
+ return 0;
+ }
+
+ if (bytes.Length % 2 != 0)
+ {
+ DecoderFallback.CreateFallbackBuffer().Fallback(
+ bytes.Slice(bytes.Length - 1).ToArray(),
+ bytes.Length - 1);
+
+ Debug.Fail("Fallback should have thrown");
+ throw new CryptographicException();
+ }
+
+ int writeIdx = 0;
+
+ for (int i = 0; i < bytes.Length; i += 2)
+ {
+ int val = bytes[i] << 8 | bytes[i + 1];
+ char c = (char)val;
+
+ if (char.IsSurrogate(c))
+ {
+ DecoderFallback.CreateFallbackBuffer().Fallback(
+ bytes.Slice(i, 2).ToArray(),
+ i);
+
+ Debug.Fail("Fallback should have thrown");
+ throw new CryptographicException();
+ }
+
+ if (write)
+ {
+ chars[writeIdx] = c;
+ }
+
+ writeIdx++;
+ }
+
+ return writeIdx;
+ }
+
+ public override int GetMaxByteCount(int charCount)
+ {
+ checked
+ {
+ return charCount * 2;
+ }
+ }
+
+ public override int GetMaxCharCount(int byteCount)
+ {
+ return byteCount / 2;
+ }
+ }
+
+ internal class SetOfValueComparer : IComparer<ReadOnlyMemory<byte>>
+ {
+ internal static SetOfValueComparer Instance { get; } = new SetOfValueComparer();
+
+ public int Compare(ReadOnlyMemory<byte> x, ReadOnlyMemory<byte> y)
+ {
+ ReadOnlySpan<byte> xSpan = x.Span;
+ ReadOnlySpan<byte> ySpan = y.Span;
+
+ int min = Math.Min(x.Length, y.Length);
+ int diff;
+
+ for (int i = 0; i < min; i++)
+ {
+ int xVal = xSpan[i];
+ byte yVal = ySpan[i];
+ diff = xVal - yVal;
+
+ if (diff != 0)
+ {
+ return diff;
+ }
+ }
+
+ // The sorting rules (T-REC-X.690-201508 sec 11.6) say that the shorter one
+ // counts as if it are padded with as many 0x00s on the right as required for
+ // comparison.
+ //
+ // But, since a shorter definite value will have already had the length bytes
+ // compared, it was already different. And a shorter indefinite value will
+ // have hit end-of-contents, making it already different.
+ //
+ // This is here because the spec says it should be, but no values are known
+ // which will make diff != 0.
+ diff = x.Length - y.Length;
+
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ return 0;
+ }
+ }
+}
diff --git a/src/Common/src/System/Security/Cryptography/AsnReader.cs b/src/Common/src/System/Security/Cryptography/AsnReader.cs
new file mode 100644
index 0000000000..50d4a9f94f
--- /dev/null
+++ b/src/Common/src/System/Security/Cryptography/AsnReader.cs
@@ -0,0 +1,2910 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Buffers.Text;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System.Security.Cryptography.Asn1
+{
+ internal class AsnReader
+ {
+ // T-REC-X.690-201508 sec 9.2
+ internal const int MaxCERSegmentSize = 1000;
+
+ // T-REC-X.690-201508 sec 8.1.5 says only 0000 is legal.
+ private const int EndOfContentsEncodedLength = 2;
+
+ private ReadOnlyMemory<byte> _data;
+ private readonly AsnEncodingRules _ruleSet;
+
+ public bool HasData => !_data.IsEmpty;
+
+ public AsnReader(ReadOnlyMemory<byte> data, AsnEncodingRules ruleSet)
+ {
+ CheckEncodingRules(ruleSet);
+
+ _data = data;
+ _ruleSet = ruleSet;
+ }
+
+ public void ThrowIfNotEmpty()
+ {
+ if (HasData)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+
+ public static bool TryPeekTag(ReadOnlySpan<byte> source, out Asn1Tag tag, out int bytesRead)
+ {
+ return Asn1Tag.TryParse(source, out tag, out bytesRead);
+ }
+
+ public Asn1Tag PeekTag()
+ {
+ if (TryPeekTag(_data.Span, out Asn1Tag tag, out int bytesRead))
+ {
+ return tag;
+ }
+
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ private static bool TryReadLength(
+ ReadOnlySpan<byte> source,
+ AsnEncodingRules ruleSet,
+ out int? length,
+ out int bytesRead)
+ {
+ length = null;
+ bytesRead = 0;
+
+ CheckEncodingRules(ruleSet);
+
+ if (source.IsEmpty)
+ {
+ return false;
+ }
+
+ // T-REC-X.690-201508 sec 8.1.3
+
+ byte lengthOrLengthLength = source[bytesRead];
+ bytesRead++;
+ const byte MultiByteMarker = 0x80;
+
+ // 0x00-0x7F are direct length values.
+ // 0x80 is BER/CER indefinite length.
+ // 0x81-0xFE says that the length takes the next 1-126 bytes.
+ // 0xFF is forbidden.
+ if (lengthOrLengthLength == MultiByteMarker)
+ {
+ // T-REC-X.690-201508 sec 10.1 (DER: Length forms)
+ if (ruleSet == AsnEncodingRules.DER)
+ {
+ bytesRead = 0;
+ return false;
+ }
+
+ // Null length == indefinite.
+ return true;
+ }
+
+ if (lengthOrLengthLength < MultiByteMarker)
+ {
+ length = lengthOrLengthLength;
+ return true;
+ }
+
+ if (lengthOrLengthLength == 0xFF)
+ {
+ bytesRead = 0;
+ return false;
+ }
+
+ byte lengthLength = (byte)(lengthOrLengthLength & ~MultiByteMarker);
+
+ // +1 for lengthOrLengthLength
+ if (lengthLength + 1 > source.Length)
+ {
+ bytesRead = 0;
+ return false;
+ }
+
+ // T-REC-X.690-201508 sec 9.1 (CER: Length forms)
+ // T-REC-X.690-201508 sec 10.1 (DER: Length forms)
+ bool minimalRepresentation =
+ ruleSet == AsnEncodingRules.DER || ruleSet == AsnEncodingRules.CER;
+
+ // The ITU-T specifications tecnically allow lengths up to ((2^128) - 1), but
+ // since Span's length is a signed Int32 we're limited to identifying memory
+ // that is within ((2^31) - 1) bytes of the tag start.
+ if (minimalRepresentation && lengthLength > sizeof(int))
+ {
+ bytesRead = 0;
+ return false;
+ }
+
+ uint parsedLength = 0;
+
+ for (int i = 0; i < lengthLength; i++)
+ {
+ byte current = source[bytesRead];
+ bytesRead++;
+
+ if (parsedLength == 0)
+ {
+ if (minimalRepresentation && current == 0)
+ {
+ bytesRead = 0;
+ return false;
+ }
+
+ if (!minimalRepresentation && current != 0)
+ {
+ // Under BER rules we could have had padding zeros, so
+ // once the first data bits come in check that we fit within
+ // sizeof(int) due to Span bounds.
+
+ if (lengthLength - i > sizeof(int))
+ {
+ bytesRead = 0;
+ return false;
+ }
+ }
+ }
+
+ parsedLength <<= 8;
+ parsedLength |= current;
+ }
+
+ // This value cannot be represented as a Span length.
+ if (parsedLength > int.MaxValue)
+ {
+ bytesRead = 0;
+ return false;
+ }
+
+ if (minimalRepresentation && parsedLength < MultiByteMarker)
+ {
+ bytesRead = 0;
+ return false;
+ }
+
+ Debug.Assert(bytesRead > 0);
+ length = (int)parsedLength;
+ return true;
+ }
+
+ internal Asn1Tag ReadTagAndLength(out int? contentsLength, out int bytesRead)
+ {
+ if (TryPeekTag(_data.Span, out Asn1Tag tag, out int tagBytesRead) &&
+ TryReadLength(_data.Slice(tagBytesRead).Span, _ruleSet, out int? length, out int lengthBytesRead))
+ {
+ int allBytesRead = tagBytesRead + lengthBytesRead;
+
+ if (tag.IsConstructed)
+ {
+ // T-REC-X.690-201508 sec 9.1 (CER: Length forms) says constructed is always indefinite.
+ if (_ruleSet == AsnEncodingRules.CER && length != null)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+ else if (length == null)
+ {
+ // T-REC-X.690-201508 sec 8.1.3.2 says primitive encodings must use a definite form.
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ bytesRead = allBytesRead;
+ contentsLength = length;
+ return tag;
+ }
+
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ private static void ValidateEndOfContents(Asn1Tag tag, int? length, int headerLength)
+ {
+ // T-REC-X.690-201508 sec 8.1.5 excludes the BER 8100 length form for 0.
+ if (tag.IsConstructed || length != 0 || headerLength != EndOfContentsEncodedLength)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+
+ /// <summary>
+ /// Get the number of bytes between the start of <paramref name="source" /> and
+ /// the End-of-Contents marker
+ /// </summary>
+ private int SeekEndOfContents(ReadOnlyMemory<byte> source)
+ {
+ ReadOnlyMemory<byte> cur = source;
+ int totalLen = 0;
+
+ AsnReader tmpReader = new AsnReader(cur, _ruleSet);
+ // Our reader is bounded by int.MaxValue.
+ // The most aggressive data input would be a one-byte tag followed by
+ // indefinite length "ad infinitum", which would be half the input.
+ // So the depth marker can never overflow the signed integer space.
+ int depth = 1;
+
+ while (tmpReader.HasData)
+ {
+ Asn1Tag tag = tmpReader.ReadTagAndLength(out int? length, out int bytesRead);
+
+ if (tag == Asn1Tag.EndOfContents)
+ {
+ ValidateEndOfContents(tag, length, bytesRead);
+
+ depth--;
+
+ if (depth == 0)
+ {
+ // T-REC-X.690-201508 sec 8.1.1.1 / 8.1.1.3 indicate that the
+ // End-of-Contents octets are "after" the contents octets, not
+ // "at the end" of them, so we don't include these bytes in the
+ // accumulator.
+ return totalLen;
+ }
+ }
+
+ // We found another indefinite length, that means we need to find another
+ // EndOfContents marker to balance it out.
+ if (length == null)
+ {
+ depth++;
+ tmpReader._data = tmpReader._data.Slice(bytesRead);
+ totalLen += bytesRead;
+ }
+ else
+ {
+ // This will throw a CryptographicException if the length exceeds our bounds.
+ ReadOnlyMemory<byte> tlv = Slice(tmpReader._data, 0, bytesRead + length.Value);
+
+ // No exception? Then slice the data and continue.
+ tmpReader._data = tmpReader._data.Slice(tlv.Length);
+ totalLen += tlv.Length;
+ }
+ }
+
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ /// <summary>
+ /// Get a ReadOnlyMemory view of the next encoded value without consuming it.
+ /// For indefinite length encodings this includes the End of Contents marker.
+ /// </summary>
+ /// <returns>A ReadOnlyMemory view of the next encoded value.</returns>
+ /// <exception cref="CryptographicException">
+ /// The reader is positioned at a point where the tag or length is invalid
+ /// under the current encoding rules.
+ /// </exception>
+ /// <seealso cref="PeekContentBytes"/>
+ /// <seealso cref="GetEncodedValue"/>
+ public ReadOnlyMemory<byte> PeekEncodedValue()
+ {
+ Asn1Tag tag = ReadTagAndLength(out int? length, out int bytesRead);
+
+ if (length == null)
+ {
+ int contentsLength = SeekEndOfContents(_data.Slice(bytesRead));
+ return Slice(_data, 0, bytesRead + contentsLength + EndOfContentsEncodedLength);
+ }
+
+ return Slice(_data, 0, bytesRead + length.Value);
+ }
+
+ /// <summary>
+ /// Get a ReadOnlyMemory view of the content octets (bytes) of the next encoded
+ /// value without consuming it.
+ /// </summary>
+ /// <returns>A ReadOnlyMemory view of the contents octets of the next encoded value.</returns>
+ /// <exception cref="CryptographicException">
+ /// The reader is positioned at a point where the tag or length is invalid
+ /// under the current encoding rules.
+ /// </exception>
+ /// <seealso cref="PeekEncodedValue"/>
+ public ReadOnlyMemory<byte> PeekContentBytes()
+ {
+ Asn1Tag tag = ReadTagAndLength(out int? length, out int bytesRead);
+
+ if (length == null)
+ {
+ return Slice(_data, bytesRead, SeekEndOfContents(_data.Slice(bytesRead)));
+ }
+
+ return Slice(_data, bytesRead, length.Value);
+ }
+
+ /// <summary>
+ /// Get a ReadOnlyMemory view of the next encoded value, and move the reader past it.
+ /// For an indefinite length encoding this includes the End of Contents marker.
+ /// </summary>
+ /// <returns>A ReadOnlyMemory view of the next encoded value.</returns>
+ /// <seealso cref="PeekEncodedValue"/>
+ public ReadOnlyMemory<byte> GetEncodedValue()
+ {
+ ReadOnlyMemory<byte> encodedValue = PeekEncodedValue();
+ _data = _data.Slice(encodedValue.Length);
+ return encodedValue;
+ }
+
+ private static bool ReadBooleanValue(
+ ReadOnlySpan<byte> source,
+ AsnEncodingRules ruleSet)
+ {
+ // T-REC-X.690-201508 sec 8.2.1
+ if (source.Length != 1)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ byte val = source[0];
+
+ // T-REC-X.690-201508 sec 8.2.2
+ if (val == 0)
+ {
+ return false;
+ }
+
+ // T-REC-X.690-201508 sec 11.1
+ if (val != 0xFF && (ruleSet == AsnEncodingRules.DER || ruleSet == AsnEncodingRules.CER))
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ return true;
+ }
+
+ public bool ReadBoolean() => ReadBoolean(Asn1Tag.Boolean);
+
+ public bool ReadBoolean(Asn1Tag expectedTag)
+ {
+ Asn1Tag tag = ReadTagAndLength(out int? length, out int headerLength);
+ CheckExpectedTag(tag, expectedTag, UniversalTagNumber.Boolean);
+
+ // T-REC-X.690-201508 sec 8.2.1
+ if (tag.IsConstructed)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ bool value = ReadBooleanValue(
+ Slice(_data, headerLength, length.Value).Span,
+ _ruleSet);
+
+ _data = _data.Slice(headerLength + length.Value);
+ return value;
+ }
+
+ private ReadOnlyMemory<byte> GetIntegerContents(
+ Asn1Tag expectedTag,
+ UniversalTagNumber tagNumber,
+ out int headerLength)
+ {
+ Asn1Tag tag = ReadTagAndLength(out int? length, out headerLength);
+ CheckExpectedTag(tag, expectedTag, tagNumber);
+
+ // T-REC-X.690-201508 sec 8.3.1
+ if (tag.IsConstructed || length < 1)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ // Slice first so that an out of bounds value triggers a CryptographicException.
+ ReadOnlyMemory<byte> contents = Slice(_data, headerLength, length.Value);
+ ReadOnlySpan<byte> contentSpan = contents.Span;
+
+ // T-REC-X.690-201508 sec 8.3.2
+ if (contents.Length > 1)
+ {
+ ushort bigEndianValue = (ushort)(contentSpan[0] << 8 | contentSpan[1]);
+ const ushort RedundancyMask = 0b1111_1111_1000_0000;
+ ushort masked = (ushort)(bigEndianValue & RedundancyMask);
+
+ // If the first 9 bits are all 0 or are all 1, the value is invalid.
+ if (masked == 0 || masked == RedundancyMask)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+
+ return contents;
+ }
+
+ public ReadOnlyMemory<byte> GetIntegerBytes() =>
+ GetIntegerBytes(Asn1Tag.Integer);
+
+ public ReadOnlyMemory<byte> GetIntegerBytes(Asn1Tag expectedTag)
+ {
+ ReadOnlyMemory<byte> contents =
+ GetIntegerContents(expectedTag, UniversalTagNumber.Integer, out int headerLength);
+
+ _data = _data.Slice(headerLength + contents.Length);
+ return contents;
+ }
+
+ private bool TryReadSignedInteger(
+ int sizeLimit,
+ Asn1Tag expectedTag,
+ UniversalTagNumber tagNumber,
+ out long value)
+ {
+ Debug.Assert(sizeLimit <= sizeof(long));
+
+ ReadOnlyMemory<byte> contents = GetIntegerContents(expectedTag, tagNumber, out int headerLength);
+
+ if (contents.Length > sizeLimit)
+ {
+ value = 0;
+ return false;
+ }
+
+ ReadOnlySpan<byte> contentSpan = contents.Span;
+
+ bool isNegative = (contentSpan[0] & 0x80) != 0;
+ long accum = isNegative ? -1 : 0;
+
+ for (int i = 0; i < contents.Length; i++)
+ {
+ accum <<= 8;
+ accum |= contentSpan[i];
+ }
+
+ _data = _data.Slice(headerLength + contents.Length);
+ value = accum;
+ return true;
+ }
+
+ private bool TryReadUnsignedInteger(
+ int sizeLimit,
+ Asn1Tag expectedTag,
+ UniversalTagNumber tagNumber,
+ out ulong value)
+ {
+ Debug.Assert(sizeLimit <= sizeof(ulong));
+
+ ReadOnlyMemory<byte> contents = GetIntegerContents(expectedTag, tagNumber, out int headerLength);
+ ReadOnlySpan<byte> contentSpan = contents.Span;
+ int contentLength = contents.Length;
+
+ bool isNegative = (contentSpan[0] & 0x80) != 0;
+
+ if (isNegative)
+ {
+ value = 0;
+ return false;
+ }
+
+ // Ignore any padding zeros.
+ if (contentSpan.Length > 1 && contentSpan[0] == 0)
+ {
+ contentSpan = contentSpan.Slice(1);
+ }
+
+ if (contentSpan.Length > sizeLimit)
+ {
+ value = 0;
+ return false;
+ }
+
+ ulong accum = 0;
+
+ for (int i = 0; i < contentSpan.Length; i++)
+ {
+ accum <<= 8;
+ accum |= contentSpan[i];
+ }
+
+ _data = _data.Slice(headerLength + contentLength);
+ value = accum;
+ return true;
+ }
+
+ public bool TryReadInt32(out int value) =>
+ TryReadInt32(Asn1Tag.Integer, out value);
+
+ public bool TryReadInt32(Asn1Tag expectedTag, out int value)
+ {
+ if (TryReadSignedInteger(sizeof(int), expectedTag, UniversalTagNumber.Integer, out long longValue))
+ {
+ value = (int)longValue;
+ return true;
+ }
+
+ value = 0;
+ return false;
+ }
+
+ public bool TryReadUInt32(out uint value) =>
+ TryReadUInt32(Asn1Tag.Integer, out value);
+
+ public bool TryReadUInt32(Asn1Tag expectedTag, out uint value)
+ {
+ if (TryReadUnsignedInteger(sizeof(uint), expectedTag, UniversalTagNumber.Integer, out ulong ulongValue))
+ {
+ value = (uint)ulongValue;
+ return true;
+ }
+
+ value = 0;
+ return false;
+ }
+
+ public bool TryReadInt64(out long value) =>
+ TryReadInt64(Asn1Tag.Integer, out value);
+
+ public bool TryReadInt64(Asn1Tag expectedTag, out long value)
+ {
+ return TryReadSignedInteger(sizeof(long), expectedTag, UniversalTagNumber.Integer, out value);
+ }
+
+ public bool TryReadUInt64(out ulong value) =>
+ TryReadUInt64(Asn1Tag.Integer, out value);
+
+ public bool TryReadUInt64(Asn1Tag expectedTag, out ulong value)
+ {
+ return TryReadUnsignedInteger(sizeof(ulong), expectedTag, UniversalTagNumber.Integer, out value);
+ }
+
+ public bool TryReadInt16(out short value) =>
+ TryReadInt16(Asn1Tag.Integer, out value);
+
+ public bool TryReadInt16(Asn1Tag expectedTag, out short value)
+ {
+ if (TryReadSignedInteger(sizeof(short), expectedTag, UniversalTagNumber.Integer, out long longValue))
+ {
+ value = (short)longValue;
+ return true;
+ }
+
+ value = 0;
+ return false;
+ }
+
+ public bool TryReadUInt16(out ushort value) =>
+ TryReadUInt16(Asn1Tag.Integer, out value);
+
+ public bool TryReadUInt16(Asn1Tag expectedTag, out ushort value)
+ {
+ if (TryReadUnsignedInteger(sizeof(ushort), expectedTag, UniversalTagNumber.Integer, out ulong ulongValue))
+ {
+ value = (ushort)ulongValue;
+ return true;
+ }
+
+ value = 0;
+ return false;
+ }
+
+ public bool TryReadInt8(out sbyte value) =>
+ TryReadInt8(Asn1Tag.Integer, out value);
+
+ public bool TryReadInt8(Asn1Tag expectedTag, out sbyte value)
+ {
+ if (TryReadSignedInteger(sizeof(sbyte), expectedTag, UniversalTagNumber.Integer, out long longValue))
+ {
+ value = (sbyte)longValue;
+ return true;
+ }
+
+ value = 0;
+ return false;
+ }
+
+ public bool TryReadUInt8(out byte value) =>
+ TryReadUInt8(Asn1Tag.Integer, out value);
+
+ public bool TryReadUInt8(Asn1Tag expectedTag, out byte value)
+ {
+ if (TryReadUnsignedInteger(sizeof(byte), expectedTag, UniversalTagNumber.Integer, out ulong ulongValue))
+ {
+ value = (byte)ulongValue;
+ return true;
+ }
+
+ value = 0;
+ return false;
+ }
+
+ private void ParsePrimitiveBitStringContents(
+ ReadOnlyMemory<byte> source,
+ out int unusedBitCount,
+ out ReadOnlyMemory<byte> value,
+ out byte normalizedLastByte)
+ {
+ // T-REC-X.690-201508 sec 9.2
+ if (_ruleSet == AsnEncodingRules.CER && source.Length > MaxCERSegmentSize)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ // T-REC-X.690-201508 sec 8.6.2.3
+ if (source.Length == 0)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ ReadOnlySpan<byte> sourceSpan = source.Span;
+ unusedBitCount = sourceSpan[0];
+
+ // T-REC-X.690-201508 sec 8.6.2.2
+ if (unusedBitCount > 7)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ if (source.Length == 1)
+ {
+ // T-REC-X.690-201508 sec 8.6.2.4
+ if (unusedBitCount > 0)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ Debug.Assert(unusedBitCount == 0);
+ value = ReadOnlyMemory<byte>.Empty;
+ normalizedLastByte = 0;
+ return;
+ }
+
+ // Build a mask for the bits that are used so the normalized value can be computed
+ //
+ // If 3 bits are "unused" then build a mask for them to check for 0.
+ // -1 << 3 => 0b1111_1111 << 3 => 0b1111_1000
+ int mask = -1 << unusedBitCount;
+ byte lastByte = sourceSpan[sourceSpan.Length - 1];
+ byte maskedByte = (byte)(lastByte & mask);
+
+ if (maskedByte != lastByte)
+ {
+ // T-REC-X.690-201508 sec 11.2.1
+ if (_ruleSet == AsnEncodingRules.DER || _ruleSet == AsnEncodingRules.CER)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+
+ normalizedLastByte = maskedByte;
+ value = source.Slice(1);
+ }
+
+ private delegate void BitStringCopyAction(
+ ReadOnlyMemory<byte> value,
+ byte normalizedLastByte,
+ Span<byte> destination);
+
+ private static void CopyBitStringValue(
+ ReadOnlyMemory<byte> value,
+ byte normalizedLastByte,
+ Span<byte> destination)
+ {
+ if (value.Length == 0)
+ {
+ return;
+ }
+
+ value.Span.CopyTo(destination);
+ // Replace the last byte with the normalized answer.
+ destination[value.Length - 1] = normalizedLastByte;
+ }
+
+ private int CountConstructedBitString(ReadOnlyMemory<byte> source, bool isIndefinite)
+ {
+ Span<byte> destination = Span<byte>.Empty;
+
+ return ProcessConstructedBitString(
+ source,
+ destination,
+ null,
+ isIndefinite,
+ out _,
+ out _);
+ }
+
+ private void CopyConstructedBitString(
+ ReadOnlyMemory<byte> source,
+ Span<byte> destination,
+ bool isIndefinite,
+ out int unusedBitCount,
+ out int bytesRead,
+ out int bytesWritten)
+ {
+ Span<byte> tmpDest = destination;
+
+ bytesWritten = ProcessConstructedBitString(
+ source,
+ tmpDest,
+ (value, lastByte, dest) => CopyBitStringValue(value, lastByte, dest),
+ isIndefinite,
+ out unusedBitCount,
+ out bytesRead);
+ }
+
+ private int ProcessConstructedBitString(
+ ReadOnlyMemory<byte> source,
+ Span<byte> destination,
+ BitStringCopyAction copyAction,
+ bool isIndefinite,
+ out int lastUnusedBitCount,
+ out int bytesRead)
+ {
+ lastUnusedBitCount = 0;
+ bytesRead = 0;
+ int lastSegmentLength = MaxCERSegmentSize;
+
+ AsnReader tmpReader = new AsnReader(source, _ruleSet);
+ Stack<(AsnReader, bool, int)> readerStack = null;
+ int totalLength = 0;
+ Asn1Tag tag = Asn1Tag.ConstructedBitString;
+ Span<byte> curDest = destination;
+
+ do
+ {
+ while (tmpReader.HasData)
+ {
+ tag = tmpReader.ReadTagAndLength(out int? length, out int headerLength);
+
+ if (tag == Asn1Tag.PrimitiveBitString)
+ {
+ if (lastUnusedBitCount != 0)
+ {
+ // T-REC-X.690-201508 sec 8.6.4, only the last segment may have
+ // a number of bits not a multiple of 8.
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ if (_ruleSet == AsnEncodingRules.CER && lastSegmentLength != MaxCERSegmentSize)
+ {
+ // T-REC-X.690-201508 sec 9.2
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ Debug.Assert(length != null);
+ ReadOnlyMemory<byte> encodedValue = Slice(tmpReader._data, headerLength, length.Value);
+
+ ParsePrimitiveBitStringContents(
+ encodedValue,
+ out lastUnusedBitCount,
+ out ReadOnlyMemory<byte> contents,
+ out byte normalizedLastByte);
+
+ int localLen = headerLength + encodedValue.Length;
+ tmpReader._data = tmpReader._data.Slice(localLen);
+
+ bytesRead += localLen;
+ totalLength += contents.Length;
+ lastSegmentLength = encodedValue.Length;
+
+ if (copyAction != null)
+ {
+ copyAction(contents, normalizedLastByte, curDest);
+ curDest = curDest.Slice(contents.Length);
+ }
+ }
+ else if (tag == Asn1Tag.EndOfContents && isIndefinite)
+ {
+ ValidateEndOfContents(tag, length, headerLength);
+
+ bytesRead += headerLength;
+
+ if (readerStack?.Count > 0)
+ {
+ (AsnReader topReader, bool wasIndefinite, int pushedBytesRead) = readerStack.Pop();
+ topReader._data = topReader._data.Slice(bytesRead);
+
+ bytesRead += pushedBytesRead;
+ isIndefinite = wasIndefinite;
+ tmpReader = topReader;
+ }
+ else
+ {
+ // We have matched the EndOfContents that brought us here.
+ break;
+ }
+ }
+ else if (tag == Asn1Tag.ConstructedBitString)
+ {
+ if (_ruleSet == AsnEncodingRules.CER)
+ {
+ // T-REC-X.690-201508 sec 9.2
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ if (readerStack == null)
+ {
+ readerStack = new Stack<(AsnReader, bool, int)>();
+ }
+
+ readerStack.Push((tmpReader, isIndefinite, bytesRead));
+
+ tmpReader = new AsnReader(
+ Slice(tmpReader._data, headerLength, length),
+ _ruleSet);
+
+ bytesRead = headerLength;
+ isIndefinite = (length == null);
+ }
+ else
+ {
+ // T-REC-X.690-201508 sec 8.6.4.1 (in particular, Note 2)
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+
+ if (isIndefinite && tag != Asn1Tag.EndOfContents)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ if (readerStack?.Count > 0)
+ {
+ (AsnReader topReader, bool wasIndefinite, int pushedBytesRead) = readerStack.Pop();
+
+ tmpReader = topReader;
+ tmpReader._data = tmpReader._data.Slice(bytesRead);
+
+ isIndefinite = wasIndefinite;
+ bytesRead += pushedBytesRead;
+ }
+ else
+ {
+ tmpReader = null;
+ }
+ } while (tmpReader != null);
+
+ return totalLength;
+ }
+
+ private bool TryCopyConstructedBitStringValue(
+ ReadOnlyMemory<byte> source,
+ Span<byte> dest,
+ bool isIndefinite,
+ out int unusedBitCount,
+ out int bytesRead,
+ out int bytesWritten)
+ {
+ // Call CountConstructedBitString to get the required byte and to verify that the
+ // data is well-formed before copying into dest.
+ int contentLength = CountConstructedBitString(source, isIndefinite);
+
+ // Since the unused bits byte from the segments don't count, only one segment
+ // returns 999 (or less), the second segment bumps the count to 1000, and is legal.
+ //
+ // T-REC-X.690-201508 sec 9.2
+ if (_ruleSet == AsnEncodingRules.CER && contentLength < MaxCERSegmentSize)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ if (dest.Length < contentLength)
+ {
+ unusedBitCount = 0;
+ bytesRead = 0;
+ bytesWritten = 0;
+ return false;
+ }
+
+ CopyConstructedBitString(
+ source,
+ dest,
+ isIndefinite,
+ out unusedBitCount,
+ out bytesRead,
+ out bytesWritten);
+
+ Debug.Assert(bytesWritten == contentLength);
+ return true;
+ }
+
+ private bool TryGetPrimitiveBitStringValue(
+ Asn1Tag expectedTag,
+ out Asn1Tag actualTag,
+ out int? contentsLength,
+ out int headerLength,
+ out int unusedBitCount,
+ out ReadOnlyMemory<byte> value,
+ out byte normalizedLastByte)
+ {
+ actualTag = ReadTagAndLength(out contentsLength, out headerLength);
+ CheckExpectedTag(actualTag, expectedTag, UniversalTagNumber.BitString);
+
+ if (actualTag.IsConstructed)
+ {
+ if (_ruleSet == AsnEncodingRules.DER)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ unusedBitCount = 0;
+ value = default(ReadOnlyMemory<byte>);
+ normalizedLastByte = 0;
+ return false;
+ }
+
+ Debug.Assert(contentsLength.HasValue);
+ ReadOnlyMemory<byte> encodedValue = Slice(_data, headerLength, contentsLength.Value);
+
+ ParsePrimitiveBitStringContents(
+ encodedValue,
+ out unusedBitCount,
+ out value,
+ out normalizedLastByte);
+
+ return true;
+ }
+
+ public bool TryGetPrimitiveBitStringValue(out int unusedBitCount, out ReadOnlyMemory<byte> contents)
+ => TryGetPrimitiveBitStringValue(Asn1Tag.PrimitiveBitString, out unusedBitCount, out contents);
+
+ /// <summary>
+ /// Gets a ReadOnlyMemory view over the data value portion of the contents of a bit string.
+ /// </summary>
+ /// <param name="expectedTag">The expected tag to read</param>
+ /// <param name="unusedBitCount">The encoded value for the number of unused bits.</param>
+ /// <param name="value">The data value portion of the bit string contents.</param>
+ /// <returns>
+ /// <c>true</c> if the bit string uses a primitive encoding and the "unused" bits have value 0,
+ /// <c>false</c> otherwise.
+ /// </returns>
+ /// <exception cref="CryptographicException">
+ /// <ul>
+ /// <li>No data remains</li>
+ /// <li>The tag read does not match the expected tag</li>
+ /// <li>The length is invalid under the chosen encoding rules</li>
+ /// <li>The unusedBitCount value is out of bounds</li>
+ /// <li>A CER or DER encoding was chosen and an "unused" bit was set to 1</li>
+ /// <li>A CER encoding was chosen and the primitive content length exceeds the maximum allowed</li>
+ /// </ul>
+ /// </exception>
+ public bool TryGetPrimitiveBitStringValue(
+ Asn1Tag expectedTag,
+ out int unusedBitCount,
+ out ReadOnlyMemory<byte> value)
+ {
+ bool isPrimitive = TryGetPrimitiveBitStringValue(
+ expectedTag,
+ out Asn1Tag actualTag,
+ out int? contentsLength,
+ out int headerLength,
+ out unusedBitCount,
+ out value,
+ out byte normalizedLastByte);
+
+ if (isPrimitive)
+ {
+ // A BER reader which encountered a situation where an "unused" bit was not
+ // set to 0.
+ if (value.Length != 0 && normalizedLastByte != value.Span[value.Length - 1])
+ {
+ unusedBitCount = 0;
+ value = default(ReadOnlyMemory<byte>);
+ return false;
+ }
+
+ // Skip the tag+length (header) and the unused bit count byte (1) and the contents.
+ _data = _data.Slice(headerLength + value.Length + 1);
+ }
+
+ return isPrimitive;
+ }
+
+ public bool TryCopyBitStringBytes(
+ Span<byte> destination,
+ out int unusedBitCount,
+ out int bytesWritten)
+ {
+ return TryCopyBitStringBytes(
+ Asn1Tag.PrimitiveBitString,
+ destination,
+ out unusedBitCount,
+ out bytesWritten);
+ }
+
+ public bool TryCopyBitStringBytes(
+ Asn1Tag expectedTag,
+ Span<byte> destination,
+ out int unusedBitCount,
+ out int bytesWritten)
+ {
+ if (TryGetPrimitiveBitStringValue(
+ expectedTag,
+ out Asn1Tag actualTag,
+ out int? contentsLength,
+ out int headerLength,
+ out unusedBitCount,
+ out ReadOnlyMemory<byte> value,
+ out byte normalizedLastByte))
+ {
+ if (value.Length > destination.Length)
+ {
+ bytesWritten = 0;
+ unusedBitCount = 0;
+ return false;
+ }
+
+ CopyBitStringValue(value, normalizedLastByte, destination);
+
+ bytesWritten = value.Length;
+ // contents doesn't include the unusedBitCount value, so add one byte for that.
+ _data = _data.Slice(headerLength + value.Length + 1);
+ return true;
+ }
+
+ Debug.Assert(actualTag.IsConstructed);
+
+ bool read = TryCopyConstructedBitStringValue(
+ Slice(_data, headerLength, contentsLength),
+ destination,
+ contentsLength == null,
+ out unusedBitCount,
+ out int bytesRead,
+ out bytesWritten);
+
+ if (read)
+ {
+ _data = _data.Slice(headerLength + bytesRead);
+ }
+
+ return read;
+ }
+
+ public TFlagsEnum GetNamedBitListValue<TFlagsEnum>() where TFlagsEnum : struct =>
+ GetNamedBitListValue<TFlagsEnum>(Asn1Tag.PrimitiveBitString);
+
+ public TFlagsEnum GetNamedBitListValue<TFlagsEnum>(Asn1Tag expectedTag) where TFlagsEnum : struct
+ {
+ Type tFlagsEnum = typeof(TFlagsEnum);
+
+ return (TFlagsEnum)Enum.ToObject(tFlagsEnum, GetNamedBitListValue(expectedTag, tFlagsEnum));
+ }
+
+ public Enum GetNamedBitListValue(Type tFlagsEnum) =>
+ GetNamedBitListValue(Asn1Tag.PrimitiveBitString, tFlagsEnum);
+
+ public Enum GetNamedBitListValue(Asn1Tag expectedTag, Type tFlagsEnum)
+ {
+ // This will throw an ArgumentException if TEnum isn't an enum type,
+ // so we don't need to validate it.
+ Type backingType = tFlagsEnum.GetEnumUnderlyingType();
+
+ if (!tFlagsEnum.IsDefined(typeof(FlagsAttribute), false))
+ {
+ throw new ArgumentException(
+ SR.Cryptography_Asn_NamedBitListRequiresFlagsEnum,
+ nameof(tFlagsEnum));
+ }
+
+ int sizeLimit = Marshal.SizeOf(backingType);
+ Span<byte> stackSpan = stackalloc byte[sizeLimit];
+ ReadOnlyMemory<byte> saveData = _data;
+
+ // If TryCopyBitStringBytes succeeds but anything else fails _data will have moved,
+ // so if anything throws here just move _data back to what it was.
+ try
+ {
+ if (!TryCopyBitStringBytes(expectedTag, stackSpan, out int unusedBitCount, out int bytesWritten))
+ {
+ throw new CryptographicException(
+ SR.Format(SR.Cryptography_Asn_NamedBitListValueTooBig, tFlagsEnum.Name));
+ }
+
+ if (bytesWritten == 0)
+ {
+ // The mode isn't relevant, zero is always zero.
+ return (Enum)Enum.ToObject(tFlagsEnum, 0);
+ }
+
+ ReadOnlySpan<byte> valueSpan = stackSpan.Slice(0, bytesWritten);
+
+ // Now that the 0-bounds check is out of the way:
+ //
+ // T-REC-X.690-201508 sec 11.2.2
+ if (_ruleSet == AsnEncodingRules.DER ||
+ _ruleSet == AsnEncodingRules.CER)
+ {
+ byte lastByte = valueSpan[bytesWritten - 1];
+
+ // No unused bits tests 0x01, 1 is 0x02, 2 is 0x04, etc.
+ // We already know that TryCopyBitStringBytes checked that the
+ // declared unused bits were 0, this checks that the last "used" bit
+ // isn't also zero.
+ byte testBit = (byte)(1 << unusedBitCount);
+
+ if ((lastByte & testBit) == 0)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+
+ // Consider a NamedBitList defined as
+ //
+ // SomeList ::= BIT STRING {
+ // a(0), b(1), c(2), d(3), e(4), f(5), g(6), h(7), i(8), j(9), k(10)
+ // }
+ //
+ // The BIT STRING encoding of (a | j) is
+ // unusedBitCount = 6,
+ // contents: 0x80 0x40 (0b10000000_01000000)
+ //
+ // A the C# exposure of this structure we adhere to is
+ //
+ // [Flags]
+ // enum SomeList
+ // {
+ // A = 1,
+ // B = 1 << 1,
+ // C = 1 << 2,
+ // ...
+ // }
+ //
+ // Which happens to be exactly backwards from how the bits are encoded, but the complexity
+ // only needs to live here.
+ return (Enum)Enum.ToObject(tFlagsEnum, InterpretNamedBitListReversed(valueSpan));
+ }
+ catch
+ {
+ _data = saveData;
+ throw;
+ }
+ }
+
+ private static long InterpretNamedBitListReversed(ReadOnlySpan<byte> valueSpan)
+ {
+ Debug.Assert(valueSpan.Length <= sizeof(long));
+
+ long accum = 0;
+ long currentBitValue = 1;
+
+ for (int byteIdx = 0; byteIdx < valueSpan.Length; byteIdx++)
+ {
+ byte byteVal = valueSpan[byteIdx];
+
+ for (int bitIndex = 7; bitIndex >= 0; bitIndex--)
+ {
+ int test = 1 << bitIndex;
+
+ if ((byteVal & test) != 0)
+ {
+ accum |= currentBitValue;
+ }
+
+ currentBitValue <<= 1;
+ }
+ }
+
+ return accum;
+ }
+
+ public ReadOnlyMemory<byte> GetEnumeratedBytes() =>
+ GetEnumeratedBytes(Asn1Tag.Enumerated);
+
+ public ReadOnlyMemory<byte> GetEnumeratedBytes(Asn1Tag expectedTag)
+ {
+ // T-REC-X.690-201508 sec 8.4 says the contents are the same as for integers.
+ ReadOnlyMemory<byte> contents =
+ GetIntegerContents(expectedTag, UniversalTagNumber.Enumerated, out int headerLength);
+
+ _data = _data.Slice(headerLength + contents.Length);
+ return contents;
+ }
+
+ public TEnum GetEnumeratedValue<TEnum>() where TEnum : struct
+ {
+ Type tEnum = typeof(TEnum);
+
+ return (TEnum)Enum.ToObject(tEnum, GetEnumeratedValue(tEnum));
+ }
+
+ public TEnum GetEnumeratedValue<TEnum>(Asn1Tag expectedTag) where TEnum : struct
+ {
+ Type tEnum = typeof(TEnum);
+
+ return (TEnum)Enum.ToObject(tEnum, GetEnumeratedValue(expectedTag, tEnum));
+ }
+
+ public Enum GetEnumeratedValue(Type tEnum) =>
+ GetEnumeratedValue(Asn1Tag.Enumerated, tEnum);
+
+ public Enum GetEnumeratedValue(Asn1Tag expectedTag, Type tEnum)
+ {
+ const UniversalTagNumber tagNumber = UniversalTagNumber.Enumerated;
+
+ // This will throw an ArgumentException if TEnum isn't an enum type,
+ // so we don't need to validate it.
+ Type backingType = tEnum.GetEnumUnderlyingType();
+
+ if (tEnum.IsDefined(typeof(FlagsAttribute), false))
+ {
+ throw new ArgumentException(
+ SR.Cryptography_Asn_EnumeratedValueRequiresNonFlagsEnum,
+ nameof(tEnum));
+ }
+
+ // T-REC-X.690-201508 sec 8.4 says the contents are the same as for integers.
+ int sizeLimit = Marshal.SizeOf(backingType);
+
+ if (backingType == typeof(int) ||
+ backingType == typeof(long) ||
+ backingType == typeof(short) ||
+ backingType == typeof(sbyte))
+ {
+ if (!TryReadSignedInteger(sizeLimit, expectedTag, tagNumber, out long value))
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ return (Enum)Enum.ToObject(tEnum, value);
+ }
+
+ if (backingType == typeof(uint) ||
+ backingType == typeof(ulong) ||
+ backingType == typeof(ushort) ||
+ backingType == typeof(byte))
+ {
+ if (!TryReadUnsignedInteger(sizeLimit, expectedTag, tagNumber, out ulong value))
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ return (Enum)Enum.ToObject(tEnum, value);
+ }
+
+ Debug.Fail($"No handler for type {backingType.Name}");
+ throw new CryptographicException();
+ }
+
+ private bool TryGetPrimitiveOctetStringBytes(
+ Asn1Tag expectedTag,
+ out Asn1Tag actualTag,
+ out int? contentLength,
+ out int headerLength,
+ out ReadOnlyMemory<byte> contents,
+ UniversalTagNumber universalTagNumber = UniversalTagNumber.OctetString)
+ {
+ actualTag = ReadTagAndLength(out contentLength, out headerLength);
+ CheckExpectedTag(actualTag, expectedTag, universalTagNumber);
+
+ if (actualTag.IsConstructed)
+ {
+ if (_ruleSet == AsnEncodingRules.DER)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ contents = default(ReadOnlyMemory<byte>);
+ return false;
+ }
+
+ Debug.Assert(contentLength.HasValue);
+ ReadOnlyMemory<byte> encodedValue = Slice(_data, headerLength, contentLength.Value);
+
+ if (_ruleSet == AsnEncodingRules.CER && encodedValue.Length > MaxCERSegmentSize)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ contents = encodedValue;
+ return true;
+ }
+
+ private bool TryGetPrimitiveOctetStringBytes(
+ Asn1Tag expectedTag,
+ UniversalTagNumber universalTagNumber,
+ out ReadOnlyMemory<byte> contents)
+ {
+ if (TryGetPrimitiveOctetStringBytes(expectedTag, out _, out _, out int headerLength, out contents, universalTagNumber))
+ {
+ _data = _data.Slice(headerLength + contents.Length);
+ return true;
+ }
+
+ return false;
+ }
+
+ public bool TryGetPrimitiveOctetStringBytes(out ReadOnlyMemory<byte> contents) =>
+ TryGetPrimitiveOctetStringBytes(Asn1Tag.PrimitiveOctetString, out contents);
+
+ /// <summary>
+ /// Gets the contents for an octet string under a primitive encoding.
+ /// </summary>
+ /// <param name="expectedTag">The expected tag value</param>
+ /// <param name="contents">The contents for the octet string.</param>
+ /// <returns>
+ /// <c>true</c> if the octet string uses a primitive encoding, <c>false</c> otherwise.
+ /// </returns>
+ /// <exception cref="CryptographicException">
+ /// <ul>
+ /// <li>No data remains</li>
+ /// <li>The tag read did not match the expected tag</li>
+ /// <li>The length is invalid under the chosen encoding rules</li>
+ /// <li>A CER encoding was chosen and the primitive content length exceeds the maximum allowed</li>
+ /// </ul>
+ /// </exception>
+ public bool TryGetPrimitiveOctetStringBytes(Asn1Tag expectedTag, out ReadOnlyMemory<byte> contents)
+ {
+ return TryGetPrimitiveOctetStringBytes(expectedTag, UniversalTagNumber.OctetString, out contents);
+ }
+
+ private int CountConstructedOctetString(ReadOnlyMemory<byte> source, bool isIndefinite)
+ {
+ int contentLength = CopyConstructedOctetString(
+ source,
+ Span<byte>.Empty,
+ false,
+ isIndefinite,
+ out _);
+
+ // T-REC-X.690-201508 sec 9.2
+ if (_ruleSet == AsnEncodingRules.CER && contentLength <= MaxCERSegmentSize)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ return contentLength;
+ }
+
+ private void CopyConstructedOctetString(
+ ReadOnlyMemory<byte> source,
+ Span<byte> destination,
+ bool isIndefinite,
+ out int bytesRead,
+ out int bytesWritten)
+ {
+ bytesWritten = CopyConstructedOctetString(
+ source,
+ destination,
+ true,
+ isIndefinite,
+ out bytesRead);
+ }
+
+ private int CopyConstructedOctetString(
+ ReadOnlyMemory<byte> source,
+ Span<byte> destination,
+ bool write,
+ bool isIndefinite,
+ out int bytesRead)
+ {
+ bytesRead = 0;
+ int lastSegmentLength = MaxCERSegmentSize;
+
+ AsnReader tmpReader = new AsnReader(source, _ruleSet);
+ Stack<(AsnReader, bool, int)> readerStack = null;
+ int totalLength = 0;
+ Asn1Tag tag = Asn1Tag.ConstructedBitString;
+ Span<byte> curDest = destination;
+
+ do
+ {
+ while (tmpReader.HasData)
+ {
+ tag = tmpReader.ReadTagAndLength(out int? length, out int headerLength);
+
+ if (tag == Asn1Tag.PrimitiveOctetString)
+ {
+ if (_ruleSet == AsnEncodingRules.CER && lastSegmentLength != MaxCERSegmentSize)
+ {
+ // T-REC-X.690-201508 sec 9.2
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ Debug.Assert(length != null);
+
+ // The call to Slice here sanity checks the data bounds, length.Value is not
+ // reliable unless this call has succeeded.
+ ReadOnlyMemory<byte> contents = Slice(tmpReader._data, headerLength, length.Value);
+
+ int localLen = headerLength + contents.Length;
+ tmpReader._data = tmpReader._data.Slice(localLen);
+
+ bytesRead += localLen;
+ totalLength += contents.Length;
+ lastSegmentLength = contents.Length;
+
+ if (_ruleSet == AsnEncodingRules.CER && lastSegmentLength > MaxCERSegmentSize)
+ {
+ // T-REC-X.690-201508 sec 9.2
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ if (write)
+ {
+ contents.Span.CopyTo(curDest);
+ curDest = curDest.Slice(contents.Length);
+ }
+ }
+ else if (tag == Asn1Tag.EndOfContents && isIndefinite)
+ {
+ ValidateEndOfContents(tag, length, headerLength);
+
+ bytesRead += headerLength;
+
+ if (readerStack?.Count > 0)
+ {
+ (AsnReader topReader, bool wasIndefinite, int pushedBytesRead) = readerStack.Pop();
+ topReader._data = topReader._data.Slice(bytesRead);
+
+ bytesRead += pushedBytesRead;
+ isIndefinite = wasIndefinite;
+ tmpReader = topReader;
+ }
+ else
+ {
+ // We have matched the EndOfContents that brought us here.
+ break;
+ }
+ }
+ else if (tag == Asn1Tag.ConstructedOctetString)
+ {
+ if (_ruleSet == AsnEncodingRules.CER)
+ {
+ // T-REC-X.690-201508 sec 9.2
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ if (readerStack == null)
+ {
+ readerStack = new Stack<(AsnReader, bool, int)>();
+ }
+
+ readerStack.Push((tmpReader, isIndefinite, bytesRead));
+
+ tmpReader = new AsnReader(
+ Slice(tmpReader._data, headerLength, length),
+ _ruleSet);
+
+ bytesRead = headerLength;
+ isIndefinite = (length == null);
+ }
+ else
+ {
+ // T-REC-X.690-201508 sec 8.6.4.1 (in particular, Note 2)
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+
+ if (isIndefinite && tag != Asn1Tag.EndOfContents)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ if (readerStack?.Count > 0)
+ {
+ (AsnReader topReader, bool wasIndefinite, int pushedBytesRead) = readerStack.Pop();
+
+ tmpReader = topReader;
+ tmpReader._data = tmpReader._data.Slice(bytesRead);
+
+ isIndefinite = wasIndefinite;
+ bytesRead += pushedBytesRead;
+ }
+ else
+ {
+ tmpReader = null;
+ }
+ } while (tmpReader != null);
+
+ return totalLength;
+ }
+
+ private bool TryCopyConstructedOctetStringContents(
+ ReadOnlyMemory<byte> source,
+ Span<byte> dest,
+ bool isIndefinite,
+ out int bytesRead,
+ out int bytesWritten)
+ {
+ bytesRead = 0;
+
+ int contentLength = CountConstructedOctetString(source, isIndefinite);
+
+ if (dest.Length < contentLength)
+ {
+ bytesWritten = 0;
+ return false;
+ }
+
+ CopyConstructedOctetString(source, dest, isIndefinite, out bytesRead, out bytesWritten);
+
+ Debug.Assert(bytesWritten == contentLength);
+ return true;
+ }
+
+ public bool TryCopyOctetStringBytes(
+ Span<byte> destination,
+ out int bytesWritten)
+ {
+ return TryCopyOctetStringBytes(
+ Asn1Tag.PrimitiveOctetString,
+ destination,
+ out bytesWritten);
+ }
+
+ public bool TryCopyOctetStringBytes(
+ Asn1Tag expectedTag,
+ Span<byte> destination,
+ out int bytesWritten)
+ {
+ if (TryGetPrimitiveOctetStringBytes(
+ expectedTag,
+ out Asn1Tag actualTag,
+ out int? contentLength,
+ out int headerLength,
+ out ReadOnlyMemory<byte> contents))
+ {
+ if (contents.Length > destination.Length)
+ {
+ bytesWritten = 0;
+ return false;
+ }
+
+ contents.Span.CopyTo(destination);
+ bytesWritten = contents.Length;
+ _data = _data.Slice(headerLength + contents.Length);
+ return true;
+ }
+
+ Debug.Assert(actualTag.IsConstructed);
+
+ bool copied = TryCopyConstructedOctetStringContents(
+ Slice(_data, headerLength, contentLength),
+ destination,
+ contentLength == null,
+ out int bytesRead,
+ out bytesWritten);
+
+ if (copied)
+ {
+ _data = _data.Slice(headerLength + bytesRead);
+ }
+
+ return copied;
+ }
+
+ public void ReadNull() => ReadNull(Asn1Tag.Null);
+
+ public void ReadNull(Asn1Tag expectedTag)
+ {
+ Asn1Tag tag = ReadTagAndLength(out int? length, out int headerLength);
+ CheckExpectedTag(tag, expectedTag, UniversalTagNumber.Null);
+
+ // T-REC-X.690-201508 sec 8.8.1
+ // T-REC-X.690-201508 sec 8.8.2
+ if (tag.IsConstructed || length != 0)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ _data = _data.Slice(headerLength);
+ }
+
+ private static void ReadSubIdentifier(
+ ReadOnlySpan<byte> source,
+ out int bytesRead,
+ out long? smallValue,
+ out BigInteger? largeValue)
+ {
+ Debug.Assert(source.Length > 0);
+
+ // T-REC-X.690-201508 sec 8.19.2 (last sentence)
+ if (source[0] == 0x80)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ // First, see how long the segment is
+ int end = -1;
+ int idx;
+
+ for (idx = 0; idx < source.Length; idx++)
+ {
+ // If the high bit isn't set this marks the end of the sub-identifier.
+ bool endOfIdentifier = (source[idx] & 0x80) == 0;
+
+ if (endOfIdentifier)
+ {
+ end = idx;
+ break;
+ }
+ }
+
+ if (end < 0)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ bytesRead = end + 1;
+ long accum = 0;
+
+ // Fast path, 9 or fewer bytes => fits in a signed long.
+ // (7 semantic bits per byte * 9 bytes = 63 bits, which leaves the sign bit alone)
+ if (bytesRead <= 9)
+ {
+ for (idx = 0; idx < bytesRead; idx++)
+ {
+ byte cur = source[idx];
+ accum <<= 7;
+ accum |= (byte)(cur & 0x7F);
+ }
+
+ largeValue = null;
+ smallValue = accum;
+ return;
+ }
+
+ // Slow path, needs temporary storage.
+
+ const int SemanticByteCount = 7;
+ const int ContentByteCount = 8;
+
+ // Every 8 content bytes turns into 7 integer bytes, so scale the count appropriately.
+ // Add one while we're shrunk to account for the needed padding byte or the len%8 discarded bytes.
+ int bytesRequired = ((bytesRead / ContentByteCount) + 1) * SemanticByteCount;
+ byte[] tmpBytes = ArrayPool<byte>.Shared.Rent(bytesRequired);
+ // Ensure all the bytes are zeroed out for BigInteger's parsing.
+ Array.Clear(tmpBytes, 0, tmpBytes.Length);
+
+ Span<byte> writeSpan = tmpBytes;
+ Span<byte> accumValueBytes = stackalloc byte[sizeof(long)];
+ int nextStop = bytesRead;
+ idx = bytesRead - ContentByteCount;
+
+ while (nextStop > 0)
+ {
+ byte cur = source[idx];
+
+ accum <<= 7;
+ accum |= (byte)(cur & 0x7F);
+
+ idx++;
+
+ if (idx >= nextStop)
+ {
+ Debug.Assert(idx == nextStop);
+ Debug.Assert(writeSpan.Length >= SemanticByteCount);
+
+ BinaryPrimitives.WriteInt64LittleEndian(accumValueBytes, accum);
+ Debug.Assert(accumValueBytes[7] == 0);
+ accumValueBytes.Slice(0, SemanticByteCount).CopyTo(writeSpan);
+ writeSpan = writeSpan.Slice(SemanticByteCount);
+
+ accum = 0;
+ nextStop -= ContentByteCount;
+ idx = Math.Max(0, nextStop - ContentByteCount);
+ }
+ }
+
+ int bytesWritten = tmpBytes.Length - writeSpan.Length;
+
+ // Verify our bytesRequired calculation. There should be at most 7 padding bytes.
+ // If the length % 8 is 7 we'll have 0 padding bytes, but the sign bit is still clear.
+ //
+ // 8 content bytes had a sign bit problem, so we gave it a second 7-byte block, 7 remain.
+ // 7 content bytes got a single block but used and wrote 7 bytes, but only 49 of the 56 bits.
+ // 6 content bytes have a padding count of 1.
+ // 1 content byte has a padding count of 6.
+ // 0 content bytes is illegal, but see 8 for the cycle.
+ int paddingByteCount = bytesRequired - bytesWritten;
+ Debug.Assert(paddingByteCount >= 0 && paddingByteCount < sizeof(long));
+
+ largeValue = new BigInteger(tmpBytes);
+ smallValue = null;
+
+ Array.Clear(tmpBytes, 0, bytesWritten);
+ ArrayPool<byte>.Shared.Return(tmpBytes);
+ }
+
+ private string ReadObjectIdentifierAsString(Asn1Tag expectedTag, out int totalBytesRead)
+ {
+ Asn1Tag tag = ReadTagAndLength(out int? length, out int headerLength);
+ CheckExpectedTag(tag, expectedTag, UniversalTagNumber.ObjectIdentifier);
+
+ // T-REC-X.690-201508 sec 8.19.1
+ // T-REC-X.690-201508 sec 8.19.2 says the minimum length is 1
+ if (tag.IsConstructed || length < 1)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ ReadOnlyMemory<byte> contentsMemory = Slice(_data, headerLength, length.Value);
+ ReadOnlySpan<byte> contents = contentsMemory.Span;
+
+ // Each byte can contribute a 3 digit value and a '.' (e.g. "126."), but usually
+ // they convey one digit and a separator.
+ //
+ // The OID with the most arcs which were found after a 30 minute search is
+ // "1.3.6.1.4.1.311.60.2.1.1" (EV cert jurisdiction of incorporation - locality)
+ // which has 11 arcs.
+ // The longest "known" segment is 16 bytes, a UUID-as-an-arc value.
+ // 16 * 11 = 176 bytes for an "extremely long" OID.
+ //
+ // So pre-allocate the StringBuilder with at most 1020 characters, an input longer than
+ // 255 encoded bytes will just have to re-allocate.
+ StringBuilder builder = new StringBuilder(((byte)contents.Length) * 4);
+
+ ReadSubIdentifier(contents, out int bytesRead, out long? smallValue, out BigInteger? largeValue);
+
+ // T-REC-X.690-201508 sec 8.19.4
+ // The first two subidentifiers (X.Y) are encoded as (X * 40) + Y, because Y is
+ // bounded [0, 39] for X in {0, 1}, and only X in {0, 1, 2} are legal.
+ // So:
+ // * identifier < 40 => X = 0, Y = identifier.
+ // * identifier < 80 => X = 1, Y = identifier - 40.
+ // * else: X = 2, Y = identifier - 80.
+ byte firstArc;
+
+ if (smallValue != null)
+ {
+ long firstIdentifier = smallValue.Value;
+
+ if (firstIdentifier < 40)
+ {
+ firstArc = 0;
+ }
+ else if (firstIdentifier < 80)
+ {
+ firstArc = 1;
+ firstIdentifier -= 40;
+ }
+ else
+ {
+ firstArc = 2;
+ firstIdentifier -= 80;
+ }
+
+ builder.Append(firstArc);
+ builder.Append('.');
+ builder.Append(firstIdentifier);
+ }
+ else
+ {
+ Debug.Assert(largeValue != null);
+ BigInteger firstIdentifier = largeValue.Value;
+
+ // We're only here because we were bigger than long.MaxValue, so
+ // we're definitely on arc 2.
+ Debug.Assert(firstIdentifier > long.MaxValue);
+
+ firstArc = 2;
+ firstIdentifier -= 80;
+
+ builder.Append(firstArc);
+ builder.Append('.');
+ builder.Append(firstIdentifier.ToString());
+ }
+
+ contents = contents.Slice(bytesRead);
+
+ while (!contents.IsEmpty)
+ {
+ ReadSubIdentifier(contents, out bytesRead, out smallValue, out largeValue);
+ // Exactly one should be non-null.
+ Debug.Assert((smallValue == null) != (largeValue == null));
+
+ builder.Append('.');
+
+ if (smallValue != null)
+ {
+ builder.Append(smallValue.Value);
+ }
+ else
+ {
+ builder.Append(largeValue.Value.ToString());
+ }
+
+ contents = contents.Slice(bytesRead);
+ }
+
+ totalBytesRead = headerLength + length.Value;
+ return builder.ToString();
+ }
+
+ public string ReadObjectIdentifierAsString() =>
+ ReadObjectIdentifierAsString(Asn1Tag.ObjectIdentifier);
+
+ public string ReadObjectIdentifierAsString(Asn1Tag expectedTag)
+ {
+ string oidValue = ReadObjectIdentifierAsString(expectedTag, out int bytesRead);
+
+ _data = _data.Slice(bytesRead);
+
+ return oidValue;
+ }
+
+ public Oid ReadObjectIdentifier(bool skipFriendlyName = false) =>
+ ReadObjectIdentifier(Asn1Tag.ObjectIdentifier, skipFriendlyName);
+
+ public Oid ReadObjectIdentifier(Asn1Tag expectedTag, bool skipFriendlyName=false)
+ {
+ string oidValue = ReadObjectIdentifierAsString(expectedTag, out int bytesRead);
+ Oid oid = skipFriendlyName ? new Oid(oidValue, oidValue) : new Oid(oidValue);
+
+ // Don't slice until the return object has been created.
+ _data = _data.Slice(bytesRead);
+
+ return oid;
+ }
+
+ // T-REC-X.690-201508 sec 8.23
+ private bool TryCopyCharacterStringBytes(
+ Asn1Tag expectedTag,
+ UniversalTagNumber universalTagNumber,
+ Span<byte> destination,
+ out int bytesRead,
+ out int bytesWritten)
+ {
+ // T-REC-X.690-201508 sec 8.23.3, all character strings are encoded as octet strings.
+ if (TryGetPrimitiveOctetStringBytes(
+ expectedTag,
+ out Asn1Tag actualTag,
+ out int? contentLength,
+ out int headerLength,
+ out ReadOnlyMemory<byte> contents,
+ universalTagNumber))
+ {
+ bytesWritten = contents.Length;
+
+ if (destination.Length < bytesWritten)
+ {
+ bytesWritten = 0;
+ bytesRead = 0;
+ return false;
+ }
+
+ contents.Span.CopyTo(destination);
+ bytesRead = headerLength + bytesWritten;
+ return true;
+ }
+
+ Debug.Assert(actualTag.IsConstructed);
+
+ bool copied = TryCopyConstructedOctetStringContents(
+ Slice(_data, headerLength, contentLength),
+ destination,
+ contentLength == null,
+ out int contentBytesRead,
+ out bytesWritten);
+
+ if (copied)
+ {
+ bytesRead = headerLength + contentBytesRead;
+ }
+ else
+ {
+ bytesRead = 0;
+ }
+
+ return copied;
+ }
+
+ private static unsafe bool TryCopyCharacterString(
+ ReadOnlySpan<byte> source,
+ Span<char> destination,
+ Text.Encoding encoding,
+ out int charsWritten)
+ {
+ if (source.Length == 0)
+ {
+ charsWritten = 0;
+ return true;
+ }
+
+ fixed (byte* bytePtr = &source.DangerousGetPinnableReference())
+ fixed (char* charPtr = &destination.DangerousGetPinnableReference())
+ {
+ try
+ {
+ int charCount = encoding.GetCharCount(bytePtr, source.Length);
+
+ if (charCount > destination.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ charsWritten = encoding.GetChars(bytePtr, source.Length, charPtr, destination.Length);
+ Debug.Assert(charCount == charsWritten);
+ }
+ catch (DecoderFallbackException e)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
+ }
+
+ return true;
+ }
+ }
+
+ private string GetCharacterString(
+ Asn1Tag expectedTag,
+ UniversalTagNumber universalTagNumber,
+ Text.Encoding encoding)
+ {
+ byte[] rented = null;
+
+ // T-REC-X.690-201508 sec 8.23.3, all character strings are encoded as octet strings.
+ ReadOnlySpan<byte> contents = GetOctetStringContents(
+ expectedTag,
+ universalTagNumber,
+ out int bytesRead,
+ ref rented);
+
+ try
+ {
+ string str;
+
+ if (contents.Length == 0)
+ {
+ str = string.Empty;
+ }
+ else
+ {
+ unsafe
+ {
+ fixed (byte* bytePtr = &contents.DangerousGetPinnableReference())
+ {
+ try
+ {
+ str = encoding.GetString(bytePtr, contents.Length);
+ }
+ catch (DecoderFallbackException e)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
+ }
+ }
+ }
+ }
+
+ _data = _data.Slice(bytesRead);
+ return str;
+ }
+ finally
+ {
+ if (rented != null)
+ {
+ Array.Clear(rented, 0, contents.Length);
+ ArrayPool<byte>.Shared.Return(rented);
+ }
+ }
+ }
+
+ private bool TryCopyCharacterString(
+ Asn1Tag expectedTag,
+ UniversalTagNumber universalTagNumber,
+ Text.Encoding encoding,
+ Span<char> destination,
+ out int charsWritten)
+ {
+ byte[] rented = null;
+
+ // T-REC-X.690-201508 sec 8.23.3, all character strings are encoded as octet strings.
+ ReadOnlySpan<byte> contents = GetOctetStringContents(
+ expectedTag,
+ universalTagNumber,
+ out int bytesRead,
+ ref rented);
+
+ try
+ {
+ bool copied = TryCopyCharacterString(
+ contents,
+ destination,
+ encoding,
+ out charsWritten);
+
+ if (copied)
+ {
+ _data = _data.Slice(bytesRead);
+ }
+
+ return copied;
+ }
+ finally
+ {
+ if (rented != null)
+ {
+ Array.Clear(rented, 0, contents.Length);
+ ArrayPool<byte>.Shared.Return(rented);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the source data for a character string under a primitive encoding and tagged as
+ /// the universal class tag for the encoding type.
+ /// </summary>
+ /// <param name="encodingType">The UniversalTagNumber for the string encoding type.</param>
+ /// <param name="contents">The content bytes for the UTF8String payload.</param>
+ /// <returns>
+ /// <c>true</c> if the character string uses a primitive encoding, <c>false</c> otherwise.
+ /// </returns>
+ /// <exception cref="CryptographicException">
+ /// <ul>
+ /// <li>No data remains</li>
+ /// <li>The tag read does not match the expected tag</li>
+ /// <li>The length is invalid under the chosen encoding rules</li>
+ /// <li>A CER encoding was chosen and the primitive content length exceeds the maximum allowed</li>
+ /// </ul>
+ /// </exception>
+ public bool TryGetPrimitiveCharacterStringBytes(UniversalTagNumber encodingType, out ReadOnlyMemory<byte> contents)
+ {
+ return TryGetPrimitiveCharacterStringBytes(new Asn1Tag(encodingType), encodingType, out contents);
+ }
+
+ /// <summary>
+ /// Gets the uninterpreted contents for a character string under a primitive encoding.
+ /// The contents are not validated as belonging to the requested encoding type.
+ /// </summary>
+ /// <param name="expectedTag">The expected tag</param>
+ /// <param name="encodingType">The UniversalTagNumber for the string encoding type.</param>
+ /// <param name="contents">The contents for the character string.</param>
+ /// <returns>
+ /// <c>true</c> if the character string uses a primitive encoding, <c>false</c> otherwise.
+ /// </returns>
+ /// <exception cref="CryptographicException">
+ /// <ul>
+ /// <li>No data remains</li>
+ /// <li>The tag read does not match the expected tag</li>
+ /// <li>The length is invalid under the chosen encoding rules</li>
+ /// <li>A CER encoding was chosen and the primitive content length exceeds the maximum allowed</li>
+ /// </ul>
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="encodingType"/> is not a known character string encoding type.
+ /// </exception>
+ public bool TryGetPrimitiveCharacterStringBytes(
+ Asn1Tag expectedTag,
+ UniversalTagNumber encodingType,
+ out ReadOnlyMemory<byte> contents)
+ {
+ CheckCharacterStringEncodingType(encodingType);
+
+ // T-REC-X.690-201508 sec 8.23.3, all character strings are encoded as octet strings.
+ return TryGetPrimitiveOctetStringBytes(expectedTag, encodingType, out contents);
+ }
+
+ public bool TryCopyCharacterStringBytes(
+ UniversalTagNumber encodingType,
+ Span<byte> destination,
+ out int bytesWritten)
+ {
+ return TryCopyCharacterStringBytes(
+ new Asn1Tag(encodingType),
+ encodingType,
+ destination,
+ out bytesWritten);
+ }
+
+ public bool TryCopyCharacterStringBytes(
+ Asn1Tag expectedTag,
+ UniversalTagNumber encodingType,
+ Span<byte> destination,
+ out int bytesWritten)
+ {
+ CheckCharacterStringEncodingType(encodingType);
+
+ bool copied = TryCopyCharacterStringBytes(
+ expectedTag,
+ encodingType,
+ destination,
+ out int bytesRead,
+ out bytesWritten);
+
+ if (copied)
+ {
+ _data = _data.Slice(bytesRead);
+ }
+
+ return copied;
+ }
+
+ public bool TryCopyCharacterString(
+ UniversalTagNumber encodingType,
+ Span<char> destination,
+ out int charsWritten)
+ {
+ return TryCopyCharacterString(
+ new Asn1Tag(encodingType),
+ encodingType,
+ destination,
+ out charsWritten);
+ }
+
+ public bool TryCopyCharacterString(
+ Asn1Tag expectedTag,
+ UniversalTagNumber encodingType,
+ Span<char> destination,
+ out int charsWritten)
+ {
+ Text.Encoding encoding = AsnCharacterStringEncodings.GetEncoding(encodingType);
+ return TryCopyCharacterString(expectedTag, encodingType, encoding, destination, out charsWritten);
+ }
+
+ public string GetCharacterString(UniversalTagNumber encodingType) =>
+ GetCharacterString(new Asn1Tag(encodingType), encodingType);
+
+ public string GetCharacterString(Asn1Tag expectedTag, UniversalTagNumber encodingType)
+ {
+ Text.Encoding encoding = AsnCharacterStringEncodings.GetEncoding(encodingType);
+ return GetCharacterString(expectedTag, encodingType, encoding);
+ }
+
+ public AsnReader ReadSequence() => ReadSequence(Asn1Tag.Sequence);
+
+ public AsnReader ReadSequence(Asn1Tag expectedTag)
+ {
+ Asn1Tag tag = ReadTagAndLength(out int? length, out int headerLength);
+ CheckExpectedTag(tag, expectedTag, UniversalTagNumber.Sequence);
+
+ // T-REC-X.690-201508 sec 8.9.1
+ // T-REC-X.690-201508 sec 8.10.1
+ if (!tag.IsConstructed)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ int suffix = 0;
+
+ if (length == null)
+ {
+ length = SeekEndOfContents(_data.Slice(headerLength));
+ suffix = EndOfContentsEncodedLength;
+ }
+
+ ReadOnlyMemory<byte> contents = Slice(_data, headerLength, length.Value);
+
+ _data = _data.Slice(headerLength + contents.Length + suffix);
+ return new AsnReader(contents, _ruleSet);
+ }
+
+ /// <summary>
+ /// Builds a new AsnReader over the bytes bounded by the current position which
+ /// corresponds to an ASN.1 SET OF value, validating the CER or DER sort ordering
+ /// unless suppressed.
+ /// </summary>
+ /// <param name="skipSortOrderValidation">
+ /// <c>false</c> to validate the sort ordering of the contents, <c>true</c> to
+ /// allow reading the data without verifying it was properly sorted by the writer.
+ /// </param>
+ /// <returns>An AsnReader over the current position, bounded by the contained length value.</returns>
+ public AsnReader ReadSetOf(bool skipSortOrderValidation = false) =>
+ ReadSetOf(Asn1Tag.SetOf, skipSortOrderValidation);
+
+ public AsnReader ReadSetOf(Asn1Tag expectedTag, bool skipSortOrderValidation = false)
+ {
+ Asn1Tag tag = ReadTagAndLength(out int? length, out int headerLength);
+ CheckExpectedTag(tag, expectedTag, UniversalTagNumber.SetOf);
+
+ // T-REC-X.690-201508 sec 8.12.1
+ if (!tag.IsConstructed)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ int suffix = 0;
+
+ if (length == null)
+ {
+ length = SeekEndOfContents(_data.Slice(headerLength));
+ suffix = EndOfContentsEncodedLength;
+ }
+
+ ReadOnlyMemory<byte> contents = Slice(_data, headerLength, length.Value);
+
+ if (!skipSortOrderValidation)
+ {
+ // T-REC-X.690-201508 sec 11.6
+ // BER data is not required to be sorted.
+ if (_ruleSet == AsnEncodingRules.DER ||
+ _ruleSet == AsnEncodingRules.CER)
+ {
+ AsnReader reader = new AsnReader(contents, _ruleSet);
+ ReadOnlyMemory<byte> current = ReadOnlyMemory<byte>.Empty;
+ SetOfValueComparer comparer = SetOfValueComparer.Instance;
+
+ while (reader.HasData)
+ {
+ ReadOnlyMemory<byte> previous = current;
+ current = reader.GetEncodedValue();
+
+ if (comparer.Compare(current, previous) < 0)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+ }
+ }
+
+ _data = _data.Slice(headerLength + contents.Length + suffix);
+ return new AsnReader(contents, _ruleSet);
+ }
+
+ private static int ParseNonNegativeIntAndSlice(ref ReadOnlySpan<byte> data, int bytesToRead)
+ {
+ int value = ParseNonNegativeInt(Slice(data, 0, bytesToRead));
+ data = data.Slice(bytesToRead);
+
+ return value;
+ }
+
+ private static int ParseNonNegativeInt(ReadOnlySpan<byte> data)
+ {
+ if (Utf8Parser.TryParse(data, out uint value, out int consumed) && value <= int.MaxValue && consumed == data.Length)
+ {
+ return (int)value;
+ }
+
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ private DateTimeOffset ParseUtcTime(ReadOnlySpan<byte> contentOctets, int twoDigitYearMax)
+ {
+ // The full allowed formats (T-REC-X.680-201510 sec 47.3)
+ // a) YYMMDD
+ // b1) hhmm
+ // b2) hhmmss
+ // c1) Z
+ // c2) {+|-}hhmm
+ //
+ // YYMMDDhhmmZ (a, b1, c1)
+ // YYMMDDhhmm+hhmm (a, b1, c2+)
+ // YYMMDDhhmm-hhmm (a, b1, c2-)
+ // YYMMDDhhmmssZ (a, b2, c1)
+ // YYMMDDhhmmss+hhmm (a, b2, c2+)
+ // YYMMDDhhmmss-hhmm (a, b2, c2-)
+
+ const int NoSecondsZulu = 11;
+ const int NoSecondsOffset = 15;
+ const int HasSecondsZulu = 13;
+ const int HasSecondsOffset = 17;
+
+ // T-REC-X.690-201510 sec 11.8
+ if (_ruleSet == AsnEncodingRules.DER || _ruleSet == AsnEncodingRules.CER)
+ {
+ if (contentOctets.Length != HasSecondsZulu)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+
+ // 11, 13, 15, 17 are legal.
+ // Range check + odd.
+ if (contentOctets.Length < NoSecondsZulu ||
+ contentOctets.Length > HasSecondsOffset ||
+ (contentOctets.Length & 1) != 1)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ ReadOnlySpan<byte> contents = contentOctets;
+
+ int year = ParseNonNegativeIntAndSlice(ref contents, 2);
+ int month = ParseNonNegativeIntAndSlice(ref contents, 2);
+ int day = ParseNonNegativeIntAndSlice(ref contents, 2);
+ int hour = ParseNonNegativeIntAndSlice(ref contents, 2);
+ int minute = ParseNonNegativeIntAndSlice(ref contents, 2);
+ int second = 0;
+ int offsetHour = 0;
+ int offsetMinute = 0;
+ bool minus = false;
+
+ if (contentOctets.Length == HasSecondsOffset ||
+ contentOctets.Length == HasSecondsZulu)
+ {
+ second = ParseNonNegativeIntAndSlice(ref contents, 2);
+ }
+
+ if (contentOctets.Length == NoSecondsZulu ||
+ contentOctets.Length == HasSecondsZulu)
+ {
+ if (contents[0] != 'Z')
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+ else
+ {
+ Debug.Assert(
+ contentOctets.Length == NoSecondsOffset ||
+ contentOctets.Length == HasSecondsOffset);
+
+ if (contents[0] == '-')
+ {
+ minus = true;
+ }
+ else if (contents[0] != '+')
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ contents = contents.Slice(1);
+ offsetHour = ParseNonNegativeIntAndSlice(ref contents, 2);
+ offsetMinute = ParseNonNegativeIntAndSlice(ref contents, 2);
+ Debug.Assert(contents.IsEmpty);
+ }
+
+ // ISO 8601:2004 4.2.1 restricts a "minute" value to [00,59].
+ // The "hour" value is effectively bound to [00,23] by the same section, but
+ // is bound to [00,14] by DateTimeOffset, so no additional check is required here.
+ if (offsetMinute > 59)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ TimeSpan offset = new TimeSpan(offsetHour, offsetMinute, 0);
+
+ if (minus)
+ {
+ offset = -offset;
+ }
+
+ // Apply the twoDigitYearMax value.
+ // Example: year=50, TDYM=2049
+ // century = 20
+ // year > 49 => century = 19
+ // scaledYear = 1900 + 50 = 1950
+ //
+ // Example: year=49, TDYM=2049
+ // century = 20
+ // year is not > 49 => century = 20
+ // scaledYear = 2000 + 49 = 2049
+ int century = twoDigitYearMax / 100;
+
+ if (year > twoDigitYearMax % 100)
+ {
+ century--;
+ }
+
+ int scaledYear = century * 100 + year;
+
+ try
+ {
+ return new DateTimeOffset(scaledYear, month, day, hour, minute, second, offset);
+ }
+ catch (Exception e)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
+ }
+ }
+
+ public DateTimeOffset GetUtcTime(int twoDigitYearMax = 2049) =>
+ GetUtcTime(Asn1Tag.UtcTime, twoDigitYearMax);
+
+ /// <summary>
+ /// Gets the DateTimeOffset represented by a UTCTime value.
+ /// </summary>
+ /// <param name="expectedTag">The expected tag</param>
+ /// <param name="twoDigitYearMax">
+ /// The largest year to represent with this value.
+ /// The default value, 2049, represents the 1950-2049 range for X.509 certificates.
+ /// </param>
+ /// <returns>
+ /// A DateTimeOffset representing the value encoded in the UTCTime.
+ /// </returns>
+ /// <seealso cref="System.Globalization.Calendar.TwoDigitYearMax"/>
+ public DateTimeOffset GetUtcTime(Asn1Tag expectedTag, int twoDigitYearMax = 2049)
+ {
+ // T-REC-X.680-201510 sec 47.3 says it is IMPLICIT VisibleString, which means
+ // that BER is allowed to do complex constructed forms.
+
+ // The full allowed formats (T-REC-X.680-201510 sec 47.3)
+ // YYMMDDhhmmZ (a, b1, c1)
+ // YYMMDDhhmm+hhmm (a, b1, c2+)
+ // YYMMDDhhmm-hhmm (a, b1, c2-)
+ // YYMMDDhhmmssZ (a, b2, c1)
+ // YYMMDDhhmmss+hhmm (a, b2, c2+)
+ // YYMMDDhhmmss-hhmm (a, b2, c2-)
+
+ // CER and DER are restricted to YYMMDDhhmmssZ
+ // T-REC-X.690-201510 sec 11.8
+
+ byte[] rented = null;
+ // The longest format is 17 bytes.
+ Span<byte> tmpSpace = stackalloc byte[17];
+
+ ReadOnlySpan<byte> contents = GetOctetStringContents(
+ expectedTag,
+ UniversalTagNumber.UtcTime,
+ out int bytesRead,
+ ref rented,
+ tmpSpace);
+
+ DateTimeOffset value = ParseUtcTime(contents, twoDigitYearMax);
+
+ if (rented != null)
+ {
+ Debug.Fail($"UtcTime did not fit in tmpSpace ({contents.Length} total)");
+ Array.Clear(rented, 0, contents.Length);
+ ArrayPool<byte>.Shared.Return(rented);
+ }
+
+ _data = _data.Slice(bytesRead);
+ return value;
+ }
+
+ private static DateTimeOffset ParseGeneralizedTime(
+ AsnEncodingRules ruleSet,
+ ReadOnlySpan<byte> contentOctets,
+ bool disallowFractions)
+ {
+ // T-REC-X.680-201510 sec 46 defines a lot of formats for GeneralizedTime.
+ //
+ // All formats start with yyyyMMdd.
+ //
+ // "Local time" formats are
+ // [date]HH.fractionOfAnHourToAnArbitraryPrecision
+ // [date]HHmm.fractionOfAMinuteToAnArbitraryPrecision
+ // [date]HHmmss.fractionOfASecondToAnArbitraryPrecision
+ //
+ // "UTC time" formats are the local formats suffixed with 'Z'
+ //
+ // "UTC offset time" formats are the local formats suffixed with
+ // +HH
+ // +HHmm
+ // -HH
+ // -HHmm
+ //
+ // Since T-REC-X.680-201510 46.3(a)(1) and 46.3(a)(2) both specify the ISO 8601:2004
+ // Basic format, we shall presume that 46.3(a)(3) also meant only the Basic format,
+ // and therefore [+/-]HH:mm (with the colon) are prohibited. (based on ISO 8601:201x-DIS)
+
+ // Since DateTimeOffset doesn't have a notion of
+ // "I'm a local time, but with an unknown offset", the computer's current offset will
+ // be used.
+
+ // T-REC-X.690-201510 sec 11.7 binds CER and DER to a much smaller set of inputs:
+ // * Only the UTC/Z format can be used.
+ // * HHmmss must always be used
+ // * If fractions are present they will be separated by period, never comma.
+ // * If fractions are present the last digit mustn't be 0.
+
+ bool strict = ruleSet == AsnEncodingRules.DER || ruleSet == AsnEncodingRules.CER;
+ if (strict && contentOctets.Length < 15)
+ {
+ // yyyyMMddHHmmssZ
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ else if (contentOctets.Length < 10)
+ {
+ // yyyyMMddHH
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ ReadOnlySpan<byte> contents = contentOctets;
+
+ int year = ParseNonNegativeIntAndSlice(ref contents, 4);
+ int month = ParseNonNegativeIntAndSlice(ref contents, 2);
+ int day = ParseNonNegativeIntAndSlice(ref contents, 2);
+ int hour = ParseNonNegativeIntAndSlice(ref contents, 2);
+ int? minute = null;
+ int? second = null;
+ ulong fraction = 0;
+ ulong fractionScale = 1;
+ byte lastFracDigit = 0xFF;
+ TimeSpan? timeOffset = null;
+ bool isZulu = false;
+
+ const byte HmsState = 0;
+ const byte FracState = 1;
+ const byte SuffixState = 2;
+ byte state = HmsState;
+
+ byte? GetNextState(byte octet)
+ {
+ if (octet == 'Z' || octet == '-' || octet == '+')
+ {
+ return SuffixState;
+ }
+
+ if (octet == '.' || octet == ',')
+ {
+ return FracState;
+ }
+
+ return null;
+ }
+
+ // This while loop could be rewritten to include the FracState and Suffix
+ // processing steps. But since there's a forward flow to the state machine
+ // the loop body then needs to account for that.
+ while (state == HmsState && contents.Length != 0)
+ {
+ byte? nextState = GetNextState(contents[0]);
+
+ if (nextState == null)
+ {
+ if (minute == null)
+ {
+ minute = ParseNonNegativeIntAndSlice(ref contents, 2);
+ }
+ else if (second == null)
+ {
+ second = ParseNonNegativeIntAndSlice(ref contents, 2);
+ }
+ else
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+ else
+ {
+ state = nextState.Value;
+ }
+ }
+
+ if (state == FracState)
+ {
+ if (disallowFractions)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ Debug.Assert(!contents.IsEmpty);
+ byte octet = contents[0];
+ Debug.Assert(state == GetNextState(octet));
+
+ if (octet == '.')
+ {
+ // Always valid
+ }
+ else if (octet == ',')
+ {
+ // Valid for BER, but not CER or DER.
+ // T-REC-X.690-201510 sec 11.7.4
+ if (strict)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+ else
+ {
+ Debug.Fail($"Unhandled value '{octet:X2}' in {nameof(FracState)}");
+ throw new CryptographicException();
+ }
+
+ contents = contents.Slice(1);
+
+ if (contents.IsEmpty)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ // There are 36,000,000,000 ticks per hour, and hour is our largest scale.
+ // In case the double -> Ticks conversion allows for rounding up we can allow
+ // for a 12th digit.
+
+ if (!Utf8Parser.TryParse(SliceAtMost(contents, 12), out fraction, out int fracLength) || fracLength == 0)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ lastFracDigit = (byte)(fraction % 10);
+
+ for (int i = 0; i < fracLength; i++)
+ {
+ fractionScale *= 10;
+ }
+
+ contents = contents.Slice(fracLength);
+
+ // Drain off any remaining digits.
+ // The unsigned parsers will not accept + or - as a leading character, so
+ // they won't eat timezone suffix.
+ // But Utf8Parser.TryParse reports false on overflow, so limit it to 9 digits at a time.
+ while (Utf8Parser.TryParse(SliceAtMost(contents, 9), out uint nonSemantic, out fracLength))
+ {
+ contents = contents.Slice(fracLength);
+ lastFracDigit = (byte)(nonSemantic % 10);
+ }
+
+ if (contents.Length != 0)
+ {
+ byte? nextState = GetNextState(contents[0]);
+
+ if (nextState == null)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ // If this produces FracState we'll finish with a non-empty contents, and still throw.
+ state = nextState.Value;
+ }
+ }
+
+ if (state == SuffixState)
+ {
+ Debug.Assert(!contents.IsEmpty);
+ byte octet = contents[0];
+ Debug.Assert(state == GetNextState(octet));
+ contents = contents.Slice(1);
+
+ if (octet == 'Z')
+ {
+ timeOffset = TimeSpan.Zero;
+ isZulu = true;
+ }
+ else
+ {
+ bool isMinus;
+
+ if (octet == '+')
+ {
+ isMinus = false;
+ }
+ else if (octet == '-')
+ {
+ isMinus = true;
+ }
+ else
+ {
+ Debug.Fail($"Unhandled value '{octet:X2}' in {nameof(SuffixState)}");
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ if (contents.IsEmpty)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ int offsetHour = ParseNonNegativeIntAndSlice(ref contents, 2);
+ int offsetMinute = 0;
+
+ if (contents.Length != 0)
+ {
+ offsetMinute = ParseNonNegativeIntAndSlice(ref contents, 2);
+ }
+
+ // ISO 8601:2004 4.2.1 restricts a "minute" value to [00,59].
+ // The "hour" value is effectively bound to [00,23] by the same section, but
+ // is bound to [00,14] by DateTimeOffset, so no additional check is required here.
+ if (offsetMinute > 59)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ TimeSpan tmp = new TimeSpan(offsetHour, offsetMinute, 0);
+
+ if (isMinus)
+ {
+ tmp = -tmp;
+ }
+
+ timeOffset = tmp;
+ }
+ }
+
+ // Was there data after a suffix, or fracstate went re-entrant?
+ if (!contents.IsEmpty)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ // T-REC-X.690-201510 sec 11.7
+ if (strict)
+ {
+ if (!isZulu || !second.HasValue)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ if (lastFracDigit == 0)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+
+ double frac = (double)fraction / fractionScale;
+ TimeSpan fractionSpan = TimeSpan.Zero;
+
+ if (!minute.HasValue)
+ {
+ minute = 0;
+ second = 0;
+
+ if (fraction != 0)
+ {
+ // No minutes means this is fractions of an hour
+ fractionSpan = TimeSpan.FromHours(frac);
+ }
+ }
+ else if (!second.HasValue)
+ {
+ second = 0;
+
+ if (fraction != 0)
+ {
+ // No seconds means this is fractions of a minute
+ fractionSpan = TimeSpan.FromMinutes(frac);
+ }
+ }
+ else if (fraction != 0)
+ {
+ // Both minutes and seconds means fractions of a second.
+ fractionSpan = TimeSpan.FromSeconds(frac);
+ }
+
+ DateTimeOffset value;
+
+ try
+ {
+ if (timeOffset == null)
+ {
+ // Use the local timezone offset since there's no information in the contents.
+ // T-REC-X.680-201510 sec 46.2(a).
+ value = new DateTimeOffset(new DateTime(year, month, day, hour, minute.Value, second.Value));
+ }
+ else
+ {
+ // T-REC-X.680-201510 sec 46.2(b) or 46.2(c).
+ value = new DateTimeOffset(year, month, day, hour, minute.Value, second.Value, timeOffset.Value);
+ }
+
+ value += fractionSpan;
+ return value;
+ }
+ catch (Exception e)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
+ }
+ }
+
+ public DateTimeOffset GetGeneralizedTime(bool disallowFractions=false) =>
+ GetGeneralizedTime(Asn1Tag.GeneralizedTime, disallowFractions);
+
+ public DateTimeOffset GetGeneralizedTime(Asn1Tag expectedTag, bool disallowFractions=false)
+ {
+ byte[] rented = null;
+
+ ReadOnlySpan<byte> contents = GetOctetStringContents(
+ expectedTag,
+ UniversalTagNumber.GeneralizedTime,
+ out int bytesRead,
+ ref rented);
+
+ DateTimeOffset value = ParseGeneralizedTime(_ruleSet, contents, disallowFractions);
+
+ if (rented != null)
+ {
+ Array.Clear(rented, 0, contents.Length);
+ ArrayPool<byte>.Shared.Return(rented);
+ }
+
+ _data = _data.Slice(bytesRead);
+ return value;
+ }
+
+ private ReadOnlySpan<byte> GetOctetStringContents(
+ Asn1Tag expectedTag,
+ UniversalTagNumber universalTagNumber,
+ out int bytesRead,
+ ref byte[] rented,
+ Span<byte> tmpSpace = default)
+ {
+ Debug.Assert(rented == null);
+
+ if (TryGetPrimitiveOctetStringBytes(
+ expectedTag,
+ out Asn1Tag actualTag,
+ out int? contentLength,
+ out int headerLength,
+ out ReadOnlyMemory<byte> contentsOctets,
+ universalTagNumber))
+ {
+ bytesRead = headerLength + contentsOctets.Length;
+ return contentsOctets.Span;
+ }
+
+ Debug.Assert(actualTag.IsConstructed);
+
+ ReadOnlyMemory<byte> source = Slice(_data, headerLength, contentLength);
+ bool isIndefinite = contentLength == null;
+ int octetStringLength = CountConstructedOctetString(source, isIndefinite);
+
+ if (tmpSpace.Length < octetStringLength)
+ {
+ rented = ArrayPool<byte>.Shared.Rent(octetStringLength);
+ tmpSpace = rented;
+ }
+
+ CopyConstructedOctetString(source, tmpSpace, isIndefinite, out int localBytesRead, out int bytesWritten);
+ Debug.Assert(bytesWritten == octetStringLength);
+
+ bytesRead = headerLength + localBytesRead;
+ return tmpSpace.Slice(0, bytesWritten);
+ }
+
+ private static ReadOnlySpan<byte> SliceAtMost(ReadOnlySpan<byte> source, int longestPermitted)
+ {
+ int len = Math.Min(longestPermitted, source.Length);
+ return source.Slice(0, len);
+ }
+
+ private static ReadOnlySpan<byte> Slice(ReadOnlySpan<byte> source, int offset, int length)
+ {
+ Debug.Assert(offset >= 0);
+
+ if (length < 0 || source.Length - offset < length)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ return source.Slice(offset, length);
+ }
+
+ private static ReadOnlyMemory<byte> Slice(ReadOnlyMemory<byte> source, int offset, int? length)
+ {
+ Debug.Assert(offset >= 0);
+
+ if (length == null)
+ {
+ return source.Slice(offset);
+ }
+
+ int lengthVal = length.Value;
+
+ if (lengthVal < 0 || source.Length - offset < lengthVal)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ return source.Slice(offset, lengthVal);
+ }
+
+ private static void CheckEncodingRules(AsnEncodingRules ruleSet)
+ {
+ if (ruleSet != AsnEncodingRules.BER &&
+ ruleSet != AsnEncodingRules.CER &&
+ ruleSet != AsnEncodingRules.DER)
+ {
+ throw new ArgumentOutOfRangeException(nameof(ruleSet));
+ }
+ }
+
+ private static void CheckExpectedTag(Asn1Tag tag, Asn1Tag expectedTag, UniversalTagNumber tagNumber)
+ {
+ if (expectedTag.TagClass == TagClass.Universal && expectedTag.TagValue != (int)tagNumber)
+ {
+ throw new ArgumentException(
+ SR.Cryptography_Asn_UniversalValueIsFixed,
+ nameof(expectedTag));
+ }
+
+ if (expectedTag.TagClass != tag.TagClass || expectedTag.TagValue != tag.TagValue)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+ }
+
+ private static void CheckCharacterStringEncodingType(UniversalTagNumber encodingType)
+ {
+ // T-REC-X.680-201508 sec 41
+ switch (encodingType)
+ {
+ case UniversalTagNumber.BMPString:
+ case UniversalTagNumber.GeneralString:
+ case UniversalTagNumber.GraphicString:
+ case UniversalTagNumber.IA5String:
+ case UniversalTagNumber.ISO646String:
+ case UniversalTagNumber.NumericString:
+ case UniversalTagNumber.PrintableString:
+ case UniversalTagNumber.TeletexString:
+ // T61String is an alias for TeletexString (already listed)
+ case UniversalTagNumber.UniversalString:
+ case UniversalTagNumber.UTF8String:
+ case UniversalTagNumber.VideotexString:
+ // VisibleString is an alias for ISO646String (already listed)
+ return;
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(encodingType));
+ }
+ }
+}
diff --git a/src/Common/src/System/Security/Cryptography/AsnWriter.cs b/src/Common/src/System/Security/Cryptography/AsnWriter.cs
new file mode 100644
index 0000000000..6119537da1
--- /dev/null
+++ b/src/Common/src/System/Security/Cryptography/AsnWriter.cs
@@ -0,0 +1,1602 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Enable CHECK_ACCURATE_ENSURE to ensure that the AsnWriter is not ever
+// abusing the normal EnsureWriteCapacity + ArrayPool behaviors of rounding up.
+//#define CHECK_ACCURATE_ENSURE
+
+using System.Buffers;
+using System.Buffers.Text;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Numerics;
+
+namespace System.Security.Cryptography.Asn1
+{
+ internal class AsnWriter
+ {
+ private byte[] _buffer;
+ private int _offset;
+ private Stack<(Asn1Tag,int)> _nestingStack;
+
+ public AsnEncodingRules RuleSet { get; }
+
+ public AsnWriter(AsnEncodingRules ruleSet)
+ {
+ if (ruleSet != AsnEncodingRules.BER &&
+ ruleSet != AsnEncodingRules.CER &&
+ ruleSet != AsnEncodingRules.DER)
+ {
+ throw new ArgumentOutOfRangeException(nameof(ruleSet));
+ }
+
+ RuleSet = ruleSet;
+ }
+
+ private void EnsureWriteCapacity(int pendingCount)
+ {
+ if (pendingCount < 0)
+ {
+ throw new OverflowException();
+ }
+
+ if (_buffer == null || _buffer.Length - _offset < pendingCount)
+ {
+#if CHECK_ACCURATE_ENSURE
+// A debug paradigm to make sure that throughout the execution nothing ever writes
+// past where the buffer was "allocated". This causes quite a number of reallocs
+// and copies, so it's a #define opt-in.
+ byte[] newBytes = new byte[_offset + pendingCount];
+
+ if (_buffer != null)
+ {
+ Buffer.BlockCopy(_buffer, 0, newBytes, 0, _offset);
+ }
+#else
+ const int BlockSize = 1024;
+ // While the ArrayPool may have similar logic, make sure we don't run into a lot of
+ // "grow a little" by asking in 1k steps.
+ int blocks = checked(_offset + pendingCount + (BlockSize - 1)) / BlockSize;
+ byte[] newBytes = ArrayPool<byte>.Shared.Rent(BlockSize * blocks);
+
+ if (_buffer != null)
+ {
+ Buffer.BlockCopy(_buffer, 0, newBytes, 0, _offset);
+ Array.Clear(_buffer, 0, _offset);
+ ArrayPool<byte>.Shared.Return(_buffer);
+ }
+#endif
+
+#if DEBUG
+ // Ensure no "implicit 0" is happening
+ for (int i = _offset; i < newBytes.Length; i++)
+ {
+ newBytes[i] ^= 0xFF;
+ }
+#endif
+
+ _buffer = newBytes;
+ }
+ }
+
+ private void WriteTag(Asn1Tag tag)
+ {
+ int spaceRequired = tag.CalculateEncodedSize();
+ EnsureWriteCapacity(spaceRequired);
+
+ if (!tag.TryWrite(_buffer.AsSpan().Slice(_offset, spaceRequired), out int written) ||
+ written != spaceRequired)
+ {
+ Debug.Fail($"TryWrite failed or written was wrong value ({written} vs {spaceRequired})");
+ throw new CryptographicException();
+ }
+
+ _offset += spaceRequired;
+ }
+
+ // T-REC-X.690-201508 sec 8.1.3
+ private void WriteLength(int length)
+ {
+ const byte MultiByteMarker = 0x80;
+ Debug.Assert(length >= -1);
+
+ // If the indefinite form has been requested.
+ // T-REC-X.690-201508 sec 8.1.3.6
+ if (length == -1)
+ {
+ EnsureWriteCapacity(1);
+ _buffer[_offset] = MultiByteMarker;
+ _offset++;
+ return;
+ }
+
+ Debug.Assert(length >= 0);
+
+ // T-REC-X.690-201508 sec 8.1.3.3, 8.1.3.4
+ if (length < MultiByteMarker)
+ {
+ // Pre-allocate the pending data since we know how much.
+ EnsureWriteCapacity(1 + length);
+ _buffer[_offset] = (byte)length;
+ _offset++;
+ return;
+ }
+
+ // The rest of the method implements T-REC-X.680-201508 sec 8.1.3.5
+ int lengthLength = GetEncodedLengthSubsequentByteCount(length);
+
+ // Pre-allocate the pending data since we know how much.
+ EnsureWriteCapacity(lengthLength + 1 + length);
+ _buffer[_offset] = (byte)(MultiByteMarker | lengthLength);
+
+ // No minus one because offset didn't get incremented yet.
+ int idx = _offset + lengthLength;
+
+ int remaining = length;
+
+ do
+ {
+ _buffer[idx] = (byte)remaining;
+ remaining >>= 8;
+ idx--;
+ } while (remaining > 0);
+
+ Debug.Assert(idx == _offset);
+ _offset += lengthLength + 1;
+ }
+
+ // T-REC-X.690-201508 sec 8.1.3.5
+ private static int GetEncodedLengthSubsequentByteCount(int length)
+ {
+ if (length <= 0x7F)
+ return 0;
+ if (length <= byte.MaxValue)
+ return 1;
+ if (length <= ushort.MaxValue)
+ return 2;
+ if (length <= 0x00FFFFFF)
+ return 3;
+
+ return 4;
+ }
+
+ public void WriteEncodedValue(ReadOnlyMemory<byte> preEncodedValue)
+ {
+ AsnReader reader = new AsnReader(preEncodedValue, RuleSet);
+
+ // Is it legal under the current rules?
+ ReadOnlyMemory<byte> parsedBack = reader.GetEncodedValue();
+
+ if (reader.HasData)
+ {
+ throw new ArgumentException(SR.Cryptography_WriteEncodedValue_OneValueAtATime, nameof(preEncodedValue));
+ }
+
+ Debug.Assert(parsedBack.Length == preEncodedValue.Length);
+
+ EnsureWriteCapacity(preEncodedValue.Length);
+ preEncodedValue.Span.CopyTo(_buffer.AsSpan().Slice(_offset));
+ _offset += preEncodedValue.Length;
+ }
+
+ // T-REC-X.690-201508 sec 8.1.5
+ private void WriteEndOfContents()
+ {
+ EnsureWriteCapacity(2);
+ _buffer[_offset++] = 0;
+ _buffer[_offset++] = 0;
+ }
+
+ public void WriteBoolean(bool value)
+ {
+ WriteBooleanCore(Asn1Tag.Boolean, value);
+ }
+
+ public void WriteBoolean(Asn1Tag tag, bool value)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.Boolean);
+
+ WriteBooleanCore(tag.AsPrimitive(), value);
+ }
+
+ // T-REC-X.690-201508 sec 11.1, 8.2
+ private void WriteBooleanCore(Asn1Tag tag, bool value)
+ {
+ Debug.Assert(!tag.IsConstructed);
+ WriteTag(tag);
+ WriteLength(1);
+ // Ensured by WriteLength
+ Debug.Assert(_offset < _buffer.Length);
+ _buffer[_offset] = (byte)(value ? 0xFF : 0x00);
+ _offset++;
+ }
+
+ public void WriteInteger(long value)
+ {
+ WriteIntegerCore(Asn1Tag.Integer, value);
+ }
+
+ public void WriteInteger(ulong value)
+ {
+ WriteNonNegativeIntegerCore(Asn1Tag.Integer, value);
+ }
+
+ public void WriteInteger(BigInteger value)
+ {
+ WriteIntegerCore(Asn1Tag.Integer, value);
+ }
+
+ public void WriteInteger(Asn1Tag tag, long value)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.Integer);
+
+ WriteIntegerCore(tag.AsPrimitive(), value);
+ }
+
+ // T-REC-X.690-201508 sec 8.3
+ private void WriteIntegerCore(Asn1Tag tag, long value)
+ {
+ if (value >= 0)
+ {
+ WriteNonNegativeIntegerCore(tag, (ulong)value);
+ return;
+ }
+
+ int valueLength;
+
+ if (value >= sbyte.MinValue)
+ valueLength = 1;
+ else if (value >= short.MinValue)
+ valueLength = 2;
+ else if (value >= unchecked((long)0xFFFFFFFF_FF800000))
+ valueLength = 3;
+ else if (value >= int.MinValue)
+ valueLength = 4;
+ else if (value >= unchecked((long)0xFFFFFF80_00000000))
+ valueLength = 5;
+ else if (value >= unchecked((long)0xFFFF8000_00000000))
+ valueLength = 6;
+ else if (value >= unchecked((long)0xFF800000_00000000))
+ valueLength = 7;
+ else
+ valueLength = 8;
+
+ Debug.Assert(!tag.IsConstructed);
+ WriteTag(tag);
+ WriteLength(valueLength);
+
+ long remaining = value;
+ int idx = _offset + valueLength - 1;
+
+ do
+ {
+ _buffer[idx] = (byte)remaining;
+ remaining >>= 8;
+ idx--;
+ } while (idx >= _offset);
+
+#if DEBUG
+ if (valueLength > 1)
+ {
+ // T-REC-X.690-201508 sec 8.3.2
+ // Cannot start with 9 bits of 1 (or 9 bits of 0, but that's not this method).
+ Debug.Assert(_buffer[_offset] != 0xFF || _buffer[_offset + 1] < 0x80);
+ }
+#endif
+
+ _offset += valueLength;
+ }
+
+ public void WriteInteger(Asn1Tag tag, ulong value)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.Integer);
+
+ WriteNonNegativeIntegerCore(tag.AsPrimitive(), value);
+ }
+
+ // T-REC-X.690-201508 sec 8.3
+ private void WriteNonNegativeIntegerCore(Asn1Tag tag, ulong value)
+ {
+ int valueLength;
+
+ // 0x80 needs two bytes: 0x00 0x80
+ if (value < 0x80)
+ valueLength = 1;
+ else if (value < 0x8000)
+ valueLength = 2;
+ else if (value < 0x800000)
+ valueLength = 3;
+ else if (value < 0x80000000)
+ valueLength = 4;
+ else if (value < 0x80_00000000)
+ valueLength = 5;
+ else if (value < 0x8000_00000000)
+ valueLength = 6;
+ else if (value < 0x800000_00000000)
+ valueLength = 7;
+ else if (value < 0x80000000_00000000)
+ valueLength = 8;
+ else
+ valueLength = 9;
+
+ // Clear the constructed bit, if it was set.
+ Debug.Assert(!tag.IsConstructed);
+ WriteTag(tag);
+ WriteLength(valueLength);
+
+ ulong remaining = value;
+ int idx = _offset + valueLength - 1;
+
+ do
+ {
+ _buffer[idx] = (byte)remaining;
+ remaining >>= 8;
+ idx--;
+ } while (idx >= _offset);
+
+#if DEBUG
+ if (valueLength > 1)
+ {
+ // T-REC-X.690-201508 sec 8.3.2
+ // Cannot start with 9 bits of 0 (or 9 bits of 1, but that's not this method).
+ Debug.Assert(_buffer[_offset] != 0 || _buffer[_offset + 1] > 0x7F);
+ }
+#endif
+
+ _offset += valueLength;
+ }
+
+ public void WriteInteger(Asn1Tag tag, BigInteger value)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.Integer);
+
+ WriteIntegerCore(tag.AsPrimitive(), value);
+ }
+
+ // T-REC-X.690-201508 sec 8.3
+ private void WriteIntegerCore(Asn1Tag tag, BigInteger value)
+ {
+ // TODO: Split this for netstandard vs netcoreapp for span-perf?.
+ byte[] encoded = value.ToByteArray();
+ Array.Reverse(encoded);
+
+ Debug.Assert(!tag.IsConstructed);
+ WriteTag(tag);
+ WriteLength(encoded.Length);
+ Buffer.BlockCopy(encoded, 0, _buffer, _offset, encoded.Length);
+ _offset += encoded.Length;
+ }
+
+ public void WriteBitString(ReadOnlySpan<byte> bitString, int unusedBitCount=0)
+ {
+ WriteBitStringCore(Asn1Tag.PrimitiveBitString, bitString, unusedBitCount);
+ }
+
+ public void WriteBitString(Asn1Tag tag, ReadOnlySpan<byte> bitString, int unusedBitCount=0)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.BitString);
+
+ // Primitive or constructed, doesn't matter.
+ WriteBitStringCore(tag, bitString, unusedBitCount);
+ }
+
+ // T-REC-X.690-201508 sec 8.6
+ private void WriteBitStringCore(Asn1Tag tag, ReadOnlySpan<byte> bitString, int unusedBitCount)
+ {
+ // T-REC-X.690-201508 sec 8.6.2.2
+ if (unusedBitCount < 0 || unusedBitCount > 7)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(unusedBitCount),
+ unusedBitCount,
+ SR.Cryptography_Asn_UnusedBitCountRange);
+ }
+
+ // T-REC-X.690-201508 sec 8.6.2.3
+ if (bitString.Length == 0 && unusedBitCount != 0)
+ {
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ // If 3 bits are "unused" then build a mask for them to check for 0.
+ // 1 << 3 => 0b0000_1000
+ // subtract 1 => 0b000_0111
+ int mask = (1 << unusedBitCount) - 1;
+ byte lastByte = bitString.IsEmpty ? (byte)0 : bitString[bitString.Length - 1];
+
+ if ((lastByte & mask) != 0)
+ {
+ // T-REC-X.690-201508 sec 11.2
+ //
+ // This could be ignored for BER, but since DER is more common and
+ // it likely suggests a program error on the caller, leave it enabled for
+ // BER for now.
+ // TODO: Probably warrants a distinct message.
+ throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
+ }
+
+ if (RuleSet == AsnEncodingRules.CER)
+ {
+ // T-REC-X.690-201508 sec 9.2
+ //
+ // If it's not within a primitive segment, use the constructed encoding.
+ // (>= instead of > because of the unused bit count byte)
+ if (bitString.Length >= AsnReader.MaxCERSegmentSize)
+ {
+ WriteConstructedCerBitString(tag, bitString, unusedBitCount);
+ return;
+ }
+ }
+
+ // Clear the constructed flag, if present.
+ WriteTag(tag.AsPrimitive());
+ // The unused bits byte requires +1.
+ WriteLength(bitString.Length + 1);
+ _buffer[_offset] = (byte)unusedBitCount;
+ _offset++;
+ bitString.CopyTo(_buffer.AsSpan().Slice(_offset));
+ _offset += bitString.Length;
+ }
+
+ // T-REC-X.690-201508 sec 9.2, 8.6
+ private void WriteConstructedCerBitString(Asn1Tag tag, ReadOnlySpan<byte> payload, int unusedBitCount)
+ {
+ const int MaxCERSegmentSize = AsnReader.MaxCERSegmentSize;
+ // Every segment has an "unused bit count" byte.
+ const int MaxCERContentSize = MaxCERSegmentSize - 1;
+ Debug.Assert(payload.Length > MaxCERContentSize);
+
+ WriteTag(tag.AsConstructed());
+ // T-REC-X.690-201508 sec 9.1
+ // Constructed CER uses the indefinite form.
+ WriteLength(-1);
+
+ int fullSegments = Math.DivRem(payload.Length, MaxCERContentSize, out int lastContentSize);
+
+ // The tag size is 1 byte.
+ // The length will always be encoded as 82 03 E8 (3 bytes)
+ // And 1000 content octets (by T-REC-X.690-201508 sec 9.2)
+ const int FullSegmentEncodedSize = 1004;
+ Debug.Assert(
+ FullSegmentEncodedSize == 1 + 1 + MaxCERSegmentSize + GetEncodedLengthSubsequentByteCount(MaxCERSegmentSize));
+
+ int remainingEncodedSize;
+
+ if (lastContentSize == 0)
+ {
+ remainingEncodedSize = 0;
+ }
+ else
+ {
+ // One byte of tag, minimum one byte of length, and one byte of unused bit count.
+ remainingEncodedSize = 3 + lastContentSize + GetEncodedLengthSubsequentByteCount(lastContentSize);
+ }
+
+ // Reduce the number of copies by pre-calculating the size.
+ // +2 for End-Of-Contents
+ int expectedSize = fullSegments * FullSegmentEncodedSize + remainingEncodedSize + 2;
+ EnsureWriteCapacity(expectedSize);
+
+ byte[] ensureNoExtraCopy = _buffer;
+ int savedOffset = _offset;
+
+ ReadOnlySpan<byte> remainingData = payload;
+ Span<byte> dest;
+ Asn1Tag primitiveBitString = Asn1Tag.PrimitiveBitString;
+
+ while (remainingData.Length > MaxCERContentSize)
+ {
+ // T-REC-X.690-201508 sec 8.6.4.1
+ WriteTag(primitiveBitString);
+ WriteLength(MaxCERSegmentSize);
+ // 0 unused bits in this segment.
+ _buffer[_offset] = 0;
+ _offset++;
+
+ dest = _buffer.AsSpan().Slice(_offset);
+ remainingData.Slice(0, MaxCERContentSize).CopyTo(dest);
+
+ remainingData = remainingData.Slice(MaxCERContentSize);
+ _offset += MaxCERContentSize;
+ }
+
+ WriteTag(primitiveBitString);
+ WriteLength(remainingData.Length + 1);
+
+ _buffer[_offset] = (byte)unusedBitCount;
+ _offset++;
+
+ dest = _buffer.AsSpan().Slice(_offset);
+ remainingData.CopyTo(dest);
+ _offset += remainingData.Length;
+
+ WriteEndOfContents();
+
+ Debug.Assert(_offset - savedOffset == expectedSize, $"expected size was {expectedSize}, actual was {_offset - savedOffset}");
+ Debug.Assert(_buffer == ensureNoExtraCopy, $"_buffer was replaced during {nameof(WriteConstructedCerBitString)}");
+ }
+
+ public void WriteNamedBitList(object enumValue)
+ {
+ if (enumValue == null)
+ throw new ArgumentNullException(nameof(enumValue));
+
+ WriteNamedBitList(Asn1Tag.PrimitiveBitString, enumValue);
+ }
+
+ public void WriteNamedBitList<TEnum>(TEnum enumValue) where TEnum : struct
+ {
+ WriteNamedBitList(Asn1Tag.PrimitiveBitString, enumValue);
+ }
+
+ public void WriteNamedBitList(Asn1Tag tag, object enumValue)
+ {
+ if (enumValue == null)
+ throw new ArgumentNullException(nameof(enumValue));
+
+ WriteNamedBitList(tag, enumValue.GetType(), enumValue);
+ }
+
+ public void WriteNamedBitList<TEnum>(Asn1Tag tag, TEnum enumValue) where TEnum : struct
+ {
+ WriteNamedBitList(tag, typeof(TEnum), enumValue);
+ }
+
+ private void WriteNamedBitList(Asn1Tag tag, Type tEnum, object enumValue)
+ {
+ Type backingType = tEnum.GetEnumUnderlyingType();
+
+ if (!tEnum.IsDefined(typeof(FlagsAttribute), false))
+ {
+ throw new ArgumentException(SR.Cryptography_Asn_NamedBitListRequiresFlagsEnum, nameof(tEnum));
+ }
+
+ ulong integralValue;
+
+ if (backingType == typeof(ulong))
+ {
+ integralValue = Convert.ToUInt64(enumValue);
+ }
+ else
+ {
+ // All other types fit in a (signed) long.
+ long numericValue = Convert.ToInt64(enumValue);
+ integralValue = unchecked((ulong)numericValue);
+ }
+
+ WriteNamedBitList(tag, integralValue);
+ }
+
+ // T-REC-X.680-201508 sec 22
+ // T-REC-X.690-201508 sec 8.6, 11.2.2
+ private void WriteNamedBitList(Asn1Tag tag, ulong integralValue)
+ {
+ Span<byte> temp = stackalloc byte[sizeof(ulong)];
+ // Reset to all zeros, since we're just going to or-in bits we need.
+ temp.Clear();
+
+ int indexOfHighestSetBit = -1;
+
+ for (int i = 0; integralValue != 0; integralValue >>= 1, i++)
+ {
+ if ((integralValue & 1) != 0)
+ {
+ temp[i / 8] |= (byte)(0x80 >> (i % 8));
+ indexOfHighestSetBit = i;
+ }
+ }
+
+ if (indexOfHighestSetBit < 0)
+ {
+ // No bits were set; this is an empty bit string.
+ // T-REC-X.690-201508 sec 11.2.2-note2
+ WriteBitString(tag, ReadOnlySpan<byte>.Empty);
+ }
+ else
+ {
+ // At least one bit was set.
+ // Determine the shortest length necessary to represent the bit string.
+
+ // Since "bit 0" gets written down 0 => 1.
+ // Since "bit 8" is in the second byte 8 => 2.
+ // That makes the formula ((bit / 8) + 1) instead of ((bit + 7) / 8).
+ int byteLen = (indexOfHighestSetBit / 8) + 1;
+ int unusedBitCount = 7 - (indexOfHighestSetBit % 8);
+
+ WriteBitString(
+ tag,
+ temp.Slice(0, byteLen),
+ unusedBitCount);
+ }
+ }
+
+ public void WriteOctetString(ReadOnlySpan<byte> octetString)
+ {
+ WriteOctetString(Asn1Tag.PrimitiveOctetString, octetString);
+ }
+
+ public void WriteOctetString(Asn1Tag tag, ReadOnlySpan<byte> octetString)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.OctetString);
+
+ // Primitive or constructed, doesn't matter.
+ WriteOctetStringCore(tag, octetString);
+ }
+
+ // T-REC-X.690-201508 sec 8.7
+ private void WriteOctetStringCore(Asn1Tag tag, ReadOnlySpan<byte> octetString)
+ {
+ if (RuleSet == AsnEncodingRules.CER)
+ {
+ // If it's bigger than a primitive segment, use the constructed encoding
+ // T-REC-X.690-201508 sec 9.2
+ if (octetString.Length > AsnReader.MaxCERSegmentSize)
+ {
+ WriteConstructedCerOctetString(tag, octetString);
+ return;
+ }
+ }
+
+ // Clear the constructed flag, if present.
+ WriteTag(tag.AsPrimitive());
+ WriteLength(octetString.Length);
+ octetString.CopyTo(_buffer.AsSpan().Slice(_offset));
+ _offset += octetString.Length;
+ }
+
+ // T-REC-X.690-201508 sec 9.2, 8.7
+ private void WriteConstructedCerOctetString(Asn1Tag tag, ReadOnlySpan<byte> payload)
+ {
+ const int MaxCERSegmentSize = AsnReader.MaxCERSegmentSize;
+ Debug.Assert(payload.Length > MaxCERSegmentSize);
+
+ WriteTag(tag.AsConstructed());
+ WriteLength(-1);
+
+ int fullSegments = Math.DivRem(payload.Length, MaxCERSegmentSize, out int lastSegmentSize);
+
+ // The tag size is 1 byte.
+ // The length will always be encoded as 82 03 E8 (3 bytes)
+ // And 1000 content octets (by T-REC-X.690-201508 sec 9.2)
+ const int FullSegmentEncodedSize = 1004;
+ Debug.Assert(
+ FullSegmentEncodedSize == 1 + 1 + MaxCERSegmentSize + GetEncodedLengthSubsequentByteCount(MaxCERSegmentSize));
+
+ int remainingEncodedSize;
+
+ if (lastSegmentSize == 0)
+ {
+ remainingEncodedSize = 0;
+ }
+ else
+ {
+ // One byte of tag, and minimum one byte of length.
+ remainingEncodedSize = 2 + lastSegmentSize + GetEncodedLengthSubsequentByteCount(lastSegmentSize);
+ }
+
+ // Reduce the number of copies by pre-calculating the size.
+ // +2 for End-Of-Contents
+ int expectedSize = fullSegments * FullSegmentEncodedSize + remainingEncodedSize + 2;
+ EnsureWriteCapacity(expectedSize);
+
+ byte[] ensureNoExtraCopy = _buffer;
+ int savedOffset = _offset;
+
+ ReadOnlySpan<byte> remainingData = payload;
+ Span<byte> dest;
+ Asn1Tag primitiveOctetString = Asn1Tag.PrimitiveOctetString;
+
+ while (remainingData.Length > MaxCERSegmentSize)
+ {
+ // T-REC-X.690-201508 sec 8.7.3.2-note2
+ WriteTag(primitiveOctetString);
+ WriteLength(MaxCERSegmentSize);
+
+ dest = _buffer.AsSpan().Slice(_offset);
+ remainingData.Slice(0, MaxCERSegmentSize).CopyTo(dest);
+
+ _offset += MaxCERSegmentSize;
+ remainingData = remainingData.Slice(MaxCERSegmentSize);
+ }
+
+ WriteTag(primitiveOctetString);
+ WriteLength(remainingData.Length);
+ dest = _buffer.AsSpan().Slice(_offset);
+ remainingData.CopyTo(dest);
+ _offset += remainingData.Length;
+
+ WriteEndOfContents();
+
+ Debug.Assert(_offset - savedOffset == expectedSize, $"expected size was {expectedSize}, actual was {_offset - savedOffset}");
+ Debug.Assert(_buffer == ensureNoExtraCopy, $"_buffer was replaced during {nameof(WriteConstructedCerOctetString)}");
+ }
+
+ public void WriteNull()
+ {
+ WriteNullCore(Asn1Tag.Null);
+ }
+
+ public void WriteNull(Asn1Tag tag)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.Null);
+
+ WriteNullCore(tag.AsPrimitive());
+ }
+
+ // T-REC-X.690-201508 sec 8.8
+ private void WriteNullCore(Asn1Tag tag)
+ {
+ Debug.Assert(!tag.IsConstructed);
+ WriteTag(tag);
+ WriteLength(0);
+ }
+
+ public void WriteObjectIdentifier(Oid oid)
+ {
+ if (oid == null)
+ throw new ArgumentNullException(nameof(oid));
+
+ WriteObjectIdentifier(oid.Value);
+ }
+
+ public void WriteObjectIdentifier(string oidValue)
+ {
+ if (oidValue == null)
+ throw new ArgumentNullException(nameof(oidValue));
+
+ WriteObjectIdentifier(oidValue.AsReadOnlySpan());
+ }
+
+ public void WriteObjectIdentifier(ReadOnlySpan<char> oidValue)
+ {
+ WriteObjectIdentifierCore(Asn1Tag.ObjectIdentifier, oidValue);
+ }
+
+ public void WriteObjectIdentifier(Asn1Tag tag, Oid oid)
+ {
+ if (oid == null)
+ throw new ArgumentNullException(nameof(oid));
+
+ WriteObjectIdentifier(tag, oid.Value);
+ }
+
+ public void WriteObjectIdentifier(Asn1Tag tag, string oidValue)
+ {
+ if (oidValue == null)
+ throw new ArgumentNullException(nameof(oidValue));
+
+ WriteObjectIdentifier(tag, oidValue.AsReadOnlySpan());
+ }
+
+ public void WriteObjectIdentifier(Asn1Tag tag, ReadOnlySpan<char> oidValue)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.ObjectIdentifier);
+
+ WriteObjectIdentifierCore(tag.AsPrimitive(), oidValue);
+ }
+
+ // T-REC-X.690-201508 sec 8.19
+ private void WriteObjectIdentifierCore(Asn1Tag tag, ReadOnlySpan<char> oidValue)
+ {
+ // T-REC-X.690-201508 sec 8.19.4
+ // The first character is in { 0, 1, 2 }, the second will be a '.', and a third (digit)
+ // will also exist.
+ if (oidValue.Length < 3)
+ throw new CryptographicException(SR.Argument_InvalidOidValue);
+ if (oidValue[1] != '.')
+ throw new CryptographicException(SR.Argument_InvalidOidValue);
+
+ // The worst case is "1.1.1.1.1", which takes 4 bytes (5 components, with the first two condensed)
+ // Longer numbers get smaller: "2.1.127" is only 2 bytes. (81d (0x51) and 127 (0x7F))
+ // So length / 2 should prevent any reallocations.
+ byte[] tmp = ArrayPool<byte>.Shared.Rent(oidValue.Length / 2);
+ int tmpOffset = 0;
+
+ try
+ {
+ int firstComponent;
+
+ switch (oidValue[0])
+ {
+ case '0':
+ firstComponent = 0;
+ break;
+ case '1':
+ firstComponent = 1;
+ break;
+ case '2':
+ firstComponent = 2;
+ break;
+ default:
+ throw new CryptographicException(SR.Argument_InvalidOidValue);
+ }
+
+ // The first two components are special:
+ // ITU X.690 8.19.4:
+ // The numerical value of the first subidentifier is derived from the values of the first two
+ // object identifier components in the object identifier value being encoded, using the formula:
+ // (X*40) + Y
+ // where X is the value of the first object identifier component and Y is the value of the
+ // second object identifier component.
+ // NOTE - This packing of the first two object identifier components recognizes that only
+ // three values are allocated from the root node, and at most 39 subsequent values from
+ // nodes reached by X = 0 and X = 1.
+
+ // skip firstComponent and the trailing .
+ ReadOnlySpan<char> remaining = oidValue.Slice(2);
+
+ BigInteger subIdentifier = ParseSubIdentifier(ref remaining);
+ subIdentifier += 40 * firstComponent;
+
+ int localLen = EncodeSubIdentifier(tmp.AsSpan().Slice(tmpOffset), ref subIdentifier);
+ tmpOffset += localLen;
+
+ while (!remaining.IsEmpty)
+ {
+ subIdentifier = ParseSubIdentifier(ref remaining);
+ localLen = EncodeSubIdentifier(tmp.AsSpan().Slice(tmpOffset), ref subIdentifier);
+ tmpOffset += localLen;
+ }
+
+ Debug.Assert(!tag.IsConstructed);
+ WriteTag(tag);
+ WriteLength(tmpOffset);
+ Buffer.BlockCopy(tmp, 0, _buffer, _offset, tmpOffset);
+ _offset += tmpOffset;
+ }
+ finally
+ {
+ Array.Clear(tmp, 0, tmpOffset);
+ ArrayPool<byte>.Shared.Return(tmp);
+ }
+ }
+
+ private static BigInteger ParseSubIdentifier(ref ReadOnlySpan<char> oidValue)
+ {
+ int endIndex = oidValue.IndexOf('.');
+
+ if (endIndex == -1)
+ {
+ endIndex = oidValue.Length;
+ }
+ else if (endIndex == oidValue.Length - 1)
+ {
+ throw new CryptographicException(SR.Argument_InvalidOidValue);
+ }
+
+ // The following code is equivalent to
+ // BigInteger.TryParse(temp, NumberStyles.None, CultureInfo.InvariantCulture, out value)
+ // TODO: Split this for netstandard vs netcoreapp for span-perf?.
+ BigInteger value = BigInteger.Zero;
+
+ for (int position = 0; position < endIndex; position++)
+ {
+ if (position > 0 && value == 0)
+ {
+ // T-REC X.680-201508 sec 12.26
+ throw new CryptographicException(SR.Argument_InvalidOidValue);
+ }
+
+ value *= 10;
+ value += AtoI(oidValue[position]);
+ }
+
+ oidValue = oidValue.Slice(Math.Min(oidValue.Length, endIndex + 1));
+ return value;
+ }
+
+ private static int AtoI(char c)
+ {
+ if (c >= '0' && c <= '9')
+ {
+ return c - '0';
+ }
+
+ throw new CryptographicException(SR.Argument_InvalidOidValue);
+ }
+
+ // ITU-T-X.690-201508 sec 8.19.5
+ private static int EncodeSubIdentifier(Span<byte> dest, ref BigInteger subIdentifier)
+ {
+ Debug.Assert(dest.Length > 0);
+
+ if (subIdentifier.IsZero)
+ {
+ dest[0] = 0;
+ return 1;
+ }
+
+ BigInteger unencoded = subIdentifier;
+ int idx = 0;
+
+ do
+ {
+ BigInteger cur = unencoded & 0x7F;
+ byte curByte = (byte)cur;
+
+ if (subIdentifier != unencoded)
+ {
+ curByte |= 0x80;
+ }
+
+ unencoded >>= 7;
+ dest[idx] = curByte;
+ idx++;
+ }
+ while (unencoded != BigInteger.Zero);
+
+ Reverse(dest.Slice(0, idx));
+ return idx;
+ }
+
+ public void WriteEnumeratedValue(object enumValue)
+ {
+ if (enumValue == null)
+ throw new ArgumentNullException(nameof(enumValue));
+
+ WriteEnumeratedValue(Asn1Tag.Enumerated, enumValue);
+ }
+
+ public void WriteEnumeratedValue<TEnum>(TEnum value) where TEnum : struct
+ {
+ WriteEnumeratedValue(Asn1Tag.Enumerated, value);
+ }
+
+ public void WriteEnumeratedValue(Asn1Tag tag, object enumValue)
+ {
+ if (enumValue == null)
+ throw new ArgumentNullException(nameof(enumValue));
+
+ WriteEnumeratedValue(tag.AsPrimitive(), enumValue.GetType(), enumValue);
+ }
+
+ public void WriteEnumeratedValue<TEnum>(Asn1Tag tag, TEnum value) where TEnum : struct
+ {
+ WriteEnumeratedValue(tag.AsPrimitive(), typeof(TEnum), value);
+ }
+
+ // T-REC-X.690-201508 sec 8.4
+ private void WriteEnumeratedValue(Asn1Tag tag, Type tEnum, object enumValue)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.Enumerated);
+
+ Type backingType = tEnum.GetEnumUnderlyingType();
+
+ if (tEnum.IsDefined(typeof(FlagsAttribute), false))
+ {
+ throw new ArgumentException(
+ SR.Cryptography_Asn_EnumeratedValueRequiresNonFlagsEnum,
+ nameof(tEnum));
+ }
+
+ if (backingType == typeof(ulong))
+ {
+ ulong numericValue = Convert.ToUInt64(enumValue);
+ // T-REC-X.690-201508 sec 8.4
+ WriteNonNegativeIntegerCore(tag, numericValue);
+ }
+ else
+ {
+ // All other types fit in a (signed) long.
+ long numericValue = Convert.ToInt64(enumValue);
+ // T-REC-X.690-201508 sec 8.4
+ WriteIntegerCore(tag, numericValue);
+ }
+ }
+
+ public void PushSequence()
+ {
+ PushSequenceCore(Asn1Tag.Sequence);
+ }
+
+ public void PushSequence(Asn1Tag tag)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.Sequence);
+
+ // Assert the constructed flag, in case it wasn't.
+ PushSequenceCore(tag.AsConstructed());
+ }
+
+ // T-REC-X.690-201508 sec 8.9, 8.10
+ private void PushSequenceCore(Asn1Tag tag)
+ {
+ PushTag(tag.AsConstructed());
+ }
+
+ public void PopSequence()
+ {
+ PopSequence(Asn1Tag.Sequence);
+ }
+
+ public void PopSequence(Asn1Tag tag)
+ {
+ // PopSequence shouldn't be used to pop a SetOf.
+ CheckUniversalTag(tag, UniversalTagNumber.Sequence);
+
+ // Assert the constructed flag, in case it wasn't.
+ PopSequenceCore(tag.AsConstructed());
+ }
+
+ // T-REC-X.690-201508 sec 8.9, 8.10
+ private void PopSequenceCore(Asn1Tag tag)
+ {
+ PopTag(tag);
+ }
+
+ public void PushSetOf()
+ {
+ PushSetOf(Asn1Tag.SetOf);
+ }
+
+ public void PushSetOf(Asn1Tag tag)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.SetOf);
+
+ // Assert the constructed flag, in case it wasn't.
+ PushSetOfCore(tag.AsConstructed());
+ }
+
+ // T-REC-X.690-201508 sec 8.12
+ // The writer claims SetOf, and not Set, so as to avoid the field
+ // ordering clause of T-REC-X.690-201508 sec 9.3
+ private void PushSetOfCore(Asn1Tag tag)
+ {
+ PushTag(tag);
+ }
+
+ public void PopSetOf()
+ {
+ PopSetOfCore(Asn1Tag.SetOf);
+ }
+
+ public void PopSetOf(Asn1Tag tag)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.SetOf);
+
+ // Assert the constructed flag, in case it wasn't.
+ PopSetOfCore(tag.AsConstructed());
+ }
+
+ // T-REC-X.690-201508 sec 8.12
+ private void PopSetOfCore(Asn1Tag tag)
+ {
+ // T-REC-X.690-201508 sec 11.6
+ bool sortContents = RuleSet == AsnEncodingRules.CER || RuleSet == AsnEncodingRules.DER;
+
+ PopTag(tag, sortContents);
+ }
+
+ public void WriteUtcTime(DateTimeOffset value)
+ {
+ WriteUtcTimeCore(Asn1Tag.UtcTime, value);
+ }
+
+ public void WriteUtcTime(Asn1Tag tag, DateTimeOffset value)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.UtcTime);
+
+ // Clear the constructed flag, if present.
+ WriteUtcTimeCore(tag.AsPrimitive(), value);
+ }
+
+ // T-REC-X.680-201508 sec 47
+ // T-REC-X.690-201508 sec 11.8
+ private void WriteUtcTimeCore(Asn1Tag tag, DateTimeOffset value)
+ {
+ // Because UtcTime is IMPLICIT VisibleString it technically can have
+ // a constructed form.
+ // DER says character strings must be primitive.
+ // CER says character strings <= 1000 encoded bytes must be primitive.
+ // So we'll just make BER be primitive, too.
+ Debug.Assert(!tag.IsConstructed);
+ WriteTag(tag);
+
+ // BER allows for omitting the seconds, but that's not an option we need to expose.
+ // BER allows for non-UTC values, but that's also not an option we need to expose.
+ // So the format is always yyMMddHHmmssZ (13)
+ const int UtcTimeValueLength = 13;
+ WriteLength(UtcTimeValueLength);
+
+ DateTimeOffset normalized = value.ToUniversalTime();
+
+ int year = normalized.Year;
+ int month = normalized.Month;
+ int day = normalized.Day;
+ int hour = normalized.Hour;
+ int minute = normalized.Minute;
+ int second = normalized.Second;
+
+ Span<byte> baseSpan = _buffer.AsSpan().Slice(_offset);
+ StandardFormat format = new StandardFormat('D', 2);
+
+ if (!Utf8Formatter.TryFormat(year % 100, baseSpan.Slice(0, 2), out _, format) ||
+ !Utf8Formatter.TryFormat(month, baseSpan.Slice(2, 2), out _, format) ||
+ !Utf8Formatter.TryFormat(day, baseSpan.Slice(4, 2), out _, format) ||
+ !Utf8Formatter.TryFormat(hour, baseSpan.Slice(6, 2), out _, format) ||
+ !Utf8Formatter.TryFormat(minute, baseSpan.Slice(8, 2), out _, format) ||
+ !Utf8Formatter.TryFormat(second, baseSpan.Slice(10, 2), out _, format))
+ {
+ Debug.Fail($"Utf8Formatter.TryFormat failed to build components of {normalized:O}");
+ throw new CryptographicException();
+ }
+
+ _buffer[_offset + 12] = (byte)'Z';
+
+ _offset += UtcTimeValueLength;
+ }
+
+ public void WriteGeneralizedTime(DateTimeOffset value, bool omitFractionalSeconds = false)
+ {
+ WriteGeneralizedTimeCore(Asn1Tag.GeneralizedTime, value, omitFractionalSeconds);
+ }
+
+ public void WriteGeneralizedTime(Asn1Tag tag, DateTimeOffset value, bool omitFractionalSeconds = false)
+ {
+ CheckUniversalTag(tag, UniversalTagNumber.GeneralizedTime);
+
+ // Clear the constructed flag, if present.
+ WriteGeneralizedTimeCore(tag.AsPrimitive(), value, omitFractionalSeconds);
+ }
+
+ // T-REC-X.680-201508 sec 46
+ // T-REC-X.690-201508 sec 11.7
+ private void WriteGeneralizedTimeCore(Asn1Tag tag, DateTimeOffset value, bool omitFractionalSeconds)
+ {
+ // GeneralizedTime under BER allows many different options:
+ // * (HHmmss), (HHmm), (HH)
+ // * "(value).frac", "(value),frac"
+ // * frac == 0 may be omitted or emitted
+ // non-UTC offset in various formats
+ //
+ // We're not allowing any of them.
+ // Just encode as the CER/DER common restrictions.
+ //
+ // This results in the following formats:
+ // yyyyMMddHHmmssZ
+ // yyyyMMddHHmmss.f?Z
+ //
+ // where "f?" is anything from "f" to "fffffff" (tenth of a second down to 100ns/1-tick)
+ // with no trailing zeros.
+ DateTimeOffset normalized = value.ToUniversalTime();
+
+ if (normalized.Year > 9999)
+ {
+ // This is unreachable since DateTimeOffset guards against this internally.
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
+ // We're only loading in sub-second ticks.
+ // Ticks are defined as 1e-7 seconds, so their printed form
+ // is at the longest "0.1234567", or 9 bytes.
+ Span<byte> fraction = stackalloc byte[0];
+
+ if (!omitFractionalSeconds)
+ {
+ long floatingTicks = normalized.Ticks % TimeSpan.TicksPerSecond;
+
+ if (floatingTicks != 0)
+ {
+ // We're only loading in sub-second ticks.
+ // Ticks are defined as 1e-7 seconds, so their printed form
+ // is at the longest "0.1234567", or 9 bytes.
+ fraction = stackalloc byte[9];
+
+ decimal decimalTicks = floatingTicks;
+ decimalTicks /= TimeSpan.TicksPerSecond;
+
+ if (!Utf8Formatter.TryFormat(decimalTicks, fraction, out int bytesWritten, new StandardFormat('G')))
+ {
+ Debug.Fail($"Utf8Formatter.TryFormat could not format {floatingTicks} / TicksPerSecond");
+ throw new CryptographicException();
+ }
+
+ Debug.Assert(bytesWritten > 2, $"{bytesWritten} should be > 2");
+ Debug.Assert(fraction[0] == (byte)'0');
+ Debug.Assert(fraction[1] == (byte)'.');
+
+ fraction = fraction.Slice(1, bytesWritten - 1);
+ }
+ }
+
+ // yyyy, MM, dd, hh, mm, ss
+ const int IntegerPortionLength = 4 + 2 + 2 + 2 + 2 + 2;
+ // Z, and the optional fraction.
+ int totalLength = IntegerPortionLength + 1 + fraction.Length;
+
+ // Because GeneralizedTime is IMPLICIT VisibleString it technically can have
+ // a constructed form.
+ // DER says character strings must be primitive.
+ // CER says character strings <= 1000 encoded bytes must be primitive.
+ // So we'll just make BER be primitive, too.
+ Debug.Assert(!tag.IsConstructed);
+ WriteTag(tag);
+ WriteLength(totalLength);
+
+ int year = normalized.Year;
+ int month = normalized.Month;
+ int day = normalized.Day;
+ int hour = normalized.Hour;
+ int minute = normalized.Minute;
+ int second = normalized.Second;
+
+ Span<byte> baseSpan = _buffer.AsSpan().Slice(_offset);
+ StandardFormat d4 = new StandardFormat('D', 4);
+ StandardFormat d2 = new StandardFormat('D', 2);
+
+ if (!Utf8Formatter.TryFormat(year, baseSpan.Slice(0, 4), out _, d4) ||
+ !Utf8Formatter.TryFormat(month, baseSpan.Slice(4, 2), out _, d2) ||
+ !Utf8Formatter.TryFormat(day, baseSpan.Slice(6, 2), out _, d2) ||
+ !Utf8Formatter.TryFormat(hour, baseSpan.Slice(8, 2), out _, d2) ||
+ !Utf8Formatter.TryFormat(minute, baseSpan.Slice(10, 2), out _, d2) ||
+ !Utf8Formatter.TryFormat(second, baseSpan.Slice(12, 2), out _, d2))
+ {
+ Debug.Fail($"Utf8Formatter.TryFormat failed to build components of {normalized:O}");
+ throw new CryptographicException();
+ }
+
+ _offset += IntegerPortionLength;
+ fraction.CopyTo(baseSpan.Slice(IntegerPortionLength));
+ _offset += fraction.Length;
+
+ _buffer[_offset] = (byte)'Z';
+ _offset++;
+ }
+
+ /// <summary>
+ /// Transfer the encoded representation of the data to <paramref name="dest"/>.
+ /// </summary>
+ /// <param name="dest">Write destination.</param>
+ /// <param name="bytesWritten">The number of bytes which were written to <paramref name="dest"/>.</param>
+ /// <returns><c>true</c> if the encode succeeded, <c>false</c> if <paramref name="dest"/> is too small.</returns>
+ /// <exception cref="InvalidOperationException">
+ /// A <see cref="PushSequence()"/> or <see cref="PushSetOf()"/> has not been closed via
+ /// <see cref="PopSequence()"/> or <see cref="PopSetOf()"/>.
+ /// </exception>
+ public bool TryEncode(Span<byte> dest, out int bytesWritten)
+ {
+ if ((_nestingStack?.Count ?? 0) != 0)
+ throw new InvalidOperationException(SR.Cryptography_AsnWriter_EncodeUnbalancedStack);
+
+ // If the stack is closed out then everything is a definite encoding (BER, DER) or a
+ // required indefinite encoding (CER). So we're correctly sized up, and ready to copy.
+ if (dest.Length < _offset)
+ {
+ bytesWritten = 0;
+ return false;
+ }
+
+ if (_offset == 0)
+ {
+ bytesWritten = 0;
+ return true;
+ }
+
+ bytesWritten = _offset;
+ _buffer.AsSpan().Slice(0, _offset).CopyTo(dest);
+ return true;
+ }
+
+ public byte[] Encode()
+ {
+ if ((_nestingStack?.Count ?? 0) != 0)
+ {
+ throw new InvalidOperationException(SR.Cryptography_AsnWriter_EncodeUnbalancedStack);
+ }
+
+ if (_offset == 0)
+ {
+ return Array.Empty<byte>();
+ }
+
+ // If the stack is closed out then everything is a definite encoding (BER, DER) or a
+ // required indefinite encoding (CER). So we're correctly sized up, and ready to copy.
+ return _buffer.AsSpan().Slice(0, _offset).ToArray();
+ }
+
+ private void PushTag(Asn1Tag tag)
+ {
+ if (_nestingStack == null)
+ {
+ _nestingStack = new Stack<(Asn1Tag,int)>();
+ }
+
+ Debug.Assert(tag.IsConstructed);
+ WriteTag(tag);
+ _nestingStack.Push((tag, _offset));
+ // Indicate that the length is indefinite.
+ // We'll come back and clean this up (as appropriate) in PopTag.
+ WriteLength(-1);
+ }
+
+ private void PopTag(Asn1Tag tag, bool sortContents=false)
+ {
+ if (_nestingStack == null || _nestingStack.Count == 0)
+ {
+ throw new ArgumentException(SR.Cryptography_AsnWriter_PopWrongTag, nameof(tag));
+ }
+
+ (Asn1Tag stackTag, int lenOffset) = _nestingStack.Peek();
+
+ Debug.Assert(tag.IsConstructed);
+ if (stackTag != tag)
+ {
+ throw new ArgumentException(SR.Cryptography_AsnWriter_PopWrongTag, nameof(tag));
+ }
+
+ _nestingStack.Pop();
+
+ if (sortContents)
+ {
+ SortContents(_buffer, lenOffset + 1, _offset);
+ }
+
+ // BER could use the indefinite encoding that CER does.
+ // But since the definite encoding form is easier to read (doesn't require a contextual
+ // parser to find the end-of-contents marker) some ASN.1 readers (including the previous
+ // incarnation of AsnReader) may choose not to support it.
+ //
+ // So, BER will use the DER rules here, in the interest of broader compatibility.
+
+ // T-REC-X.690-201508 sec 9.1 (constructed CER => indefinite length)
+ // T-REC-X.690-201508 sec 8.1.3.6
+ if (RuleSet == AsnEncodingRules.CER)
+ {
+ WriteEndOfContents();
+ return;
+ }
+
+ int containedLength = _offset - 1 - lenOffset;
+ Debug.Assert(containedLength >= 0);
+
+ int shiftSize = GetEncodedLengthSubsequentByteCount(containedLength);
+
+ // Best case, length fits in the compact byte
+ if (shiftSize == 0)
+ {
+ _buffer[lenOffset] = (byte)containedLength;
+ return;
+ }
+
+ // We're currently at the end, so ensure we have room for N more bytes.
+ EnsureWriteCapacity(shiftSize);
+
+ // Buffer.BlockCopy correctly does forward-overlapped, so use it.
+ int start = lenOffset + 1;
+ Buffer.BlockCopy(_buffer, start, _buffer, start + shiftSize, containedLength);
+
+ int tmp = _offset;
+ _offset = lenOffset;
+ WriteLength(containedLength);
+ Debug.Assert(_offset - lenOffset - 1 == shiftSize);
+ _offset = tmp + shiftSize;
+ }
+
+ public void WriteCharacterString(UniversalTagNumber encodingType, string str)
+ {
+ if (str == null)
+ throw new ArgumentNullException(nameof(str));
+
+ WriteCharacterString(encodingType, str.AsReadOnlySpan());
+ }
+
+ public void WriteCharacterString(UniversalTagNumber encodingType, ReadOnlySpan<char> str)
+ {
+ Text.Encoding encoding = AsnCharacterStringEncodings.GetEncoding(encodingType);
+
+ WriteCharacterStringCore(new Asn1Tag(encodingType), encoding, str);
+ }
+
+ public void WriteCharacterString(Asn1Tag tag, UniversalTagNumber encodingType, string str)
+ {
+ if (str == null)
+ throw new ArgumentNullException(nameof(str));
+
+ WriteCharacterString(tag, encodingType, str.AsReadOnlySpan());
+ }
+
+ public void WriteCharacterString(Asn1Tag tag, UniversalTagNumber encodingType, ReadOnlySpan<char> str)
+ {
+ CheckUniversalTag(tag, encodingType);
+
+ Text.Encoding encoding = AsnCharacterStringEncodings.GetEncoding(encodingType);
+ WriteCharacterStringCore(tag, encoding, str);
+ }
+
+ // T-REC-X.690-201508 sec 8.23
+ private void WriteCharacterStringCore(Asn1Tag tag, Text.Encoding encoding, ReadOnlySpan<char> str)
+ {
+ int size = -1;
+
+ // T-REC-X.690-201508 sec 9.2
+ if (RuleSet == AsnEncodingRules.CER)
+ {
+ // TODO: Split this for netstandard vs netcoreapp for span?.
+ unsafe
+ {
+ fixed (char* strPtr = &str.DangerousGetPinnableReference())
+ {
+ size = encoding.GetByteCount(strPtr, str.Length);
+
+ // If it exceeds the primitive segment size, use the constructed encoding.
+ if (size > AsnReader.MaxCERSegmentSize)
+ {
+ WriteConstructedCerCharacterString(tag, encoding, str, size);
+ return;
+ }
+ }
+ }
+ }
+
+ // TODO: Split this for netstandard vs netcoreapp for span?.
+ unsafe
+ {
+ fixed (char* strPtr = &str.DangerousGetPinnableReference())
+ {
+ if (size < 0)
+ {
+ size = encoding.GetByteCount(strPtr, str.Length);
+ }
+
+ // Clear the constructed tag, if present.
+ WriteTag(tag.AsPrimitive());
+ WriteLength(size);
+ Span<byte> dest = _buffer.AsSpan().Slice(_offset, size);
+
+ fixed (byte* destPtr = &dest.DangerousGetPinnableReference())
+ {
+ int written = encoding.GetBytes(strPtr, str.Length, destPtr, dest.Length);
+
+ if (written != size)
+ {
+ Debug.Fail($"Encoding produced different answer for GetByteCount ({size}) and GetBytes ({written})");
+ throw new InvalidOperationException();
+ }
+ }
+
+ _offset += size;
+ }
+ }
+ }
+
+ private void WriteConstructedCerCharacterString(Asn1Tag tag, Text.Encoding encoding, ReadOnlySpan<char> str, int size)
+ {
+ Debug.Assert(size > AsnReader.MaxCERSegmentSize);
+
+ byte[] tmp;
+
+ // TODO: Split this for netstandard vs netcoreapp for span?.
+ unsafe
+ {
+ fixed (char* strPtr = &str.DangerousGetPinnableReference())
+ {
+ tmp = ArrayPool<byte>.Shared.Rent(size);
+
+ fixed (byte* destPtr = tmp)
+ {
+ int written = encoding.GetBytes(strPtr, str.Length, destPtr, tmp.Length);
+
+ if (written != size)
+ {
+ Debug.Fail(
+ $"Encoding produced different answer for GetByteCount ({size}) and GetBytes ({written})");
+ throw new InvalidOperationException();
+ }
+ }
+ }
+ }
+
+ WriteConstructedCerOctetString(tag, tmp.AsSpan().Slice(0, size));
+ Array.Clear(tmp, 0, size);
+ ArrayPool<byte>.Shared.Return(tmp);
+ }
+
+ private static void SortContents(byte[] buffer, int start, int end)
+ {
+ Debug.Assert(buffer != null);
+ Debug.Assert(end >= start);
+
+ int len = end - start;
+
+ if (len == 0)
+ {
+ return;
+ }
+
+ // Since BER can read everything and the reader does not mutate data
+ // just use a BER reader for identifying the positions of the values
+ // within this memory segment.
+ //
+ // Since it's not mutating, any restrictions imposed by CER or DER will
+ // still be maintained.
+ var reader = new AsnReader(new ReadOnlyMemory<byte>(buffer, start, len), AsnEncodingRules.BER);
+
+ List<(int, int)> positions = new List<(int, int)>();
+
+ int pos = start;
+
+ while (reader.HasData)
+ {
+ ReadOnlyMemory<byte> encoded = reader.GetEncodedValue();
+ positions.Add((pos, encoded.Length));
+ pos += encoded.Length;
+ }
+
+ Debug.Assert(pos == end);
+
+ var comparer = new ArrayIndexSetOfValueComparer(buffer);
+ positions.Sort(comparer);
+
+ byte[] tmp = ArrayPool<byte>.Shared.Rent(len);
+
+ pos = 0;
+
+ foreach ((int offset, int length) in positions)
+ {
+ Buffer.BlockCopy(buffer, offset, tmp, pos, length);
+ pos += length;
+ }
+
+ Debug.Assert(pos == len);
+
+ Buffer.BlockCopy(tmp, 0, buffer, start, len);
+ Array.Clear(tmp, 0, len);
+ ArrayPool<byte>.Shared.Return(tmp);
+ }
+
+ private static void Reverse(Span<byte> span)
+ {
+ int i = 0;
+ int j = span.Length - 1;
+
+ while (i < j)
+ {
+ byte tmp = span[i];
+ span[i] = span[j];
+ span[j] = tmp;
+
+ i++;
+ j--;
+ }
+ }
+
+ private static void CheckUniversalTag(Asn1Tag tag, UniversalTagNumber universalTagNumber)
+ {
+ if (tag.TagClass == TagClass.Universal && tag.TagValue != (int)universalTagNumber)
+ {
+ throw new ArgumentException(
+ SR.Cryptography_Asn_UniversalValueIsFixed,
+ nameof(tag));
+ }
+ }
+
+ private class ArrayIndexSetOfValueComparer : IComparer<(int, int)>
+ {
+ private readonly byte[] _data;
+
+ public ArrayIndexSetOfValueComparer(byte[] data)
+ {
+ _data = data;
+ }
+
+ public int Compare((int, int) x, (int, int) y)
+ {
+ (int xOffset, int xLength) = x;
+ (int yOffset, int yLength) = y;
+
+ int value =
+ SetOfValueComparer.Instance.Compare(
+ new ReadOnlyMemory<byte>(_data, xOffset, xLength),
+ new ReadOnlyMemory<byte>(_data, yOffset, yLength));
+
+ if (value == 0)
+ {
+ // Whichever had the lowest index wins (once sorted, stay sorted)
+ return xOffset - yOffset;
+ }
+
+ return value;
+ }
+ }
+ }
+}
diff --git a/src/Common/tests/System/Security/Cryptography/ByteUtils.cs b/src/Common/tests/System/Security/Cryptography/ByteUtils.cs
index 38c8c5f918..f39df0409f 100644
--- a/src/Common/tests/System/Security/Cryptography/ByteUtils.cs
+++ b/src/Common/tests/System/Security/Cryptography/ByteUtils.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System;
using System.Globalization;
using System.Text;
@@ -36,11 +37,26 @@ namespace Test.Cryptography
internal static string ByteArrayToHex(this byte[] bytes)
{
+ return ByteArrayToHex(bytes.AsReadOnlySpan());
+ }
+
+ internal static string ByteArrayToHex(this Span<byte> bytes)
+ {
+ return ByteArrayToHex((ReadOnlySpan<byte>)bytes);
+ }
+
+ internal static string ByteArrayToHex(this ReadOnlyMemory<byte> bytes)
+ {
+ return ByteArrayToHex(bytes.Span);
+ }
+
+ internal static string ByteArrayToHex(this ReadOnlySpan<byte> bytes)
+ {
StringBuilder builder = new StringBuilder(bytes.Length * 2);
- foreach (byte b in bytes)
+ for (int i = 0; i < bytes.Length; i++)
{
- builder.Append(b.ToString("X2"));
+ builder.Append(bytes[i].ToString("X2"));
}
return builder.ToString();
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/Asn1ReaderTests.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/Asn1ReaderTests.cs
new file mode 100644
index 0000000000..a609c6661e
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/Asn1ReaderTests.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public abstract partial class Asn1ReaderTests
+ {
+ public enum PublicTagClass : byte
+ {
+ Universal = TagClass.Universal,
+ Application = TagClass.Application,
+ ContextSpecific = TagClass.ContextSpecific,
+ Private = TagClass.Private,
+ }
+
+ public enum PublicEncodingRules
+ {
+ BER = AsnEncodingRules.BER,
+ CER = AsnEncodingRules.CER,
+ DER = AsnEncodingRules.DER,
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ComprehensiveReadTests.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ComprehensiveReadTests.cs
new file mode 100644
index 0000000000..768a56e948
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ComprehensiveReadTests.cs
@@ -0,0 +1,265 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+using X509KeyUsageCSharpStyle=System.Security.Cryptography.Tests.Asn1.ReadNamedBitList.X509KeyUsageCSharpStyle;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public static class ComprehensiveReadTests
+ {
+ [Fact]
+ public static void ReadMicrosoftComCert()
+ {
+ byte[] bytes = MicrosoftDotComSslCertBytes;
+ AsnReader fileReader = new AsnReader(bytes, AsnEncodingRules.DER);
+
+ AsnReader certReader = fileReader.ReadSequence();
+ Assert.False(fileReader.HasData, "fileReader.HasData");
+
+ AsnReader tbsCertReader = certReader.ReadSequence();
+ AsnReader sigAlgReader = certReader.ReadSequence();
+
+ Assert.True(
+ certReader.TryGetPrimitiveBitStringValue(
+ out int unusedBitCount,
+ out ReadOnlyMemory<byte> signature),
+ "certReader.TryGetBitStringBytes");
+
+ Assert.Equal(0, unusedBitCount);
+ AssertRefSame(signature, ref bytes[1176], "Signature is a ref to bytes[1176]");
+
+ Assert.False(certReader.HasData, "certReader.HasData");
+
+ AsnReader versionExplicitWrapper = tbsCertReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0));
+ Assert.True(versionExplicitWrapper.TryReadInt32(out int certVersion));
+ Assert.Equal(2, certVersion);
+ Assert.False(versionExplicitWrapper.HasData, "versionExplicitWrapper.HasData");
+
+ ReadOnlyMemory<byte> serialBytes = tbsCertReader.GetIntegerBytes();
+ AssertRefSame(serialBytes, ref bytes[15], "Serial number starts at bytes[15]");
+
+ AsnReader tbsSigAlgReader = tbsCertReader.ReadSequence();
+ Assert.Equal("1.2.840.113549.1.1.11", tbsSigAlgReader.ReadObjectIdentifierAsString());
+ Assert.True(tbsSigAlgReader.HasData, "tbsSigAlgReader.HasData before ReadNull");
+ tbsSigAlgReader.ReadNull();
+ Assert.False(tbsSigAlgReader.HasData, "tbsSigAlgReader.HasData after ReadNull");
+
+ AsnReader issuerReader = tbsCertReader.ReadSequence();
+ Asn1Tag printableString = new Asn1Tag(UniversalTagNumber.PrintableString);
+ AssertRdn(issuerReader, "2.5.4.6", 57, printableString, bytes, "issuer[C]");
+ AssertRdn(issuerReader, "2.5.4.10", 70, printableString, bytes, "issuer[O]");
+ AssertRdn(issuerReader, "2.5.4.11", 101, printableString, bytes, "issuer[OU]");
+ AssertRdn(issuerReader, "2.5.4.3", 134, printableString, bytes, "issuer[CN]");
+ Assert.False(issuerReader.HasData, "issuerReader.HasData");
+
+ AsnReader validityReader = tbsCertReader.ReadSequence();
+ Assert.Equal(new DateTimeOffset(2014, 10, 15, 0, 0, 0, TimeSpan.Zero), validityReader.GetUtcTime());
+ Assert.Equal(new DateTimeOffset(2016, 10, 15, 23, 59, 59, TimeSpan.Zero), validityReader.GetUtcTime());
+ Assert.False(validityReader.HasData, "validityReader.HasData");
+
+ AsnReader subjectReader = tbsCertReader.ReadSequence();
+ Asn1Tag utf8String = new Asn1Tag(UniversalTagNumber.UTF8String);
+ AssertRdn(subjectReader, "1.3.6.1.4.1.311.60.2.1.3", 220, printableString, bytes, "subject[EV Country]");
+ AssertRdn(subjectReader, "1.3.6.1.4.1.311.60.2.1.2", 241, utf8String, bytes, "subject[EV State]", "Washington");
+ AssertRdn(subjectReader, "2.5.4.15", 262, printableString, bytes, "subject[Business Category]");
+ AssertRdn(subjectReader, "2.5.4.5", 293, printableString, bytes, "subject[Serial Number]");
+ AssertRdn(subjectReader, "2.5.4.6", 313, printableString, bytes, "subject[C]");
+ AssertRdn(subjectReader, "2.5.4.17", 326, utf8String, bytes, "subject[Postal Code]", "98052");
+ AssertRdn(subjectReader, "2.5.4.8", 342, utf8String, bytes, "subject[ST]", "Washington");
+ AssertRdn(subjectReader, "2.5.4.7", 363, utf8String, bytes, "subject[L]", "Redmond");
+ AssertRdn(subjectReader, "2.5.4.9", 381, utf8String, bytes, "subject[Street Address]", "1 Microsoft Way");
+ AssertRdn(subjectReader, "2.5.4.10", 407, utf8String, bytes, "subject[O]", "Microsoft Corporation");
+ AssertRdn(subjectReader, "2.5.4.11", 439, utf8String, bytes, "subject[OU]", "MSCOM");
+ AssertRdn(subjectReader, "2.5.4.3", 455, utf8String, bytes, "subject[CN]", "www.microsoft.com");
+ Assert.False(subjectReader.HasData, "subjectReader.HasData");
+
+ AsnReader subjectPublicKeyInfo = tbsCertReader.ReadSequence();
+ AsnReader spkiAlgorithm = subjectPublicKeyInfo.ReadSequence();
+ Assert.Equal("1.2.840.113549.1.1.1", spkiAlgorithm.ReadObjectIdentifierAsString());
+ spkiAlgorithm.ReadNull();
+ Assert.False(spkiAlgorithm.HasData, "spkiAlgorithm.HasData");
+
+ Assert.True(
+ subjectPublicKeyInfo.TryGetPrimitiveBitStringValue(
+ out unusedBitCount,
+ out ReadOnlyMemory<byte> encodedPublicKey),
+ "subjectPublicKeyInfo.TryGetBitStringBytes");
+
+ Assert.Equal(0, unusedBitCount);
+ AssertRefSame(encodedPublicKey, ref bytes[498], "Encoded public key starts at byte 498");
+
+ Assert.False(subjectPublicKeyInfo.HasData, "subjectPublicKeyInfo.HasData");
+
+ AsnReader publicKeyReader = new AsnReader(encodedPublicKey, AsnEncodingRules.DER);
+ AsnReader rsaPublicKeyReader = publicKeyReader.ReadSequence();
+ AssertRefSame(rsaPublicKeyReader.GetIntegerBytes(), ref bytes[506], "RSA Modulus is at bytes[502]");
+ Assert.True(rsaPublicKeyReader.TryReadInt32(out int rsaExponent));
+ Assert.Equal(65537, rsaExponent);
+ Assert.False(rsaPublicKeyReader.HasData, "rsaPublicKeyReader.HasData");
+ Assert.False(publicKeyReader.HasData, "publicKeyReader.HasData");
+
+ AsnReader extensionsContainer = tbsCertReader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 3));
+ AsnReader extensions = extensionsContainer.ReadSequence();
+ Assert.False(extensionsContainer.HasData, "extensionsContainer.HasData");
+
+ AsnReader sanExtension = extensions.ReadSequence();
+ Assert.Equal("2.5.29.17", sanExtension.ReadObjectIdentifierAsString());
+ Assert.True(sanExtension.TryGetPrimitiveOctetStringBytes(out ReadOnlyMemory<byte> sanExtensionBytes));
+ Assert.False(sanExtension.HasData, "sanExtension.HasData");
+
+ AsnReader sanExtensionPayload = new AsnReader(sanExtensionBytes, AsnEncodingRules.DER);
+ AsnReader sanExtensionValue = sanExtensionPayload.ReadSequence();
+ Assert.False(sanExtensionPayload.HasData, "sanExtensionPayload.HasData");
+ Asn1Tag dnsName = new Asn1Tag(TagClass.ContextSpecific, 2);
+ Assert.Equal("www.microsoft.com", sanExtensionValue.GetCharacterString(dnsName, UniversalTagNumber.IA5String));
+ Assert.Equal("wwwqa.microsoft.com", sanExtensionValue.GetCharacterString(dnsName, UniversalTagNumber.IA5String));
+ Assert.False(sanExtensionValue.HasData, "sanExtensionValue.HasData");
+
+ AsnReader basicConstraints = extensions.ReadSequence();
+ Assert.Equal("2.5.29.19", basicConstraints.ReadObjectIdentifierAsString());
+ Assert.True(basicConstraints.TryGetPrimitiveOctetStringBytes(out ReadOnlyMemory<byte> basicConstraintsBytes));
+
+ AsnReader basicConstraintsPayload = new AsnReader(basicConstraintsBytes, AsnEncodingRules.DER);
+ AsnReader basicConstraintsValue = basicConstraintsPayload.ReadSequence();
+ Assert.False(basicConstraintsValue.HasData, "basicConstraintsValue.HasData");
+ Assert.False(basicConstraintsPayload.HasData, "basicConstraintsPayload.HasData");
+
+ AsnReader keyUsageExtension = extensions.ReadSequence();
+ Assert.Equal("2.5.29.15", keyUsageExtension.ReadObjectIdentifierAsString());
+ Assert.True(keyUsageExtension.ReadBoolean(), "keyUsageExtension.ReadBoolean() (IsCritical)");
+ Assert.True(keyUsageExtension.TryGetPrimitiveOctetStringBytes(out ReadOnlyMemory<byte> keyUsageBytes));
+
+ AsnReader keyUsagePayload = new AsnReader(keyUsageBytes, AsnEncodingRules.DER);
+
+ Assert.Equal(
+ X509KeyUsageCSharpStyle.DigitalSignature | X509KeyUsageCSharpStyle.KeyEncipherment,
+ keyUsagePayload.GetNamedBitListValue<X509KeyUsageCSharpStyle>());
+
+ Assert.False(keyUsagePayload.HasData, "keyUsagePayload.HasData");
+
+ AssertExtension(extensions, "2.5.29.37", false, 863, bytes);
+ AssertExtension(extensions, "2.5.29.32", false, 894, bytes);
+ AssertExtension(extensions, "2.5.29.35", false, 998, bytes);
+ AssertExtension(extensions, "2.5.29.31", false, 1031, bytes);
+ AssertExtension(extensions, "1.3.6.1.5.5.7.1.1", false, 1081, bytes);
+ Assert.False(extensions.HasData, "extensions.HasData");
+
+ Assert.Equal("1.2.840.113549.1.1.11", sigAlgReader.ReadObjectIdentifierAsString());
+ sigAlgReader.ReadNull();
+ Assert.False(sigAlgReader.HasData);
+ }
+
+ private static void AssertExtension(AsnReader extensions, string oid, bool critical, int index, byte[] bytes)
+ {
+ AsnReader extension = extensions.ReadSequence();
+ Assert.Equal(oid, extension.ReadObjectIdentifierAsString());
+
+ if (critical)
+ {
+ Assert.True(extension.ReadBoolean(), $"{oid} is critical");
+ }
+
+ Assert.True(extension.TryGetPrimitiveOctetStringBytes(out ReadOnlyMemory<byte> extensionBytes));
+ AssertRefSame(extensionBytes, ref bytes[index], $"{oid} extension value is at byte {index}");
+ }
+
+ private static void AssertRdn(
+ AsnReader reader,
+ string atvOid,
+ int offset,
+ Asn1Tag valueTag,
+ byte[] bytes,
+ string label,
+ string stringValue=null)
+ {
+ AsnReader rdn = reader.ReadSetOf();
+ AsnReader attributeTypeAndValue = rdn.ReadSequence();
+ Assert.Equal(atvOid, attributeTypeAndValue.ReadObjectIdentifierAsString());
+
+ ReadOnlyMemory<byte> value = attributeTypeAndValue.GetEncodedValue();
+ ReadOnlySpan<byte> valueSpan = value.Span;
+
+ Assert.True(Asn1Tag.TryParse(valueSpan, out Asn1Tag actualTag, out int bytesRead));
+ Assert.Equal(1, bytesRead);
+ Assert.Equal(valueTag, actualTag);
+
+ AssertRefSame(
+ ref valueSpan.DangerousGetPinnableReference(),
+ ref bytes[offset],
+ $"{label} is at bytes[{offset}]");
+
+ if (stringValue != null)
+ {
+ AsnReader valueReader = new AsnReader(value, AsnEncodingRules.DER);
+ Assert.Equal(stringValue, valueReader.GetCharacterString((UniversalTagNumber)valueTag.TagValue));
+ Assert.False(valueReader.HasData, "valueReader.HasData");
+ }
+
+ Assert.False(attributeTypeAndValue.HasData, $"attributeTypeAndValue.HasData ({label})");
+ Assert.False(rdn.HasData, $"rdn.HasData ({label})");
+ }
+
+ private static void AssertRefSame(ReadOnlyMemory<byte> a, ref byte b, string msg)
+ {
+ AssertRefSame(ref a.Span.DangerousGetPinnableReference(), ref b, msg);
+ }
+
+ private static void AssertRefSame(ref byte a, ref byte b, string msg)
+ {
+ Assert.True(Unsafe.AreSame(ref a, ref b), msg);
+ }
+
+ internal static readonly byte[] MicrosoftDotComSslCertBytes = (
+ "308205943082047CA00302010202103DF70C5D9903F8D8868B9B8CCF20DF6930" +
+ "0D06092A864886F70D01010B05003077310B3009060355040613025553311D30" +
+ "1B060355040A131453796D616E74656320436F72706F726174696F6E311F301D" +
+ "060355040B131653796D616E746563205472757374204E6574776F726B312830" +
+ "260603550403131F53796D616E74656320436C61737320332045562053534C20" +
+ "4341202D204733301E170D3134313031353030303030305A170D313631303135" +
+ "3233353935395A3082010F31133011060B2B0601040182373C02010313025553" +
+ "311B3019060B2B0601040182373C0201020C0A57617368696E67746F6E311D30" +
+ "1B060355040F131450726976617465204F7267616E697A6174696F6E31123010" +
+ "06035504051309363030343133343835310B3009060355040613025553310E30" +
+ "0C06035504110C0539383035323113301106035504080C0A57617368696E6774" +
+ "6F6E3110300E06035504070C075265646D6F6E643118301606035504090C0F31" +
+ "204D6963726F736F667420576179311E301C060355040A0C154D6963726F736F" +
+ "667420436F72706F726174696F6E310E300C060355040B0C054D53434F4D311A" +
+ "301806035504030C117777772E6D6963726F736F66742E636F6D30820122300D" +
+ "06092A864886F70D01010105000382010F003082010A0282010100A46861FA9D" +
+ "5DB763633BF5A64EF6E7C2C2367F48D2D46643A22DFCFCCB24E58A14D0F06BDC" +
+ "956437F2A56BA4BEF70BA361BF12964A0D665AFD84B0F7494C8FA4ABC5FCA2E0" +
+ "17C06178AEF2CDAD1B5F18E997A14B965C074E8F564970607276B00583932240" +
+ "FE6E2DD013026F9AE13D7C91CC07C4E1E8E87737DC06EF2B575B89D62EFE4685" +
+ "9F8255A123692A706C68122D4DAFE11CB205A7B3DE06E553F7B95F978EF8601A" +
+ "8DF819BF32040BDF92A0DE0DF269B4514282E17AC69934E8440A48AB9D1F5DF8" +
+ "9A502CEF6DFDBE790045BD45E0C94E5CA8ADD76A013E9C978440FC8A9E2A9A49" +
+ "40B2460819C3E302AA9C9F355AD754C86D3ED77DDAA3DA13810B4D0203010001" +
+ "A38201803082017C30310603551D11042A302882117777772E6D6963726F736F" +
+ "66742E636F6D821377777771612E6D6963726F736F66742E636F6D3009060355" +
+ "1D1304023000300E0603551D0F0101FF0404030205A0301D0603551D25041630" +
+ "1406082B0601050507030106082B0601050507030230660603551D20045F305D" +
+ "305B060B6086480186F84501071706304C302306082B06010505070201161768" +
+ "747470733A2F2F642E73796D63622E636F6D2F637073302506082B0601050507" +
+ "020230191A1768747470733A2F2F642E73796D63622E636F6D2F727061301F06" +
+ "03551D230418301680140159ABE7DD3A0B59A66463D6CF200757D591E76A302B" +
+ "0603551D1F042430223020A01EA01C861A687474703A2F2F73722E73796D6362" +
+ "2E636F6D2F73722E63726C305706082B06010505070101044B3049301F06082B" +
+ "060105050730018613687474703A2F2F73722E73796D63642E636F6D30260608" +
+ "2B06010505073002861A687474703A2F2F73722E73796D63622E636F6D2F7372" +
+ "2E637274300D06092A864886F70D01010B0500038201010015F8505B627ED7F9" +
+ "F96707097E93A51E7A7E05A3D420A5C258EC7A1CFE1843EC20ACF728AAFA7A1A" +
+ "1BC222A7CDBF4AF90AA26DEEB3909C0B3FB5C78070DAE3D645BFCF840A4A3FDD" +
+ "988C7B3308BFE4EB3FD66C45641E96CA3352DBE2AEB4488A64A9C5FB96932BA7" +
+ "0059CE92BD278B41299FD213471BD8165F924285AE3ECD666C703885DCA65D24" +
+ "DA66D3AFAE39968521995A4C398C7DF38DFA82A20372F13D4A56ADB21B582254" +
+ "9918015647B5F8AC131CC5EB24534D172BC60218A88B65BCF71C7F388CE3E0EF" +
+ "697B4203720483BB5794455B597D80D48CD3A1D73CBBC609C058767D1FF060A6" +
+ "09D7E3D4317079AF0CD0A8A49251AB129157F9894A036487").HexToByteArray();
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ParseTag.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ParseTag.cs
new file mode 100644
index 0000000000..a9211778f1
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ParseTag.cs
@@ -0,0 +1,160 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ParseTag : Asn1ReaderTests
+ {
+ [Theory]
+ [InlineData(PublicTagClass.Universal, false, 0, "00")]
+ [InlineData(PublicTagClass.Universal, false, 1, "01")]
+ [InlineData(PublicTagClass.Application, true, 1, "61")]
+ [InlineData(PublicTagClass.ContextSpecific, false, 1, "81")]
+ [InlineData(PublicTagClass.ContextSpecific, true, 1, "A1")]
+ [InlineData(PublicTagClass.Private, false, 1, "C1")]
+ [InlineData(PublicTagClass.Universal, false, 30, "1E")]
+ [InlineData(PublicTagClass.Application, false, 30, "5E")]
+ [InlineData(PublicTagClass.ContextSpecific, false, 30, "9E")]
+ [InlineData(PublicTagClass.Private, false, 30, "DE")]
+ [InlineData(PublicTagClass.Universal, false, 31, "1F1F")]
+ [InlineData(PublicTagClass.Application, false, 31, "5F1F")]
+ [InlineData(PublicTagClass.ContextSpecific, false, 31, "9F1F")]
+ [InlineData(PublicTagClass.Private, false, 31, "DF1F")]
+ [InlineData(PublicTagClass.Private, false, 127, "DF7F")]
+ [InlineData(PublicTagClass.Private, false, 128, "DF8100")]
+ [InlineData(PublicTagClass.Private, false, 253, "DF817D")]
+ [InlineData(PublicTagClass.Private, false, 255, "DF817F")]
+ [InlineData(PublicTagClass.Private, false, 256, "DF8200")]
+ [InlineData(PublicTagClass.Private, false, 1 << 9, "DF8400")]
+ [InlineData(PublicTagClass.Private, false, 1 << 10, "DF8800")]
+ [InlineData(PublicTagClass.Private, false, 0b0011_1101_1110_0111, "DFFB67")]
+ [InlineData(PublicTagClass.Private, false, 1 << 14, "DF818000")]
+ [InlineData(PublicTagClass.Private, false, 1 << 18, "DF908000")]
+ [InlineData(PublicTagClass.Private, false, 1 << 18 | 1 << 9, "DF908400")]
+ [InlineData(PublicTagClass.Private, false, 1 << 20, "DFC08000")]
+ [InlineData(PublicTagClass.Private, false, 0b0001_1110_1010_0111_0000_0001, "DFFACE01")]
+ [InlineData(PublicTagClass.Private, false, 1 << 21, "DF81808000")]
+ [InlineData(PublicTagClass.Private, false, 1 << 27, "DFC0808000")]
+ [InlineData(PublicTagClass.Private, false, 1 << 28, "DF8180808000")]
+ [InlineData(PublicTagClass.Private, true, int.MaxValue, "FF87FFFFFF7F")]
+ [InlineData(PublicTagClass.Universal, false, 119, "1F77")]
+ public static void ParseValidTag(
+ PublicTagClass tagClass,
+ bool isConstructed,
+ int tagValue,
+ string inputHex)
+ {
+ byte[] inputBytes = inputHex.HexToByteArray();
+
+ bool parsed = Asn1Tag.TryParse(inputBytes, out Asn1Tag tag, out int bytesRead);
+
+ Assert.True(parsed, "Asn1Tag.TryParse");
+ Assert.Equal(inputBytes.Length, bytesRead);
+ Assert.Equal((TagClass)tagClass, tag.TagClass);
+ Assert.Equal(tagValue, tag.TagValue);
+
+ if (isConstructed)
+ {
+ Assert.True(tag.IsConstructed, "tag.IsConstructed");
+ }
+ else
+ {
+ Assert.False(tag.IsConstructed, "tag.IsConstructed");
+ }
+
+ byte[] secondBytes = new byte[inputBytes.Length];
+ int written;
+ Assert.False(tag.TryWrite(secondBytes.AsSpan().Slice(0, inputBytes.Length - 1), out written));
+ Assert.Equal(0, written);
+ Assert.True(tag.TryWrite(secondBytes, out written));
+ Assert.Equal(inputBytes.Length, written);
+ Assert.Equal(inputHex, secondBytes.ByteArrayToHex());
+ }
+
+ [Theory]
+ [InlineData("Empty", "")]
+ [InlineData("MultiByte-NoFollow", "1F")]
+ [InlineData("MultiByte-NoFollow2", "1F81")]
+ [InlineData("MultiByte-NoFollow3", "1F8180")]
+ [InlineData("MultiByte-TooLow", "1F01")]
+ [InlineData("MultiByte-TooLowMax", "1F1E")]
+ [InlineData("MultiByte-Leading0", "1F807F")]
+ [InlineData("MultiByte-ValueTooBig", "FF8880808000")]
+ [InlineData("MultiByte-ValueSubtlyTooBig", "DFC1C0808000")]
+ public static void ParseCorruptTag(string description, string inputHex)
+ {
+ byte[] inputBytes = inputHex.HexToByteArray();
+
+ Assert.False(Asn1Tag.TryParse(inputBytes, out Asn1Tag tag, out var bytesRead));
+
+ Assert.Equal(default(Asn1Tag), tag);
+ Assert.Equal(0, bytesRead);
+ }
+
+ [Fact]
+ public static void TestEquals()
+ {
+ Asn1Tag integer = new Asn1Tag(TagClass.Universal, 2);
+ Asn1Tag integerAgain = new Asn1Tag(TagClass.Universal, 2);
+ Asn1Tag context2 = new Asn1Tag(TagClass.ContextSpecific, 2);
+ Asn1Tag constructedContext2 = new Asn1Tag(TagClass.ContextSpecific, 2, true);
+ Asn1Tag application2 = new Asn1Tag(TagClass.Application, 2);
+
+ Assert.False(integer.Equals(null));
+ Assert.False(integer.Equals(0x02));
+ Assert.False(integer.Equals(context2));
+ Assert.False(context2.Equals(constructedContext2));
+ Assert.False(context2.Equals(application2));
+
+ Assert.Equal(integer, integerAgain);
+ Assert.True(integer == integerAgain);
+ Assert.True(integer != context2);
+ Assert.False(integer == context2);
+ Assert.False(context2 == constructedContext2);
+ Assert.False(context2 == application2);
+
+ Assert.NotEqual(integer.GetHashCode(), context2.GetHashCode());
+ Assert.NotEqual(context2.GetHashCode(), constructedContext2.GetHashCode());
+ Assert.NotEqual(context2.GetHashCode(), application2.GetHashCode());
+ Assert.Equal(integer.GetHashCode(), integerAgain.GetHashCode());
+ }
+
+ [Theory]
+ [InlineData(PublicTagClass.Universal, false, 0, "00")]
+ [InlineData(PublicTagClass.ContextSpecific, true, 1, "A1")]
+ [InlineData(PublicTagClass.Application, false, 31, "5F1F")]
+ [InlineData(PublicTagClass.Private, false, 128, "DF8100")]
+ [InlineData(PublicTagClass.Private, false, 0b0001_1110_1010_0111_0000_0001, "DFFACE01")]
+ [InlineData(PublicTagClass.Private, true, int.MaxValue, "FF87FFFFFF7F")]
+ public static void ParseTagWithMoreData(
+ PublicTagClass tagClass,
+ bool isConstructed,
+ int tagValue,
+ string inputHex)
+ {
+ byte[] inputBytes = inputHex.HexToByteArray();
+ Array.Resize(ref inputBytes, inputBytes.Length + 3);
+
+ bool parsed = Asn1Tag.TryParse(inputBytes, out Asn1Tag tag, out int bytesRead);
+
+ Assert.True(parsed, "Asn1Tag.TryParse");
+ Assert.Equal(inputHex.Length / 2, bytesRead);
+ Assert.Equal((TagClass)tagClass, tag.TagClass);
+ Assert.Equal(tagValue, tag.TagValue);
+
+ if (isConstructed)
+ {
+ Assert.True(tag.IsConstructed, "tag.IsConstructed");
+ }
+ else
+ {
+ Assert.False(tag.IsConstructed, "tag.IsConstructed");
+ }
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/PeekTests.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/PeekTests.cs
new file mode 100644
index 0000000000..c5814b41c8
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/PeekTests.cs
@@ -0,0 +1,213 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class PeekTests : Asn1ReaderTests
+ {
+ [Fact]
+ public static void ReaderPeekTag_Valid()
+ {
+ // SEQUENCE(NULL)
+ byte[] data = { 0x30, 0x02, 0x05, 0x00 };
+ AsnReader reader = new AsnReader(data, AsnEncodingRules.BER);
+ Asn1Tag tag = reader.PeekTag();
+
+ Assert.Equal((int)UniversalTagNumber.Sequence, tag.TagValue);
+ Assert.True(tag.IsConstructed, "tag.IsConstructed");
+ Assert.Equal(TagClass.Universal, tag.TagClass);
+ }
+
+ [Fact]
+ public static void ReaderPeekTag_Invalid()
+ {
+ // (UNIVERSAL [continue into next byte])
+ byte[] data = { 0x1F };
+ AsnReader reader = new AsnReader(data, AsnEncodingRules.BER);
+
+ try
+ {
+ reader.PeekTag();
+ Assert.True(false, "CryptographicException was thrown");
+ }
+ catch (CryptographicException)
+ {
+ }
+ }
+
+ [Fact]
+ public static void PeekEncodedValue_Primitive()
+ {
+ const string EncodedContents = "010203040506";
+ const string EncodedValue = "0406" + EncodedContents;
+
+ byte[] data = (EncodedValue + "0500").HexToByteArray();
+
+ AsnReader reader = new AsnReader(data, AsnEncodingRules.BER);
+ Assert.Equal(EncodedValue, reader.PeekEncodedValue().ByteArrayToHex());
+
+ // It's Peek, so it's reproducible.
+ Assert.Equal(EncodedValue, reader.PeekEncodedValue().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void PeekEncodedValue_Indefinite()
+ {
+ const string EncodedContents = "040101" + "04050203040506";
+ const string EncodedValue = "2480" + EncodedContents + "0000";
+
+ byte[] data = (EncodedValue + "0500").HexToByteArray();
+
+ AsnReader reader = new AsnReader(data, AsnEncodingRules.BER);
+ Assert.Equal(EncodedValue, reader.PeekEncodedValue().ByteArrayToHex());
+
+ // It's Peek, so it's reproducible.
+ Assert.Equal(EncodedValue, reader.PeekEncodedValue().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void PeekEncodedValue_Corrupt_Throws()
+ {
+ const string EncodedContents = "040101" + "04050203040506";
+ // Constructed bit isn't set, so indefinite length is invalid.
+ const string EncodedValue = "0480" + EncodedContents + "0000";
+
+ byte[] data = (EncodedValue + "0500").HexToByteArray();
+
+ Assert.Throws<CryptographicException>(
+ () =>
+ {
+ AsnReader reader = new AsnReader(data, AsnEncodingRules.BER);
+ reader.PeekEncodedValue();
+ });
+ }
+
+ [Fact]
+ public static void PeekContentSpan_Primitive()
+ {
+ const string EncodedContents = "010203040506";
+ const string EncodedValue = "0406" + EncodedContents;
+
+ byte[] data = (EncodedValue + "0500").HexToByteArray();
+
+ AsnReader reader = new AsnReader(data, AsnEncodingRules.BER);
+ Assert.Equal(EncodedContents, reader.PeekContentBytes().ByteArrayToHex());
+
+ // It's Peek, so it's reproducible.
+ Assert.Equal(EncodedValue, reader.PeekEncodedValue().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void PeekContentSpan_Indefinite()
+ {
+ const string EncodedContents = "040101" + "04050203040506";
+ const string EncodedValue = "2480" + EncodedContents + "0000";
+
+ byte[] data = (EncodedValue + "0500").HexToByteArray();
+
+ AsnReader reader = new AsnReader(data, AsnEncodingRules.BER);
+ Assert.Equal(EncodedContents, reader.PeekContentBytes().ByteArrayToHex());
+
+ // It's Peek, so it's reproducible.
+ Assert.Equal(EncodedValue, reader.PeekEncodedValue().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void PeekContentSpan_Corrupt_Throws()
+ {
+ const string EncodedContents = "040101" + "04050203040506";
+ // Constructed bit isn't set, so indefinite length is invalid.
+ const string EncodedValue = "0480" + EncodedContents + "0000";
+
+ byte[] data = (EncodedValue + "0500").HexToByteArray();
+
+ Assert.Throws<CryptographicException>(
+ () =>
+ {
+ AsnReader reader = new AsnReader(data, AsnEncodingRules.BER);
+ reader.PeekContentBytes();
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public static void PeekContentSpan_ExtremelyNested(bool fullArray)
+ {
+ byte[] dataBytes = new byte[4 * 16384];
+
+ // For a full array this will build 2^14 nested indefinite length values.
+ // PeekContentBytes should return dataBytes.Slice(2, dataBytes.Length - 4)
+ //
+ // For what it's worth, the initial algorithm succeeded at 1650, and StackOverflowed with 1651.
+ //
+ // With the counter-and-no-recursion algorithm a nesting depth of 534773759 was verified,
+ // at a cost of 10 minutes of execution and a 2139095036 byte array.
+ // (The size was "a little bit less than int.MaxValue, since that OOMed my 32-bit process")
+ int end = dataBytes.Length / 2;
+ int expectedLength = dataBytes.Length - 4;
+
+ if (!fullArray)
+ {
+ // Use 3/4 of what's available, just to prove we're not counting from the end.
+ // So with "full" being a nesting value 16384 this will use 12288
+ end = end / 4 * 3;
+ expectedLength = 2 * end - 4;
+ }
+
+ for (int i = 0; i < end; i += 2)
+ {
+ // Context-Specific 0 [Constructed]
+ dataBytes[i] = 0xA0;
+ // Indefinite length
+ dataBytes[i + 1] = 0x80;
+ }
+
+ AsnReader reader = new AsnReader(dataBytes, AsnEncodingRules.BER);
+ ReadOnlyMemory<byte> contents = reader.PeekContentBytes();
+ Assert.Equal(expectedLength, contents.Length);
+ Assert.True(Unsafe.AreSame(ref dataBytes[2], ref contents.Span.DangerousGetPinnableReference()));
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public static void PeekEncodedValue_ExtremelyNested(bool fullArray)
+ {
+ byte[] dataBytes = new byte[4 * 16384];
+
+ // For a full array this will build 2^14 nested indefinite length values.
+ // PeekEncodedValue should return the whole array.
+ int end = dataBytes.Length / 2;
+ int expectedLength = dataBytes.Length;
+
+ if (!fullArray)
+ {
+ // Use 3/4 of what's available, just to prove we're not counting from the end.
+ // So with "full" being a nesting value 16384 this will use 12288, and
+ // PeekEncodedValue should give us back 48k, not 64k.
+ end = end / 4 * 3;
+ expectedLength = 2 * end;
+ }
+
+ for (int i = 0; i < end; i += 2)
+ {
+ // Context-Specific 0 [Constructed]
+ dataBytes[i] = 0xA0;
+ // Indefinite length
+ dataBytes[i + 1] = 0x80;
+ }
+
+ AsnReader reader = new AsnReader(dataBytes, AsnEncodingRules.BER);
+ ReadOnlyMemory<byte> contents = reader.PeekEncodedValue();
+ Assert.Equal(expectedLength, contents.Length);
+ Assert.True(Unsafe.AreSame(ref dataBytes[0], ref contents.Span.DangerousGetPinnableReference()));
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadBMPString.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadBMPString.cs
new file mode 100644
index 0000000000..c8e8c42cea
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadBMPString.cs
@@ -0,0 +1,755 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadBMPString : Asn1ReaderTests
+ {
+ public static IEnumerable<object[]> ValidEncodingData { get; } =
+ new object[][]
+ {
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "1E1A004A006F0068006E00200051002E00200053006D006900740068",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.CER,
+ "1E1A004A006F0068006E00200051002E00200053006D006900740068",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.DER,
+ "1E1A004A006F0068006E00200051002E00200053006D006900740068",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "3E80" + "041A004A006F0068006E00200051002E00200053006D006900740068" + "0000",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "3E1C" + "041A004A006F0068006E00200051002E00200053006D006900740068",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "1E00",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.CER,
+ "1E00",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.DER,
+ "1E00",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "3E00",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "3E80" + "0000",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "3E80" +
+ "2480" +
+ // "Dr."
+ "040600440072002E" +
+ // " & "
+ "0406002000260020" +
+ // "Mrs."
+ "0408004D00720073002E" +
+ "0000" +
+ // " "
+ "04020020" +
+ "2480" +
+ "2410" +
+ // "Smith"
+ "040A0053006D006900740068" +
+ // hyphen (U+2010)
+ "04022010" +
+ "0000" +
+ // "Jones"
+ "040A004A006F006E00650073" +
+ "2480" +
+ // " "
+ "04020020" +
+ "2480" +
+ // The next two bytes are U+FE60, small ampersand
+ // Since UCS-2 would always chunk evenly under CER the odds of
+ // misaligned data are low in reality, but maybe some BER encoder
+ // chunks odd, so a split scenario could still happen.
+ "0401FE" +
+ "040160" +
+ "0000" +
+ // " "
+ "04020020" +
+ // "children"
+ "0410006300680069006C006400720065006E" +
+ "0000" +
+ "0000",
+ "Dr. & Mrs. Smith\u2010Jones \uFE60 children",
+ },
+ };
+
+ [Theory]
+ [MemberData(nameof(ValidEncodingData))]
+ public static void GetBMPString_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ string expectedValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ string value = reader.GetCharacterString(UniversalTagNumber.BMPString);
+
+ Assert.Equal(expectedValue, value);
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidEncodingData))]
+ public static void TryCopyBMPString(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ string expectedValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ char[] output = new char[expectedValue.Length];
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ bool copied;
+ int charsWritten;
+
+ if (output.Length > 0)
+ {
+ output[0] = 'a';
+
+ copied = reader.TryCopyBMPString(
+ output.AsSpan().Slice(0, expectedValue.Length - 1),
+ out charsWritten);
+
+ Assert.False(copied, "reader.TryCopyBMPString - too short");
+ Assert.Equal(0, charsWritten);
+ Assert.Equal('a', output[0]);
+ }
+
+ copied = reader.TryCopyBMPString(
+ output,
+ out charsWritten);
+
+ Assert.True(copied, "reader.TryCopyBMPString");
+
+ string actualValue = new string(output, 0, charsWritten);
+ Assert.Equal(expectedValue, actualValue);
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidEncodingData))]
+ public static void TryCopyBMPStringBytes(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ string expectedString)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ string expectedHex = Text.Encoding.BigEndianUnicode.GetBytes(expectedString).ByteArrayToHex();
+ byte[] output = new byte[expectedHex.Length / 2];
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ bool copied;
+ int bytesWritten;
+
+ if (output.Length > 0)
+ {
+ output[0] = 32;
+
+ copied = reader.TryCopyBMPStringBytes(output.AsSpan().Slice(0, output.Length - 1),
+ out bytesWritten);
+
+ Assert.False(copied, "reader.TryCopyBMPStringBytes - too short");
+ Assert.Equal(0, bytesWritten);
+ Assert.Equal(32, output[0]);
+ }
+
+ copied = reader.TryCopyBMPStringBytes(output,
+ out bytesWritten);
+
+ Assert.True(copied, "reader.TryCopyBMPStringBytes");
+
+ Assert.Equal(
+ expectedHex,
+ new ReadOnlySpan<byte>(output, 0, bytesWritten).ByteArrayToHex());
+
+ Assert.Equal(output.Length, bytesWritten);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "1E020020", true)]
+ [InlineData(PublicEncodingRules.BER, "3E80" + "04020020" + "0000", false)]
+ [InlineData(PublicEncodingRules.BER, "3E04" + "04020020", false)]
+ public static void TryGetBMPStringBytes(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ bool expectSuccess)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ bool got = reader.TryGetBMPStringBytes(out ReadOnlyMemory<byte> contents);
+
+ if (expectSuccess)
+ {
+ Assert.True(got, "reader.TryGetBMPStringBytes");
+
+ Assert.True(
+ Unsafe.AreSame(
+ ref contents.Span.DangerousGetPinnableReference(),
+ ref inputData[2]));
+ }
+ else
+ {
+ Assert.False(got, "reader.TryGetBMPStringBytes");
+ Assert.True(contents.IsEmpty, "contents.IsEmpty");
+ }
+ }
+
+ [Theory]
+ [InlineData("Incomplete Tag", PublicEncodingRules.BER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.CER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.DER, "1F")]
+ [InlineData("Missing Length", PublicEncodingRules.BER, "1E")]
+ [InlineData("Missing Length", PublicEncodingRules.CER, "1E")]
+ [InlineData("Missing Length", PublicEncodingRules.DER, "1E")]
+ [InlineData("Missing Contents", PublicEncodingRules.BER, "1E02")]
+ [InlineData("Missing Contents", PublicEncodingRules.CER, "1E02")]
+ [InlineData("Missing Contents", PublicEncodingRules.DER, "1E02")]
+ [InlineData("Length Too Long", PublicEncodingRules.BER, "1E0600480069")]
+ [InlineData("Length Too Long", PublicEncodingRules.CER, "1E0600480069")]
+ [InlineData("Length Too Long", PublicEncodingRules.DER, "1E0600480069")]
+ [InlineData("Constructed Form", PublicEncodingRules.DER, "3E0404020049")]
+ public static void TryGetBMPStringBytes_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () =>
+ {
+ reader.TryGetBMPStringBytes(out ReadOnlyMemory<byte> contents);
+ });
+ }
+
+ [Theory]
+ [InlineData("Empty", PublicEncodingRules.BER, "")]
+ [InlineData("Empty", PublicEncodingRules.CER, "")]
+ [InlineData("Empty", PublicEncodingRules.DER, "")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.BER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.CER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.DER, "1F")]
+ [InlineData("Missing Length", PublicEncodingRules.BER, "1E")]
+ [InlineData("Missing Length", PublicEncodingRules.CER, "1E")]
+ [InlineData("Missing Length", PublicEncodingRules.DER, "1E")]
+ [InlineData("Missing Contents", PublicEncodingRules.BER, "1E02")]
+ [InlineData("Missing Contents", PublicEncodingRules.CER, "1E02")]
+ [InlineData("Missing Contents", PublicEncodingRules.DER, "1E02")]
+ [InlineData("Missing Contents - Constructed", PublicEncodingRules.BER, "3E02")]
+ [InlineData("Missing Contents - Constructed Indef", PublicEncodingRules.BER, "3E80")]
+ [InlineData("Missing Contents - Constructed Indef", PublicEncodingRules.CER, "3E80")]
+ [InlineData("Length Too Long", PublicEncodingRules.BER, "1E034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.CER, "1E034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.DER, "1E034869")]
+ [InlineData("Definite Constructed Form", PublicEncodingRules.CER, "3E03040149")]
+ [InlineData("Definite Constructed Form", PublicEncodingRules.DER, "3E03040149")]
+ [InlineData("Indefinite Constructed Form - Short Payload", PublicEncodingRules.CER, "3E800401490000")]
+ [InlineData("Indefinite Constructed Form", PublicEncodingRules.DER, "3E800401490000")]
+ [InlineData("No nested content", PublicEncodingRules.CER, "3E800000")]
+ [InlineData("No EoC", PublicEncodingRules.BER, "3E80" + "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.BER, "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.CER, "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.DER, "04024869")]
+ [InlineData("Wrong Tag - Constructed", PublicEncodingRules.BER, "240404024869")]
+ [InlineData("Wrong Tag - Constructed Indef", PublicEncodingRules.BER, "2480" + "04024869" + "0000")]
+ [InlineData("Wrong Tag - Constructed Indef", PublicEncodingRules.CER, "2480" + "04024869" + "0000")]
+ [InlineData("Wrong Tag - Constructed", PublicEncodingRules.DER, "240404024869")]
+ [InlineData("Nested Bad Tag", PublicEncodingRules.BER, "3E04" + "1E024869")]
+ [InlineData("Nested context-specific", PublicEncodingRules.BER, "3E04800400FACE")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.BER, "3E80800400FACE0000")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.CER, "3E80800400FACE0000")]
+ [InlineData("Nested Length Too Long", PublicEncodingRules.BER, "3E07" + ("2402" + "0404") + "04020049")]
+ [InlineData("Nested Simple Length Too Long", PublicEncodingRules.BER, "3E03" + "040548656C6C6F")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.BER, "3E8020000000")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.CER, "3E8020000000")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.BER, "3E80000100")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.CER, "3E80000100")]
+ [InlineData("LongLength EndOfContents", PublicEncodingRules.BER, "3E80008100")]
+ public static void TryCopyBMPStringBytes_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ byte[] outputData = new byte[inputData.Length + 1];
+ outputData[0] = 252;
+
+ int bytesWritten = -1;
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () =>
+ {
+ reader.TryCopyBMPStringBytes(outputData, out bytesWritten);
+ });
+
+ Assert.Equal(-1, bytesWritten);
+ Assert.Equal(252, outputData[0]);
+ }
+
+ private static void TryCopyBMPString_Throws(PublicEncodingRules ruleSet, byte[] inputData)
+ {
+ char[] outputData = new char[inputData.Length + 1];
+ outputData[0] = 'a';
+
+ int bytesWritten = -1;
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () =>
+ {
+ reader.TryCopyBMPString(
+ outputData,
+ out bytesWritten);
+ });
+
+ Assert.Equal(-1, bytesWritten);
+ Assert.Equal('a', outputData[0]);
+ }
+
+ [Theory]
+ [InlineData("Incomplete Tag", PublicEncodingRules.BER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.CER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.DER, "1F")]
+ [InlineData("Missing Length", PublicEncodingRules.BER, "1E")]
+ [InlineData("Missing Length", PublicEncodingRules.CER, "1E")]
+ [InlineData("Missing Length", PublicEncodingRules.DER, "1E")]
+ [InlineData("Missing Contents", PublicEncodingRules.BER, "1E02")]
+ [InlineData("Missing Contents", PublicEncodingRules.CER, "1E02")]
+ [InlineData("Missing Contents", PublicEncodingRules.DER, "1E02")]
+ [InlineData("Length Too Long", PublicEncodingRules.BER, "1E0600480069")]
+ [InlineData("Length Too Long", PublicEncodingRules.CER, "1E0600480069")]
+ [InlineData("Length Too Long", PublicEncodingRules.DER, "1E0600480069")]
+ [InlineData("Constructed Form", PublicEncodingRules.DER, "3E0404020049")]
+ [InlineData("Bad BMP value (odd length)", PublicEncodingRules.BER, "1E0120")]
+ [InlineData("Bad BMP value (high surrogate)", PublicEncodingRules.BER, "1E02D800")]
+ [InlineData("Bad BMP value (high private surrogate)", PublicEncodingRules.BER, "1E02DB81")]
+ [InlineData("Bad BMP value (low surrogate)", PublicEncodingRules.BER, "1E02DC00")]
+ [InlineData("Wrong Tag", PublicEncodingRules.BER, "04024869")]
+ public static void GetBMPString_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () =>
+ {
+ reader.GetCharacterString(UniversalTagNumber.BMPString);
+ });
+ }
+
+ [Theory]
+ [InlineData("Empty", PublicEncodingRules.BER, "")]
+ [InlineData("Empty", PublicEncodingRules.CER, "")]
+ [InlineData("Empty", PublicEncodingRules.DER, "")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.BER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.CER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.DER, "1F")]
+ [InlineData("Missing Length", PublicEncodingRules.BER, "1E")]
+ [InlineData("Missing Length", PublicEncodingRules.CER, "1E")]
+ [InlineData("Missing Length", PublicEncodingRules.DER, "1E")]
+ [InlineData("Missing Contents", PublicEncodingRules.BER, "1E02")]
+ [InlineData("Missing Contents", PublicEncodingRules.CER, "1E02")]
+ [InlineData("Missing Contents", PublicEncodingRules.DER, "1E02")]
+ [InlineData("Missing Contents - Constructed", PublicEncodingRules.BER, "3E02")]
+ [InlineData("Missing Contents - Constructed Indef", PublicEncodingRules.BER, "3E80")]
+ [InlineData("Missing Contents - Constructed Indef", PublicEncodingRules.CER, "3E80")]
+ [InlineData("Length Too Long", PublicEncodingRules.BER, "1E034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.CER, "1E034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.DER, "1E034869")]
+ [InlineData("Definite Constructed Form", PublicEncodingRules.CER, "3E03040149")]
+ [InlineData("Definite Constructed Form", PublicEncodingRules.DER, "3E03040149")]
+ [InlineData("Indefinite Constructed Form - Short Payload", PublicEncodingRules.CER, "3E800401490000")]
+ [InlineData("Indefinite Constructed Form", PublicEncodingRules.DER, "3E800401490000")]
+ [InlineData("No nested content", PublicEncodingRules.CER, "3E800000")]
+ [InlineData("No EoC", PublicEncodingRules.BER, "3E80" + "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.BER, "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.CER, "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.DER, "04024869")]
+ [InlineData("Wrong Tag - Constructed", PublicEncodingRules.BER, "240404024869")]
+ [InlineData("Wrong Tag - Constructed Indef", PublicEncodingRules.BER, "2480" + "04024869" + "0000")]
+ [InlineData("Wrong Tag - Constructed Indef", PublicEncodingRules.CER, "2480" + "04024869" + "0000")]
+ [InlineData("Wrong Tag - Constructed", PublicEncodingRules.DER, "240404024869")]
+ [InlineData("Nested Bad Tag", PublicEncodingRules.BER, "3E04" + "1E024869")]
+ [InlineData("Nested context-specific", PublicEncodingRules.BER, "3E04800400FACE")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.BER, "3E80800400FACE0000")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.CER, "3E80800400FACE0000")]
+ [InlineData("Nested Length Too Long", PublicEncodingRules.BER, "3E07" + ("2402" + "0404") + "04020049")]
+ [InlineData("Nested Simple Length Too Long", PublicEncodingRules.BER, "3E03" + "040548656C6C6F")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.BER, "3E8020000000")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.CER, "3E8020000000")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.BER, "3E80000100")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.CER, "3E80000100")]
+ [InlineData("LongLength EndOfContents", PublicEncodingRules.BER, "3E80008100")]
+ [InlineData("Bad BMP value (odd length)", PublicEncodingRules.BER, "1E0120")]
+ [InlineData("Bad BMP value (high surrogate)", PublicEncodingRules.BER, "1E02D800")]
+ [InlineData("Bad BMP value (high private surrogate)", PublicEncodingRules.BER, "1E02DB81")]
+ [InlineData("Bad BMP value (low surrogate)", PublicEncodingRules.BER, "1E02DC00")]
+ public static void TryCopyBMPString_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ TryCopyBMPString_Throws(ruleSet, inputData);
+ }
+
+ [Fact]
+ public static void TryCopyBMPString_Throws_CER_NestedTooLong()
+ {
+ // CER says that the maximum encoding length for a BMPString primitive
+ // is 1000.
+ //
+ // This test checks it for a primitive contained within a constructed.
+ //
+ // So we need 04 [1001] { 1001 0x00s }
+ // 1001 => 0x3E9, so the length encoding is 82 03 E9.
+ // 1001 + 3 + 1 == 1005
+ //
+ // Plus a leading 3E 80 (indefinite length constructed)
+ // and a trailing 00 00 (End of contents)
+ // == 1009
+ byte[] input = new byte[1009];
+ // CONSTRUCTED BMPSTRING (indefinite)
+ input[0] = 0x3E;
+ input[1] = 0x80;
+ // OCTET STRING (1001)
+ input[2] = 0x04;
+ input[3] = 0x82;
+ input[4] = 0x03;
+ input[5] = 0xE9;
+ // EOC implicit since the byte[] initializes to zeros
+
+ TryCopyBMPString_Throws(PublicEncodingRules.CER, input);
+ }
+
+ [Fact]
+ public static void TryCopyBMPString_Throws_CER_NestedTooShortIntermediate()
+ {
+ // CER says that the maximum encoding length for a BMPString primitive
+ // is 1000, and in the constructed form the lengths must be
+ // [ 1000, 1000, 1000, ..., len%1000 ]
+ //
+ // So 1000, 2, 2 is illegal.
+ //
+ // 3E 80 (indefinite constructed BMP string)
+ // 04 82 03 08 (octet string, 1000 bytes)
+ // [1000 content bytes]
+ // 04 02 (octet string, 2 bytes)
+ // [2 content bytes]
+ // 04 02 (octet string, 2 bytes)
+ // [2 content bytes]
+ // 00 00 (end of contents)
+ // Looks like 1,016 bytes.
+ byte[] input = new byte[1016];
+ // CONSTRUCTED BMP STRING (indefinite)
+ input[0] = 0x3E;
+ input[1] = 0x80;
+ // OCTET STRING (1000)
+ input[2] = 0x03;
+ input[3] = 0x82;
+ input[4] = 0x03;
+ input[5] = 0xE8;
+ // OCTET STRING (2)
+ input[1006] = 0x04;
+ input[1007] = 0x02;
+ // OCTET STRING (2)
+ input[1010] = 0x04;
+ input[1011] = 0x02;
+ // EOC implicit since the byte[] initializes to zeros
+
+ TryCopyBMPString_Throws(PublicEncodingRules.CER, input);
+ }
+
+ [Fact]
+ public static void TryCopyBMPStringBytes_Success_CER_MaxPrimitiveLength()
+ {
+ // CER says that the maximum encoding length for a BMPString primitive
+ // is 1000.
+ //
+ // So we need 1E [1000] { 1000 anythings }
+ // 1000 => 0x3E8, so the length encoding is 82 03 E8.
+ // 1000 + 3 + 1 == 1004
+ byte[] input = new byte[1004];
+ input[0] = 0x1E;
+ input[1] = 0x82;
+ input[2] = 0x03;
+ input[3] = 0xE8;
+
+ // Content
+ input[4] = 0x65;
+ input[5] = 0x65;
+ input[1002] = 0x61;
+ input[1003] = 0x61;
+
+ byte[] output = new byte[1000];
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+ bool success = reader.TryCopyBMPStringBytes(output, out int bytesWritten);
+
+ Assert.True(success, "reader.TryCopyBMPStringBytes");
+ Assert.Equal(1000, bytesWritten);
+
+ Assert.Equal(
+ input.AsReadOnlySpan().Slice(4).ByteArrayToHex(),
+ output.ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void TryCopyBMPStringBytes_Success_CER_MinConstructedLength()
+ {
+ // CER says that the maximum encoding length for a BMPString primitive
+ // is 1000, and that a constructed form must be used for values greater
+ // than 1000 bytes, with segments dividing up for each thousand
+ // [1000, 1000, ..., len%1000].
+ //
+ // So our smallest constructed form is 1001 bytes, [1000, 1]
+ //
+ // 3E 80 (indefinite constructed BMPString)
+ // 04 82 03 E9 (primitive octet string, 1000 bytes)
+ // [1000 content bytes]
+ // 04 01 (primitive octet string, 1 byte)
+ // pp
+ // 00 00 (end of contents, 0 bytes)
+ // 1011 total.
+ byte[] input = new byte[1011];
+ int offset = 0;
+ // CONSTRUCTED BMPSTRING (Indefinite)
+ input[offset++] = 0x3E;
+ input[offset++] = 0x80;
+ // OCTET STRING (1000)
+ input[offset++] = 0x04;
+ input[offset++] = 0x82;
+ input[offset++] = 0x03;
+ input[offset++] = 0xE8;
+
+ // Primitive 1: (65 65 :: 61 61) (1000)
+ input[offset++] = 0x65;
+ input[offset] = 0x65;
+ offset += 997;
+ input[offset++] = 0x61;
+ input[offset++] = 0x61;
+
+ // OCTET STRING (1)
+ input[offset++] = 0x04;
+ input[offset++] = 0x01;
+
+ // Primitive 2: One more byte
+ input[offset] = 0x2E;
+
+ byte[] expected = new byte[1001];
+ offset = 0;
+ expected[offset++] = 0x65;
+ expected[offset] = 0x65;
+ offset += 997;
+ expected[offset++] = 0x61;
+ expected[offset++] = 0x61;
+ expected[offset] = 0x2E;
+
+ byte[] output = new byte[1001];
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+ bool success = reader.TryCopyBMPStringBytes(output, out int bytesWritten);
+
+ Assert.True(success, "reader.TryCopyBMPStringBytes");
+ Assert.Equal(1001, bytesWritten);
+
+ Assert.Equal(
+ expected.ByteArrayToHex(),
+ output.ByteArrayToHex());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x1E, 4, 0, (byte)'h', 0, (byte)'i' };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.TryGetBMPStringBytes(Asn1Tag.Null, out _));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetBMPStringBytes(new Asn1Tag(TagClass.ContextSpecific, 0), out _));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ Assert.True(reader.TryGetBMPStringBytes(out ReadOnlyMemory<byte> value));
+ Assert.Equal("00680069", value.ByteArrayToHex());
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x87, 2, 0x20, 0x10 };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.TryGetBMPStringBytes(Asn1Tag.Null, out _));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.TryGetBMPStringBytes(out _));
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetBMPStringBytes(new Asn1Tag(TagClass.Application, 0), out _));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetBMPStringBytes(new Asn1Tag(TagClass.ContextSpecific, 1), out _));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ Assert.True(
+ reader.TryGetBMPStringBytes(
+ new Asn1Tag(TagClass.ContextSpecific, 7),
+ out ReadOnlyMemory<byte> value));
+
+ Assert.Equal("2010", value.ByteArrayToHex());
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "1E022010", PublicTagClass.Universal, 30)]
+ [InlineData(PublicEncodingRules.CER, "1E022010", PublicTagClass.Universal, 30)]
+ [InlineData(PublicEncodingRules.DER, "1E022010", PublicTagClass.Universal, 30)]
+ [InlineData(PublicEncodingRules.BER, "8002FE60", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.CER, "4C02FE60", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "DF8A4602FE60", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.True(
+ reader.TryGetBMPStringBytes(
+ new Asn1Tag((TagClass)tagClass, tagValue, true),
+ out ReadOnlyMemory<byte> val1));
+
+ Assert.False(reader.HasData);
+
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.True(
+ reader.TryGetBMPStringBytes(
+ new Asn1Tag((TagClass)tagClass, tagValue, false),
+ out ReadOnlyMemory<byte> val2));
+
+ Assert.False(reader.HasData);
+
+ Assert.Equal(val1.ByteArrayToHex(), val2.ByteArrayToHex());
+ }
+ }
+
+ internal static class ReaderBMPExtensions
+ {
+ public static bool TryGetBMPStringBytes(
+ this AsnReader reader,
+ out ReadOnlyMemory<byte> contents)
+ {
+ return reader.TryGetPrimitiveCharacterStringBytes(
+ UniversalTagNumber.BMPString,
+ out contents);
+ }
+
+ public static bool TryGetBMPStringBytes(
+ this AsnReader reader,
+ Asn1Tag expectedTag,
+ out ReadOnlyMemory<byte> contents)
+ {
+ return reader.TryGetPrimitiveCharacterStringBytes(
+ expectedTag,
+ UniversalTagNumber.BMPString,
+ out contents);
+ }
+
+ public static bool TryCopyBMPStringBytes(
+ this AsnReader reader,
+ Span<byte> destination,
+ out int bytesWritten)
+ {
+ return reader.TryCopyCharacterStringBytes(
+ UniversalTagNumber.BMPString,
+ destination,
+ out bytesWritten);
+ }
+
+ public static bool TryCopyBMPString(
+ this AsnReader reader,
+ Span<char> destination,
+ out int charsWritten)
+ {
+ return reader.TryCopyCharacterString(
+ UniversalTagNumber.BMPString,
+ destination,
+ out charsWritten);
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadBitString.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadBitString.cs
new file mode 100644
index 0000000000..2455c2efcb
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadBitString.cs
@@ -0,0 +1,671 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadBitString : Asn1ReaderTests
+ {
+ [Theory]
+ [InlineData("Uncleared unused bit", PublicEncodingRules.BER, "030201FF")]
+ [InlineData("Constructed Payload", PublicEncodingRules.BER, "2302030100")]
+ [InlineData("Constructed Payload-Indefinite", PublicEncodingRules.BER, "238003010000")]
+ // This value is actually invalid CER, but it returns false since it's not primitive and
+ // it isn't worth preempting the descent to find out it was invalid.
+ [InlineData("Constructed Payload-Indefinite", PublicEncodingRules.CER, "238003010000")]
+ public static void TryGetBitStringBytes_Fails(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryGetPrimitiveBitStringValue(
+ out int unusedBitCount,
+ out ReadOnlyMemory<byte> contents);
+
+ Assert.False(didRead, "reader.TryGetBitStringBytes");
+ Assert.Equal(0, unusedBitCount);
+ Assert.Equal(0, contents.Length);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, 0, 0, "030100")]
+ [InlineData(PublicEncodingRules.BER, 1, 1, "030201FE")]
+ [InlineData(PublicEncodingRules.CER, 2, 4, "030502FEEFF00C")]
+ [InlineData(PublicEncodingRules.DER, 7, 1, "03020780")]
+ [InlineData(PublicEncodingRules.DER, 0, 4, "030500FEEFF00D" + "0500")]
+ public static void TryGetBitStringBytes_Success(
+ PublicEncodingRules ruleSet,
+ int expectedUnusedBitCount,
+ int expectedLength,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryGetPrimitiveBitStringValue(
+ out int unusedBitCount,
+ out ReadOnlyMemory<byte> contents);
+
+ Assert.True(didRead, "reader.TryGetBitStringBytes");
+ Assert.Equal(expectedUnusedBitCount, unusedBitCount);
+ Assert.Equal(expectedLength, contents.Length);
+ }
+
+ [Theory]
+ [InlineData("Wrong Tag", PublicEncodingRules.BER, "0500")]
+ [InlineData("Wrong Tag", PublicEncodingRules.CER, "0500")]
+ [InlineData("Wrong Tag", PublicEncodingRules.DER, "0500")]
+ [InlineData("Zero Length", PublicEncodingRules.BER, "0300")]
+ [InlineData("Zero Length", PublicEncodingRules.CER, "0300")]
+ [InlineData("Zero Length", PublicEncodingRules.DER, "0300")]
+ [InlineData("Bad Length", PublicEncodingRules.BER, "030200")]
+ [InlineData("Bad Length", PublicEncodingRules.CER, "030200")]
+ [InlineData("Bad Length", PublicEncodingRules.DER, "030200")]
+ [InlineData("Constructed Form", PublicEncodingRules.DER, "2303030100")]
+ public static void TryGetBitStringBytes_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () =>
+ {
+ reader.TryGetPrimitiveBitStringValue(
+ out int unusedBitCount,
+ out ReadOnlyMemory<byte> contents);
+ });
+ }
+
+ [Fact]
+ public static void TryGetBitStringBytes_Throws_CER_TooLong()
+ {
+ // CER says that the maximum encoding length for a BitString primitive is
+ // 1000 (999 value bytes and 1 unused bit count byte).
+ //
+ // So we need 03 [1001] { 1001 0x00s }
+ // 1001 => 0x3E9, so the length encoding is 82 03 E9.
+ // 1001 + 3 + 1 == 1005
+ byte[] input = new byte[1005];
+ input[0] = 0x03;
+ input[1] = 0x82;
+ input[2] = 0x03;
+ input[3] = 0xE9;
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+
+ Assert.Throws<CryptographicException>(
+ () =>
+ {
+ reader.TryGetPrimitiveBitStringValue(
+ out int unusedBitCount,
+ out ReadOnlyMemory<byte> contents);
+ });
+ }
+
+ [Fact]
+ public static void TryGetBitStringBytes_Success_CER_MaxLength()
+ {
+ // CER says that the maximum encoding length for a BitString primitive is
+ // 1000 (999 value bytes and 1 unused bit count byte).
+ //
+ // So we need 03 [1000] [0x00-0x07] { 998 anythings } [a byte that's legal for the bitmask]
+ // 1000 => 0x3E8, so the length encoding is 82 03 E8.
+ // 1000 + 3 + 1 == 1004
+ byte[] input = new byte[1004];
+ input[0] = 0x03;
+ input[1] = 0x82;
+ input[2] = 0x03;
+ input[3] = 0xE8;
+
+ // Unused bits
+ input[4] = 0x02;
+
+ // Payload
+ input[5] = 0xA0;
+ input[1002] = 0xA5;
+ input[1003] = 0xFC;
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+
+ bool success = reader.TryGetPrimitiveBitStringValue(
+ out int unusedBitCount,
+ out ReadOnlyMemory<byte> contents);
+
+ Assert.True(success, "reader.TryGetBitStringBytes");
+ Assert.Equal(input[4], unusedBitCount);
+ Assert.Equal(999, contents.Length);
+
+ // Check that it is, in fact, the same memory. No copies with this API.
+ Assert.True(
+ Unsafe.AreSame(
+ ref contents.Span.DangerousGetPinnableReference(),
+ ref input[5]));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "03020780")]
+ [InlineData(PublicEncodingRules.BER, "030207FF")]
+ [InlineData(PublicEncodingRules.CER, "03020780")]
+ [InlineData(PublicEncodingRules.DER, "03020780")]
+ [InlineData(
+ PublicEncodingRules.BER,
+ "2380" +
+ "2380" +
+ "0000" +
+ "03020000" +
+ "0000")]
+ public static void TryCopyBitStringBytes_Fails(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryCopyBitStringBytes(
+ Span<byte>.Empty,
+ out int unusedBitCount,
+ out int bytesWritten);
+
+ Assert.False(didRead, "reader.TryCopyBitStringBytes");
+ Assert.Equal(0, unusedBitCount);
+ Assert.Equal(0, bytesWritten);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "03020780", "80", 7)]
+ [InlineData(PublicEncodingRules.BER, "030207FF", "80", 7)]
+ [InlineData(PublicEncodingRules.CER, "03020780", "80", 7)]
+ [InlineData(PublicEncodingRules.DER, "03020680", "80", 6)]
+ [InlineData(PublicEncodingRules.BER, "23800000", "", 0)]
+ [InlineData(PublicEncodingRules.BER, "2300", "", 0)]
+ [InlineData(PublicEncodingRules.BER, "2300" + "0500", "", 0)]
+ [InlineData(PublicEncodingRules.BER, "0303010203" + "0500", "0202", 1)]
+ [InlineData(
+ PublicEncodingRules.BER,
+ "2380" +
+ "2380" +
+ "0000" +
+ "03020000" +
+ "0000",
+ "00",
+ 0)]
+ [InlineData(
+ PublicEncodingRules.BER,
+ "230C" +
+ "2380" +
+ "2380" +
+ "0000" +
+ "03020000" +
+ "0000",
+ "00",
+ 0)]
+ [InlineData(
+ PublicEncodingRules.BER,
+ "2380" +
+ "2308" +
+ "030200FA" +
+ "030200CE" +
+ "2380" +
+ "2380" +
+ "2380" +
+ "030300F00D" +
+ "0000" +
+ "0000" +
+ "03020001" +
+ "0000" +
+ "0303000203" +
+ "030203FF" +
+ "2380" +
+ "0000" +
+ "0000",
+ "FACEF00D010203F8",
+ 3)]
+ public static void TryCopyBitStringBytes_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ string expectedHex,
+ int expectedUnusedBitCount)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ byte[] output = new byte[expectedHex.Length / 2];
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryCopyBitStringBytes(
+ output,
+ out int unusedBitCount,
+ out int bytesWritten);
+
+ Assert.True(didRead, "reader.TryCopyBitStringBytes");
+ Assert.Equal(expectedUnusedBitCount, unusedBitCount);
+ Assert.Equal(expectedHex, output.AsReadOnlySpan().Slice(0, bytesWritten).ByteArrayToHex());
+ }
+
+ private static void TryCopyBitStringBytes_Throws(
+ PublicEncodingRules ruleSet,
+ byte[] input)
+ {
+ AsnReader reader = new AsnReader(input, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () =>
+ {
+ reader.TryCopyBitStringBytes(
+ Span<byte>.Empty,
+ out int unusedBitCount,
+ out int bytesWritten);
+ });
+ }
+
+ [Theory]
+ [InlineData("Wrong Tag", PublicEncodingRules.BER, "0500")]
+ [InlineData("Wrong Tag", PublicEncodingRules.CER, "0500")]
+ [InlineData("Wrong Tag", PublicEncodingRules.DER, "0500")]
+ [InlineData("Zero Length", PublicEncodingRules.BER, "0300")]
+ [InlineData("Zero Length", PublicEncodingRules.CER, "0300")]
+ [InlineData("Zero Length", PublicEncodingRules.DER, "0300")]
+ [InlineData("Bad Length", PublicEncodingRules.BER, "030200")]
+ [InlineData("Bad Length", PublicEncodingRules.CER, "030200")]
+ [InlineData("Bad Length", PublicEncodingRules.DER, "030200")]
+ [InlineData("Constructed Form", PublicEncodingRules.DER, "2303030100")]
+ [InlineData("Bad unused bits", PublicEncodingRules.BER, "03020800")]
+ [InlineData("Bad unused bits", PublicEncodingRules.CER, "03020800")]
+ [InlineData("Bad unused bits", PublicEncodingRules.DER, "03020800")]
+ [InlineData("Bad unused bits-nodata", PublicEncodingRules.BER, "030101")]
+ [InlineData("Bad unused bits-nodata", PublicEncodingRules.CER, "030101")]
+ [InlineData("Bad unused bits-nodata", PublicEncodingRules.DER, "030101")]
+ [InlineData("Bad nested unused bits", PublicEncodingRules.BER, "230403020800")]
+ [InlineData("Bad nested unused bits-indef", PublicEncodingRules.BER, "2380030208000000")]
+ [InlineData("Bad nested unused bits-indef", PublicEncodingRules.CER, "2380030208000000")]
+ [InlineData("Bad nested unused bits-nodata", PublicEncodingRules.BER, "2303030101")]
+ [InlineData("Bad nested unused bits-nodata-indef", PublicEncodingRules.BER, "23800301010000")]
+ [InlineData("Bad nested unused bits-nodata-indef", PublicEncodingRules.CER, "23800301010000")]
+ [InlineData("Bad mask", PublicEncodingRules.CER, "030201FF")]
+ [InlineData("Bad mask", PublicEncodingRules.DER, "030201FF")]
+ [InlineData("Bad nested mask", PublicEncodingRules.CER, "2380030201FF0000")]
+ [InlineData("Nested context-specific", PublicEncodingRules.BER, "2304800300FACE")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.BER, "2380800300FACE0000")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.CER, "2380800300FACE0000")]
+ [InlineData("Nested boolean", PublicEncodingRules.BER, "2303010100")]
+ [InlineData("Nested boolean (indef)", PublicEncodingRules.BER, "23800101000000")]
+ [InlineData("Nested boolean (indef)", PublicEncodingRules.CER, "23800101000000")]
+ [InlineData("Nested constructed form", PublicEncodingRules.CER, "2380" + "2380" + "03010" + "000000000")]
+ [InlineData("No terminator", PublicEncodingRules.BER, "2380" + "03020000" + "")]
+ [InlineData("No terminator", PublicEncodingRules.CER, "2380" + "03020000" + "")]
+ [InlineData("No content", PublicEncodingRules.BER, "2380")]
+ [InlineData("No content", PublicEncodingRules.CER, "2380")]
+ [InlineData("No nested content", PublicEncodingRules.CER, "23800000")]
+ [InlineData("Nested value too long", PublicEncodingRules.BER, "2380030A00")]
+ [InlineData("Nested value too long - constructed", PublicEncodingRules.BER, "2380230A00")]
+ [InlineData("Nested value too long - simple", PublicEncodingRules.BER, "2303" + "03050000000000")]
+ [InlineData(
+ "Unused bits in intermediate segment",
+ PublicEncodingRules.BER,
+ "2380" +
+ "0303000102" +
+ "0303020304" +
+ "0303010506" +
+ "0000")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.BER, "238020000000")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.CER, "238020000000")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.BER, "2380000100")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.CER, "2380000100")]
+ [InlineData("LongLength EndOfContents", PublicEncodingRules.BER, "2380008100")]
+ [InlineData("Constructed Payload-TooShort", PublicEncodingRules.CER, "23800301000000")]
+ public static void TryCopyBitStringBytes_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ TryCopyBitStringBytes_Throws(ruleSet, inputData);
+ }
+
+ [Fact]
+ public static void TryCopyBitStringBytes_Throws_CER_TooLong()
+ {
+ // CER says that the maximum encoding length for a BitString primitive is
+ // 1000 (999 value bytes and 1 unused bit count byte).
+ //
+ // So we need 03 [1001] { 1001 0x00s }
+ // 1001 => 0x3E9, so the length encoding is 82 03 E9.
+ // 1001 + 3 + 1 == 1004
+ byte[] input = new byte[1004];
+ input[0] = 0x03;
+ input[1] = 0x82;
+ input[2] = 0x03;
+ input[3] = 0xE9;
+
+ TryCopyBitStringBytes_Throws(PublicEncodingRules.CER, input);
+ }
+
+ [Fact]
+ public static void TryCopyBitStringBytes_Throws_CER_NestedTooLong()
+ {
+ // CER says that the maximum encoding length for a BitString primitive is
+ // 1000 (999 value bytes and 1 unused bit count byte).
+ //
+ // This test checks it for a primitive contained within a constructed.
+ //
+ // So we need 03 [1001] { 1001 0x00s }
+ // 1001 => 0x3E9, so the length encoding is 82 03 E9.
+ // 1001 + 3 + 1 == 1005
+ //
+ // Plus a leading 23 80 (indefinite length constructed)
+ // and a trailing 00 00 (End of contents)
+ // == 1009
+ byte[] input = new byte[1009];
+ // CONSTRUCTED BIT STRING (indefinite)
+ input[0] = 0x23;
+ input[1] = 0x80;
+ // BIT STRING (1001)
+ input[2] = 0x03;
+ input[3] = 0x82;
+ input[4] = 0x03;
+ input[5] = 0xE9;
+ // EOC implicit since the byte[] initializes to zeros
+
+ TryCopyBitStringBytes_Throws(PublicEncodingRules.CER, input);
+ }
+
+ [Fact]
+ public static void TryCopyBitStringBytes_Throws_CER_NestedTooShortIntermediate()
+ {
+ // CER says that the maximum encoding length for a BitString primitive is
+ // 1000 (999 value bytes and 1 unused bit count byte), and in the constructed
+ // form the lengths must be
+ // [ 1000, 1000, 1000, ..., len%1000 ]
+ //
+ // So 1000, 2, 2 is illegal.
+ //
+ // 23 80 (indefinite constructed bit string)
+ // 03 82 03 08 (bit string, 1000 bytes)
+ // [1000 content bytes]
+ // 03 02 (bit string, 2 bytes)
+ // [2 content bytes]
+ // 03 02 (bit string, 2 bytes)
+ // [2 content bytes]
+ // 00 00 (end of contents)
+ // Looks like 1,016 bytes.
+ byte[] input = new byte[1016];
+ // CONSTRUCTED BIT STRING (indefinite)
+ input[0] = 0x23;
+ input[1] = 0x80;
+ // BIT STRING (1000)
+ input[2] = 0x03;
+ input[3] = 0x82;
+ input[4] = 0x03;
+ input[5] = 0xE8;
+ // BIT STRING (2)
+ input[1006] = 0x03;
+ input[1007] = 0x02;
+ // BIT STRING (2)
+ input[1010] = 0x03;
+ input[1011] = 0x02;
+ // EOC implicit since the byte[] initializes to zeros
+
+ TryCopyBitStringBytes_Throws(PublicEncodingRules.CER, input);
+ }
+
+ [Fact]
+ public static void TryCopyBitStringBytes_Success_CER_MaxPrimitiveLength()
+ {
+ // CER says that the maximum encoding length for a BitString primitive is
+ // 1000 (999 value bytes and 1 unused bit count byte).
+ //
+ // So we need 03 [1000] [0x00-0x07] { 998 anythings } [a byte that's legal for the bitmask]
+ // 1000 => 0x3E8, so the length encoding is 82 03 E8.
+ // 1000 + 3 + 1 == 1003
+ byte[] input = new byte[1004];
+ input[0] = 0x03;
+ input[1] = 0x82;
+ input[2] = 0x03;
+ input[3] = 0xE8;
+
+ // Unused bits
+ input[4] = 0x02;
+
+ // Payload
+ input[5] = 0xA0;
+ input[1002] = 0xA5;
+ input[1003] = 0xFC;
+
+ byte[] output = new byte[999];
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+
+ bool success = reader.TryCopyBitStringBytes(
+ output,
+ out int unusedBitCount,
+ out int bytesWritten);
+
+ Assert.True(success, "reader.TryCopyBitStringBytes");
+ Assert.Equal(input[4], unusedBitCount);
+ Assert.Equal(999, bytesWritten);
+
+ Assert.Equal(
+ input.AsReadOnlySpan().Slice(5).ByteArrayToHex(),
+ output.ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void TryCopyBitStringBytes_Success_CER_MinConstructedLength()
+ {
+ // CER says that the maximum encoding length for a BitString primitive is
+ // 1000 (999 value bytes and 1 unused bit count byte), and that a constructed
+ // form must be used for values greater than 1000 bytes, with segments dividing
+ // up for each thousand [1000, 1000, ..., len%1000].
+ //
+ // Bit string primitives are one byte of "unused bits" and the rest are payload,
+ // so the minimum constructed payload has total content length 1002:
+ // [1000 (1+999), 2 (1+1)]
+ //
+ // 23 80 (indefinite constructed bit string)
+ // 03 82 03 E9 (primitive bit string, 1000 bytes)
+ // 00 [999 more payload bytes]
+ // 03 02 (primitive bit string, 2 bytes)
+ // uu pp
+ // 00 00 (end of contents, 0 bytes)
+ // 1010 total.
+ byte[] input = new byte[1012];
+ int offset = 0;
+ // CONSTRUCTED BIT STRING (Indefinite)
+ input[offset++] = 0x23;
+ input[offset++] = 0x80;
+ // BIT STRING (1000)
+ input[offset++] = 0x03;
+ input[offset++] = 0x82;
+ input[offset++] = 0x03;
+ input[offset++] = 0xE8;
+
+ // Primitive 1: Unused bits MUST be 0.
+ input[offset++] = 0x00;
+
+ // Payload (A0 :: A5 FC) (999)
+ input[offset] = 0xA0;
+ offset += 997;
+ input[offset++] = 0xA5;
+ input[offset++] = 0xFC;
+
+ // BIT STRING (2)
+ input[offset++] = 0x03;
+ input[offset++] = 0x02;
+
+ // Primitive 2: Unused bits 0-7
+ input[offset++] = 0x3;
+
+ // Payload (must have the three least significant bits unset)
+ input[offset] = 0b0000_1000;
+
+ byte[] expected = new byte[1000];
+ offset = 0;
+ expected[offset] = 0xA0;
+ offset += 997;
+ expected[offset++] = 0xA5;
+ expected[offset++] = 0xFC;
+ expected[offset] = 0b0000_1000;
+
+ byte[] output = new byte[1000];
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+
+ bool success = reader.TryCopyBitStringBytes(
+ output,
+ out int unusedBitCount,
+ out int bytesWritten);
+
+ Assert.True(success, "reader.TryCopyBitStringBytes");
+ Assert.Equal(input[1006], unusedBitCount);
+ Assert.Equal(1000, bytesWritten);
+
+ Assert.Equal(
+ expected.ByteArrayToHex(),
+ output.ByteArrayToHex());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 3, 2, 1, 0x7E };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.TryGetPrimitiveBitStringValue(Asn1Tag.Null, out _, out _));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetPrimitiveBitStringValue(new Asn1Tag(TagClass.ContextSpecific, 0), out _, out _));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ Assert.True(reader.TryGetPrimitiveBitStringValue(out int unusedBitCount, out ReadOnlyMemory<byte> contents));
+ Assert.Equal("7E", contents.ByteArrayToHex());
+ Assert.Equal(1, unusedBitCount);
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x87, 2, 0, 0x80 };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.TryGetPrimitiveBitStringValue(Asn1Tag.Null, out _, out _));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.TryGetPrimitiveBitStringValue(out _, out _));
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetPrimitiveBitStringValue(new Asn1Tag(TagClass.Application, 0), out _, out _));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetPrimitiveBitStringValue(new Asn1Tag(TagClass.ContextSpecific, 1), out _, out _));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ Assert.True(
+ reader.TryGetPrimitiveBitStringValue(
+ new Asn1Tag(TagClass.ContextSpecific, 7),
+ out int unusedBitCount,
+ out ReadOnlyMemory<byte> contents));
+
+ Assert.Equal("80", contents.ByteArrayToHex());
+ Assert.Equal(0, unusedBitCount);
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "030400010203", PublicTagClass.Universal, 3)]
+ [InlineData(PublicEncodingRules.CER, "030400010203", PublicTagClass.Universal, 3)]
+ [InlineData(PublicEncodingRules.DER, "030400010203", PublicTagClass.Universal, 3)]
+ [InlineData(PublicEncodingRules.BER, "800200FF", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.CER, "4C0200FF", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "DF8A460200FF", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.True(
+ reader.TryGetPrimitiveBitStringValue(
+ new Asn1Tag((TagClass)tagClass, tagValue, true),
+ out int ubc1,
+ out ReadOnlyMemory<byte> val1));
+
+ Assert.False(reader.HasData);
+
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.True(
+ reader.TryGetPrimitiveBitStringValue(
+ new Asn1Tag((TagClass)tagClass, tagValue, false),
+ out int ubc2,
+ out ReadOnlyMemory<byte> val2));
+
+ Assert.False(reader.HasData);
+
+ Assert.Equal(val1.ByteArrayToHex(), val2.ByteArrayToHex());
+ Assert.Equal(ubc1, ubc2);
+ }
+
+ [Fact]
+ public static void TryCopyBitStringBytes_ExtremelyNested()
+ {
+ byte[] dataBytes = new byte[4 * 16384];
+
+ // This will build 2^14 nested indefinite length values.
+ // In the end, none of them contain any content.
+ //
+ // For what it's worth, the initial algorithm succeeded at 1017, and StackOverflowed with 1018.
+ int end = dataBytes.Length / 2;
+
+ // UNIVERSAL BIT STRING [Constructed]
+ const byte Tag = 0x20 | (byte)UniversalTagNumber.BitString;
+
+ for (int i = 0; i < end; i += 2)
+ {
+ dataBytes[i] = Tag;
+ // Indefinite length
+ dataBytes[i + 1] = 0x80;
+ }
+
+ AsnReader reader = new AsnReader(dataBytes, AsnEncodingRules.BER);
+
+ int bytesWritten;
+ int unusedBitCount;
+
+ Assert.True(
+ reader.TryCopyBitStringBytes(Span<byte>.Empty, out unusedBitCount, out bytesWritten));
+
+ Assert.Equal(0, bytesWritten);
+ Assert.Equal(0, unusedBitCount);
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadBoolean.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadBoolean.cs
new file mode 100644
index 0000000000..6aaa15048e
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadBoolean.cs
@@ -0,0 +1,220 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadBoolean : Asn1ReaderTests
+ {
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, false, 3, "010100")]
+ [InlineData(PublicEncodingRules.BER, true, 3, "010101")]
+ // Padded length
+ [InlineData(PublicEncodingRules.BER, true, 4, "01810101")]
+ [InlineData(PublicEncodingRules.BER, true, 3, "0101FF0500")]
+ [InlineData(PublicEncodingRules.CER, false, 3, "0101000500")]
+ [InlineData(PublicEncodingRules.CER, true, 3, "0101FF")]
+ [InlineData(PublicEncodingRules.DER, false, 3, "010100")]
+ [InlineData(PublicEncodingRules.DER, true, 3, "0101FF0500")]
+ // Context Specific 0
+ [InlineData(PublicEncodingRules.DER, true, 3, "8001FF0500")]
+ // Application 31
+ [InlineData(PublicEncodingRules.DER, true, 4, "5F1F01FF0500")]
+ // Private 253
+ [InlineData(PublicEncodingRules.CER, false, 5, "DF817D01000500")]
+ public static void ReadBoolean_Success(
+ PublicEncodingRules ruleSet,
+ bool expectedValue,
+ int expectedBytesRead,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Asn1Tag tag = reader.PeekTag();
+ bool value;
+
+ if (tag.TagClass == TagClass.Universal)
+ {
+ value = reader.ReadBoolean();
+ }
+ else
+ {
+ value = reader.ReadBoolean(tag);
+ }
+
+ if (inputData.Length == expectedBytesRead)
+ {
+ Assert.False(reader.HasData, "reader.HasData");
+ }
+ else
+ {
+ Assert.True(reader.HasData, "reader.HasData");
+ }
+
+ if (expectedValue)
+ {
+ Assert.True(value, "value");
+ }
+ else
+ {
+ Assert.False(value, "value");
+ }
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 1, 1, 0 };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.ReadBoolean(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.ReadBoolean(new Asn1Tag(TagClass.ContextSpecific, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ bool value = reader.ReadBoolean();
+ Assert.False(value, "value");
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x80, 1, 0xFF };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.ReadBoolean(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.ReadBoolean());
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(() => reader.ReadBoolean(new Asn1Tag(TagClass.Application, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(() => reader.ReadBoolean(new Asn1Tag(TagClass.ContextSpecific, 1)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ bool value = reader.ReadBoolean(new Asn1Tag(TagClass.ContextSpecific, 0));
+ Assert.True(value, "value");
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "0101FF", PublicTagClass.Universal, 1)]
+ [InlineData(PublicEncodingRules.CER, "0101FF", PublicTagClass.Universal, 1)]
+ [InlineData(PublicEncodingRules.DER, "0101FF", PublicTagClass.Universal, 1)]
+ [InlineData(PublicEncodingRules.BER, "8001FF", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.CER, "4C01FF", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "DF8A4601FF", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ bool val1 = reader.ReadBoolean(new Asn1Tag((TagClass)tagClass, tagValue, true));
+ Assert.False(reader.HasData);
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ bool val2 = reader.ReadBoolean(new Asn1Tag((TagClass)tagClass, tagValue, false));
+ Assert.False(reader.HasData);
+
+ Assert.Equal(val1, val2);
+ }
+
+ [Theory]
+ [InlineData("Empty", PublicEncodingRules.DER, "")]
+ [InlineData("Empty", PublicEncodingRules.CER, "")]
+ [InlineData("Empty", PublicEncodingRules.BER, "")]
+ [InlineData("TagOnly", PublicEncodingRules.BER, "01")]
+ [InlineData("TagOnly", PublicEncodingRules.CER, "01")]
+ [InlineData("TagOnly", PublicEncodingRules.DER, "01")]
+ [InlineData("MultiByte TagOnly", PublicEncodingRules.DER, "9F1F")]
+ [InlineData("MultiByte TagOnly", PublicEncodingRules.CER, "9F1F")]
+ [InlineData("MultiByte TagOnly", PublicEncodingRules.BER, "9F1F")]
+ [InlineData("TagAndLength", PublicEncodingRules.BER, "0101")]
+ [InlineData("Tag and MultiByteLength", PublicEncodingRules.BER, "01820001")]
+ [InlineData("TagAndLength", PublicEncodingRules.CER, "8001")]
+ [InlineData("TagAndLength", PublicEncodingRules.DER, "C001")]
+ [InlineData("MultiByteTagAndLength", PublicEncodingRules.DER, "9F2001")]
+ [InlineData("MultiByteTagAndLength", PublicEncodingRules.CER, "9F2001")]
+ [InlineData("MultiByteTagAndLength", PublicEncodingRules.BER, "9F2001")]
+ [InlineData("MultiByteTagAndMultiByteLength", PublicEncodingRules.BER, "9F28200001")]
+ [InlineData("TooShort", PublicEncodingRules.BER, "0100")]
+ [InlineData("TooShort", PublicEncodingRules.CER, "8000")]
+ [InlineData("TooShort", PublicEncodingRules.DER, "0100")]
+ [InlineData("TooLong", PublicEncodingRules.DER, "C0020000")]
+ [InlineData("TooLong", PublicEncodingRules.CER, "01020000")]
+ [InlineData("TooLong", PublicEncodingRules.BER, "C081020000")]
+ [InlineData("MissingContents", PublicEncodingRules.BER, "C001")]
+ [InlineData("MissingContents", PublicEncodingRules.CER, "0101")]
+ [InlineData("MissingContents", PublicEncodingRules.DER, "8001")]
+ [InlineData("NonCanonical", PublicEncodingRules.DER, "0101FE")]
+ [InlineData("NonCanonical", PublicEncodingRules.CER, "800101")]
+ [InlineData("Constructed", PublicEncodingRules.BER, "2103010101")]
+ [InlineData("Constructed", PublicEncodingRules.CER, "2103010101")]
+ [InlineData("Constructed", PublicEncodingRules.DER, "2103010101")]
+ [InlineData("WrongTag", PublicEncodingRules.DER, "0400")]
+ [InlineData("WrongTag", PublicEncodingRules.CER, "0400")]
+ [InlineData("WrongTag", PublicEncodingRules.BER, "0400")]
+ public static void ReadBoolean_Failure(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ Asn1Tag tag = default(Asn1Tag);
+
+ if (inputData.Length > 0)
+ {
+ tag = reader.PeekTag();
+ }
+
+ if (tag.TagClass == TagClass.Universal)
+ {
+ Assert.Throws<CryptographicException>(() => reader.ReadBoolean());
+ }
+ else
+ {
+ Assert.Throws<CryptographicException>(() => reader.ReadBoolean(tag));
+ }
+
+ if (inputData.Length == 0)
+ {
+ // If we started with nothing, where did the data come from?
+ Assert.False(reader.HasData, "reader.HasData");
+ }
+ else
+ {
+ // Nothing should have moved
+ Assert.True(reader.HasData, "reader.HasData");
+ }
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadEnumerated.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadEnumerated.cs
new file mode 100644
index 0000000000..b921496d79
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadEnumerated.cs
@@ -0,0 +1,760 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Reflection;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadEnumerated : Asn1ReaderTests
+ {
+ public enum ByteBacked : byte
+ {
+ Zero = 0,
+ NotFluffy = 11,
+ Fluff = 12,
+ }
+
+ public enum SByteBacked : sbyte
+ {
+ Zero = 0,
+ Fluff = 83,
+ Pillow = -17,
+ }
+
+ public enum ShortBacked : short
+ {
+ Zero = 0,
+ Fluff = 521,
+ Pillow = -1024,
+ }
+
+ public enum UShortBacked : ushort
+ {
+ Zero = 0,
+ Fluff = 32768,
+ }
+
+ public enum IntBacked : int
+ {
+ Zero = 0,
+ Fluff = 0x010001,
+ Pillow = -Fluff,
+ }
+
+ public enum UIntBacked : uint
+ {
+ Zero = 0,
+ Fluff = 0x80000005,
+ }
+
+ public enum LongBacked : long
+ {
+ Zero = 0,
+ Fluff = 0x0200000441,
+ Pillow = -0x100000000L,
+ }
+
+ public enum ULongBacked : ulong
+ {
+ Zero = 0,
+ Fluff = 0xFACEF00DCAFEBEEF,
+ }
+
+ private static void GetExpectedValue<TEnum>(
+ PublicEncodingRules ruleSet,
+ TEnum expectedValue,
+ string inputHex)
+ where TEnum : struct
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ TEnum value = reader.GetEnumeratedValue<TEnum>();
+ Assert.Equal(expectedValue, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, ByteBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.CER, ByteBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.DER, ByteBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.BER, ByteBacked.Fluff, "0A010C")]
+ [InlineData(PublicEncodingRules.CER, ByteBacked.Fluff, "0A010C")]
+ [InlineData(PublicEncodingRules.DER, ByteBacked.Fluff, "0A010C")]
+ [InlineData(PublicEncodingRules.BER, (ByteBacked)255, "0A0200FF")]
+ [InlineData(PublicEncodingRules.CER, (ByteBacked)128, "0A020080")]
+ [InlineData(PublicEncodingRules.DER, (ByteBacked)129, "0A020081")]
+ [InlineData(PublicEncodingRules.BER, (ByteBacked)254, "0A82000200FE")]
+ public static void GetExpectedValue_ByteBacked(
+ PublicEncodingRules ruleSet,
+ ByteBacked expectedValue,
+ string inputHex)
+ {
+ GetExpectedValue(ruleSet, expectedValue, inputHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, SByteBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.CER, SByteBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.DER, SByteBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.BER, SByteBacked.Fluff, "0A0153")]
+ [InlineData(PublicEncodingRules.CER, SByteBacked.Fluff, "0A0153")]
+ [InlineData(PublicEncodingRules.DER, SByteBacked.Fluff, "0A0153")]
+ [InlineData(PublicEncodingRules.BER, SByteBacked.Pillow, "0A01EF")]
+ [InlineData(PublicEncodingRules.CER, (SByteBacked)sbyte.MinValue, "0A0180")]
+ [InlineData(PublicEncodingRules.DER, (SByteBacked)sbyte.MinValue + 1, "0A0181")]
+ [InlineData(PublicEncodingRules.BER, SByteBacked.Pillow, "0A820001EF")]
+ public static void GetExpectedValue_SByteBacked(
+ PublicEncodingRules ruleSet,
+ SByteBacked expectedValue,
+ string inputHex)
+ {
+ GetExpectedValue(ruleSet, expectedValue, inputHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, ShortBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.CER, ShortBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.DER, ShortBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.BER, ShortBacked.Fluff, "0A020209")]
+ [InlineData(PublicEncodingRules.CER, ShortBacked.Fluff, "0A020209")]
+ [InlineData(PublicEncodingRules.DER, ShortBacked.Fluff, "0A020209")]
+ [InlineData(PublicEncodingRules.BER, ShortBacked.Pillow, "0A02FC00")]
+ [InlineData(PublicEncodingRules.CER, (ShortBacked)short.MinValue, "0A028000")]
+ [InlineData(PublicEncodingRules.DER, (ShortBacked)short.MinValue + 1, "0A028001")]
+ [InlineData(PublicEncodingRules.BER, ShortBacked.Pillow, "0A820002FC00")]
+ public static void GetExpectedValue_ShortBacked(
+ PublicEncodingRules ruleSet,
+ ShortBacked expectedValue,
+ string inputHex)
+ {
+ GetExpectedValue(ruleSet, expectedValue, inputHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, UShortBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.CER, UShortBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.DER, UShortBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.BER, UShortBacked.Fluff, "0A03008000")]
+ [InlineData(PublicEncodingRules.CER, UShortBacked.Fluff, "0A03008000")]
+ [InlineData(PublicEncodingRules.DER, UShortBacked.Fluff, "0A03008000")]
+ [InlineData(PublicEncodingRules.BER, (UShortBacked)255, "0A0200FF")]
+ [InlineData(PublicEncodingRules.CER, (UShortBacked)256, "0A020100")]
+ [InlineData(PublicEncodingRules.DER, (UShortBacked)0x7FED, "0A027FED")]
+ [InlineData(PublicEncodingRules.BER, (UShortBacked)ushort.MaxValue, "0A82000300FFFF")]
+ [InlineData(PublicEncodingRules.BER, (UShortBacked)0x8123, "0A820003008123")]
+ public static void GetExpectedValue_UShortBacked(
+ PublicEncodingRules ruleSet,
+ UShortBacked expectedValue,
+ string inputHex)
+ {
+ GetExpectedValue(ruleSet, expectedValue, inputHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, IntBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.CER, IntBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.DER, IntBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.BER, IntBacked.Fluff, "0A03010001")]
+ [InlineData(PublicEncodingRules.CER, IntBacked.Fluff, "0A03010001")]
+ [InlineData(PublicEncodingRules.DER, IntBacked.Fluff, "0A03010001")]
+ [InlineData(PublicEncodingRules.BER, IntBacked.Pillow, "0A03FEFFFF")]
+ [InlineData(PublicEncodingRules.CER, (IntBacked)int.MinValue, "0A0480000000")]
+ [InlineData(PublicEncodingRules.DER, (IntBacked)int.MinValue + 1, "0A0480000001")]
+ [InlineData(PublicEncodingRules.BER, IntBacked.Pillow, "0A820003FEFFFF")]
+ public static void GetExpectedValue_IntBacked(
+ PublicEncodingRules ruleSet,
+ IntBacked expectedValue,
+ string inputHex)
+ {
+ GetExpectedValue(ruleSet, expectedValue, inputHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, UIntBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.CER, UIntBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.DER, UIntBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.BER, UIntBacked.Fluff, "0A050080000005")]
+ [InlineData(PublicEncodingRules.CER, UIntBacked.Fluff, "0A050080000005")]
+ [InlineData(PublicEncodingRules.DER, UIntBacked.Fluff, "0A050080000005")]
+ [InlineData(PublicEncodingRules.BER, (UIntBacked)255, "0A0200FF")]
+ [InlineData(PublicEncodingRules.CER, (UIntBacked)256, "0A020100")]
+ [InlineData(PublicEncodingRules.DER, (UIntBacked)0x7FED, "0A027FED")]
+ [InlineData(PublicEncodingRules.BER, (UIntBacked)uint.MaxValue, "0A82000500FFFFFFFF")]
+ [InlineData(PublicEncodingRules.BER, (UIntBacked)0x8123, "0A820003008123")]
+ public static void GetExpectedValue_UIntBacked(
+ PublicEncodingRules ruleSet,
+ UIntBacked expectedValue,
+ string inputHex)
+ {
+ GetExpectedValue(ruleSet, expectedValue, inputHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, LongBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.CER, LongBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.DER, LongBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.BER, LongBacked.Fluff, "0A050200000441")]
+ [InlineData(PublicEncodingRules.CER, LongBacked.Fluff, "0A050200000441")]
+ [InlineData(PublicEncodingRules.DER, LongBacked.Fluff, "0A050200000441")]
+ [InlineData(PublicEncodingRules.BER, LongBacked.Pillow, "0A05FF00000000")]
+ [InlineData(PublicEncodingRules.CER, (LongBacked)short.MinValue, "0A028000")]
+ [InlineData(PublicEncodingRules.DER, (LongBacked)short.MinValue + 1, "0A028001")]
+ [InlineData(PublicEncodingRules.BER, LongBacked.Pillow, "0A820005FF00000000")]
+ public static void GetExpectedValue_LongBacked(
+ PublicEncodingRules ruleSet,
+ LongBacked expectedValue,
+ string inputHex)
+ {
+ GetExpectedValue(ruleSet, expectedValue, inputHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, ULongBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.CER, ULongBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.DER, ULongBacked.Zero, "0A0100")]
+ [InlineData(PublicEncodingRules.BER, ULongBacked.Fluff, "0A0900FACEF00DCAFEBEEF")]
+ [InlineData(PublicEncodingRules.CER, ULongBacked.Fluff, "0A0900FACEF00DCAFEBEEF")]
+ [InlineData(PublicEncodingRules.DER, ULongBacked.Fluff, "0A0900FACEF00DCAFEBEEF")]
+ [InlineData(PublicEncodingRules.BER, (ULongBacked)255, "0A0200FF")]
+ [InlineData(PublicEncodingRules.CER, (ULongBacked)256, "0A020100")]
+ [InlineData(PublicEncodingRules.DER, (ULongBacked)0x7FED, "0A027FED")]
+ [InlineData(PublicEncodingRules.BER, (ULongBacked)uint.MaxValue, "0A82000500FFFFFFFF")]
+ [InlineData(PublicEncodingRules.BER, (ULongBacked)ulong.MaxValue, "0A82000900FFFFFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.BER, (ULongBacked)0x8123, "0A820003008123")]
+ public static void GetExpectedValue_ULongBacked(
+ PublicEncodingRules ruleSet,
+ ULongBacked expectedValue,
+ string inputHex)
+ {
+ GetExpectedValue(ruleSet, expectedValue, inputHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "")]
+ [InlineData(PublicEncodingRules.CER, "")]
+ [InlineData(PublicEncodingRules.DER, "")]
+ [InlineData(PublicEncodingRules.BER, "0A")]
+ [InlineData(PublicEncodingRules.CER, "0A")]
+ [InlineData(PublicEncodingRules.DER, "0A")]
+ [InlineData(PublicEncodingRules.BER, "0A00")]
+ [InlineData(PublicEncodingRules.CER, "0A00")]
+ [InlineData(PublicEncodingRules.DER, "0A00")]
+ [InlineData(PublicEncodingRules.BER, "0A01")]
+ [InlineData(PublicEncodingRules.CER, "0A01")]
+ [InlineData(PublicEncodingRules.DER, "0A01")]
+ [InlineData(PublicEncodingRules.BER, "0A81")]
+ [InlineData(PublicEncodingRules.CER, "0A81")]
+ [InlineData(PublicEncodingRules.DER, "0A81")]
+ [InlineData(PublicEncodingRules.BER, "9F00")]
+ [InlineData(PublicEncodingRules.CER, "9F00")]
+ [InlineData(PublicEncodingRules.DER, "9F00")]
+ [InlineData(PublicEncodingRules.BER, "0A01FF")]
+ [InlineData(PublicEncodingRules.CER, "0A01FF")]
+ [InlineData(PublicEncodingRules.DER, "0A01FF")]
+ [InlineData(PublicEncodingRules.BER, "0A02007F")]
+ [InlineData(PublicEncodingRules.CER, "0A02007F")]
+ [InlineData(PublicEncodingRules.DER, "0A02007F")]
+ [InlineData(PublicEncodingRules.BER, "0A020102")]
+ [InlineData(PublicEncodingRules.CER, "0A020102")]
+ [InlineData(PublicEncodingRules.DER, "0A020102")]
+ [InlineData(PublicEncodingRules.BER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.CER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.DER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.BER, "0A03010203")]
+ [InlineData(PublicEncodingRules.CER, "0A03010203")]
+ [InlineData(PublicEncodingRules.DER, "0A03010203")]
+ [InlineData(PublicEncodingRules.BER, "0A0401020304")]
+ [InlineData(PublicEncodingRules.CER, "0A0401020304")]
+ [InlineData(PublicEncodingRules.DER, "0A0401020304")]
+ [InlineData(PublicEncodingRules.BER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.CER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.DER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.BER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.CER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.DER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.BER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.CER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.DER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.BER, "2A030A0100")]
+ public static void GetEnumeratedValue_Invalid_Byte(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.GetEnumeratedValue<ByteBacked>());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "")]
+ [InlineData(PublicEncodingRules.CER, "")]
+ [InlineData(PublicEncodingRules.DER, "")]
+ [InlineData(PublicEncodingRules.BER, "0A")]
+ [InlineData(PublicEncodingRules.CER, "0A")]
+ [InlineData(PublicEncodingRules.DER, "0A")]
+ [InlineData(PublicEncodingRules.BER, "0A00")]
+ [InlineData(PublicEncodingRules.CER, "0A00")]
+ [InlineData(PublicEncodingRules.DER, "0A00")]
+ [InlineData(PublicEncodingRules.BER, "0A01")]
+ [InlineData(PublicEncodingRules.CER, "0A01")]
+ [InlineData(PublicEncodingRules.DER, "0A01")]
+ [InlineData(PublicEncodingRules.BER, "0A81")]
+ [InlineData(PublicEncodingRules.CER, "0A81")]
+ [InlineData(PublicEncodingRules.DER, "0A81")]
+ [InlineData(PublicEncodingRules.BER, "9F00")]
+ [InlineData(PublicEncodingRules.CER, "9F00")]
+ [InlineData(PublicEncodingRules.DER, "9F00")]
+ [InlineData(PublicEncodingRules.BER, "0A02007F")]
+ [InlineData(PublicEncodingRules.CER, "0A02007F")]
+ [InlineData(PublicEncodingRules.DER, "0A02007F")]
+ [InlineData(PublicEncodingRules.BER, "0A020102")]
+ [InlineData(PublicEncodingRules.CER, "0A020102")]
+ [InlineData(PublicEncodingRules.DER, "0A020102")]
+ [InlineData(PublicEncodingRules.BER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.CER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.DER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.BER, "0A03010203")]
+ [InlineData(PublicEncodingRules.CER, "0A03010203")]
+ [InlineData(PublicEncodingRules.DER, "0A03010203")]
+ [InlineData(PublicEncodingRules.BER, "0A0401020304")]
+ [InlineData(PublicEncodingRules.CER, "0A0401020304")]
+ [InlineData(PublicEncodingRules.DER, "0A0401020304")]
+ [InlineData(PublicEncodingRules.BER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.CER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.DER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.BER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.CER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.DER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.BER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.CER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.DER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.BER, "2A030A0100")]
+ public static void GetEnumeratedValue_Invalid_SByte(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.GetEnumeratedValue<SByteBacked>());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "")]
+ [InlineData(PublicEncodingRules.CER, "")]
+ [InlineData(PublicEncodingRules.DER, "")]
+ [InlineData(PublicEncodingRules.BER, "0A")]
+ [InlineData(PublicEncodingRules.CER, "0A")]
+ [InlineData(PublicEncodingRules.DER, "0A")]
+ [InlineData(PublicEncodingRules.BER, "0A00")]
+ [InlineData(PublicEncodingRules.CER, "0A00")]
+ [InlineData(PublicEncodingRules.DER, "0A00")]
+ [InlineData(PublicEncodingRules.BER, "0A01")]
+ [InlineData(PublicEncodingRules.CER, "0A01")]
+ [InlineData(PublicEncodingRules.DER, "0A01")]
+ [InlineData(PublicEncodingRules.BER, "0A81")]
+ [InlineData(PublicEncodingRules.CER, "0A81")]
+ [InlineData(PublicEncodingRules.DER, "0A81")]
+ [InlineData(PublicEncodingRules.BER, "9F00")]
+ [InlineData(PublicEncodingRules.CER, "9F00")]
+ [InlineData(PublicEncodingRules.DER, "9F00")]
+ [InlineData(PublicEncodingRules.BER, "0A02007F")]
+ [InlineData(PublicEncodingRules.CER, "0A02007F")]
+ [InlineData(PublicEncodingRules.DER, "0A02007F")]
+ [InlineData(PublicEncodingRules.BER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.CER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.DER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.BER, "0A03010203")]
+ [InlineData(PublicEncodingRules.CER, "0A03010203")]
+ [InlineData(PublicEncodingRules.DER, "0A03010203")]
+ [InlineData(PublicEncodingRules.BER, "0A0401020304")]
+ [InlineData(PublicEncodingRules.CER, "0A0401020304")]
+ [InlineData(PublicEncodingRules.DER, "0A0401020304")]
+ [InlineData(PublicEncodingRules.BER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.CER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.DER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.BER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.CER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.DER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.BER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.CER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.DER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.BER, "2A030A0100")]
+ public static void GetEnumeratedValue_Invalid_Short(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.GetEnumeratedValue<ShortBacked>());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "")]
+ [InlineData(PublicEncodingRules.CER, "")]
+ [InlineData(PublicEncodingRules.DER, "")]
+ [InlineData(PublicEncodingRules.BER, "0A")]
+ [InlineData(PublicEncodingRules.CER, "0A")]
+ [InlineData(PublicEncodingRules.DER, "0A")]
+ [InlineData(PublicEncodingRules.BER, "0A00")]
+ [InlineData(PublicEncodingRules.CER, "0A00")]
+ [InlineData(PublicEncodingRules.DER, "0A00")]
+ [InlineData(PublicEncodingRules.BER, "0A01")]
+ [InlineData(PublicEncodingRules.CER, "0A01")]
+ [InlineData(PublicEncodingRules.DER, "0A01")]
+ [InlineData(PublicEncodingRules.BER, "0A81")]
+ [InlineData(PublicEncodingRules.CER, "0A81")]
+ [InlineData(PublicEncodingRules.DER, "0A81")]
+ [InlineData(PublicEncodingRules.BER, "9F00")]
+ [InlineData(PublicEncodingRules.CER, "9F00")]
+ [InlineData(PublicEncodingRules.DER, "9F00")]
+ [InlineData(PublicEncodingRules.BER, "0A01FF")]
+ [InlineData(PublicEncodingRules.CER, "0A01FF")]
+ [InlineData(PublicEncodingRules.DER, "0A01FF")]
+ [InlineData(PublicEncodingRules.BER, "0A02007F")]
+ [InlineData(PublicEncodingRules.CER, "0A02007F")]
+ [InlineData(PublicEncodingRules.DER, "0A02007F")]
+ [InlineData(PublicEncodingRules.BER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.CER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.DER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.BER, "0A03010203")]
+ [InlineData(PublicEncodingRules.CER, "0A03010203")]
+ [InlineData(PublicEncodingRules.DER, "0A03010203")]
+ [InlineData(PublicEncodingRules.BER, "0A0401020304")]
+ [InlineData(PublicEncodingRules.CER, "0A0401020304")]
+ [InlineData(PublicEncodingRules.DER, "0A0401020304")]
+ [InlineData(PublicEncodingRules.BER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.CER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.DER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.BER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.CER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.DER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.BER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.CER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.DER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.BER, "2A030A0100")]
+ public static void GetEnumeratedValue_Invalid_UShort(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.GetEnumeratedValue<UShortBacked>());
+ }
+
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "")]
+ [InlineData(PublicEncodingRules.CER, "")]
+ [InlineData(PublicEncodingRules.DER, "")]
+ [InlineData(PublicEncodingRules.BER, "0A")]
+ [InlineData(PublicEncodingRules.CER, "0A")]
+ [InlineData(PublicEncodingRules.DER, "0A")]
+ [InlineData(PublicEncodingRules.BER, "0A00")]
+ [InlineData(PublicEncodingRules.CER, "0A00")]
+ [InlineData(PublicEncodingRules.DER, "0A00")]
+ [InlineData(PublicEncodingRules.BER, "0A01")]
+ [InlineData(PublicEncodingRules.CER, "0A01")]
+ [InlineData(PublicEncodingRules.DER, "0A01")]
+ [InlineData(PublicEncodingRules.BER, "0A81")]
+ [InlineData(PublicEncodingRules.CER, "0A81")]
+ [InlineData(PublicEncodingRules.DER, "0A81")]
+ [InlineData(PublicEncodingRules.BER, "9F00")]
+ [InlineData(PublicEncodingRules.CER, "9F00")]
+ [InlineData(PublicEncodingRules.DER, "9F00")]
+ [InlineData(PublicEncodingRules.BER, "0A02007F")]
+ [InlineData(PublicEncodingRules.CER, "0A02007F")]
+ [InlineData(PublicEncodingRules.DER, "0A02007F")]
+ [InlineData(PublicEncodingRules.BER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.CER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.DER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.BER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.CER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.DER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.BER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.CER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.DER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.BER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.CER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.DER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.BER, "2A030A0100")]
+ public static void GetEnumeratedValue_Invalid_Int(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.GetEnumeratedValue<IntBacked>());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "")]
+ [InlineData(PublicEncodingRules.CER, "")]
+ [InlineData(PublicEncodingRules.DER, "")]
+ [InlineData(PublicEncodingRules.BER, "0A")]
+ [InlineData(PublicEncodingRules.CER, "0A")]
+ [InlineData(PublicEncodingRules.DER, "0A")]
+ [InlineData(PublicEncodingRules.BER, "0A00")]
+ [InlineData(PublicEncodingRules.CER, "0A00")]
+ [InlineData(PublicEncodingRules.DER, "0A00")]
+ [InlineData(PublicEncodingRules.BER, "0A01")]
+ [InlineData(PublicEncodingRules.CER, "0A01")]
+ [InlineData(PublicEncodingRules.DER, "0A01")]
+ [InlineData(PublicEncodingRules.BER, "0A81")]
+ [InlineData(PublicEncodingRules.CER, "0A81")]
+ [InlineData(PublicEncodingRules.DER, "0A81")]
+ [InlineData(PublicEncodingRules.BER, "9F00")]
+ [InlineData(PublicEncodingRules.CER, "9F00")]
+ [InlineData(PublicEncodingRules.DER, "9F00")]
+ [InlineData(PublicEncodingRules.BER, "0A01FF")]
+ [InlineData(PublicEncodingRules.CER, "0A01FF")]
+ [InlineData(PublicEncodingRules.DER, "0A01FF")]
+ [InlineData(PublicEncodingRules.BER, "0A02007F")]
+ [InlineData(PublicEncodingRules.CER, "0A02007F")]
+ [InlineData(PublicEncodingRules.DER, "0A02007F")]
+ [InlineData(PublicEncodingRules.BER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.CER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.DER, "0A050102030405")]
+ [InlineData(PublicEncodingRules.BER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.CER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.DER, "0A080102030405060708")]
+ [InlineData(PublicEncodingRules.BER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.CER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.DER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.BER, "2A030A0100")]
+ public static void GetEnumeratedValue_Invalid_UInt(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.GetEnumeratedValue<UIntBacked>());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "")]
+ [InlineData(PublicEncodingRules.CER, "")]
+ [InlineData(PublicEncodingRules.DER, "")]
+ [InlineData(PublicEncodingRules.BER, "0A")]
+ [InlineData(PublicEncodingRules.CER, "0A")]
+ [InlineData(PublicEncodingRules.DER, "0A")]
+ [InlineData(PublicEncodingRules.BER, "0A00")]
+ [InlineData(PublicEncodingRules.CER, "0A00")]
+ [InlineData(PublicEncodingRules.DER, "0A00")]
+ [InlineData(PublicEncodingRules.BER, "0A01")]
+ [InlineData(PublicEncodingRules.CER, "0A01")]
+ [InlineData(PublicEncodingRules.DER, "0A01")]
+ [InlineData(PublicEncodingRules.BER, "0A81")]
+ [InlineData(PublicEncodingRules.CER, "0A81")]
+ [InlineData(PublicEncodingRules.DER, "0A81")]
+ [InlineData(PublicEncodingRules.BER, "9F00")]
+ [InlineData(PublicEncodingRules.CER, "9F00")]
+ [InlineData(PublicEncodingRules.DER, "9F00")]
+ [InlineData(PublicEncodingRules.BER, "0A02007F")]
+ [InlineData(PublicEncodingRules.CER, "0A02007F")]
+ [InlineData(PublicEncodingRules.DER, "0A02007F")]
+ [InlineData(PublicEncodingRules.BER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.CER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.DER, "0A02FF80")]
+ [InlineData(PublicEncodingRules.BER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.CER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.DER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.BER, "2A030A0100")]
+ public static void GetEnumeratedValue_Invalid_Long(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.GetEnumeratedValue<LongBacked>());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "")]
+ [InlineData(PublicEncodingRules.CER, "")]
+ [InlineData(PublicEncodingRules.DER, "")]
+ [InlineData(PublicEncodingRules.BER, "0A")]
+ [InlineData(PublicEncodingRules.CER, "0A")]
+ [InlineData(PublicEncodingRules.DER, "0A")]
+ [InlineData(PublicEncodingRules.BER, "0A00")]
+ [InlineData(PublicEncodingRules.CER, "0A00")]
+ [InlineData(PublicEncodingRules.DER, "0A00")]
+ [InlineData(PublicEncodingRules.BER, "0A01")]
+ [InlineData(PublicEncodingRules.CER, "0A01")]
+ [InlineData(PublicEncodingRules.DER, "0A01")]
+ [InlineData(PublicEncodingRules.BER, "0A81")]
+ [InlineData(PublicEncodingRules.CER, "0A81")]
+ [InlineData(PublicEncodingRules.DER, "0A81")]
+ [InlineData(PublicEncodingRules.BER, "9F00")]
+ [InlineData(PublicEncodingRules.CER, "9F00")]
+ [InlineData(PublicEncodingRules.DER, "9F00")]
+ [InlineData(PublicEncodingRules.BER, "0A01FF")]
+ [InlineData(PublicEncodingRules.CER, "0A01FF")]
+ [InlineData(PublicEncodingRules.DER, "0A01FF")]
+ [InlineData(PublicEncodingRules.BER, "0A02007F")]
+ [InlineData(PublicEncodingRules.CER, "0A02007F")]
+ [InlineData(PublicEncodingRules.DER, "0A02007F")]
+ [InlineData(PublicEncodingRules.BER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.CER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.DER, "0A09010203040506070809")]
+ [InlineData(PublicEncodingRules.BER, "2A030A0100")]
+ public static void GetEnumeratedValue_Invalid_ULong(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.GetEnumeratedValue<ULongBacked>());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void GetEnumeratedValue_NonEnumType(PublicEncodingRules ruleSet)
+ {
+ byte[] data = { 0x0A, 0x01, 0x00 };
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<ArgumentException>(() => reader.GetEnumeratedValue<Guid>());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void GetEnumeratedValue_FlagsEnum(PublicEncodingRules ruleSet)
+ {
+ byte[] data = { 0x0A, 0x01, 0x00 };
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tEnum",
+ () => reader.GetEnumeratedValue<AssemblyFlags>());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void GetEnumeratedBytes(PublicEncodingRules ruleSet)
+ {
+ const string Payload = "0102030405060708090A0B0C0D0E0F10";
+
+ // ENUMERATED (payload) followed by INTEGER (0)
+ byte[] data = ("0A10" + Payload + "020100").HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ ReadOnlyMemory<byte> contents = reader.GetEnumeratedBytes();
+ Assert.Equal(0x10, contents.Length);
+ Assert.Equal(Payload, contents.ByteArrayToHex());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "")]
+ [InlineData(PublicEncodingRules.CER, "")]
+ [InlineData(PublicEncodingRules.DER, "")]
+ [InlineData(PublicEncodingRules.BER, "0A")]
+ [InlineData(PublicEncodingRules.CER, "0A")]
+ [InlineData(PublicEncodingRules.DER, "0A")]
+ [InlineData(PublicEncodingRules.BER, "0A00")]
+ [InlineData(PublicEncodingRules.CER, "0A00")]
+ [InlineData(PublicEncodingRules.DER, "0A00")]
+ [InlineData(PublicEncodingRules.BER, "0A01")]
+ [InlineData(PublicEncodingRules.CER, "0A01")]
+ [InlineData(PublicEncodingRules.DER, "0A01")]
+ [InlineData(PublicEncodingRules.BER, "010100")]
+ [InlineData(PublicEncodingRules.CER, "010100")]
+ [InlineData(PublicEncodingRules.DER, "010100")]
+ [InlineData(PublicEncodingRules.BER, "9F00")]
+ [InlineData(PublicEncodingRules.CER, "9F00")]
+ [InlineData(PublicEncodingRules.DER, "9F00")]
+ [InlineData(PublicEncodingRules.BER, "0A81")]
+ [InlineData(PublicEncodingRules.CER, "0A81")]
+ [InlineData(PublicEncodingRules.DER, "0A81")]
+ public static void GetEnumeratedBytes_Throws(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.GetEnumeratedBytes());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x0A, 1, 0x7E };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.GetEnumeratedValue<ShortBacked>(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetEnumeratedValue<ShortBacked>(new Asn1Tag(TagClass.ContextSpecific, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ ShortBacked value = reader.GetEnumeratedValue<ShortBacked>();
+ Assert.Equal((ShortBacked)0x7E, value);
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x87, 2, 0, 0x80 };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.GetEnumeratedValue<ShortBacked>(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.GetEnumeratedValue<ShortBacked>());
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetEnumeratedValue<ShortBacked>(new Asn1Tag(TagClass.Application, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetEnumeratedValue<ShortBacked>(new Asn1Tag(TagClass.ContextSpecific, 1)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ ShortBacked value = reader.GetEnumeratedValue<ShortBacked>(new Asn1Tag(TagClass.ContextSpecific, 7));
+ Assert.Equal((ShortBacked)0x80, value);
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "0A01FF", PublicTagClass.Universal, 10)]
+ [InlineData(PublicEncodingRules.CER, "0A01FF", PublicTagClass.Universal, 10)]
+ [InlineData(PublicEncodingRules.DER, "0A01FF", PublicTagClass.Universal, 10)]
+ [InlineData(PublicEncodingRules.BER, "8001FF", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.CER, "4C01FF", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "DF8A4601FF", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ ShortBacked val1 = reader.GetEnumeratedValue<ShortBacked>(new Asn1Tag((TagClass)tagClass, tagValue, true));
+ Assert.False(reader.HasData);
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ ShortBacked val2 = reader.GetEnumeratedValue<ShortBacked>(new Asn1Tag((TagClass)tagClass, tagValue, false));
+ Assert.False(reader.HasData);
+
+ Assert.Equal(val1, val2);
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadGeneralizedTime.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadGeneralizedTime.cs
new file mode 100644
index 0000000000..02dfa3810d
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadGeneralizedTime.cs
@@ -0,0 +1,522 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Linq;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadGeneralizedTime : Asn1ReaderTests
+ {
+ [Theory]
+ // yyyyMMddHH (2017090821)
+ [InlineData(PublicEncodingRules.BER, "180A32303137303930383231", 2017, 9, 8, 21, 0, 0, 0, null, 0)]
+ // yyyyMMddHHZ (2017090821Z)
+ [InlineData(PublicEncodingRules.BER, "180B323031373039303832315A", 2017, 9, 8, 21, 0, 0, 0, 0, 0)]
+ // yyyyMMddHH-HH (2017090821-01)
+ [InlineData(PublicEncodingRules.BER, "180D323031373039303832312D3031", 2017, 9, 8, 21, 0, 0, 0, -1, 0)]
+ // yyyyMMddHH+HHmm (2017090821+0118)
+ [InlineData(PublicEncodingRules.BER, "180F323031373039303832312B30313138", 2017, 9, 8, 21, 0, 0, 0, 1, 18)]
+ // yyyyMMddHH,hourFrac (2017090821.1)
+ [InlineData(PublicEncodingRules.BER, "180C323031373039303832312C31", 2017, 9, 8, 21, 6, 0, 0, null, 0)]
+ // yyyyMMddHH.hourFracZ (2017090821.2010Z)
+ [InlineData(PublicEncodingRules.BER, "1810323031373039303832312E323031305A", 2017, 9, 8, 21, 12, 3, 600, 0, 0)]
+ // yyyyMMddHH,hourFrac-HH (2017090821,3099-01)
+ [InlineData(PublicEncodingRules.BER, "1812323031373039303832312C333039392D3031", 2017, 9, 8, 21, 18, 35, 640, -1, 0)]
+ // yyyyMMddHH.hourFrac+HHmm (2017090821.201+0118)
+ [InlineData(PublicEncodingRules.BER, "1813323031373039303832312E3230312B30313138", 2017, 9, 8, 21, 12, 3, 600, 1, 18)]
+ // yyyyMMddHHmm (201709082358)
+ [InlineData(PublicEncodingRules.BER, "180C323031373039303832333538", 2017, 9, 8, 23, 58, 0, 0, null, 0)]
+ // yyyyMMddHHmmZ (201709082358Z)
+ [InlineData(PublicEncodingRules.BER, "180D3230313730393038323335385A", 2017, 9, 8, 23, 58, 0, 0, 0, 0)]
+ // yyyyMMddHHmm-HH (201709082358-01)
+ [InlineData(PublicEncodingRules.BER, "180F3230313730393038323335382D3031", 2017, 9, 8, 23, 58, 0, 0, -1, 0)]
+ // yyyyMMddHHmm+HHmm (201709082358+0118)
+ [InlineData(PublicEncodingRules.BER, "18113230313730393038323335382B30313138", 2017, 9, 8, 23, 58, 0, 0, 1, 18)]
+ // yyyyMMddHHmm.minuteFrac (201709082358.01)
+ [InlineData(PublicEncodingRules.BER, "180F3230313730393038323335382E3031", 2017, 9, 8, 23, 58, 0, 600, null, 0)]
+ // yyyyMMddHHmm,minuteFracZ (201709082358,11Z)
+ [InlineData(PublicEncodingRules.BER, "18103230313730393038323335382C31315A", 2017, 9, 8, 23, 58, 6, 600, 0, 0)]
+ // yyyyMMddHHmm.minuteFrac-HH (201709082358.05-01)
+ [InlineData(PublicEncodingRules.BER, "18123230313730393038323335382E30352D3031", 2017, 9, 8, 23, 58, 3, 0, -1, 0)]
+ // yyyyMMddHHmm,minuteFrac+HHmm (201709082358,007+0118)
+ [InlineData(PublicEncodingRules.BER, "18153230313730393038323335382C3030372B30313138", 2017, 9, 8, 23, 58, 0, 420, 1, 18)]
+ // yyyyMMddHHmmss (20161106012345) - Ambiguous time due to DST "fall back" in US & Canada
+ [InlineData(PublicEncodingRules.BER, "180E3230313631313036303132333435", 2016, 11, 6, 1, 23, 45, 0, null, 0)]
+ // yyyyMMddHHmmssZ (20161106012345Z)
+ [InlineData(PublicEncodingRules.BER, "180F32303136313130363031323334355A", 2016, 11, 6, 1, 23, 45, 0, 0, 0)]
+ // yyyyMMddHHmmss-HH (20161106012345-01)
+ [InlineData(PublicEncodingRules.BER, "181132303136313130363031323334352D3031", 2016, 11, 6, 1, 23, 45, 0, -1, 0)]
+ // yyyyMMddHHmmss+HHmm (20161106012345+0118)
+ [InlineData(PublicEncodingRules.BER, "181332303136313130363031323334352B30313138", 2016, 11, 6, 1, 23, 45, 0, 1, 18)]
+ // yyyyMMddHHmmss.secondFrac (20161106012345.6789) - Ambiguous time due to DST "fall back" in US & Canada
+ [InlineData(PublicEncodingRules.BER, "181332303136313130363031323334352E36373839", 2016, 11, 6, 1, 23, 45, 679, null, 0)]
+ // yyyyMMddHHmmss,secondFracZ (20161106012345,7654Z)
+ [InlineData(PublicEncodingRules.BER, "181432303136313130363031323334352C373635345A", 2016, 11, 6, 1, 23, 45, 765, 0, 0)]
+ // yyyyMMddHHmmss.secondFrac-HH (20161106012345.001-01)
+ [InlineData(PublicEncodingRules.BER, "181532303136313130363031323334352E3030312D3031", 2016, 11, 6, 1, 23, 45, 1, -1, 0)]
+ // yyyyMMddHHmmss,secondFrac+HHmm (20161106012345,0009+0118)
+ [InlineData(PublicEncodingRules.BER, "181832303136313130363031323334352C303030392B30313138", 2016, 11, 6, 1, 23, 45, 1, 1, 18)]
+
+ // yyyyMMddHHmmssZ (20161106012345Z)
+ [InlineData(PublicEncodingRules.CER, "180F32303136313130363031323334355A", 2016, 11, 6, 1, 23, 45, 0, 0, 0)]
+ [InlineData(PublicEncodingRules.DER, "180F32303136313130363031323334355A", 2016, 11, 6, 1, 23, 45, 0, 0, 0)]
+
+ // yyyyMMddHHmmss.secondFracZ (20161106012345,7654Z)
+ [InlineData(PublicEncodingRules.CER, "181432303136313130363031323334352E373635345A", 2016, 11, 6, 1, 23, 45, 765, 0, 0)]
+ [InlineData(PublicEncodingRules.DER, "181432303136313130363031323334352E373635345A", 2016, 11, 6, 1, 23, 45, 765, 0, 0)]
+ public static void ParseTime_Valid(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ int year,
+ int month,
+ int day,
+ int hour,
+ int minute,
+ int second,
+ int millisecond,
+ int? offsetHour,
+ int offsetMinute)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ DateTimeOffset value = reader.GetGeneralizedTime();
+ Assert.False(reader.HasData, "reader.HasData");
+
+ Assert.Equal(year, value.Year);
+ Assert.Equal(month, value.Month);
+ Assert.Equal(day, value.Day);
+ Assert.Equal(hour, value.Hour);
+ Assert.Equal(minute, value.Minute);
+ Assert.Equal(second, value.Second);
+ Assert.Equal(millisecond, value.Millisecond);
+
+ TimeSpan timeOffset;
+
+ if (offsetHour == null)
+ {
+ // Ask the system what offset it thinks was relevant for that time.
+ // Includes DST ambiguity.
+ timeOffset = new DateTimeOffset(value.LocalDateTime).Offset;
+ }
+ else
+ {
+ timeOffset = new TimeSpan(offsetHour.Value, offsetMinute, 0);
+ }
+
+ Assert.Equal(timeOffset, value.Offset);
+ }
+
+ [Theory]
+ // yyyyMMddHH (2017090821)
+ [InlineData("180A32303137303930383231")]
+ // yyyyMMddHHZ (2017090821Z)
+ [InlineData("180B323031373039303832315A")]
+ // yyyyMMddHH-HH (2017090821-01)
+ [InlineData("180D323031373039303832312D3031")]
+ // yyyyMMddHH+HHmm (2017090821+0118)
+ [InlineData("180F323031373039303832312B30313138")]
+ // yyyyMMddHH,hourFrac (2017090821,1)
+ [InlineData("180C323031373039303832312C31")]
+ // yyyyMMddHH.hourFrac (2017090821.1)
+ [InlineData("180C323031373039303832312E31")]
+ // yyyyMMddHH,hourFracZ (2017090821,2010Z)
+ [InlineData("1810323031373039303832312C323031305A")]
+ // yyyyMMddHH.hourFracZ (2017090821.2010Z)
+ [InlineData("1810323031373039303832312E323031305A")]
+ // yyyyMMddHH,hourFrac-HH (2017090821,3099-01)
+ [InlineData("1812323031373039303832312C333039392D3031")]
+ // yyyyMMddHH.hourFrac-HH (2017090821.3099-01)
+ [InlineData("1812323031373039303832312E333039392D3031")]
+ // yyyyMMddHH,hourFrac+HHmm (2017090821,201+0118)
+ [InlineData("1813323031373039303832312C3230312B30313138")]
+ // yyyyMMddHH.hourFrac+HHmm (2017090821.201+0118)
+ [InlineData("1813323031373039303832312E3230312B30313138")]
+ // yyyyMMddHHmm (201709082358)
+ [InlineData("180C323031373039303832333538")]
+ // yyyyMMddHHmmZ (201709082358Z)
+ [InlineData("180D3230313730393038323335385A")]
+ // yyyyMMddHHmm-HH (201709082358-01)
+ [InlineData("180F3230313730393038323335382D3031")]
+ // yyyyMMddHHmm+HHmm (201709082358+0118)
+ [InlineData("18113230313730393038323335382B30313138")]
+ // yyyyMMddHHmm,minuteFrac (201709082358,01)
+ [InlineData("180F3230313730393038323335382C3031")]
+ // yyyyMMddHHmm.minuteFrac (201709082358.01)
+ [InlineData("180F3230313730393038323335382E3031")]
+ // yyyyMMddHHmm,minuteFracZ (201709082358,11Z)
+ [InlineData("18103230313730393038323335382C31315A")]
+ // yyyyMMddHHmm.minuteFracZ (201709082358.11Z)
+ [InlineData("18103230313730393038323335382E31315A")]
+ // yyyyMMddHHmm,minuteFrac-HH (201709082358m05-01)
+ [InlineData("18123230313730393038323335382C30352D3031")]
+ // yyyyMMddHHmm.minuteFrac-HH (201709082358.05-01)
+ [InlineData("18123230313730393038323335382E30352D3031")]
+ // yyyyMMddHHmm,minuteFrac+HHmm (201709082358,007+0118)
+ [InlineData("18153230313730393038323335382C3030372B30313138")]
+ // yyyyMMddHHmm.minuteFrac+HHmm (201709082358.007+0118)
+ [InlineData("18153230313730393038323335382E3030372B30313138")]
+ // yyyyMMddHHmmss (20161106012345)
+ [InlineData("180E3230313631313036303132333435")]
+ // yyyyMMddHHmmss-HH (20161106012345-01)
+ [InlineData("181132303136313130363031323334352D3031")]
+ // yyyyMMddHHmmss+HHmm (20161106012345+0118)
+ [InlineData("181332303136313130363031323334352B30313138")]
+ // yyyyMMddHHmmss,secondFrac (20161106012345,6789)
+ [InlineData("181332303136313130363031323334352C36373839")]
+ // yyyyMMddHHmmss.secondFrac (20161106012345.6789)
+ [InlineData("181332303136313130363031323334352E36373839")]
+ // yyyyMMddHHmmss,secondFracZ (20161106012345,7654Z)
+ [InlineData("181432303136313130363031323334352C373635345A")]
+ // yyyyMMddHHmmss,secondFrac-HH (20161106012345,001-01)
+ [InlineData("181532303136313130363031323334352C3030312D3031")]
+ // yyyyMMddHHmmss.secondFrac-HH (20161106012345.001-01)
+ [InlineData("181532303136313130363031323334352E3030312D3031")]
+ // yyyyMMddHHmmss,secondFrac+HHmm (20161106012345,0009+0118)
+ [InlineData("181832303136313130363031323334352C303030392B30313138")]
+ // yyyyMMddHHmmss.secondFrac+HHmm (20161106012345.0009+0118)
+ [InlineData("181832303136313130363031323334352E303030392B30313138")]
+ // yyyyMMddHHmmss.secondFrac0Z (20161106012345.76540Z)
+ [InlineData("181532303136313130363031323334352E37363534305A")]
+ // Constructed encoding of yyyyMMddHHmmssZ
+ [InlineData(
+ "3880" +
+ "040432303136" +
+ "04023131" +
+ "0403303630" +
+ "040131" +
+ "0405323334355A" +
+ "0000")]
+ public static void ParseTime_BerOnly(string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader cerReader = new AsnReader(inputData, AsnEncodingRules.CER);
+ AsnReader derReader = new AsnReader(inputData, AsnEncodingRules.DER);
+
+ Assert.Throws<CryptographicException>(() => cerReader.GetGeneralizedTime());
+ Assert.Throws<CryptographicException>(() => derReader.GetGeneralizedTime());
+
+ // Prove it was not just corrupt input
+ AsnReader berReader = new AsnReader(inputData, AsnEncodingRules.BER);
+ berReader.GetGeneralizedTime();
+ Assert.False(berReader.HasData, "berReader.HasData");
+ Assert.True(cerReader.HasData, "cerReader.HasData");
+ Assert.True(derReader.HasData, "derReader.HasData");
+ }
+
+ [Fact]
+ public static void ExcessivelyPreciseFraction()
+ {
+ byte[] inputData = Text.Encoding.ASCII.GetBytes("\u0018\u002A2017092118.012345678901234567890123456789Z");
+
+ AsnReader berReader = new AsnReader(inputData, AsnEncodingRules.BER);
+ DateTimeOffset value = berReader.GetGeneralizedTime();
+ Assert.False(berReader.HasData, "berReader.HasData");
+
+ DateTimeOffset expected = new DateTimeOffset(2017, 9, 21, 18, 0, 44, 444, TimeSpan.Zero);
+
+ Assert.Equal(expected, value);
+ }
+
+ [Fact]
+ public static void ExcessivelyPreciseFraction_OneTenthPlusEpsilon()
+ {
+ byte[] inputData = Text.Encoding.ASCII.GetBytes("\u0018\u002A20170921180044.10000000000000000000000001Z");
+
+ AsnReader derReader = new AsnReader(inputData, AsnEncodingRules.DER);
+ DateTimeOffset value = derReader.GetGeneralizedTime();
+ Assert.False(derReader.HasData, "derReader.HasData");
+
+ DateTimeOffset expected = new DateTimeOffset(2017, 9, 21, 18, 0, 44, 100, TimeSpan.Zero);
+
+ Assert.Equal(expected, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ public static void MultiSegmentExcessivelyPreciseFraction(PublicEncodingRules ruleSet)
+ {
+ // This builds "20171207173522.0000...0001Z" where the Z required a second CER segment.
+ // This is a bit of nonsense, really, because it is encoding 1e-985 seconds, which is
+ // oodles of orders of magnitude smaller than Planck time (~5e-44).
+ // But, the spec says "any number of decimal places", and 985 is a number.
+
+ // A0 80 (context specifc 0, constructed, indefinite length)
+ // 04 82 03 E8 (octet string, primitive, 1000 bytes)
+ // ASCII("20171207173522." + new string('0', 984) + '1')
+ // 04 01 (octet string, primitive, 1 byte)
+ // ASCII("Z")
+ // 00 00 (end of contents)
+ //
+ // 1001 content bytes + 10 bytes of structure.
+ byte[] header = "A080048203E8".HexToByteArray();
+ byte[] contents0 = Text.Encoding.ASCII.GetBytes("20171207173522." + new string('0', 984) + "1");
+ byte[] cdr = { 0x04, 0x01, (byte)'Z', 0x00, 0x00 };
+ byte[] inputData = header.Concat(contents0).Concat(cdr).ToArray();
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ DateTimeOffset value = reader.GetGeneralizedTime(new Asn1Tag(TagClass.ContextSpecific, 0));
+ DateTimeOffset expected = new DateTimeOffset(2017, 12, 7, 17, 35, 22, TimeSpan.Zero);
+ Assert.Equal(expected, value);
+ }
+
+ [Fact]
+ public static void ExcessivelyPreciseFraction_OneTenthPlusEpsilonAndZero()
+ {
+ byte[] inputData = Text.Encoding.ASCII.GetBytes("\u0018\u002A20170921180044.10000000000000000000000010Z");
+
+ AsnReader berReader = new AsnReader(inputData, AsnEncodingRules.BER);
+ DateTimeOffset value = berReader.GetGeneralizedTime();
+ Assert.False(berReader.HasData, "berReader.HasData");
+
+ DateTimeOffset expected = new DateTimeOffset(2017, 9, 21, 18, 0, 44, 100, TimeSpan.Zero);
+ Assert.Equal(expected, value);
+
+ AsnReader cerReader = new AsnReader(inputData, AsnEncodingRules.CER);
+ AsnReader derReader = new AsnReader(inputData, AsnEncodingRules.DER);
+ Assert.Throws<CryptographicException>(() => cerReader.GetGeneralizedTime());
+ Assert.Throws<CryptographicException>(() => derReader.GetGeneralizedTime());
+ }
+
+ [Fact]
+ public static void ExcessivelyPreciseNonFraction()
+ {
+ byte[] inputData = Text.Encoding.ASCII.GetBytes("\u0018\u002A2017092118.012345678901234567890123Q56789Z");
+ AsnReader berReader = new AsnReader(inputData, AsnEncodingRules.BER);
+
+ Assert.Throws<CryptographicException>(() => berReader.GetGeneralizedTime());
+ }
+
+ [Theory]
+ // yyyyMMddHH,hourFrac (2017090821,1)
+ [InlineData("180C323031373039303832312C31")]
+ // yyyyMMddHH.hourFrac (2017090821.1)
+ [InlineData("180C323031373039303832312E31")]
+ // yyyyMMddHH,hourFracZ (2017090821,2010Z)
+ [InlineData("1810323031373039303832312C323031305A")]
+ // yyyyMMddHH.hourFracZ (2017090821.2010Z)
+ [InlineData("1810323031373039303832312E323031305A")]
+ // yyyyMMddHH,hourFrac-HH (2017090821,3099-01)
+ [InlineData("1812323031373039303832312C333039392D3031")]
+ // yyyyMMddHH.hourFrac-HH (2017090821.3099-01)
+ [InlineData("1812323031373039303832312E333039392D3031")]
+ // yyyyMMddHH,hourFrac+HHmm (2017090821,201+0118)
+ [InlineData("1813323031373039303832312C3230312B30313138")]
+ // yyyyMMddHH.hourFrac+HHmm (2017090821.201+0118)
+ [InlineData("1813323031373039303832312E3230312B30313138")]
+ // yyyyMMddHHmm,minuteFrac (201709082358,01)
+ [InlineData("180F3230313730393038323335382C3031")]
+ // yyyyMMddHHmm.minuteFrac (201709082358.01)
+ [InlineData("180F3230313730393038323335382E3031")]
+ // yyyyMMddHHmm,minuteFracZ (201709082358,11Z)
+ [InlineData("18103230313730393038323335382C31315A")]
+ // yyyyMMddHHmm.minuteFracZ (201709082358.11Z)
+ [InlineData("18103230313730393038323335382E31315A")]
+ // yyyyMMddHHmm,minuteFrac-HH (201709082358m05-01)
+ [InlineData("18123230313730393038323335382C30352D3031")]
+ // yyyyMMddHHmm.minuteFrac-HH (201709082358.05-01)
+ [InlineData("18123230313730393038323335382E30352D3031")]
+ // yyyyMMddHHmm,minuteFrac+HHmm (201709082358,007+0118)
+ [InlineData("18153230313730393038323335382C3030372B30313138")]
+ // yyyyMMddHHmm.minuteFrac+HHmm (201709082358.007+0118)
+ [InlineData("18153230313730393038323335382E3030372B30313138")]
+ // yyyyMMddHHmmss,secondFrac (20161106012345,6789)
+ [InlineData("181332303136313130363031323334352C36373839")]
+ // yyyyMMddHHmmss.secondFrac (20161106012345.6789)
+ [InlineData("181332303136313130363031323334352E36373839")]
+ // yyyyMMddHHmmss,secondFracZ (20161106012345,7654Z)
+ [InlineData("181432303136313130363031323334352C373635345A")]
+ // yyyyMMddHHmmss,secondFrac-HH (20161106012345,001-01)
+ [InlineData("181532303136313130363031323334352C3030312D3031")]
+ // yyyyMMddHHmmss.secondFrac-HH (20161106012345.001-01)
+ [InlineData("181532303136313130363031323334352E3030312D3031")]
+ // yyyyMMddHHmmss,secondFrac+HHmm (20161106012345,0009+0118)
+ [InlineData("181832303136313130363031323334352C303030392B30313138")]
+ // yyyyMMddHHmmss.secondFrac+HHmm (20161106012345.0009+0118)
+ [InlineData("181832303136313130363031323334352E303030392B30313138")]
+ // yyyyMMddHHmmss.secondFrac0Z (20161106012345.76540Z)
+ [InlineData("181532303136313130363031323334352E37363534305A")]
+ public static void VerifyDisallowFraction_BER(string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+
+ AsnReader berReader = new AsnReader(inputData, AsnEncodingRules.BER);
+
+ Assert.Throws<CryptographicException>(() => berReader.GetGeneralizedTime(disallowFractions: true));
+
+ // Legit if the fraction is allowed
+ berReader.GetGeneralizedTime();
+ Assert.False(berReader.HasData, "berReader.HasData");
+ }
+
+ [Theory]
+ [InlineData("Wrong Tag", "170F32303136313130363031323334355A")]
+ [InlineData("Incomplete Tag", "1F")]
+ [InlineData("No Length", "18")]
+ [InlineData("No payload", "180F")]
+ [InlineData("Length exceeds content", "181032303136313130363031323334355A")]
+ [InlineData("yyyyMMdd", "18083230313631313036")]
+ [InlineData("yyyyMMddZ", "180932303136313130365A")]
+ [InlineData("yyyyMMdd+HH", "180B32303136313130362D3030")]
+ [InlineData("yyyyMMdd-HHmm", "180D32303136313130362B30303030")]
+ [InlineData("yyyyMMddH", "1809323031363131303630")]
+ [InlineData("yyyyMMddHZ", "180A3230313631313036305A")]
+ [InlineData("yQyyMMddHH", "180A32513136313130363031")]
+ [InlineData("yyyyMQddHH", "180A32303136315130363031")]
+ [InlineData("yyyyMMQdHH", "180A32303136313151363031")]
+ [InlineData("yyyyMMddHQ", "180A32303136313130363051")]
+ [InlineData("yyyyMMddHH,", "180B323031363131303630312C")]
+ [InlineData("yyyyMMddHH.", "180B323031363131303630312E")]
+ [InlineData("yyyyMMddHHQ", "180B3230313631313036303151")]
+ [InlineData("yyyyMMddHH,Q", "180C323031363131303630312C51")]
+ [InlineData("yyyyMMddHH.Q", "180C323031363131303630312E51")]
+ [InlineData("yyyyMMddHH..", "180C323031363131303630312E2E")]
+ [InlineData("yyyyMMddHH-", "180B323031363131303630312B")]
+ [InlineData("yyyyMMddHH+", "180B323031363131303630312D")]
+ [InlineData("yyyyMMddHHmQ", "180C323031363131303630313251")]
+ [InlineData("yyyyMMddHHm+", "180C32303136313130363031322D")]
+ [InlineData("yyyyMMddHHmmQ", "180D32303136313130363031323351")]
+ [InlineData("yyyyMMddHHmm-", "180D3230313631313036303132332B")]
+ [InlineData("yyyyMMddHHmm,", "180D3230313631313036303132332C")]
+ [InlineData("yyyyMMddHHmm+", "180D3230313631313036303132332D")]
+ [InlineData("yyyyMMddHHmm.", "180D3230313631313036303132332E")]
+ [InlineData("yyyyMMddHHmm+Q", "180E3230313631313036303132332D51")]
+ [InlineData("yyyyMMddHHmm.Q", "180E3230313631313036303132332E51")]
+ [InlineData("yyyyMMddHHmm..", "180E3230313631313036303132332E2E")]
+ [InlineData("yyyyMMddHHmm.ss,", "18103230313631313036303132332E31312E")]
+ [InlineData("yyyyMMddHHmms", "180D32303136313130363031323334")]
+ [InlineData("yyyyMMddHHmmsQ", "180E3230313631313036303132333451")]
+ [InlineData("yyyyMMddHHmmss-", "180F32303136313130363031323334352B")]
+ [InlineData("yyyyMMddHHmmss,", "180F32303136313130363031323334352C")]
+ [InlineData("yyyyMMddHHmmss+", "180F32303136313130363031323334352D")]
+ [InlineData("yyyyMMddHHmmss.", "180F32303136313130363031323334352E")]
+ [InlineData("yyyyMMddHHmmssQ", "180F323031363131303630313233343551")]
+ [InlineData("yyyyMMddHHmmss.Q", "181032303136313130363031323334352E51")]
+ [InlineData("yyyyMMddHHmmss.Q", "181032303136313130363031323334352E2E")]
+ [InlineData("yyyyMMddHHZmm", "180D323031363131303630315A3233")]
+ [InlineData("yyyyMMddHHmmZss", "180F3230313631313036303132335A3435")]
+ [InlineData("yyyyMMddHHmmssZ.s", "181232303136313130363031323334355A2E36")]
+ [InlineData("yyyyMMddHH+H", "180C323031363131303630312D30")]
+ [InlineData("yyyyMMddHH+HQ", "180D323031363131303630312D3051")]
+ [InlineData("yyyyMMddHH+HHQ", "180E323031363131303630312D303051")]
+ [InlineData("yyyyMMddHH+HHmQ", "180F323031363131303630312D30303051")]
+ [InlineData("yyyyMMddHH+HHmmQ", "1810323031363131303630312D3030303151")]
+ [InlineData("yyyyMMdd+H", "180A32303137313230382D30")]
+ [InlineData("yyyyMMddH+", "180A3230313731323038302D")]
+ [InlineData("yyyyMMddHH+0060", "180F323031373132303830392D30303630")]
+ [InlineData("yyyyMMddHH-2400", "180F323031373132303830392D32343030")]
+ [InlineData("yyyyMMddHH-HH:mm", "1810323031373039303832312D30313A3138")]
+ [InlineData("yyyyMMddHHmm-HH:mm", "18123230313730393038323131302D30313A3138")]
+ [InlineData("yyyyMMddHHmmss-HH:mm", "181432303137303930383231313032302D30313A3138")]
+ [InlineData("yyyyMMddHH,hourFrac-HH:mm", "1812323031373039303832312C312D30313A3138")]
+ [InlineData("yyyyMMddHH.hourFrac-HH:mm", "1812323031373039303832312E312D30313A3138")]
+ [InlineData("yyyyMMddHHmm,minuteFrac-HH:mm", "18183230313730393038323335382C30303030352D30313A3138")]
+ [InlineData("yyyyMMddHHmm.minuteFrac-HH:mm", "18183230313730393038323335382E30303030352D30313A3138")]
+ [InlineData("yyyyMMddHHmmss,secondFrac-HH:mm", "181932303136313130363031323334352C393939392D30313A3138")]
+ [InlineData("yyyyMMddHHmmss.secondFrac-HH:mm", "181932303136313130363031323334352E393939392D30313A3138")]
+ public static void GetGeneralizedTime_Throws(string description, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, AsnEncodingRules.BER);
+
+ Assert.Throws<CryptographicException>(() => reader.GetGeneralizedTime());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "180F32303136313130363031323334355A".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.GetGeneralizedTime(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetGeneralizedTime(new Asn1Tag(TagClass.ContextSpecific, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ Assert.Equal(
+ new DateTimeOffset(2016, 11, 6, 1, 23, 45, TimeSpan.Zero),
+ reader.GetGeneralizedTime());
+
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "850F32303136313130363031323334355A".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.GetGeneralizedTime(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.GetUtcTime());
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetGeneralizedTime(new Asn1Tag(TagClass.Application, 5)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetGeneralizedTime(new Asn1Tag(TagClass.ContextSpecific, 7)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ Assert.Equal(
+ new DateTimeOffset(2016, 11, 6, 1, 23, 45, TimeSpan.Zero),
+ reader.GetGeneralizedTime(new Asn1Tag(TagClass.ContextSpecific, 5)));
+
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "180F32303136313130363031323334355A", PublicTagClass.Universal, 24)]
+ [InlineData(PublicEncodingRules.CER, "180F32303136313130363031323334355A", PublicTagClass.Universal, 24)]
+ [InlineData(PublicEncodingRules.DER, "180F32303136313130363031323334355A", PublicTagClass.Universal, 24)]
+ [InlineData(PublicEncodingRules.BER, "800F31393530303130323132333435365A", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.CER, "4C0F31393530303130323132333435365A", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "DF8A460F31393530303130323132333435365A", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ DateTimeOffset val1 = reader.GetGeneralizedTime(new Asn1Tag((TagClass)tagClass, tagValue, true));
+
+ Assert.False(reader.HasData);
+
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ DateTimeOffset val2 = reader.GetGeneralizedTime(new Asn1Tag((TagClass)tagClass, tagValue, false));
+
+ Assert.False(reader.HasData);
+
+ Assert.Equal(val1, val2);
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadIA5String.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadIA5String.cs
new file mode 100644
index 0000000000..8319493513
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadIA5String.cs
@@ -0,0 +1,721 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadIA5String : Asn1ReaderTests
+ {
+ public static IEnumerable<object[]> ValidEncodingData { get; } =
+ new object[][]
+ {
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "160D4A6F686E20512E20536D697468",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.CER,
+ "160D4A6F686E20512E20536D697468",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.DER,
+ "160D4A6F686E20512E20536D697468",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "3680" + "040D4A6F686E20512E20536D697468" + "0000",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "360F" + "040D4A6F686E20512E20536D697468",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "1600",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.CER,
+ "1600",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.DER,
+ "1600",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "3600",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "3680" + "0000",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "3680" +
+ "2480" +
+ // "Dr."
+ "040344722E" +
+ // " & "
+ "0403202620" +
+ // "Mrs."
+ "04044D72732E" +
+ "0000" +
+ // " "
+ "040120" +
+ "2480" +
+ "240A" +
+ // "Smith"
+ "0405536D697468" +
+ // hyphen (U+2010) is not valid, so use hyphen-minus
+ "04012D" +
+ "0000" +
+ // "Jones"
+ "04054A6F6E6573" +
+ "2480" +
+ // " "
+ "040120" +
+ "2480" +
+ // small ampersand (U+FE60) is not valid, so use ampersand.
+ "040126" +
+ "0000" +
+ // " "
+ "040120" +
+ // "children"
+ "04086368696C6472656E" +
+ "0000" +
+ "0000",
+ "Dr. & Mrs. Smith-Jones & children",
+ },
+ };
+
+ [Theory]
+ [MemberData(nameof(ValidEncodingData))]
+ public static void GetIA5String_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ string expectedValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ string value = reader.GetCharacterString(UniversalTagNumber.IA5String);
+
+ Assert.Equal(expectedValue, value);
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidEncodingData))]
+ public static void TryCopyIA5String(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ string expectedValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ char[] output = new char[expectedValue.Length];
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ bool copied;
+ int charsWritten;
+
+ if (output.Length > 0)
+ {
+ output[0] = 'a';
+
+ copied = reader.TryCopyIA5String(output.AsSpan().Slice(0, expectedValue.Length - 1),
+ out charsWritten);
+
+ Assert.False(copied, "reader.TryCopyIA5String - too short");
+ Assert.Equal(0, charsWritten);
+ Assert.Equal('a', output[0]);
+ }
+
+ copied = reader.TryCopyIA5String(output,
+ out charsWritten);
+
+ Assert.True(copied, "reader.TryCopyIA5String");
+
+ string actualValue = new string(output, 0, charsWritten);
+ Assert.Equal(expectedValue, actualValue);
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidEncodingData))]
+ public static void TryCopyIA5StringBytes(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ string expectedString)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ string expectedHex = Text.Encoding.ASCII.GetBytes(expectedString).ByteArrayToHex();
+ byte[] output = new byte[expectedHex.Length / 2];
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ bool copied;
+ int bytesWritten;
+
+ if (output.Length > 0)
+ {
+ output[0] = 32;
+
+ copied = reader.TryCopyIA5StringBytes(output.AsSpan().Slice(0, output.Length - 1),
+ out bytesWritten);
+
+ Assert.False(copied, "reader.TryCopyIA5StringBytes - too short");
+ Assert.Equal(0, bytesWritten);
+ Assert.Equal(32, output[0]);
+ }
+
+ copied = reader.TryCopyIA5StringBytes(output,
+ out bytesWritten);
+
+ Assert.True(copied, "reader.TryCopyIA5StringBytes");
+
+ Assert.Equal(
+ expectedHex,
+ new ReadOnlySpan<byte>(output, 0, bytesWritten).ByteArrayToHex());
+
+ Assert.Equal(output.Length, bytesWritten);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "160120", true)]
+ [InlineData(PublicEncodingRules.BER, "3680" + "040120" + "0000", false)]
+ [InlineData(PublicEncodingRules.BER, "3603" + "040120", false)]
+ public static void TryGetIA5StringBytes(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ bool expectSuccess)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ bool got = reader.TryGetIA5StringBytes(out ReadOnlyMemory<byte> contents);
+
+ if (expectSuccess)
+ {
+ Assert.True(got, "reader.TryGetIA5StringBytes");
+
+ Assert.True(
+ Unsafe.AreSame(
+ ref contents.Span.DangerousGetPinnableReference(),
+ ref inputData[2]));
+ }
+ else
+ {
+ Assert.False(got, "reader.TryGetIA5StringBytes");
+ Assert.True(contents.IsEmpty, "contents.IsEmpty");
+ }
+ }
+
+ [Theory]
+ [InlineData("Incomplete Tag", PublicEncodingRules.BER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.CER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.DER, "1F")]
+ [InlineData("Missing Length", PublicEncodingRules.BER, "16")]
+ [InlineData("Missing Length", PublicEncodingRules.CER, "16")]
+ [InlineData("Missing Length", PublicEncodingRules.DER, "16")]
+ [InlineData("Missing Contents", PublicEncodingRules.BER, "1601")]
+ [InlineData("Missing Contents", PublicEncodingRules.CER, "1601")]
+ [InlineData("Missing Contents", PublicEncodingRules.DER, "1601")]
+ [InlineData("Length Too Long", PublicEncodingRules.BER, "16034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.CER, "16034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.DER, "16034869")]
+ [InlineData("Constructed Form", PublicEncodingRules.DER, "3603040149")]
+ public static void TryGetIA5StringBytes_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetIA5StringBytes(out ReadOnlyMemory<byte> contents));
+ }
+
+ [Theory]
+ [InlineData("Empty", PublicEncodingRules.BER, "")]
+ [InlineData("Empty", PublicEncodingRules.CER, "")]
+ [InlineData("Empty", PublicEncodingRules.DER, "")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.BER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.CER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.DER, "1F")]
+ [InlineData("Missing Length", PublicEncodingRules.BER, "16")]
+ [InlineData("Missing Length", PublicEncodingRules.CER, "16")]
+ [InlineData("Missing Length", PublicEncodingRules.DER, "16")]
+ [InlineData("Missing Contents", PublicEncodingRules.BER, "1601")]
+ [InlineData("Missing Contents", PublicEncodingRules.CER, "1601")]
+ [InlineData("Missing Contents", PublicEncodingRules.DER, "1601")]
+ [InlineData("Missing Contents - Constructed", PublicEncodingRules.BER, "3601")]
+ [InlineData("Missing Contents - Constructed Indef", PublicEncodingRules.BER, "3680")]
+ [InlineData("Missing Contents - Constructed Indef", PublicEncodingRules.CER, "3680")]
+ [InlineData("Length Too Long", PublicEncodingRules.BER, "16034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.CER, "16034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.DER, "16034869")]
+ [InlineData("Definite Constructed Form", PublicEncodingRules.CER, "3603040149")]
+ [InlineData("Definite Constructed Form", PublicEncodingRules.DER, "3603040149")]
+ [InlineData("Indefinite Constructed Form - Short Payload", PublicEncodingRules.CER, "36800401490000")]
+ [InlineData("Indefinite Constructed Form", PublicEncodingRules.DER, "36800401490000")]
+ [InlineData("No nested content", PublicEncodingRules.CER, "36800000")]
+ [InlineData("No EoC", PublicEncodingRules.BER, "3680" + "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.BER, "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.CER, "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.DER, "04024869")]
+ [InlineData("Wrong Tag - Constructed", PublicEncodingRules.BER, "240404024869")]
+ [InlineData("Wrong Tag - Constructed Indef", PublicEncodingRules.BER, "2480" + "04024869" + "0000")]
+ [InlineData("Wrong Tag - Constructed Indef", PublicEncodingRules.CER, "2480" + "04024869" + "0000")]
+ [InlineData("Wrong Tag - Constructed", PublicEncodingRules.DER, "240404024869")]
+ [InlineData("Nested Bad Tag", PublicEncodingRules.BER, "3604" + "16024869")]
+ [InlineData("Nested context-specific", PublicEncodingRules.BER, "3604800400FACE")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.BER, "3680800400FACE0000")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.CER, "3680800400FACE0000")]
+ [InlineData("Nested Length Too Long", PublicEncodingRules.BER, "3607" + ("2402" + "0403") + "040149")]
+ [InlineData("Nested Simple Length Too Long", PublicEncodingRules.BER, "3603" + "040548656C6C6F")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.BER, "368020000000")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.CER, "368020000000")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.BER, "3680000100")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.CER, "3680000100")]
+ [InlineData("LongLength EndOfContents", PublicEncodingRules.BER, "3680008100")]
+ public static void TryCopyIA5StringBytes_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ byte[] outputData = new byte[inputData.Length + 1];
+ outputData[0] = 252;
+
+ int bytesWritten = -1;
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryCopyIA5StringBytes(outputData, out bytesWritten));
+
+ Assert.Equal(-1, bytesWritten);
+ Assert.Equal(252, outputData[0]);
+ }
+
+ private static void TryCopyIA5String_Throws(PublicEncodingRules ruleSet, byte[] inputData)
+ {
+ char[] outputData = new char[inputData.Length + 1];
+ outputData[0] = 'a';
+
+ int bytesWritten = -1;
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryCopyIA5String(outputData, out bytesWritten));
+
+ Assert.Equal(-1, bytesWritten);
+ Assert.Equal('a', outputData[0]);
+ }
+
+ [Theory]
+ [InlineData("Bad IA5 value", PublicEncodingRules.BER, "1602E280")]
+ [InlineData("Bad IA5 value", PublicEncodingRules.CER, "1602E280")]
+ [InlineData("Bad IA5 value", PublicEncodingRules.DER, "1602E280")]
+ [InlineData("Wrong Tag", PublicEncodingRules.BER, "04024869")]
+ public static void GetIA5String_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetCharacterString(UniversalTagNumber.IA5String));
+ }
+
+ [Theory]
+ [InlineData("Empty", PublicEncodingRules.BER, "")]
+ [InlineData("Empty", PublicEncodingRules.CER, "")]
+ [InlineData("Empty", PublicEncodingRules.DER, "")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.BER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.CER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.DER, "1F")]
+ [InlineData("Missing Length", PublicEncodingRules.BER, "16")]
+ [InlineData("Missing Length", PublicEncodingRules.CER, "16")]
+ [InlineData("Missing Length", PublicEncodingRules.DER, "16")]
+ [InlineData("Missing Contents", PublicEncodingRules.BER, "1601")]
+ [InlineData("Missing Contents", PublicEncodingRules.CER, "1601")]
+ [InlineData("Missing Contents", PublicEncodingRules.DER, "1601")]
+ [InlineData("Missing Contents - Constructed", PublicEncodingRules.BER, "3601")]
+ [InlineData("Missing Contents - Constructed Indef", PublicEncodingRules.BER, "3680")]
+ [InlineData("Missing Contents - Constructed Indef", PublicEncodingRules.CER, "3680")]
+ [InlineData("Length Too Long", PublicEncodingRules.BER, "16034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.CER, "16034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.DER, "16034869")]
+ [InlineData("Definite Constructed Form", PublicEncodingRules.CER, "3603040149")]
+ [InlineData("Definite Constructed Form", PublicEncodingRules.DER, "3603040149")]
+ [InlineData("Indefinite Constructed Form - Short Payload", PublicEncodingRules.CER, "36800401490000")]
+ [InlineData("Indefinite Constructed Form", PublicEncodingRules.DER, "36800401490000")]
+ [InlineData("No nested content", PublicEncodingRules.CER, "36800000")]
+ [InlineData("No EoC", PublicEncodingRules.BER, "3680" + "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.BER, "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.CER, "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.DER, "04024869")]
+ [InlineData("Wrong Tag - Constructed", PublicEncodingRules.BER, "240404024869")]
+ [InlineData("Wrong Tag - Constructed Indef", PublicEncodingRules.BER, "2480" + "04024869" + "0000")]
+ [InlineData("Wrong Tag - Constructed Indef", PublicEncodingRules.CER, "2480" + "04024869" + "0000")]
+ [InlineData("Wrong Tag - Constructed", PublicEncodingRules.DER, "240404024869")]
+ [InlineData("Nested Bad Tag", PublicEncodingRules.BER, "3604" + "16024869")]
+ [InlineData("Nested context-specific", PublicEncodingRules.BER, "3604800400FACE")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.BER, "3680800400FACE0000")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.CER, "3680800400FACE0000")]
+ [InlineData("Nested Length Too Long", PublicEncodingRules.BER, "3607" + ("2402" + "0403") + "040149")]
+ [InlineData("Nested Simple Length Too Long", PublicEncodingRules.BER, "3603" + "040548656C6C6F")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.BER, "368020000000")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.CER, "368020000000")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.BER, "3680000100")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.CER, "3680000100")]
+ [InlineData("LongLength EndOfContents", PublicEncodingRules.BER, "3680008100")]
+ [InlineData("Bad IA5 value", PublicEncodingRules.BER, "1602E280")]
+ public static void TryCopyIA5String_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ TryCopyIA5String_Throws(ruleSet, inputData);
+ }
+
+ [Fact]
+ public static void TryCopyIA5String_Throws_CER_NestedTooLong()
+ {
+ // CER says that the maximum encoding length for a IA5String primitive
+ // is 1000.
+ //
+ // This test checks it for a primitive contained within a constructed.
+ //
+ // So we need 04 [1001] { 1001 0x00s }
+ // 1001 => 0x3E9, so the length encoding is 82 03 E9.
+ // 1001 + 3 + 1 == 1005
+ //
+ // Plus a leading 36 80 (indefinite length constructed)
+ // and a trailing 00 00 (End of contents)
+ // == 1009
+ byte[] input = new byte[1009];
+ // CONSTRUCTED IA5STRING (indefinite)
+ input[0] = 0x36;
+ input[1] = 0x80;
+ // OCTET STRING (1001)
+ input[2] = 0x04;
+ input[3] = 0x82;
+ input[4] = 0x03;
+ input[5] = 0xE9;
+ // EOC implicit since the byte[] initializes to zeros
+
+ TryCopyIA5String_Throws(PublicEncodingRules.CER, input);
+ }
+
+ [Fact]
+ public static void TryCopyIA5String_Throws_CER_NestedTooShortIntermediate()
+ {
+ // CER says that the maximum encoding length for a IA5String primitive
+ // is 1000, and in the constructed form the lengths must be
+ // [ 1000, 1000, 1000, ..., len%1000 ]
+ //
+ // So 1000, 2, 2 is illegal.
+ //
+ // 36 80 (indefinite constructed IA5 string)
+ // 04 82 03 08 (octet string, 1000 bytes)
+ // [1000 content bytes]
+ // 04 02 (octet string, 2 bytes)
+ // [2 content bytes]
+ // 04 02 (octet string, 2 bytes)
+ // [2 content bytes]
+ // 00 00 (end of contents)
+ // Looks like 1,016 bytes.
+ byte[] input = new byte[1016];
+ // CONSTRUCTED IA5STRING (indefinite)
+ input[0] = 0x36;
+ input[1] = 0x80;
+ // OCTET STRING (1000)
+ input[2] = 0x03;
+ input[3] = 0x82;
+ input[4] = 0x03;
+ input[5] = 0xE8;
+ // OCTET STRING (2)
+ input[1006] = 0x04;
+ input[1007] = 0x02;
+ // OCTET STRING (2)
+ input[1010] = 0x04;
+ input[1011] = 0x02;
+ // EOC implicit since the byte[] initializes to zeros
+
+ TryCopyIA5String_Throws(PublicEncodingRules.CER, input);
+ }
+
+ [Fact]
+ public static void TryCopyIA5StringBytes_Success_CER_MaxPrimitiveLength()
+ {
+ // CER says that the maximum encoding length for a IA5String primitive
+ // is 1000.
+ //
+ // So we need 16 [1000] { 1000 anythings }
+ // 1000 => 0x3E8, so the length encoding is 82 03 E8.
+ // 1000 + 3 + 1 == 1004
+ byte[] input = new byte[1004];
+ input[0] = 0x16;
+ input[1] = 0x82;
+ input[2] = 0x03;
+ input[3] = 0xE8;
+
+ // Content
+ input[4] = 0x65;
+ input[5] = 0x65;
+ input[1002] = 0x61;
+ input[1003] = 0x61;
+
+ byte[] output = new byte[1000];
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+
+ bool success = reader.TryCopyIA5StringBytes(output,
+ out int bytesWritten);
+
+ Assert.True(success, "reader.TryCopyIA5StringBytes");
+ Assert.Equal(1000, bytesWritten);
+
+ Assert.Equal(
+ input.AsReadOnlySpan().Slice(4).ByteArrayToHex(),
+ output.ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void TryCopyIA5StringBytes_Success_CER_MinConstructedLength()
+ {
+ // CER says that the maximum encoding length for a IA5String primitive
+ // is 1000, and that a constructed form must be used for values greater
+ // than 1000 bytes, with segments dividing up for each thousand
+ // [1000, 1000, ..., len%1000].
+ //
+ // So our smallest constructed form is 1001 bytes, [1000, 1]
+ //
+ // 36 80 (indefinite constructed IA5 string)
+ // 04 82 03 E9 (primitive octet string, 1000 bytes)
+ // [1000 content bytes]
+ // 04 01 (primitive octet string, 1 byte)
+ // pp
+ // 00 00 (end of contents, 0 bytes)
+ // 1011 total.
+ byte[] input = new byte[1011];
+ int offset = 0;
+ // CONSTRUCTED IA5STRING (Indefinite)
+ input[offset++] = 0x36;
+ input[offset++] = 0x80;
+ // OCTET STRING (1000)
+ input[offset++] = 0x04;
+ input[offset++] = 0x82;
+ input[offset++] = 0x03;
+ input[offset++] = 0xE8;
+
+ // Primitive 1: (65 65 :: 61 61) (1000)
+ input[offset++] = 0x65;
+ input[offset] = 0x65;
+ offset += 997;
+ input[offset++] = 0x61;
+ input[offset++] = 0x61;
+
+ // OCTET STRING (1)
+ input[offset++] = 0x04;
+ input[offset++] = 0x01;
+
+ // Primitive 2: One more byte
+ input[offset] = 0x2E;
+
+ byte[] expected = new byte[1001];
+ offset = 0;
+ expected[offset++] = 0x65;
+ expected[offset] = 0x65;
+ offset += 997;
+ expected[offset++] = 0x61;
+ expected[offset++] = 0x61;
+ expected[offset] = 0x2E;
+
+ byte[] output = new byte[1001];
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+
+ bool success = reader.TryCopyIA5StringBytes(output,
+ out int bytesWritten);
+
+ Assert.True(success, "reader.TryCopyIA5StringBytes");
+ Assert.Equal(1001, bytesWritten);
+
+ Assert.Equal(
+ expected.ByteArrayToHex(),
+ output.ByteArrayToHex());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x16, 2, (byte)'e', (byte)'l' };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.TryGetIA5StringBytes(Asn1Tag.Null, out _));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetIA5StringBytes(new Asn1Tag(TagClass.ContextSpecific, 0), out _));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ Assert.True(reader.TryGetIA5StringBytes(out ReadOnlyMemory<byte> value));
+ Assert.Equal("656C", value.ByteArrayToHex());
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x87, 2, (byte)'h', (byte)'i' };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.TryGetIA5StringBytes(Asn1Tag.Null, out _));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.TryGetIA5StringBytes(out _));
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetIA5StringBytes(new Asn1Tag(TagClass.Application, 0), out _));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetIA5StringBytes(new Asn1Tag(TagClass.ContextSpecific, 1), out _));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ Assert.True(
+ reader.TryGetIA5StringBytes(
+ new Asn1Tag(TagClass.ContextSpecific, 7),
+ out ReadOnlyMemory<byte> value));
+
+ Assert.Equal("6869", value.ByteArrayToHex());
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "16026869", PublicTagClass.Universal, 22)]
+ [InlineData(PublicEncodingRules.CER, "16026869", PublicTagClass.Universal, 22)]
+ [InlineData(PublicEncodingRules.DER, "16026869", PublicTagClass.Universal, 22)]
+ [InlineData(PublicEncodingRules.BER, "80023132", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.CER, "4C023132", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "DF8A46023132", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.True(
+ reader.TryGetIA5StringBytes(
+ new Asn1Tag((TagClass)tagClass, tagValue, true),
+ out ReadOnlyMemory<byte> val1));
+
+ Assert.False(reader.HasData);
+
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.True(
+ reader.TryGetIA5StringBytes(
+ new Asn1Tag((TagClass)tagClass, tagValue, false),
+ out ReadOnlyMemory<byte> val2));
+
+ Assert.False(reader.HasData);
+
+ Assert.Equal(val1.ByteArrayToHex(), val2.ByteArrayToHex());
+ }
+ }
+
+ internal static class ReaderIA5Extensions
+ {
+ public static bool TryGetIA5StringBytes(
+ this AsnReader reader,
+ out ReadOnlyMemory<byte> contents)
+ {
+ return reader.TryGetPrimitiveCharacterStringBytes(
+ UniversalTagNumber.IA5String,
+ out contents);
+ }
+
+ public static bool TryGetIA5StringBytes(
+ this AsnReader reader,
+ Asn1Tag expectedTag,
+ out ReadOnlyMemory<byte> contents)
+ {
+ return reader.TryGetPrimitiveCharacterStringBytes(
+ expectedTag,
+ UniversalTagNumber.IA5String,
+ out contents);
+ }
+
+ public static bool TryCopyIA5StringBytes(
+ this AsnReader reader,
+ Span<byte> destination,
+ out int bytesWritten)
+ {
+ return reader.TryCopyCharacterStringBytes(
+ UniversalTagNumber.IA5String,
+ destination,
+ out bytesWritten);
+ }
+
+ public static bool TryCopyIA5String(
+ this AsnReader reader,
+ Span<char> destination,
+ out int charsWritten)
+ {
+ return reader.TryCopyCharacterString(
+ UniversalTagNumber.IA5String,
+ destination,
+ out charsWritten);
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadInteger.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadInteger.cs
new file mode 100644
index 0000000000..6cf2e754d6
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadInteger.cs
@@ -0,0 +1,498 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadInteger : Asn1ReaderTests
+ {
+ [Theory]
+ [InlineData("Constructed Encoding", PublicEncodingRules.BER, "2203020100")]
+ [InlineData("Constructed Encoding-Indefinite", PublicEncodingRules.BER, "228002010000")]
+ [InlineData("Constructed Encoding-Indefinite", PublicEncodingRules.CER, "228002010000")]
+ [InlineData("Constructed Encoding", PublicEncodingRules.DER, "2203020100")]
+ [InlineData("Wrong Universal Tag", PublicEncodingRules.BER, "030100")]
+ [InlineData("Bad Length", PublicEncodingRules.BER, "02030102")]
+ [InlineData("Incorrect Zero Encoding", PublicEncodingRules.BER, "0200")]
+ [InlineData("Incorrect Zero Encoding", PublicEncodingRules.CER, "0200")]
+ [InlineData("Incorrect Zero Encoding", PublicEncodingRules.DER, "0200")]
+ [InlineData("Redundant Leading 0x00", PublicEncodingRules.BER, "0202007F")]
+ [InlineData("Redundant Leading 0x00", PublicEncodingRules.CER, "0202007F")]
+ [InlineData("Redundant Leading 0x00", PublicEncodingRules.DER, "0202007F")]
+ [InlineData("Redundant Leading 0xFF", PublicEncodingRules.BER, "0202FF80")]
+ [InlineData("Redundant Leading 0xFF", PublicEncodingRules.CER, "0202FF80")]
+ [InlineData("Redundant Leading 0xFF", PublicEncodingRules.DER, "0202FF80")]
+ public static void InvalidData(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.GetIntegerBytes());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "020100", 0)]
+ [InlineData(PublicEncodingRules.CER, "020100", 0)]
+ [InlineData(PublicEncodingRules.DER, "020100", 0)]
+ [InlineData(PublicEncodingRules.DER, "02017F", sbyte.MaxValue)]
+ [InlineData(PublicEncodingRules.DER, "020180", sbyte.MinValue)]
+ [InlineData(PublicEncodingRules.DER, "0201FF", -1)]
+ public static void ReadInt8_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ sbyte expectedValue)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadInt8(out sbyte value);
+
+ Assert.True(didRead, "reader.TryReadInt8");
+ Assert.Equal(expectedValue, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "02020102")]
+ [InlineData(PublicEncodingRules.CER, "02020102")]
+ [InlineData(PublicEncodingRules.DER, "02020102")]
+ public static void ReadInt8_TooMuchData(
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadInt8(out sbyte value);
+
+ Assert.False(didRead, "reader.TryReadInt8");
+ Assert.Equal(0, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "020100", 0)]
+ [InlineData(PublicEncodingRules.CER, "02017F", 0x7F)]
+ [InlineData(PublicEncodingRules.CER, "02020080", 0x80)]
+ [InlineData(PublicEncodingRules.CER, "020200FF", 0xFF)]
+ public static void ReadUInt8_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ byte expectedValue)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadUInt8(out byte value);
+
+ Assert.True(didRead, "reader.TryReadUInt8");
+ Assert.Equal(expectedValue, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "020180")]
+ [InlineData(PublicEncodingRules.CER, "020180")]
+ [InlineData(PublicEncodingRules.DER, "020180")]
+ [InlineData(PublicEncodingRules.BER, "0201FF")]
+ [InlineData(PublicEncodingRules.CER, "0201FF")]
+ [InlineData(PublicEncodingRules.DER, "0201FF")]
+ public static void ReadUInt8_Failure(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadUInt8(out byte value);
+
+ Assert.False(didRead, "reader.TryReadUInt8");
+ Assert.Equal((byte)0, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "020100", 0)]
+ [InlineData(PublicEncodingRules.CER, "020100", 0)]
+ [InlineData(PublicEncodingRules.DER, "020100", 0)]
+ [InlineData(PublicEncodingRules.DER, "0201FF", -1)]
+ [InlineData(PublicEncodingRules.CER, "0202FEFF", unchecked((short)0xFEFF))]
+ [InlineData(PublicEncodingRules.BER, "028102FEEF", unchecked((short)0xFEEF))]
+ [InlineData(PublicEncodingRules.BER, "0281028000", short.MinValue)]
+ [InlineData(PublicEncodingRules.CER, "02028000", short.MinValue)]
+ [InlineData(PublicEncodingRules.DER, "02027FFF", short.MaxValue)]
+ [InlineData(PublicEncodingRules.DER, "02026372", 0x6372)]
+ [InlineData(PublicEncodingRules.CER, "0202008A", 0x8A)]
+ [InlineData(PublicEncodingRules.CER, "02028ACE", unchecked((short)0x8ACE))]
+ public static void ReadInt16_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ short expectedValue)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadInt16(out short value);
+
+ Assert.True(didRead, "reader.TryReadInt16");
+ Assert.Equal(expectedValue, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "0203010203")]
+ [InlineData(PublicEncodingRules.CER, "0203010203")]
+ [InlineData(PublicEncodingRules.DER, "0203010203")]
+ public static void ReadInt16_TooMuchData(
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadInt16(out short value);
+
+ Assert.False(didRead, "reader.TryReadInt16");
+ Assert.Equal(0, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "020100", 0)]
+ [InlineData(PublicEncodingRules.CER, "02020080", 0x80)]
+ [InlineData(PublicEncodingRules.DER, "02027F80", 0x7F80)]
+ [InlineData(PublicEncodingRules.DER, "0203008180", 0x8180)]
+ public static void ReadUInt16_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ ushort expectedValue)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadUInt16(out ushort value);
+
+ Assert.True(didRead, "reader.TryReadUInt16");
+ Assert.Equal(expectedValue, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "020180")]
+ [InlineData(PublicEncodingRules.CER, "020180")]
+ [InlineData(PublicEncodingRules.DER, "020180")]
+ [InlineData(PublicEncodingRules.BER, "0201FF")]
+ [InlineData(PublicEncodingRules.CER, "0201FF")]
+ [InlineData(PublicEncodingRules.DER, "0201FF")]
+ [InlineData(PublicEncodingRules.DER, "02028000")]
+ public static void ReadUInt16_Failure(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadUInt16(out ushort value);
+
+ Assert.False(didRead, "reader.TryReadUInt16");
+ Assert.Equal((ushort)0, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "020100", 0)]
+ [InlineData(PublicEncodingRules.CER, "020100", 0)]
+ [InlineData(PublicEncodingRules.DER, "020100", 0)]
+ [InlineData(PublicEncodingRules.DER, "0201FF", -1)]
+ [InlineData(PublicEncodingRules.CER, "0202FEFF", unchecked((int)0xFFFF_FEFF))]
+ [InlineData(PublicEncodingRules.BER, "028102FEEF", unchecked((int)0xFFFF_FEEF))]
+ [InlineData(PublicEncodingRules.BER, "02810480000000", int.MinValue)]
+ [InlineData(PublicEncodingRules.CER, "020480000000", int.MinValue)]
+ [InlineData(PublicEncodingRules.DER, "02047FFFFFFF", int.MaxValue)]
+ [InlineData(PublicEncodingRules.DER, "02026372", 0x6372)]
+ [InlineData(PublicEncodingRules.CER, "0203008ACE", 0x8ACE)]
+ [InlineData(PublicEncodingRules.BER, "0203FACE01", unchecked((int)0xFFFA_CE01))]
+ [InlineData(PublicEncodingRules.BER, "02820003FACE01", unchecked((int)0xFFFA_CE01))]
+ public static void ReadInt32_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ int expectedValue)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadInt32(out int value);
+
+ Assert.True(didRead, "reader.TryReadInt32");
+ Assert.Equal(expectedValue, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "02050102030405")]
+ [InlineData(PublicEncodingRules.CER, "02050102030405")]
+ [InlineData(PublicEncodingRules.DER, "02050102030405")]
+ public static void ReadInt32_TooMuchData(
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadInt32(out int value);
+
+ Assert.False(didRead, "reader.TryReadInt32");
+ Assert.Equal(0, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "020100", 0)]
+ [InlineData(PublicEncodingRules.CER, "02020080", 0x80)]
+ [InlineData(PublicEncodingRules.DER, "02027F80", 0x7F80)]
+ [InlineData(PublicEncodingRules.DER, "0203008180", 0x8180)]
+ [InlineData(PublicEncodingRules.DER, "02030A8180", 0xA8180)]
+ [InlineData(PublicEncodingRules.DER, "020400828180", 0x828180)]
+ [InlineData(PublicEncodingRules.DER, "020475828180", 0x75828180)]
+ [InlineData(PublicEncodingRules.DER, "02050083828180", 0x83828180)]
+ [InlineData(PublicEncodingRules.BER, "02830000050083828180", 0x83828180)]
+ public static void ReadUInt32_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ uint expectedValue)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadUInt32(out uint value);
+
+ Assert.True(didRead, "reader.TryReadUInt32");
+ Assert.Equal(expectedValue, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "020180")]
+ [InlineData(PublicEncodingRules.CER, "020180")]
+ [InlineData(PublicEncodingRules.DER, "020180")]
+ [InlineData(PublicEncodingRules.BER, "0201FF")]
+ [InlineData(PublicEncodingRules.CER, "0201FF")]
+ [InlineData(PublicEncodingRules.DER, "0201FF")]
+ [InlineData(PublicEncodingRules.DER, "02028000")]
+ [InlineData(PublicEncodingRules.DER, "0203800000")]
+ [InlineData(PublicEncodingRules.DER, "020480000000")]
+ [InlineData(PublicEncodingRules.DER, "02050100000000")]
+ public static void ReadUInt32_Failure(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadUInt32(out uint value);
+
+ Assert.False(didRead, "reader.TryReadUInt32");
+ Assert.Equal((uint)0, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "020100", 0)]
+ [InlineData(PublicEncodingRules.CER, "02020080", 0x80)]
+ [InlineData(PublicEncodingRules.DER, "02027F80", 0x7F80)]
+ [InlineData(PublicEncodingRules.DER, "0203008180", 0x8180)]
+ [InlineData(PublicEncodingRules.DER, "02030A8180", 0xA8180)]
+ [InlineData(PublicEncodingRules.DER, "020400828180", 0x828180)]
+ [InlineData(PublicEncodingRules.DER, "020475828180", 0x75828180)]
+ [InlineData(PublicEncodingRules.DER, "02050083828180", 0x83828180)]
+ [InlineData(PublicEncodingRules.BER, "02830000050083828180", 0x83828180)]
+ [InlineData(PublicEncodingRules.DER, "02050183828180", 0x0183828180)]
+ [InlineData(PublicEncodingRules.DER, "0206018483828180", 0x018483828180)]
+ [InlineData(PublicEncodingRules.DER, "020701858483828180", 0x01858483828180)]
+ [InlineData(PublicEncodingRules.DER, "02080186858483828180", 0x0186858483828180)]
+ [InlineData(PublicEncodingRules.DER, "02087F86858483828180", 0x7F86858483828180)]
+ [InlineData(PublicEncodingRules.DER, "02087FFFFFFFFFFFFFFF", long.MaxValue)]
+ [InlineData(PublicEncodingRules.DER, "0201FF", -1)]
+ [InlineData(PublicEncodingRules.DER, "0201FE", -2)]
+ [InlineData(PublicEncodingRules.DER, "02028012", unchecked((long)0xFFFFFFFF_FFFF8012))]
+ [InlineData(PublicEncodingRules.DER, "0203818012", unchecked((long)0xFFFFFFFF_FF818012))]
+ [InlineData(PublicEncodingRules.DER, "020482818012", unchecked((long)0xFFFFFFFF_82818012))]
+ [InlineData(PublicEncodingRules.DER, "02058382818012", unchecked((long)0xFFFFFF83_82818012))]
+ [InlineData(PublicEncodingRules.DER, "0206848382818012", unchecked((long)0xFFFF8483_82818012))]
+ [InlineData(PublicEncodingRules.DER, "020785848382818012", unchecked((long)0xFF858483_82818012))]
+ [InlineData(PublicEncodingRules.DER, "02088685848382818012", unchecked((long)0x86858483_82818012))]
+ [InlineData(PublicEncodingRules.DER, "02088000000000000000", long.MinValue)]
+ [InlineData(PublicEncodingRules.BER, "028800000000000000088000000000000000", long.MinValue)]
+ public static void ReadInt64_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ long expectedValue)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadInt64(out long value);
+
+ Assert.True(didRead, "reader.TryReadInt64");
+ Assert.Equal(expectedValue, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "0209010203040506070809")]
+ [InlineData(PublicEncodingRules.CER, "0209010203040506070809")]
+ [InlineData(PublicEncodingRules.DER, "0209010203040506070809")]
+ public static void ReadInt64_TooMuchData(
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadInt64(out long value);
+
+ Assert.False(didRead, "reader.TryReadInt64");
+ Assert.Equal(0, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "020100", 0)]
+ [InlineData(PublicEncodingRules.CER, "02020080", 0x80)]
+ [InlineData(PublicEncodingRules.DER, "02027F80", 0x7F80)]
+ [InlineData(PublicEncodingRules.DER, "0203008180", 0x8180)]
+ [InlineData(PublicEncodingRules.DER, "02030A8180", 0xA8180)]
+ [InlineData(PublicEncodingRules.DER, "020400828180", 0x828180)]
+ [InlineData(PublicEncodingRules.DER, "020475828180", 0x75828180)]
+ [InlineData(PublicEncodingRules.DER, "02050083828180", 0x83828180)]
+ [InlineData(PublicEncodingRules.BER, "02830000050083828180", 0x83828180)]
+ [InlineData(PublicEncodingRules.DER, "02050183828180", 0x0183828180)]
+ [InlineData(PublicEncodingRules.DER, "0206018483828180", 0x018483828180)]
+ [InlineData(PublicEncodingRules.DER, "020701858483828180", 0x01858483828180)]
+ [InlineData(PublicEncodingRules.DER, "02080186858483828180", 0x0186858483828180)]
+ [InlineData(PublicEncodingRules.DER, "02087F86858483828180", 0x7F86858483828180)]
+ [InlineData(PublicEncodingRules.DER, "02087FFFFFFFFFFFFFFF", long.MaxValue)]
+ [InlineData(PublicEncodingRules.DER, "0209008000000000000000", 0x80000000_00000000)]
+ [InlineData(PublicEncodingRules.DER, "020900FFFFFFFFFFFFFFFF", ulong.MaxValue)]
+ public static void ReadUInt64_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ ulong expectedValue)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadUInt64(out ulong value);
+
+ Assert.True(didRead, "reader.TryReadUInt64");
+ Assert.Equal(expectedValue, value);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "020180")]
+ [InlineData(PublicEncodingRules.CER, "020180")]
+ [InlineData(PublicEncodingRules.DER, "020180")]
+ [InlineData(PublicEncodingRules.BER, "0201FF")]
+ [InlineData(PublicEncodingRules.CER, "0201FF")]
+ [InlineData(PublicEncodingRules.DER, "0201FF")]
+ [InlineData(PublicEncodingRules.DER, "02028000")]
+ [InlineData(PublicEncodingRules.DER, "0203800000")]
+ [InlineData(PublicEncodingRules.DER, "020480000000")]
+ [InlineData(PublicEncodingRules.DER, "02058000000000")]
+ [InlineData(PublicEncodingRules.DER, "0206800000000000")]
+ [InlineData(PublicEncodingRules.DER, "020780000000000000")]
+ [InlineData(PublicEncodingRules.DER, "02088000000000000000")]
+ [InlineData(PublicEncodingRules.DER, "0209010000000000000000")]
+ public static void ReadUInt64_Failure(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] data = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryReadUInt64(out ulong value);
+
+ Assert.False(didRead, "reader.TryReadUInt64");
+ Assert.Equal((uint)0, value);
+ }
+
+ [Fact]
+ public static void GetIntegerBytes()
+ {
+ const string Payload = "0102030405060708090A0B0C0D0E0F10";
+
+ // INTEGER (payload) followed by INTEGER (0)
+ byte[] data = ("0210" + Payload + "020100").HexToByteArray();
+ AsnReader reader = new AsnReader(data, AsnEncodingRules.DER);
+
+ ReadOnlyMemory<byte> contents = reader.GetIntegerBytes();
+ Assert.Equal(0x10, contents.Length);
+ Assert.Equal(Payload, contents.ByteArrayToHex());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 2, 1, 0x7E };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.GetIntegerBytes(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.GetIntegerBytes(new Asn1Tag(TagClass.ContextSpecific, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ ReadOnlyMemory<byte> value = reader.GetIntegerBytes();
+ Assert.Equal("7E", value.ByteArrayToHex());
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x87, 2, 0, 0x80 };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.GetIntegerBytes(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.GetIntegerBytes());
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(() => reader.GetIntegerBytes(new Asn1Tag(TagClass.Application, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(() => reader.GetIntegerBytes(new Asn1Tag(TagClass.ContextSpecific, 1)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ ReadOnlyMemory<byte> value = reader.GetIntegerBytes(new Asn1Tag(TagClass.ContextSpecific, 7));
+ Assert.Equal("0080", value.ByteArrayToHex());
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "0201FF", PublicTagClass.Universal, 2)]
+ [InlineData(PublicEncodingRules.CER, "0201FF", PublicTagClass.Universal, 2)]
+ [InlineData(PublicEncodingRules.DER, "0201FF", PublicTagClass.Universal, 2)]
+ [InlineData(PublicEncodingRules.BER, "8001FF", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.CER, "4C01FF", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "DF8A4601FF", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ ReadOnlyMemory<byte> val1 = reader.GetIntegerBytes(new Asn1Tag((TagClass)tagClass, tagValue, true));
+ Assert.False(reader.HasData);
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ ReadOnlyMemory<byte> val2 = reader.GetIntegerBytes(new Asn1Tag((TagClass)tagClass, tagValue, false));
+ Assert.False(reader.HasData);
+
+ Assert.Equal(val1.ByteArrayToHex(), val2.ByteArrayToHex());
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadLength.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadLength.cs
new file mode 100644
index 0000000000..5b8ebe9574
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadLength.cs
@@ -0,0 +1,162 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadLength : Asn1ReaderTests
+ {
+ [Theory]
+ [InlineData(4, 0, "0400")]
+ [InlineData(1, 1, "0101")]
+ [InlineData(4, 127, "047F")]
+ [InlineData(4, 128, "048180")]
+ [InlineData(4, 255, "0481FF")]
+ [InlineData(2, 256, "02820100")]
+ [InlineData(4, int.MaxValue, "04847FFFFFFF")]
+ public static void MinimalPrimitiveLength(int tagValue, int length, string inputHex)
+ {
+ byte[] inputBytes = inputHex.HexToByteArray();
+
+ foreach (PublicEncodingRules rules in Enum.GetValues(typeof(PublicEncodingRules)))
+ {
+ AsnReader reader = new AsnReader(inputBytes, (AsnEncodingRules)rules);
+
+ Asn1Tag tag = reader.ReadTagAndLength(out int ? parsedLength, out int bytesRead);
+
+ Assert.Equal(inputBytes.Length, bytesRead);
+ Assert.False(tag.IsConstructed, "tag.IsConstructed");
+ Assert.Equal(tagValue, tag.TagValue);
+ Assert.Equal(length, parsedLength.Value);
+
+ // ReadTagAndLength doesn't move the _data span forward.
+ Assert.True(reader.HasData, "reader.HasData");
+ }
+ }
+
+ [Theory]
+ [InlineData(-1)]
+ [InlineData(3)]
+ public static void ReadWithUnknownRuleSet(int invalidRuleSetValue)
+ {
+ byte[] data = { 0x05, 0x00 };
+
+ Assert.Throws<ArgumentOutOfRangeException>(
+ () => new AsnReader(data, (AsnEncodingRules)invalidRuleSetValue));
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData("05")]
+ [InlineData("0481")]
+ [InlineData("048201")]
+ [InlineData("04830102")]
+ [InlineData("0484010203")]
+ public static void ReadWithInsufficientData(string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, AsnEncodingRules.DER);
+
+ Assert.Throws<CryptographicException>(() => reader.ReadTagAndLength(out _, out _));
+ }
+
+ [Theory]
+ [InlineData("DER indefinite constructed", PublicEncodingRules.DER, "3080" + "0500" + "0000")]
+ [InlineData("0xFF-BER", PublicEncodingRules.BER, "04FF")]
+ [InlineData("0xFF-CER", PublicEncodingRules.CER, "04FF")]
+ [InlineData("0xFF-DER", PublicEncodingRules.DER, "04FF")]
+ [InlineData("CER definite constructed", PublicEncodingRules.CER, "30820500")]
+ [InlineData("BER indefinite primitive", PublicEncodingRules.BER, "0480" + "0000")]
+ [InlineData("CER indefinite primitive", PublicEncodingRules.CER, "0480" + "0000")]
+ [InlineData("DER indefinite primitive", PublicEncodingRules.DER, "0480" + "0000")]
+ [InlineData("DER non-minimal 0", PublicEncodingRules.DER, "048100")]
+ [InlineData("DER non-minimal 7F", PublicEncodingRules.DER, "04817F")]
+ [InlineData("DER non-minimal 80", PublicEncodingRules.DER, "04820080")]
+ [InlineData("CER non-minimal 0", PublicEncodingRules.CER, "048100")]
+ [InlineData("CER non-minimal 7F", PublicEncodingRules.CER, "04817F")]
+ [InlineData("CER non-minimal 80", PublicEncodingRules.CER, "04820080")]
+ [InlineData("BER too large", PublicEncodingRules.BER, "048480000000")]
+ [InlineData("CER too large", PublicEncodingRules.CER, "048480000000")]
+ [InlineData("DER too large", PublicEncodingRules.DER, "048480000000")]
+ [InlineData("BER padded too large", PublicEncodingRules.BER, "0486000080000000")]
+ [InlineData("BER uint.MaxValue", PublicEncodingRules.BER, "0484FFFFFFFF")]
+ [InlineData("CER uint.MaxValue", PublicEncodingRules.CER, "0484FFFFFFFF")]
+ [InlineData("DER uint.MaxValue", PublicEncodingRules.DER, "0484FFFFFFFF")]
+ [InlineData("BER padded uint.MaxValue", PublicEncodingRules.BER, "048800000000FFFFFFFF")]
+ [InlineData("BER 5 byte spread", PublicEncodingRules.BER, "04850100000000")]
+ [InlineData("CER 5 byte spread", PublicEncodingRules.CER, "04850100000000")]
+ [InlineData("DER 5 byte spread", PublicEncodingRules.DER, "04850100000000")]
+ [InlineData("BER padded 5 byte spread", PublicEncodingRules.BER, "0486000100000000")]
+ public static void InvalidLengths(
+ string description,
+ PublicEncodingRules rules,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)rules);
+
+ Assert.Throws<CryptographicException>(() => reader.ReadTagAndLength(out _, out _));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ public static void IndefiniteLength(PublicEncodingRules ruleSet)
+ {
+ // SEQUENCE (indefinite)
+ // NULL
+ // End-of-Contents
+ byte[] data = { 0x30, 0x80, 0x05, 0x00, 0x00, 0x00 };
+ AsnReader reader = new AsnReader(data, (AsnEncodingRules)ruleSet);
+
+ Asn1Tag tag = reader.ReadTagAndLength(out int? length, out int bytesRead);
+
+ Assert.Equal(2, bytesRead);
+ Assert.False(length.HasValue, "length.HasValue");
+ Assert.Equal((int)UniversalTagNumber.Sequence, tag.TagValue);
+ Assert.True(tag.IsConstructed, "tag.IsConstructed");
+ }
+
+ [Theory]
+ [InlineData(0, "0483000000")]
+ [InlineData(1, "048A00000000000000000001")]
+ [InlineData(128, "049000000000000000000000000000000080")]
+ public static void BerNonMinimalLength(int expectedLength, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, AsnEncodingRules.BER);
+
+ Asn1Tag tag = reader.ReadTagAndLength(out int? length, out int bytesRead);
+
+ Assert.Equal(inputData.Length, bytesRead);
+ Assert.Equal(expectedLength, length.Value);
+ // ReadTagAndLength doesn't move the _data span forward.
+ Assert.True(reader.HasData, "reader.HasData");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, 4, 0, 5, "0483000000" + "0500")]
+ [InlineData(PublicEncodingRules.DER, 1, 1, 2, "0101" + "FF")]
+ [InlineData(PublicEncodingRules.CER, 0x10, null, 2, "3080" + "0500" + "0000")]
+ public static void ReadWithDataRemaining(
+ PublicEncodingRules ruleSet,
+ int tagValue,
+ int? expectedLength,
+ int expectedBytesRead,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Asn1Tag tag = reader.ReadTagAndLength(out int? length, out int bytesRead);
+
+ Assert.Equal(expectedBytesRead, bytesRead);
+ Assert.Equal(tagValue, tag.TagValue);
+ Assert.Equal(expectedLength, length);
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadNamedBitList.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadNamedBitList.cs
new file mode 100644
index 0000000000..687831de4f
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadNamedBitList.cs
@@ -0,0 +1,315 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadNamedBitList : Asn1ReaderTests
+ {
+ [Flags]
+ public enum X509KeyUsageCSharpStyle
+ {
+ None = 0,
+ DigitalSignature = 1,
+ NonRepudiation = 1 << 1,
+ KeyEncipherment = 1 << 2,
+ DataEncipherment = 1 << 3,
+ KeyAgreement = 1 << 4,
+ KeyCertSign = 1 << 5,
+ CrlSign = 1 << 6,
+ EncipherOnly = 1 << 7,
+ DecipherOnly = 1 << 8,
+ }
+
+ [Flags]
+ public enum ULongFlags : ulong
+ {
+ None = 0,
+ Min = 1,
+ Mid = 1L << 32,
+ AlmostMax = 1L << 62,
+ Max = 1UL << 63,
+ }
+
+ [Flags]
+ public enum LongFlags : long
+ {
+ None = 0,
+ Mid = 1L << 32,
+ Max = 1L << 62,
+ Min = long.MinValue,
+ }
+
+ [Theory]
+ [InlineData(
+ PublicEncodingRules.BER,
+ typeof(X509KeyUsageCSharpStyle),
+ X509KeyUsageCSharpStyle.None,
+ "030100")]
+ [InlineData(
+ PublicEncodingRules.CER,
+ typeof(X509KeyUsageCSharpStyle),
+ X509KeyUsageCSharpStyle.DecipherOnly | X509KeyUsageCSharpStyle.KeyCertSign,
+ "0303070480")]
+ [InlineData(
+ PublicEncodingRules.DER,
+ typeof(X509KeyUsageCSharpStyle),
+ X509KeyUsageCSharpStyle.KeyAgreement,
+ "03020308")]
+ [InlineData(
+ PublicEncodingRules.BER,
+ typeof(LongFlags),
+ LongFlags.Mid | LongFlags.Max,
+ "0309010000000080000002")]
+ [InlineData(
+ PublicEncodingRules.CER,
+ typeof(LongFlags),
+ LongFlags.Mid | LongFlags.Min,
+ "0309000000000080000001")]
+ [InlineData(
+ PublicEncodingRules.DER,
+ typeof(LongFlags),
+ LongFlags.Min | LongFlags.Max,
+ "0309000000000000000003")]
+ // BER: Unused bits are unmapped, regardless of value.
+ [InlineData(
+ PublicEncodingRules.BER,
+ typeof(X509KeyUsageCSharpStyle),
+ X509KeyUsageCSharpStyle.DecipherOnly | X509KeyUsageCSharpStyle.KeyCertSign,
+ "030307048F")]
+ // BER: Trailing zeros are permitted.
+ [InlineData(
+ PublicEncodingRules.BER,
+ typeof(X509KeyUsageCSharpStyle),
+ X509KeyUsageCSharpStyle.DecipherOnly | X509KeyUsageCSharpStyle.KeyCertSign | X509KeyUsageCSharpStyle.DataEncipherment,
+ "03050014800000")]
+ // BER: Trailing 0-bits don't have to be declared "unused"
+ [InlineData(
+ PublicEncodingRules.BER,
+ typeof(X509KeyUsageCSharpStyle),
+ X509KeyUsageCSharpStyle.DecipherOnly | X509KeyUsageCSharpStyle.KeyCertSign | X509KeyUsageCSharpStyle.DataEncipherment,
+ "0303001480")]
+ public static void VerifyReadNamedBitListEncodings(
+ PublicEncodingRules ruleSet,
+ Type enumType,
+ long enumValue,
+ string inputHex)
+ {
+ byte[] inputBytes = inputHex.HexToByteArray();
+
+ AsnReader reader = new AsnReader(inputBytes, (AsnEncodingRules)ruleSet);
+ Enum readValue = reader.GetNamedBitListValue(enumType);
+
+ Assert.Equal(Enum.ToObject(enumType, enumValue), readValue);
+ }
+
+ [Theory]
+ [InlineData(
+ PublicEncodingRules.BER,
+ typeof(ULongFlags),
+ ULongFlags.Mid | ULongFlags.Max,
+ "0309000000000080000001")]
+ [InlineData(
+ PublicEncodingRules.CER,
+ typeof(ULongFlags),
+ ULongFlags.Min | ULongFlags.Mid,
+ "0306078000000080")]
+ [InlineData(
+ PublicEncodingRules.DER,
+ typeof(ULongFlags),
+ ULongFlags.Min | ULongFlags.Max,
+ "0309008000000000000001")]
+ public static void VerifyReadNamedBitListEncodings_ULong(
+ PublicEncodingRules ruleSet,
+ Type enumType,
+ ulong enumValue,
+ string inputHex)
+ {
+ byte[] inputBytes = inputHex.HexToByteArray();
+
+ AsnReader reader = new AsnReader(inputBytes, (AsnEncodingRules)ruleSet);
+ Enum readValue = reader.GetNamedBitListValue(enumType);
+
+ Assert.Equal(Enum.ToObject(enumType, enumValue), readValue);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void VerifyGenericReadNamedBitList(PublicEncodingRules ruleSet)
+ {
+ string inputHex = "0306078000000080" + "0309010000000080000002";
+ AsnReader reader = new AsnReader(inputHex.HexToByteArray(), (AsnEncodingRules)ruleSet);
+
+ ULongFlags uLongFlags = reader.GetNamedBitListValue<ULongFlags>();
+ LongFlags longFlags = reader.GetNamedBitListValue<LongFlags>();
+
+ Assert.False(reader.HasData);
+ Assert.Equal(ULongFlags.Mid | ULongFlags.Min, uLongFlags);
+ Assert.Equal(LongFlags.Mid | LongFlags.Max, longFlags);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void ReadNamedBitList_RequiresFlags(PublicEncodingRules ruleSet)
+ {
+ string inputHex = "030100";
+ AsnReader reader = new AsnReader(inputHex.HexToByteArray(), (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tFlagsEnum",
+ () => reader.GetNamedBitListValue<AsnEncodingRules>());
+
+ Assert.True(reader.HasData, "reader.HasData");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void ReadNamedBitList_DataOutOfRange(PublicEncodingRules ruleSet)
+ {
+ string inputHex = "0309000000000100000001";
+
+ AsnReader reader = new AsnReader(inputHex.HexToByteArray(), (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetNamedBitListValue<X509KeyUsageCSharpStyle>());
+
+ Assert.True(reader.HasData, "reader.HasData");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void ReadNamedBitList_ExcessiveBytes(PublicEncodingRules ruleSet)
+ {
+ string inputHex = "03050014800000";
+
+ AsnReader reader = new AsnReader(inputHex.HexToByteArray(), (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetNamedBitListValue<X509KeyUsageCSharpStyle>());
+
+ Assert.True(reader.HasData, "reader.HasData");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void ReadNamedBitList_ExcessiveBits(PublicEncodingRules ruleSet)
+ {
+ string inputHex = "0303061480";
+
+ AsnReader reader = new AsnReader(inputHex.HexToByteArray(), (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetNamedBitListValue<X509KeyUsageCSharpStyle>());
+
+ Assert.True(reader.HasData, "reader.HasData");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 3, 2, 1, 2 };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.GetNamedBitListValue<X509KeyUsageCSharpStyle>(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetNamedBitListValue<X509KeyUsageCSharpStyle>(new Asn1Tag(TagClass.ContextSpecific, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ Assert.Equal(
+ X509KeyUsageCSharpStyle.CrlSign,
+ reader.GetNamedBitListValue<X509KeyUsageCSharpStyle>());
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x87, 2, 2, 4 };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.GetNamedBitListValue<X509KeyUsageCSharpStyle>(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetNamedBitListValue<X509KeyUsageCSharpStyle>());
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetNamedBitListValue<X509KeyUsageCSharpStyle>(new Asn1Tag(TagClass.Application, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetNamedBitListValue<X509KeyUsageCSharpStyle>(new Asn1Tag(TagClass.ContextSpecific, 1)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ Assert.Equal(
+ X509KeyUsageCSharpStyle.KeyCertSign,
+ reader.GetNamedBitListValue<X509KeyUsageCSharpStyle>(new Asn1Tag(TagClass.ContextSpecific, 7)));
+
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "0303070080", PublicTagClass.Universal, 3)]
+ [InlineData(PublicEncodingRules.CER, "0303070080", PublicTagClass.Universal, 3)]
+ [InlineData(PublicEncodingRules.DER, "0303070080", PublicTagClass.Universal, 3)]
+ [InlineData(PublicEncodingRules.BER, "8003070080", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.CER, "4C03070080", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "DF8A4603070080", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Equal(
+ X509KeyUsageCSharpStyle.DecipherOnly,
+ reader.GetNamedBitListValue<X509KeyUsageCSharpStyle>(
+ new Asn1Tag((TagClass)tagClass, tagValue, true)));
+
+ Assert.False(reader.HasData);
+
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Equal(
+ X509KeyUsageCSharpStyle.DecipherOnly,
+ reader.GetNamedBitListValue<X509KeyUsageCSharpStyle>(
+ new Asn1Tag((TagClass)tagClass, tagValue, false)));
+
+ Assert.False(reader.HasData);
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadNull.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadNull.cs
new file mode 100644
index 0000000000..570f3dabd3
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadNull.cs
@@ -0,0 +1,130 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadNull : Asn1ReaderTests
+ {
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "0500")]
+ [InlineData(PublicEncodingRules.CER, "0500")]
+ [InlineData(PublicEncodingRules.DER, "0500")]
+ [InlineData(PublicEncodingRules.BER, "0583000000")]
+ public static void ReadNull_Success(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ reader.ReadNull();
+ Assert.False(reader.HasData, "reader.HasData");
+ }
+
+ [Theory]
+ [InlineData("Long length", PublicEncodingRules.CER, "0583000000")]
+ [InlineData("Long length", PublicEncodingRules.DER, "0583000000")]
+ [InlineData("Constructed definite length", PublicEncodingRules.BER, "2500")]
+ [InlineData("Constructed definite length", PublicEncodingRules.DER, "2500")]
+ [InlineData("Constructed indefinite length", PublicEncodingRules.BER, "25800000")]
+ [InlineData("Constructed indefinite length", PublicEncodingRules.CER, "25800000")]
+ [InlineData("No length", PublicEncodingRules.BER, "05")]
+ [InlineData("No length", PublicEncodingRules.CER, "05")]
+ [InlineData("No length", PublicEncodingRules.DER, "05")]
+ [InlineData("No data", PublicEncodingRules.BER, "")]
+ [InlineData("No data", PublicEncodingRules.CER, "")]
+ [InlineData("No data", PublicEncodingRules.DER, "")]
+ [InlineData("NonEmpty", PublicEncodingRules.BER, "050100")]
+ [InlineData("NonEmpty", PublicEncodingRules.CER, "050100")]
+ [InlineData("NonEmpty", PublicEncodingRules.DER, "050100")]
+ [InlineData("Incomplete length", PublicEncodingRules.BER, "0581")]
+ public static void ReadNull_Throws(string description, PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.ReadNull());
+ }
+
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 5, 0 };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.ReadNull(new Asn1Tag(UniversalTagNumber.Integer)));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.ReadNull(new Asn1Tag(TagClass.ContextSpecific, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ reader.ReadNull();
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x87, 0 };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.ReadNull(new Asn1Tag(UniversalTagNumber.Integer)));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.ReadNull());
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(() => reader.ReadNull(new Asn1Tag(TagClass.Application, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(() => reader.ReadNull(new Asn1Tag(TagClass.ContextSpecific, 1)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ reader.ReadNull(new Asn1Tag(TagClass.ContextSpecific, 7));
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "0500", PublicTagClass.Universal, 5)]
+ [InlineData(PublicEncodingRules.CER, "0500", PublicTagClass.Universal, 5)]
+ [InlineData(PublicEncodingRules.DER, "0500", PublicTagClass.Universal, 5)]
+ [InlineData(PublicEncodingRules.BER, "8000", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.CER, "4C00", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "DF8A4600", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ reader.ReadNull(new Asn1Tag((TagClass)tagClass, tagValue, true));
+ Assert.False(reader.HasData);
+
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ reader.ReadNull(new Asn1Tag((TagClass)tagClass, tagValue, false));
+ Assert.False(reader.HasData);
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadObjectIdentifier.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadObjectIdentifier.cs
new file mode 100644
index 0000000000..92322468e7
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadObjectIdentifier.cs
@@ -0,0 +1,286 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using System.Text;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadObjectIdentifier : Asn1ReaderTests
+ {
+ [Theory]
+ [InlineData("Wrong tag", PublicEncodingRules.BER, "010100")]
+ [InlineData("Wrong tag", PublicEncodingRules.CER, "010100")]
+ [InlineData("Wrong tag", PublicEncodingRules.DER, "010100")]
+ [InlineData("Overreaching length", PublicEncodingRules.BER, "0608883703")]
+ [InlineData("Overreaching length", PublicEncodingRules.CER, "0608883703")]
+ [InlineData("Overreaching length", PublicEncodingRules.DER, "0608883703")]
+ [InlineData("Zero length", PublicEncodingRules.BER, "0600")]
+ [InlineData("Zero length", PublicEncodingRules.CER, "0600")]
+ [InlineData("Zero length", PublicEncodingRules.DER, "0600")]
+ [InlineData("Constructed Definite Form", PublicEncodingRules.BER, "2605" + "0603883703")]
+ [InlineData("Constructed Indefinite Form", PublicEncodingRules.BER, "2680" + "0603883703" + "0000")]
+ [InlineData("Constructed Indefinite Form", PublicEncodingRules.CER, "2680" + "0603883703" + "0000")]
+ [InlineData("Unresolved carry-bit (first sub-identifier)", PublicEncodingRules.BER, "060188")]
+ [InlineData("Unresolved carry-bit (first sub-identifier)", PublicEncodingRules.CER, "060188")]
+ [InlineData("Unresolved carry-bit (first sub-identifier)", PublicEncodingRules.DER, "060188")]
+ [InlineData("Unresolved carry-bit (later sub-identifier)", PublicEncodingRules.BER, "0603883781")]
+ [InlineData("Unresolved carry-bit (later sub-identifier)", PublicEncodingRules.CER, "0603883781")]
+ [InlineData("Unresolved carry-bit (later sub-identifier)", PublicEncodingRules.DER, "0603883781")]
+ [InlineData("Sub-Identifier with leading 0x80", PublicEncodingRules.BER, "060488378001")]
+ [InlineData("Sub-Identifier with leading 0x80", PublicEncodingRules.CER, "060488378001")]
+ [InlineData("Sub-Identifier with leading 0x80", PublicEncodingRules.DER, "060488378001")]
+ public static void ReadObjectIdentifier_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.ReadObjectIdentifier(true));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "0603883703", "2.999.3")]
+ [InlineData(PublicEncodingRules.CER, "06028837", "2.999")]
+ [InlineData(PublicEncodingRules.DER, "06068837C27B0302", "2.999.8571.3.2")]
+ [InlineData(PublicEncodingRules.BER, "0603550406", "2.5.4.6")]
+ [InlineData(PublicEncodingRules.CER, "06092A864886F70D010105", "1.2.840.113549.1.1.5")]
+ [InlineData(PublicEncodingRules.DER, "060100", "0.0")]
+ [InlineData(PublicEncodingRules.BER, "06080992268993F22C63", "0.9.2342.19200300.99")]
+ [InlineData(
+ PublicEncodingRules.DER,
+ "0616824F83F09DA7EBCFDEE0C7A1A7B2C0948CC8F9D77603",
+ // Using the rules of ITU-T-REC-X.667-201210 for 2.25.{UUID} unregistered arcs, and
+ // their sample value of f81d4fae-7dec-11d0-a765-00a0c91e6bf6
+ // this is
+ // { joint-iso-itu-t(2) uuid(255) thatuuid(329800735698586629295641978511506172918) three(3) }
+ "2.255.329800735698586629295641978511506172918.3")]
+ public static void ReadObjectIdentifierAsString_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ string expectedValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ string oidValue = reader.ReadObjectIdentifierAsString();
+ Assert.Equal(expectedValue, oidValue);
+ }
+
+ [Theory]
+ // Start at a UUID as a big integer. 128 semantic bits takes 19
+ // content bytes to write down. Walk it backwards to 1.
+ // This uses the OID from the last case of ReadObjectIdentifierAsString_Success, but
+ // without the "255" arc (therefore the initial second arc is the UUID decimal value - 80)
+ [InlineData("061383F09DA7EBCFDEE0C7A1A7B2C0948CC8F9D776", "2.329800735698586629295641978511506172838")]
+ // Drop the last byte, clear the high bit in the last remaining byte, secondArc = (secondArc + 80) >> 7 - 80.
+ [InlineData("061283F09DA7EBCFDEE0C7A1A7B2C0948CC8F957", "2.2576568247645208041372202957121141895")]
+ [InlineData("061183F09DA7EBCFDEE0C7A1A7B2C0948CC879", "2.20129439434728187823220335602508841")]
+ [InlineData("061083F09DA7EBCFDEE0C7A1A7B2C0948C48", "2.157261245583813967368908871894520")]
+ [InlineData("060F83F09DA7EBCFDEE0C7A1A7B2C0940C", "2.1228603481123546620069600561596")]
+ [InlineData("060E83F09DA7EBCFDEE0C7A1A7B2C014", "2.9598464696277707969293754308")]
+ [InlineData("060D83F09DA7EBCFDEE0C7A1A7B240", "2.74988005439669593510107376")]
+ [InlineData("060C83F09DA7EBCFDEE0C7A1A732", "2.585843792497418699297634")]
+ [InlineData("060B83F09DA7EBCFDEE0C7A127", "2.4576904628886083588183")]
+ [InlineData("060A83F09DA7EBCFDEE0C721", "2.35757067413172527953")]
+ [InlineData("060983F09DA7EBCFDEE047", "2.279352089165410295")]
+ [InlineData("060883F09DA7EBCFDE60", "2.2182438196604688")]
+ [InlineData("060783F09DA7EBCF5E", "2.17050298410894")]
+ [InlineData("060683F09DA7EB4F", "2.133205456255")]
+ [InlineData("060583F09DA76B", "2.1040667547")]
+ [InlineData("060483F09D27", "2.8130135")]
+ [InlineData("060383F01D", "2.63437")]
+ [InlineData("06028370", "2.416")]
+ [InlineData("060103", "0.3")]
+ public static void VerifyMultiByteParsing(string inputHex, string expectedValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, AsnEncodingRules.DER);
+
+ string oidValue = reader.ReadObjectIdentifierAsString();
+ Assert.Equal(expectedValue, oidValue);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "06082A864886F70D0307", false, "3des")]
+ [InlineData(PublicEncodingRules.CER, "06082A864886F70D0307", true, "1.2.840.113549.3.7")]
+ [InlineData(PublicEncodingRules.DER, "0609608648016503040201", true, "2.16.840.1.101.3.4.2.1")]
+ [InlineData(PublicEncodingRules.BER, "0609608648016503040201", false, "sha256")]
+ public static void ReadObjectIdentifier_SkipFriendlyName(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ bool skipFriendlyName,
+ string expectedFriendlyName)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Oid oid = reader.ReadObjectIdentifier(skipFriendlyName);
+ Assert.Equal(expectedFriendlyName, oid.FriendlyName);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "06028837".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.GetIntegerBytes(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadObjectIdentifierAsString(new Asn1Tag(TagClass.ContextSpecific, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ Assert.Equal("2.999", reader.ReadObjectIdentifierAsString());
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "87028837".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.GetIntegerBytes(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.ReadObjectIdentifierAsString());
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadObjectIdentifierAsString(new Asn1Tag(TagClass.Application, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadObjectIdentifierAsString(new Asn1Tag(TagClass.ContextSpecific, 1)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ Assert.Equal(
+ "2.999",
+ reader.ReadObjectIdentifierAsString(new Asn1Tag(TagClass.ContextSpecific, 7)));
+
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "06028837", PublicTagClass.Universal, 6)]
+ [InlineData(PublicEncodingRules.CER, "06028837", PublicTagClass.Universal, 6)]
+ [InlineData(PublicEncodingRules.DER, "06028837", PublicTagClass.Universal, 6)]
+ [InlineData(PublicEncodingRules.BER, "80028837", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.CER, "4C028837", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "DF8A46028837", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ string val1 = reader.ReadObjectIdentifierAsString(new Asn1Tag((TagClass)tagClass, tagValue, true));
+
+ Assert.False(reader.HasData);
+
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ string val2 = reader.ReadObjectIdentifierAsString(new Asn1Tag((TagClass)tagClass, tagValue, false));
+
+ Assert.False(reader.HasData);
+
+ Assert.Equal(val1, val2);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void ReadVeryLongOid(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = new byte[100000];
+ // 06 83 02 00 00 (OBJECT IDENTIFIER, 65536 bytes).
+ inputData[0] = 0x06;
+ inputData[1] = 0x83;
+ inputData[2] = 0x01;
+ inputData[3] = 0x00;
+ inputData[4] = 0x00;
+ // and the rest are all zero.
+
+ // The first byte produces "0.0". Each of the remaining 65535 bytes produce
+ // another ".0".
+ const int ExpectedLength = 65536 * 2 + 1;
+ StringBuilder builder = new StringBuilder(ExpectedLength);
+ builder.Append('0');
+
+ for (int i = 0; i <= ushort.MaxValue; i++)
+ {
+ builder.Append('.');
+ builder.Append(0);
+ }
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ string oidString = reader.ReadObjectIdentifierAsString();
+
+ Assert.Equal(ExpectedLength, oidString.Length);
+ Assert.Equal(builder.ToString(), oidString);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void ReadVeryLongOidArc(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = new byte[255];
+ // 06 81 93 (OBJECT IDENTIFIER, 147 bytes).
+ inputData[0] = 0x06;
+ inputData[1] = 0x81;
+ inputData[2] = 0x93;
+
+ // With 147 bytes we get 147*7 = 1029 value bits.
+ // The smallest legal number to encode would have a top byte of 0x81,
+ // leaving 1022 bits remaining. If they're all zero then we have 2^1022.
+ //
+ // Since it's our first sub-identifier it's really encoding "2.(2^1022 - 80)".
+ inputData[3] = 0x81;
+ // Leave the last byte as 0.
+ new Span<byte>(inputData, 4, 145).Fill(0x80);
+
+ const string ExpectedOid =
+ "2." +
+ "449423283715578976932326297697256183404494244735576643183575" +
+ "202894331689513752407831771193306018840052800284699678483394" +
+ "146974422036041556232118576598685310944419733562163713190755" +
+ "549003115235298632707380212514422095376705856157203684782776" +
+ "352068092908376276711465745599868114846199290762088390824060" +
+ "56034224";
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ string oidString = reader.ReadObjectIdentifierAsString();
+ Assert.Equal(ExpectedOid, oidString);
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadOctetString.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadOctetString.cs
new file mode 100644
index 0000000000..4167418b43
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadOctetString.cs
@@ -0,0 +1,559 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadOctetString : Asn1ReaderTests
+ {
+ [Theory]
+ [InlineData("Constructed Payload", PublicEncodingRules.BER, "2402040100")]
+ [InlineData("Constructed Payload-Indefinite", PublicEncodingRules.BER, "248004010000")]
+ // This value is actually invalid CER, but it returns false since it's not primitive and
+ // it isn't worth preempting the descent to find out it was invalid.
+ [InlineData("Constructed Payload-Indefinite", PublicEncodingRules.CER, "248004010000")]
+ public static void TryGetOctetStringBytes_Fails(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryGetPrimitiveOctetStringBytes(out ReadOnlyMemory<byte> contents);
+
+ Assert.False(didRead, "reader.TryGetOctetStringBytes");
+ Assert.Equal(0, contents.Length);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, 0, "0400")]
+ [InlineData(PublicEncodingRules.BER, 1, "040100")]
+ [InlineData(PublicEncodingRules.BER, 2, "040201FE")]
+ [InlineData(PublicEncodingRules.CER, 5, "040502FEEFF00C")]
+ [InlineData(PublicEncodingRules.DER, 2, "04020780")]
+ [InlineData(PublicEncodingRules.DER, 5, "040500FEEFF00D" + "0500")]
+ public static void TryGetOctetStringBytes_Success(
+ PublicEncodingRules ruleSet,
+ int expectedLength,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryGetPrimitiveOctetStringBytes(out ReadOnlyMemory<byte> contents);
+
+ Assert.True(didRead, "reader.TryGetOctetStringBytes");
+ Assert.Equal(expectedLength, contents.Length);
+ }
+
+ [Theory]
+ [InlineData("Wrong Tag", PublicEncodingRules.BER, "0500")]
+ [InlineData("Wrong Tag", PublicEncodingRules.CER, "0500")]
+ [InlineData("Wrong Tag", PublicEncodingRules.DER, "0500")]
+ [InlineData("Bad Length", PublicEncodingRules.BER, "040200")]
+ [InlineData("Bad Length", PublicEncodingRules.CER, "040200")]
+ [InlineData("Bad Length", PublicEncodingRules.DER, "040200")]
+ [InlineData("Constructed Form", PublicEncodingRules.DER, "2403040100")]
+ public static void TryGetOctetStringBytes_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetPrimitiveOctetStringBytes(out ReadOnlyMemory<byte> contents));
+ }
+
+ [Fact]
+ public static void TryGetOctetStringBytes_Throws_CER_TooLong()
+ {
+ // CER says that the maximum encoding length for an OctetString primitive
+ // is 1000.
+ //
+ // So we need 04 [1001] { 1001 0x00s }
+ // 1001 => 0x3E9, so the length encoding is 82 03 E9.
+ // 1001 + 3 + 1 == 1005
+ byte[] input = new byte[1005];
+ input[0] = 0x04;
+ input[1] = 0x82;
+ input[2] = 0x03;
+ input[3] = 0xE9;
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetPrimitiveOctetStringBytes(out ReadOnlyMemory<byte> contents));
+ }
+
+ [Fact]
+ public static void TryGetOctetStringBytes_Success_CER_MaxLength()
+ {
+ // CER says that the maximum encoding length for an OctetString primitive
+ // is 1000.
+ //
+ // So we need 04 [1000] { 1000 anythings }
+ // 1000 => 0x3E8, so the length encoding is 82 03 E8.
+ // 1000 + 3 + 1 == 1004
+ byte[] input = new byte[1004];
+ input[0] = 0x04;
+ input[1] = 0x82;
+ input[2] = 0x03;
+ input[3] = 0xE8;
+
+ // Contents
+ input[4] = 0x02;
+ input[5] = 0xA0;
+ input[1002] = 0xA5;
+ input[1003] = 0xFC;
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+
+ bool success = reader.TryGetPrimitiveOctetStringBytes(out ReadOnlyMemory<byte> contents);
+
+ Assert.True(success, "reader.TryGetOctetStringBytes");
+ Assert.Equal(1000, contents.Length);
+
+ // Check that it is, in fact, the same memory. No copies with this API.
+ Assert.True(
+ Unsafe.AreSame(
+ ref contents.Span.DangerousGetPinnableReference(),
+ ref input[4]));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "04020780")]
+ [InlineData(PublicEncodingRules.BER, "040207FF")]
+ [InlineData(PublicEncodingRules.CER, "04020780")]
+ [InlineData(PublicEncodingRules.DER, "04020780")]
+ [InlineData(
+ PublicEncodingRules.BER,
+ "2480" +
+ "2480" +
+ "0000" +
+ "04020000" +
+ "0000")]
+ public static void TryCopyOctetStringBytes_Fails(PublicEncodingRules ruleSet, string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryCopyOctetStringBytes(
+ Span<byte>.Empty,
+ out int bytesWritten);
+
+ Assert.False(didRead, "reader.TryCopyOctetStringBytes");
+ Assert.Equal(0, bytesWritten);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "04020780", "0780")]
+ [InlineData(PublicEncodingRules.BER, "040207FF", "07FF")]
+ [InlineData(PublicEncodingRules.CER, "04020780", "0780")]
+ [InlineData(PublicEncodingRules.DER, "04020680", "0680")]
+ [InlineData(PublicEncodingRules.BER, "24800000", "")]
+ [InlineData(PublicEncodingRules.BER, "2400", "")]
+ [InlineData(PublicEncodingRules.BER, "2400" + "0500", "")]
+ [InlineData(
+ PublicEncodingRules.BER,
+ "2480" +
+ "2480" +
+ "0000" +
+ "04020005" +
+ "0000",
+ "0005")]
+ [InlineData(
+ PublicEncodingRules.BER,
+ "2480" +
+ "2406" +
+ "0401FA" +
+ "0401CE" +
+ "2480" +
+ "2480" +
+ "2480" +
+ "0402F00D" +
+ "0000" +
+ "0000" +
+ "04020001" +
+ "0000" +
+ "0403000203" +
+ "040203FF" +
+ "2480" +
+ "0000" +
+ "0000",
+ "FACEF00D000100020303FF")]
+ public static void TryCopyOctetStringBytes_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ string expectedHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ byte[] output = new byte[expectedHex.Length / 2];
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ bool didRead = reader.TryCopyOctetStringBytes(
+ output,
+ out int bytesWritten);
+
+ Assert.True(didRead, "reader.TryCopyOctetStringBytes");
+ Assert.Equal(expectedHex, output.AsReadOnlySpan().Slice(0, bytesWritten).ByteArrayToHex());
+ }
+
+ private static void TryCopyOctetStringBytes_Throws(
+ PublicEncodingRules ruleSet,
+ byte[] input)
+ {
+ Assert.Throws<CryptographicException>(
+ () =>
+ {
+ AsnReader reader = new AsnReader(input, (AsnEncodingRules)ruleSet);
+ reader.TryCopyOctetStringBytes(
+ Span<byte>.Empty,
+ out int bytesWritten);
+ });
+ }
+
+ [Theory]
+ [InlineData("Wrong Tag", PublicEncodingRules.BER, "0500")]
+ [InlineData("Wrong Tag", PublicEncodingRules.CER, "0500")]
+ [InlineData("Wrong Tag", PublicEncodingRules.DER, "0500")]
+ [InlineData("Bad Length", PublicEncodingRules.BER, "040200")]
+ [InlineData("Bad Length", PublicEncodingRules.CER, "040200")]
+ [InlineData("Bad Length", PublicEncodingRules.DER, "040200")]
+ [InlineData("Constructed Form", PublicEncodingRules.DER, "2403040100")]
+ [InlineData("Nested context-specific", PublicEncodingRules.BER, "2404800400FACE")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.BER, "2480800400FACE0000")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.CER, "2480800400FACE0000")]
+ [InlineData("Nested boolean", PublicEncodingRules.BER, "2403010100")]
+ [InlineData("Nested boolean (indef)", PublicEncodingRules.BER, "24800101000000")]
+ [InlineData("Nested boolean (indef)", PublicEncodingRules.CER, "24800101000000")]
+ [InlineData("Nested constructed form", PublicEncodingRules.CER, "2480" + "2480" + "04010" + "000000000")]
+ [InlineData("No terminator", PublicEncodingRules.BER, "2480" + "04020000" + "")]
+ [InlineData("No terminator", PublicEncodingRules.CER, "2480" + "04020000" + "")]
+ [InlineData("No content", PublicEncodingRules.BER, "2480")]
+ [InlineData("No content", PublicEncodingRules.CER, "2480")]
+ [InlineData("No nested content", PublicEncodingRules.CER, "24800000")]
+ [InlineData("Nested value too long", PublicEncodingRules.BER, "2480040A00")]
+ [InlineData("Nested value too long - constructed", PublicEncodingRules.BER, "2480240A00")]
+ [InlineData("Nested value too long - simple", PublicEncodingRules.BER, "2403" + "04050000000000")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.BER, "248020000000")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.CER, "248020000000")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.BER, "2480000100")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.CER, "2480000100")]
+ [InlineData("LongLength EndOfContents", PublicEncodingRules.BER, "2480008100")]
+ [InlineData("Constructed Payload-TooShort", PublicEncodingRules.CER, "24800401000000")]
+ public static void TryCopyOctetStringBytes_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ TryCopyOctetStringBytes_Throws(ruleSet, inputData);
+ }
+
+ [Fact]
+ public static void TryCopyOctetStringBytes_Throws_CER_NestedTooLong()
+ {
+ // CER says that the maximum encoding length for an OctetString primitive
+ // is 1000.
+ //
+ // This test checks it for a primitive contained within a constructed.
+ //
+ // So we need 04 [1001] { 1001 0x00s }
+ // 1001 => 0x3E9, so the length encoding is 82 03 E9.
+ // 1001 + 3 + 1 == 1005
+ //
+ // Plus a leading 24 80 (indefinite length constructed)
+ // and a trailing 00 00 (End of contents)
+ // == 1009
+ byte[] input = new byte[1009];
+ // CONSTRUCTED OCTET STRING (indefinite)
+ input[0] = 0x24;
+ input[1] = 0x80;
+ // OCTET STRING (1001)
+ input[2] = 0x04;
+ input[3] = 0x82;
+ input[4] = 0x03;
+ input[5] = 0xE9;
+ // EOC implicit since the byte[] initializes to zeros
+
+ TryCopyOctetStringBytes_Throws(PublicEncodingRules.CER, input);
+ }
+
+ [Fact]
+ public static void TryCopyOctetStringBytes_Throws_CER_NestedTooShortIntermediate()
+ {
+ // CER says that the maximum encoding length for an OctetString primitive
+ // is 1000, and in the constructed form the lengths must be
+ // [ 1000, 1000, 1000, ..., len%1000 ]
+ //
+ // So 1000, 2, 2 is illegal.
+ //
+ // 24 80 (indefinite constructed octet string)
+ // 04 82 03 08 (octet string, 1000 bytes)
+ // [1000 content bytes]
+ // 04 02 (octet string, 2 bytes)
+ // [2 content bytes]
+ // 04 02 (octet string, 2 bytes)
+ // [2 content bytes]
+ // 00 00 (end of contents)
+ // Looks like 1,016 bytes.
+ byte[] input = new byte[1016];
+ // CONSTRUCTED OCTET STRING (indefinite)
+ input[0] = 0x23;
+ input[1] = 0x80;
+ // OCTET STRING (1000)
+ input[2] = 0x04;
+ input[3] = 0x82;
+ input[4] = 0x03;
+ input[5] = 0xE8;
+ // OCTET STRING (2)
+ input[1006] = 0x04;
+ input[1007] = 0x02;
+ // OCTET STRING (2)
+ input[1010] = 0x04;
+ input[1011] = 0x02;
+ // EOC implicit since the byte[] initializes to zeros
+
+ TryCopyOctetStringBytes_Throws(PublicEncodingRules.CER, input);
+ }
+
+ [Fact]
+ public static void TryCopyOctetStringBytes_Success_CER_MaxPrimitiveLength()
+ {
+ // CER says that the maximum encoding length for an OctetString primitive
+ // is 1000.
+ //
+ // So we need 04 [1000] { 1000 anythings }
+ // 1000 => 0x3E8, so the length encoding is 82 03 E8.
+ // 1000 + 3 + 1 == 1004
+ byte[] input = new byte[1004];
+ input[0] = 0x04;
+ input[1] = 0x82;
+ input[2] = 0x03;
+ input[3] = 0xE8;
+
+ // Content
+ input[4] = 0x02;
+ input[5] = 0xA0;
+ input[1002] = 0xA5;
+ input[1003] = 0xFC;
+
+ byte[] output = new byte[1000];
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+
+ bool success = reader.TryCopyOctetStringBytes(
+ output,
+ out int bytesWritten);
+
+ Assert.True(success, "reader.TryCopyOctetStringBytes");
+ Assert.Equal(1000, bytesWritten);
+
+ Assert.Equal(
+ input.AsReadOnlySpan().Slice(4).ByteArrayToHex(),
+ output.ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void TryCopyOctetStringBytes_Success_CER_MinConstructedLength()
+ {
+ // CER says that the maximum encoding length for an OctetString primitive
+ // is 1000, and that a constructed form must be used for values greater
+ // than 1000 bytes, with segments dividing up for each thousand
+ // [1000, 1000, ..., len%1000].
+ //
+ // So our smallest constructed form is 1001 bytes, [1000, 1]
+ //
+ // 24 80 (indefinite constructed octet string)
+ // 04 82 03 E9 (primitive octet string, 1000 bytes)
+ // [1000 content bytes]
+ // 04 01 (primitive octet string, 1 byte)
+ // pp
+ // 00 00 (end of contents, 0 bytes)
+ // 1011 total.
+ byte[] input = new byte[1011];
+ int offset = 0;
+ // CONSTRUCTED OCTET STRING (Indefinite)
+ input[offset++] = 0x24;
+ input[offset++] = 0x80;
+ // OCTET STRING (1000)
+ input[offset++] = 0x04;
+ input[offset++] = 0x82;
+ input[offset++] = 0x03;
+ input[offset++] = 0xE8;
+
+ // Primitive 1: (55 A0 :: A5 FC) (1000)
+ input[offset++] = 0x55;
+ input[offset] = 0xA0;
+ offset += 997;
+ input[offset++] = 0xA5;
+ input[offset++] = 0xFC;
+
+ // OCTET STRING (1)
+ input[offset++] = 0x04;
+ input[offset++] = 0x01;
+
+ // Primitive 2: One more byte
+ input[offset] = 0xF7;
+
+ byte[] expected = new byte[1001];
+ offset = 0;
+ expected[offset++] = 0x55;
+ expected[offset] = 0xA0;
+ offset += 997;
+ expected[offset++] = 0xA5;
+ expected[offset++] = 0xFC;
+ expected[offset] = 0xF7;
+
+ byte[] output = new byte[1001];
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+
+ bool success = reader.TryCopyOctetStringBytes(
+ output,
+ out int bytesWritten);
+
+ Assert.True(success, "reader.TryCopyOctetStringBytes");
+ Assert.Equal(1001, bytesWritten);
+
+ Assert.Equal(
+ expected.ByteArrayToHex(),
+ output.ByteArrayToHex());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 4, 1, 0x7E };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.TryGetPrimitiveOctetStringBytes(Asn1Tag.Null, out _));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetPrimitiveOctetStringBytes(new Asn1Tag(TagClass.ContextSpecific, 0), out _));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ Assert.True(reader.TryGetPrimitiveOctetStringBytes(out ReadOnlyMemory<byte> value));
+ Assert.Equal("7E", value.ByteArrayToHex());
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x87, 2, 0, 0x80 };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.TryGetPrimitiveOctetStringBytes(Asn1Tag.Null, out _));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.TryGetPrimitiveOctetStringBytes(out _));
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetPrimitiveOctetStringBytes(new Asn1Tag(TagClass.Application, 0), out _));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetPrimitiveOctetStringBytes(new Asn1Tag(TagClass.ContextSpecific, 1), out _));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ Assert.True(
+ reader.TryGetPrimitiveOctetStringBytes(
+ new Asn1Tag(TagClass.ContextSpecific, 7),
+ out ReadOnlyMemory<byte> value));
+
+ Assert.Equal("0080", value.ByteArrayToHex());
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "0401FF", PublicTagClass.Universal, 4)]
+ [InlineData(PublicEncodingRules.CER, "0401FF", PublicTagClass.Universal, 4)]
+ [InlineData(PublicEncodingRules.DER, "0401FF", PublicTagClass.Universal, 4)]
+ [InlineData(PublicEncodingRules.BER, "8001FF", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.CER, "4C01FF", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "DF8A4601FF", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.True(
+ reader.TryGetPrimitiveOctetStringBytes(
+ new Asn1Tag((TagClass)tagClass, tagValue, true),
+ out ReadOnlyMemory<byte> val1));
+
+ Assert.False(reader.HasData);
+
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.True(
+ reader.TryGetPrimitiveOctetStringBytes(
+ new Asn1Tag((TagClass)tagClass, tagValue, false),
+ out ReadOnlyMemory<byte> val2));
+
+ Assert.False(reader.HasData);
+
+ Assert.Equal(val1.ByteArrayToHex(), val2.ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void TryCopyOctetStringBytes_ExtremelyNested()
+ {
+ byte[] dataBytes = new byte[4 * 16384];
+
+ // This will build 2^14 nested indefinite length values.
+ // In the end, none of them contain any content.
+ //
+ // For what it's worth, the initial algorithm succeeded at 1061, and StackOverflowed with 1062.
+ int end = dataBytes.Length / 2;
+
+ // UNIVERSAL OCTET STRING [Constructed]
+ const byte Tag = 0x20 | (byte)UniversalTagNumber.OctetString;
+
+ for (int i = 0; i < end; i += 2)
+ {
+ dataBytes[i] = Tag;
+ // Indefinite length
+ dataBytes[i + 1] = 0x80;
+ }
+
+ AsnReader reader = new AsnReader(dataBytes, AsnEncodingRules.BER);
+
+ int bytesWritten;
+
+ Assert.True(reader.TryCopyOctetStringBytes(Span<byte>.Empty, out bytesWritten));
+ Assert.Equal(0, bytesWritten);
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadSequence.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadSequence.cs
new file mode 100644
index 0000000000..2c22fc89e9
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadSequence.cs
@@ -0,0 +1,338 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadSequence : Asn1ReaderTests
+ {
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "3000", false, -1)]
+ [InlineData(PublicEncodingRules.BER, "30800000", false, -1)]
+ [InlineData(PublicEncodingRules.BER, "3083000000", false, -1)]
+ [InlineData(PublicEncodingRules.CER, "30800000", false, -1)]
+ [InlineData(PublicEncodingRules.DER, "3000", false, -1)]
+ [InlineData(PublicEncodingRules.BER, "3000" + "0500", true, -1)]
+ [InlineData(PublicEncodingRules.BER, "3002" + "0500", false, 5)]
+ [InlineData(PublicEncodingRules.CER, "3080" + "0500" + "0000", false, 5)]
+ [InlineData(PublicEncodingRules.CER, "3080" + "010100" + "0000" + "0500", true, 1)]
+ [InlineData(PublicEncodingRules.DER, "3005" + "0500" + "0101FF", false, 5)]
+ public static void ReadSequence_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ bool expectDataRemaining,
+ int expectedSequenceTagNumber)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ AsnReader sequence = reader.ReadSequence();
+
+ if (expectDataRemaining)
+ {
+ Assert.True(reader.HasData, "reader.HasData");
+ }
+ else
+ {
+ Assert.False(reader.HasData, "reader.HasData");
+ }
+
+ if (expectedSequenceTagNumber < 0)
+ {
+ Assert.False(sequence.HasData, "sequence.HasData");
+ }
+ else
+ {
+ Assert.True(sequence.HasData, "sequence.HasData");
+
+ Asn1Tag firstTag = sequence.PeekTag();
+ Assert.Equal(expectedSequenceTagNumber, firstTag.TagValue);
+ }
+ }
+
+ [Theory]
+ [InlineData("Empty", PublicEncodingRules.BER, "")]
+ [InlineData("Empty", PublicEncodingRules.CER, "")]
+ [InlineData("Empty", PublicEncodingRules.DER, "")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.BER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.CER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.DER, "1F")]
+ [InlineData("Missing Length", PublicEncodingRules.BER, "30")]
+ [InlineData("Missing Length", PublicEncodingRules.CER, "30")]
+ [InlineData("Missing Length", PublicEncodingRules.DER, "30")]
+ [InlineData("Primitive Encoding", PublicEncodingRules.BER, "1000")]
+ [InlineData("Primitive Encoding", PublicEncodingRules.CER, "1000")]
+ [InlineData("Primitive Encoding", PublicEncodingRules.DER, "1000")]
+ [InlineData("Definite Length Encoding", PublicEncodingRules.CER, "3000")]
+ [InlineData("Indefinite Length Encoding", PublicEncodingRules.DER, "3080" + "0000")]
+ [InlineData("Missing Content", PublicEncodingRules.BER, "3001")]
+ [InlineData("Missing Content", PublicEncodingRules.DER, "3001")]
+ [InlineData("Length Out Of Bounds", PublicEncodingRules.BER, "3005" + "010100")]
+ [InlineData("Length Out Of Bounds", PublicEncodingRules.DER, "3005" + "010100")]
+ [InlineData("Missing Content - Indefinite", PublicEncodingRules.BER, "3080")]
+ [InlineData("Missing Content - Indefinite", PublicEncodingRules.CER, "3080")]
+ [InlineData("Missing EoC", PublicEncodingRules.BER, "3080" + "010100")]
+ [InlineData("Missing EoC", PublicEncodingRules.CER, "3080" + "010100")]
+ [InlineData("Missing Outer EoC", PublicEncodingRules.BER, "3080" + "010100" + ("3080" + "0000"))]
+ [InlineData("Missing Outer EoC", PublicEncodingRules.CER, "3080" + "010100" + ("3080" + "0000"))]
+ [InlineData("Wrong Tag - Definite", PublicEncodingRules.BER, "3100")]
+ [InlineData("Wrong Tag - Definite", PublicEncodingRules.DER, "3100")]
+ [InlineData("Wrong Tag - Indefinite", PublicEncodingRules.BER, "3180" + "0000")]
+ [InlineData("Wrong Tag - Indefinite", PublicEncodingRules.CER, "3180" + "0000")]
+ public static void ReadSequence_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.ReadSequence());
+ }
+
+ private static void ReadEcPublicKey(AsnEncodingRules ruleSet, byte[] inputData)
+ {
+ AsnReader mainReader = new AsnReader(inputData, ruleSet);
+
+ AsnReader spkiReader = mainReader.ReadSequence();
+ Assert.False(mainReader.HasData, "mainReader.HasData after reading SPKI");
+
+ AsnReader algorithmReader = spkiReader.ReadSequence();
+ Assert.True(spkiReader.HasData, "spkiReader.HasData after reading algorithm");
+
+ ReadOnlyMemory<byte> publicKeyValue;
+ int unusedBitCount;
+
+ if (!spkiReader.TryGetPrimitiveBitStringValue(out unusedBitCount, out publicKeyValue))
+ {
+ // The correct answer is 65 bytes.
+ for (int i = 10; ; i *= 2)
+ {
+ byte[] buf = new byte[i];
+
+ if (spkiReader.TryCopyBitStringBytes(buf, out unusedBitCount, out int bytesWritten))
+ {
+ publicKeyValue = new ReadOnlyMemory<byte>(buf, 0, bytesWritten);
+ break;
+ }
+ }
+ }
+
+ Assert.False(spkiReader.HasData, "spkiReader.HasData after reading subjectPublicKey");
+ Assert.True(algorithmReader.HasData, "algorithmReader.HasData before reading");
+
+ Oid algorithmOid = algorithmReader.ReadObjectIdentifier(true);
+ Assert.True(algorithmReader.HasData, "algorithmReader.HasData after reading first OID");
+
+ Assert.Equal("1.2.840.10045.2.1", algorithmOid.Value);
+
+ Oid curveOid = algorithmReader.ReadObjectIdentifier(true);
+ Assert.False(algorithmReader.HasData, "algorithmReader.HasData after reading second OID");
+
+ Assert.Equal("1.2.840.10045.3.1.7", curveOid.Value);
+
+ const string PublicKeyValue =
+ "04" +
+ "2363DD131DA65E899A2E63E9E05E50C830D4994662FFE883DB2B9A767DCCABA2" +
+ "F07081B5711BE1DEE90DFC8DE17970C2D937A16CD34581F52B8D59C9E9532D13";
+
+ Assert.Equal(PublicKeyValue, publicKeyValue.ByteArrayToHex());
+ Assert.Equal(0, unusedBitCount);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void ReadEcPublicKey_DefiniteLength(PublicEncodingRules ruleSet)
+ {
+ const string InputHex =
+ "3059" +
+ "3013" +
+ "06072A8648CE3D0201" +
+ "06082A8648CE3D030107" +
+ "0342" +
+ "00" +
+ "04" +
+ "2363DD131DA65E899A2E63E9E05E50C830D4994662FFE883DB2B9A767DCCABA2" +
+ "F07081B5711BE1DEE90DFC8DE17970C2D937A16CD34581F52B8D59C9E9532D13";
+
+ byte[] inputData = InputHex.HexToByteArray();
+ ReadEcPublicKey((AsnEncodingRules)ruleSet, inputData);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ public static void ReadEcPublicKey_IndefiniteLength(PublicEncodingRules ruleSet)
+ {
+ const string InputHex =
+ "3080" +
+ "3080" +
+ "06072A8648CE3D0201" +
+ "06082A8648CE3D030107" +
+ "0000" +
+ "0342" +
+ "00" +
+ "04" +
+ "2363DD131DA65E899A2E63E9E05E50C830D4994662FFE883DB2B9A767DCCABA2" +
+ "F07081B5711BE1DEE90DFC8DE17970C2D937A16CD34581F52B8D59C9E9532D13" +
+ "0000";
+
+ byte[] inputData = InputHex.HexToByteArray();
+ ReadEcPublicKey((AsnEncodingRules)ruleSet, inputData);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal_Definite(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "30020500".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.ReadSequence(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ AsnReader seq = reader.ReadSequence();
+ Assert.Equal("0500", seq.GetEncodedValue().ByteArrayToHex());
+
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ public static void TagMustBeCorrect_Universal_Indefinite(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "308005000000".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.ReadSequence(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ AsnReader seq = reader.ReadSequence();
+ Assert.Equal("0500", seq.GetEncodedValue().ByteArrayToHex());
+
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom_Definite(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "A5020500".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.ReadSequence(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.ReadSequence());
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadSequence(new Asn1Tag(TagClass.Application, 5)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 7)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ AsnReader seq = reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 5));
+ Assert.Equal("0500", seq.GetEncodedValue().ByteArrayToHex());
+
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ public static void TagMustBeCorrect_Custom_Indefinite(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "A58005000000".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.ReadSequence(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.ReadSequence());
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadSequence(new Asn1Tag(TagClass.Application, 5)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 7)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ AsnReader seq = reader.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 5));
+ Assert.Equal("0500", seq.GetEncodedValue().ByteArrayToHex());
+
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "30030101FF", PublicTagClass.Universal, 16)]
+ [InlineData(PublicEncodingRules.BER, "30800101000000", PublicTagClass.Universal, 16)]
+ [InlineData(PublicEncodingRules.CER, "30800101000000", PublicTagClass.Universal, 16)]
+ [InlineData(PublicEncodingRules.DER, "30030101FF", PublicTagClass.Universal, 16)]
+ [InlineData(PublicEncodingRules.BER, "A0030101FF", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.BER, "A1800101000000", PublicTagClass.ContextSpecific, 1)]
+ [InlineData(PublicEncodingRules.CER, "6C800101000000", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "FF8A46030101FF", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AsnReader val1 = reader.ReadSequence(new Asn1Tag((TagClass)tagClass, tagValue, true));
+
+ Assert.False(reader.HasData);
+
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AsnReader val2 = reader.ReadSequence(new Asn1Tag((TagClass)tagClass, tagValue, false));
+
+ Assert.False(reader.HasData);
+
+ Assert.Equal(val1.GetEncodedValue().ByteArrayToHex(), val2.GetEncodedValue().ByteArrayToHex());
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadSetOf.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadSetOf.cs
new file mode 100644
index 0000000000..369b203492
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadSetOf.cs
@@ -0,0 +1,304 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadSetOf : Asn1ReaderTests
+ {
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "3100", false, -1)]
+ [InlineData(PublicEncodingRules.BER, "31800000", false, -1)]
+ [InlineData(PublicEncodingRules.BER, "3183000000", false, -1)]
+ [InlineData(PublicEncodingRules.CER, "31800000", false, -1)]
+ [InlineData(PublicEncodingRules.DER, "3100", false, -1)]
+ [InlineData(PublicEncodingRules.BER, "3100" + "0500", true, -1)]
+ [InlineData(PublicEncodingRules.BER, "3102" + "0500", false, 5)]
+ [InlineData(PublicEncodingRules.CER, "3180" + "0500" + "0000", false, 5)]
+ [InlineData(PublicEncodingRules.CER, "3180" + "010100" + "0000" + "0500", true, 1)]
+ [InlineData(PublicEncodingRules.CER, "3180" + "010100" + "0101FF" + "0500" + "0000", false, 1)]
+ [InlineData(PublicEncodingRules.DER, "3105" + "0101FF" + "0500", false, 1)]
+ public static void ReadSetOf_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ bool expectDataRemaining,
+ int expectedSequenceTagNumber)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ AsnReader sequence = reader.ReadSetOf();
+
+ if (expectDataRemaining)
+ {
+ Assert.True(reader.HasData, "reader.HasData");
+ }
+ else
+ {
+ Assert.False(reader.HasData, "reader.HasData");
+ }
+
+ if (expectedSequenceTagNumber < 0)
+ {
+ Assert.False(sequence.HasData, "sequence.HasData");
+ }
+ else
+ {
+ Assert.True(sequence.HasData, "sequence.HasData");
+
+ Asn1Tag firstTag = sequence.PeekTag();
+ Assert.Equal(expectedSequenceTagNumber, firstTag.TagValue);
+ }
+ }
+
+ [Theory]
+ [InlineData("Empty", PublicEncodingRules.BER, "")]
+ [InlineData("Empty", PublicEncodingRules.CER, "")]
+ [InlineData("Empty", PublicEncodingRules.DER, "")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.BER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.CER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.DER, "1F")]
+ [InlineData("Missing Length", PublicEncodingRules.BER, "31")]
+ [InlineData("Missing Length", PublicEncodingRules.CER, "31")]
+ [InlineData("Missing Length", PublicEncodingRules.DER, "31")]
+ [InlineData("Primitive Encoding", PublicEncodingRules.BER, "1100")]
+ [InlineData("Primitive Encoding", PublicEncodingRules.CER, "1100")]
+ [InlineData("Primitive Encoding", PublicEncodingRules.DER, "1100")]
+ [InlineData("Definite Length Encoding", PublicEncodingRules.CER, "3100")]
+ [InlineData("Indefinite Length Encoding", PublicEncodingRules.DER, "3180" + "0000")]
+ [InlineData("Missing Content", PublicEncodingRules.BER, "3101")]
+ [InlineData("Missing Content", PublicEncodingRules.DER, "3101")]
+ [InlineData("Length Out Of Bounds", PublicEncodingRules.BER, "3105" + "010100")]
+ [InlineData("Length Out Of Bounds", PublicEncodingRules.DER, "3105" + "010100")]
+ [InlineData("Missing Content - Indefinite", PublicEncodingRules.BER, "3180")]
+ [InlineData("Missing Content - Indefinite", PublicEncodingRules.CER, "3180")]
+ [InlineData("Missing EoC", PublicEncodingRules.BER, "3180" + "010100")]
+ [InlineData("Missing EoC", PublicEncodingRules.CER, "3180" + "010100")]
+ [InlineData("Missing Outer EoC", PublicEncodingRules.BER, "3180" + "010100" + ("3180" + "0000"))]
+ [InlineData("Missing Outer EoC", PublicEncodingRules.CER, "3180" + "010100" + ("3180" + "0000"))]
+ [InlineData("Wrong Tag - Definite", PublicEncodingRules.BER, "3000")]
+ [InlineData("Wrong Tag - Definite", PublicEncodingRules.DER, "3000")]
+ [InlineData("Wrong Tag - Indefinite", PublicEncodingRules.BER, "3080" + "0000")]
+ [InlineData("Wrong Tag - Indefinite", PublicEncodingRules.CER, "3080" + "0000")]
+ public static void ReadSetOf_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.ReadSetOf());
+ }
+
+ [Theory]
+ // BER can read out of order (indefinite)
+ [InlineData(PublicEncodingRules.BER, "3180" + "0101FF" + "010100" + "0000", true, 1)]
+ // BER can read out of order (definite)
+ [InlineData(PublicEncodingRules.BER, "3106" + "0101FF" + "010100", true, 1)]
+ // CER will not read out of order
+ [InlineData(PublicEncodingRules.CER, "3180" + "0500" + "010100" + "0000", false, 1)]
+ [InlineData(PublicEncodingRules.CER, "3180" + "0101FF" + "010100" + "0000", false, 1)]
+ // CER is happy in order:
+ [InlineData(PublicEncodingRules.CER, "3180" + "010100" + "0500" + "0000", true, 5)]
+ [InlineData(PublicEncodingRules.CER, "3180" + "010100" + "0101FF" + "0500" + "0000", true, 5)]
+ [InlineData(PublicEncodingRules.CER, "3180" + "010100" + "010100" + "0500" + "0000", true, 5)]
+ // DER will not read out of order
+ [InlineData(PublicEncodingRules.DER, "3106" + "0101FF" + "010100", false, 1)]
+ [InlineData(PublicEncodingRules.DER, "3105" + "0500" + "010100", false, 1)]
+ // DER is happy in order:
+ [InlineData(PublicEncodingRules.DER, "3105" + "010100" + "0500", true, 5)]
+ [InlineData(PublicEncodingRules.DER, "3108" + "010100" + "0101FF" + "0500", true, 5)]
+ [InlineData(PublicEncodingRules.DER, "3108" + "010100" + "010100" + "0500", true, 5)]
+ public static void ReadSetOf_DataSorting(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ bool expectSuccess,
+ int lastTagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ AsnReader setOf;
+
+ if (expectSuccess)
+ {
+ setOf = reader.ReadSetOf();
+ }
+ else
+ {
+ AsnReader alsoReader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => alsoReader.ReadSetOf());
+
+ setOf = reader.ReadSetOf(skipSortOrderValidation: true);
+ }
+
+ int lastTag = -1;
+
+ while (setOf.HasData)
+ {
+ Asn1Tag tag = setOf.PeekTag();
+ lastTag = tag.TagValue;
+
+ // Ignore the return, just drain it.
+ setOf.GetEncodedValue();
+ }
+
+ Assert.Equal(lastTagValue, lastTag);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal_Definite(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "31020500".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.ReadSetOf(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadSetOf(new Asn1Tag(TagClass.ContextSpecific, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ AsnReader seq = reader.ReadSetOf();
+ Assert.Equal("0500", seq.GetEncodedValue().ByteArrayToHex());
+
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ public static void TagMustBeCorrect_Universal_Indefinite(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "318005000000".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.ReadSetOf(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadSetOf(new Asn1Tag(TagClass.ContextSpecific, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ AsnReader seq = reader.ReadSetOf();
+ Assert.Equal("0500", seq.GetEncodedValue().ByteArrayToHex());
+
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom_Definite(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "A5020500".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.ReadSetOf(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.ReadSetOf());
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadSetOf(new Asn1Tag(TagClass.Application, 5)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadSetOf(new Asn1Tag(TagClass.ContextSpecific, 7)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ AsnReader seq = reader.ReadSetOf(new Asn1Tag(TagClass.ContextSpecific, 5));
+ Assert.Equal("0500", seq.GetEncodedValue().ByteArrayToHex());
+
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ public static void TagMustBeCorrect_Custom_Indefinite(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "A58005000000".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.ReadSetOf(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.ReadSetOf());
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadSetOf(new Asn1Tag(TagClass.Application, 5)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.ReadSetOf(new Asn1Tag(TagClass.ContextSpecific, 7)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ AsnReader seq = reader.ReadSetOf(new Asn1Tag(TagClass.ContextSpecific, 5));
+ Assert.Equal("0500", seq.GetEncodedValue().ByteArrayToHex());
+
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "31030101FF", PublicTagClass.Universal, 17)]
+ [InlineData(PublicEncodingRules.BER, "31800101000000", PublicTagClass.Universal, 17)]
+ [InlineData(PublicEncodingRules.CER, "31800101000000", PublicTagClass.Universal, 17)]
+ [InlineData(PublicEncodingRules.DER, "31030101FF", PublicTagClass.Universal, 17)]
+ [InlineData(PublicEncodingRules.BER, "A0030101FF", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.BER, "A1800101000000", PublicTagClass.ContextSpecific, 1)]
+ [InlineData(PublicEncodingRules.CER, "6C800101000000", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "FF8A46030101FF", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AsnReader val1 = reader.ReadSetOf(new Asn1Tag((TagClass)tagClass, tagValue, true));
+
+ Assert.False(reader.HasData);
+
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AsnReader val2 = reader.ReadSetOf(new Asn1Tag((TagClass)tagClass, tagValue, false));
+
+ Assert.False(reader.HasData);
+
+ Assert.Equal(val1.GetEncodedValue().ByteArrayToHex(), val2.GetEncodedValue().ByteArrayToHex());
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadUTF8String.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadUTF8String.cs
new file mode 100644
index 0000000000..8397bfc654
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadUTF8String.cs
@@ -0,0 +1,701 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadUTF8String : Asn1ReaderTests
+ {
+ public static IEnumerable<object[]> ValidEncodingData { get; } =
+ new object[][]
+ {
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "0C0D4A6F686E20512E20536D697468",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.CER,
+ "0C0D4A6F686E20512E20536D697468",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.DER,
+ "0C0D4A6F686E20512E20536D697468",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "2C80" + "040D4A6F686E20512E20536D697468" + "0000",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "2C0F" + "040D4A6F686E20512E20536D697468",
+ "John Q. Smith",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "0C00",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.CER,
+ "0C00",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.DER,
+ "0C00",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "2C00",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "2C80" + "0000",
+ "",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "2C80" +
+ "2480" +
+ // "Dr."
+ "040344722E" +
+ // " & "
+ "0403202620" +
+ // "Mrs."
+ "04044D72732E" +
+ "0000" +
+ // " "
+ "040120" +
+ "2480" +
+ "240C" +
+ // "Smith"
+ "0405536D697468" +
+ // hyphen (U+2010)
+ "0403E28090" +
+ "0000" +
+ // "Jones"
+ "04054A6F6E6573" +
+ "2480" +
+ // " "
+ "040120" +
+ "2480" +
+ // The next three bytes are U+FE60, small ampersand
+ // Don't know why any system would break this one character
+ // into three primitives, but it should work.
+ "0401EF" +
+ "0401B9" +
+ "0401A0" +
+ "0000" +
+ // " "
+ "040120" +
+ // "children"
+ "04086368696C6472656E" +
+ "0000" +
+ "0000",
+ "Dr. & Mrs. Smith\u2010Jones \uFE60 children",
+ },
+ };
+
+ [Theory]
+ [MemberData(nameof(ValidEncodingData))]
+ public static void GetUTF8String_Success(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ string expectedValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ string value = reader.GetCharacterString(UniversalTagNumber.UTF8String);
+
+ Assert.Equal(expectedValue, value);
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidEncodingData))]
+ public static void TryCopyUTF8String(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ string expectedValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ char[] output = new char[expectedValue.Length];
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ bool copied;
+ int charsWritten;
+
+ if (output.Length > 0)
+ {
+ output[0] = 'a';
+
+ copied = reader.TryCopyCharacterString(
+ UniversalTagNumber.UTF8String,
+ output.AsSpan().Slice(0, expectedValue.Length - 1),
+ out charsWritten);
+
+ Assert.False(copied, "reader.TryCopyUTF8String - too short");
+ Assert.Equal(0, charsWritten);
+ Assert.Equal('a', output[0]);
+ }
+
+ copied = reader.TryCopyCharacterString(
+ UniversalTagNumber.UTF8String,
+ output,
+ out charsWritten);
+
+ Assert.True(copied, "reader.TryCopyUTF8String");
+
+ string actualValue = new string(output, 0, charsWritten);
+ Assert.Equal(expectedValue, actualValue);
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidEncodingData))]
+ public static void TryCopyUTF8StringBytes(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ string expectedString)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ string expectedHex = Text.Encoding.UTF8.GetBytes(expectedString).ByteArrayToHex();
+ byte[] output = new byte[expectedHex.Length / 2];
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ bool copied;
+ int bytesWritten;
+
+ if (output.Length > 0)
+ {
+ output[0] = 32;
+
+ copied = reader.TryCopyCharacterStringBytes(
+ UniversalTagNumber.UTF8String,
+ output.AsSpan().Slice(0, output.Length - 1),
+ out bytesWritten);
+
+ Assert.False(copied, "reader.TryCopyUTF8StringBytes - too short");
+ Assert.Equal(0, bytesWritten);
+ Assert.Equal(32, output[0]);
+ }
+
+ copied = reader.TryCopyCharacterStringBytes(
+ UniversalTagNumber.UTF8String,
+ output,
+ out bytesWritten);
+
+ Assert.True(copied, "reader.TryCopyUTF8StringBytes");
+
+ Assert.Equal(
+ expectedHex,
+ new ReadOnlySpan<byte>(output, 0, bytesWritten).ByteArrayToHex());
+
+ Assert.Equal(output.Length, bytesWritten);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "0C0120", true)]
+ [InlineData(PublicEncodingRules.BER, "2C80" + "040120" + "0000", false)]
+ [InlineData(PublicEncodingRules.BER, "2C03" + "040120", false)]
+ public static void TryGetUTF8StringBytes(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ bool expectSuccess)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ bool got = reader.TryGetPrimitiveCharacterStringBytes(
+ UniversalTagNumber.UTF8String,
+ out ReadOnlyMemory<byte> contents);
+
+ if (expectSuccess)
+ {
+ Assert.True(got, "reader.TryGetUTF8StringBytes");
+
+ Assert.True(
+ Unsafe.AreSame(
+ ref contents.Span.DangerousGetPinnableReference(),
+ ref inputData[2]));
+ }
+ else
+ {
+ Assert.False(got, "reader.TryGetUTF8StringBytes");
+ Assert.True(contents.IsEmpty, "contents.IsEmpty");
+ }
+ }
+
+ [Theory]
+ [InlineData("Incomplete Tag", PublicEncodingRules.BER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.CER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.DER, "1F")]
+ [InlineData("Missing Length", PublicEncodingRules.BER, "0C")]
+ [InlineData("Missing Length", PublicEncodingRules.CER, "0C")]
+ [InlineData("Missing Length", PublicEncodingRules.DER, "0C")]
+ [InlineData("Missing Contents", PublicEncodingRules.BER, "0C01")]
+ [InlineData("Missing Contents", PublicEncodingRules.CER, "0C01")]
+ [InlineData("Missing Contents", PublicEncodingRules.DER, "0C01")]
+ [InlineData("Length Too Long", PublicEncodingRules.BER, "0C034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.CER, "0C034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.DER, "0C034869")]
+ [InlineData("Constructed Form", PublicEncodingRules.DER, "2C03040149")]
+ public static void TryGetUTF8StringBytes_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetPrimitiveCharacterStringBytes(UniversalTagNumber.UTF8String, out _));
+ }
+
+ [Theory]
+ [InlineData("Empty", PublicEncodingRules.BER, "")]
+ [InlineData("Empty", PublicEncodingRules.CER, "")]
+ [InlineData("Empty", PublicEncodingRules.DER, "")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.BER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.CER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.DER, "1F")]
+ [InlineData("Missing Length", PublicEncodingRules.BER, "0C")]
+ [InlineData("Missing Length", PublicEncodingRules.CER, "0C")]
+ [InlineData("Missing Length", PublicEncodingRules.DER, "0C")]
+ [InlineData("Missing Contents", PublicEncodingRules.BER, "0C01")]
+ [InlineData("Missing Contents", PublicEncodingRules.CER, "0C01")]
+ [InlineData("Missing Contents", PublicEncodingRules.DER, "0C01")]
+ [InlineData("Missing Contents - Constructed", PublicEncodingRules.BER, "2C01")]
+ [InlineData("Missing Contents - Constructed Indef", PublicEncodingRules.BER, "2C80")]
+ [InlineData("Missing Contents - Constructed Indef", PublicEncodingRules.CER, "2C80")]
+ [InlineData("Length Too Long", PublicEncodingRules.BER, "0C034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.CER, "0C034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.DER, "0C034869")]
+ [InlineData("Definite Constructed Form", PublicEncodingRules.CER, "2C03040149")]
+ [InlineData("Definite Constructed Form", PublicEncodingRules.DER, "2C03040149")]
+ [InlineData("Indefinite Constructed Form - Short Payload", PublicEncodingRules.CER, "2C800401490000")]
+ [InlineData("Indefinite Constructed Form", PublicEncodingRules.DER, "2C800401490000")]
+ [InlineData("No nested content", PublicEncodingRules.CER, "2C800000")]
+ [InlineData("No EoC", PublicEncodingRules.BER, "2C80" + "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.BER, "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.CER, "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.DER, "04024869")]
+ [InlineData("Wrong Tag - Constructed", PublicEncodingRules.BER, "240404024869")]
+ [InlineData("Wrong Tag - Constructed Indef", PublicEncodingRules.BER, "2480" + "04024869" + "0000")]
+ [InlineData("Wrong Tag - Constructed Indef", PublicEncodingRules.CER, "2480" + "04024869" + "0000")]
+ [InlineData("Wrong Tag - Constructed", PublicEncodingRules.DER, "240404024869")]
+ [InlineData("Nested Bad Tag", PublicEncodingRules.BER, "2C04" + "0C024869")]
+ [InlineData("Nested context-specific", PublicEncodingRules.BER, "2C04800400FACE")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.BER, "2C80800400FACE0000")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.CER, "2C80800400FACE0000")]
+ [InlineData("Nested Length Too Long", PublicEncodingRules.BER, "2C07" + ("2402" + "0403") + "040149")]
+ [InlineData("Nested Simple Length Too Long", PublicEncodingRules.BER, "2C03" + "040548656C6C6F")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.BER, "2C8020000000")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.CER, "2C8020000000")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.BER, "2C80000100")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.CER, "2C80000100")]
+ [InlineData("LongLength EndOfContents", PublicEncodingRules.BER, "2C80008100")]
+ public static void TryCopyUTF8StringBytes_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ byte[] outputData = new byte[inputData.Length + 1];
+ outputData[0] = 252;
+
+ int bytesWritten = -1;
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryCopyCharacterStringBytes(UniversalTagNumber.UTF8String, outputData, out bytesWritten));
+
+ Assert.Equal(-1, bytesWritten);
+ Assert.Equal(252, outputData[0]);
+ }
+
+ private static void TryCopyUTF8String_Throws(PublicEncodingRules ruleSet, byte[] inputData)
+ {
+ char[] outputData = new char[inputData.Length + 1];
+ outputData[0] = 'a';
+
+ int bytesWritten = -1;
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryCopyCharacterString(UniversalTagNumber.UTF8String, outputData, out bytesWritten));
+
+ Assert.Equal(-1, bytesWritten);
+ Assert.Equal('a', outputData[0]);
+ }
+
+ [Theory]
+ [InlineData("Bad UTF8 value", PublicEncodingRules.BER, "0C02E280")]
+ [InlineData("Bad UTF8 value", PublicEncodingRules.CER, "0C02E280")]
+ [InlineData("Bad UTF8 value", PublicEncodingRules.DER, "0C02E280")]
+ [InlineData("Wrong Tag", PublicEncodingRules.BER, "04024869")]
+ public static void GetUTF8String_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetCharacterString(UniversalTagNumber.UTF8String));
+ }
+
+ [Theory]
+ [InlineData("Empty", PublicEncodingRules.BER, "")]
+ [InlineData("Empty", PublicEncodingRules.CER, "")]
+ [InlineData("Empty", PublicEncodingRules.DER, "")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.BER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.CER, "1F")]
+ [InlineData("Incomplete Tag", PublicEncodingRules.DER, "1F")]
+ [InlineData("Missing Length", PublicEncodingRules.BER, "0C")]
+ [InlineData("Missing Length", PublicEncodingRules.CER, "0C")]
+ [InlineData("Missing Length", PublicEncodingRules.DER, "0C")]
+ [InlineData("Missing Contents", PublicEncodingRules.BER, "0C01")]
+ [InlineData("Missing Contents", PublicEncodingRules.CER, "0C01")]
+ [InlineData("Missing Contents", PublicEncodingRules.DER, "0C01")]
+ [InlineData("Missing Contents - Constructed", PublicEncodingRules.BER, "2C01")]
+ [InlineData("Missing Contents - Constructed Indef", PublicEncodingRules.BER, "2C80")]
+ [InlineData("Missing Contents - Constructed Indef", PublicEncodingRules.CER, "2C80")]
+ [InlineData("Length Too Long", PublicEncodingRules.BER, "0C034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.CER, "0C034869")]
+ [InlineData("Length Too Long", PublicEncodingRules.DER, "0C034869")]
+ [InlineData("Definite Constructed Form", PublicEncodingRules.CER, "2C03040149")]
+ [InlineData("Definite Constructed Form", PublicEncodingRules.DER, "2C03040149")]
+ [InlineData("Indefinite Constructed Form - Short Payload", PublicEncodingRules.CER, "2C800401490000")]
+ [InlineData("Indefinite Constructed Form", PublicEncodingRules.DER, "2C800401490000")]
+ [InlineData("No nested content", PublicEncodingRules.CER, "2C800000")]
+ [InlineData("No EoC", PublicEncodingRules.BER, "2C80" + "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.BER, "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.CER, "04024869")]
+ [InlineData("Wrong Tag - Primitive", PublicEncodingRules.DER, "04024869")]
+ [InlineData("Wrong Tag - Constructed", PublicEncodingRules.BER, "240404024869")]
+ [InlineData("Wrong Tag - Constructed Indef", PublicEncodingRules.BER, "2480" + "04024869" + "0000")]
+ [InlineData("Wrong Tag - Constructed Indef", PublicEncodingRules.CER, "2480" + "04024869" + "0000")]
+ [InlineData("Wrong Tag - Constructed", PublicEncodingRules.DER, "240404024869")]
+ [InlineData("Nested Bad Tag", PublicEncodingRules.BER, "2C04" + "0C024869")]
+ [InlineData("Nested context-specific", PublicEncodingRules.BER, "2C04800400FACE")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.BER, "2C80800400FACE0000")]
+ [InlineData("Nested context-specific (indef)", PublicEncodingRules.CER, "2C80800400FACE0000")]
+ [InlineData("Nested Length Too Long", PublicEncodingRules.BER, "2C07" + ("2402" + "0403") + "040149")]
+ [InlineData("Nested Simple Length Too Long", PublicEncodingRules.BER, "2C03" + "040548656C6C6F")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.BER, "2C8020000000")]
+ [InlineData("Constructed EndOfContents", PublicEncodingRules.CER, "2C8020000000")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.BER, "2C80000100")]
+ [InlineData("NonEmpty EndOfContents", PublicEncodingRules.CER, "2C80000100")]
+ [InlineData("LongLength EndOfContents", PublicEncodingRules.BER, "2C80008100")]
+ [InlineData("Bad UTF8 value", PublicEncodingRules.BER, "0C02E280")]
+ public static void TryCopyUTF8String_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ TryCopyUTF8String_Throws(ruleSet, inputData);
+ }
+
+ [Fact]
+ public static void TryCopyUTF8String_Throws_CER_NestedTooLong()
+ {
+ // CER says that the maximum encoding length for a UTF8String primitive
+ // is 1000.
+ //
+ // This test checks it for a primitive contained within a constructed.
+ //
+ // So we need 04 [1001] { 1001 0x00s }
+ // 1001 => 0x3E9, so the length encoding is 82 03 E9.
+ // 1001 + 3 + 1 == 1005
+ //
+ // Plus a leading 2C 80 (indefinite length constructed)
+ // and a trailing 00 00 (End of contents)
+ // == 1009
+ byte[] input = new byte[1009];
+ // CONSTRUCTED UTF8 STRING (indefinite)
+ input[0] = 0x2C;
+ input[1] = 0x80;
+ // OCTET STRING (1001)
+ input[2] = 0x04;
+ input[3] = 0x82;
+ input[4] = 0x03;
+ input[5] = 0xE9;
+ // EOC implicit since the byte[] initializes to zeros
+
+ TryCopyUTF8String_Throws(PublicEncodingRules.CER, input);
+ }
+
+ [Fact]
+ public static void TryCopyUTF8String_Throws_CER_NestedTooShortIntermediate()
+ {
+ // CER says that the maximum encoding length for a UTF8String primitive
+ // is 1000, and in the constructed form the lengths must be
+ // [ 1000, 1000, 1000, ..., len%1000 ]
+ //
+ // So 1000, 2, 2 is illegal.
+ //
+ // 2C 80 (indefinite constructed utf8 string)
+ // 04 82 03 08 (octet string, 1000 bytes)
+ // [1000 content bytes]
+ // 04 02 (octet string, 2 bytes)
+ // [2 content bytes]
+ // 04 02 (octet string, 2 bytes)
+ // [2 content bytes]
+ // 00 00 (end of contents)
+ // Looks like 1,016 bytes.
+ byte[] input = new byte[1016];
+ // CONSTRUCTED UTF8 STRING (indefinite)
+ input[0] = 0x2C;
+ input[1] = 0x80;
+ // OCTET STRING (1000)
+ input[2] = 0x03;
+ input[3] = 0x82;
+ input[4] = 0x03;
+ input[5] = 0xE8;
+ // OCTET STRING (2)
+ input[1006] = 0x04;
+ input[1007] = 0x02;
+ // OCTET STRING (2)
+ input[1010] = 0x04;
+ input[1011] = 0x02;
+ // EOC implicit since the byte[] initializes to zeros
+
+ TryCopyUTF8String_Throws(PublicEncodingRules.CER, input);
+ }
+
+ [Fact]
+ public static void TryCopyUTF8StringBytes_Success_CER_MaxPrimitiveLength()
+ {
+ // CER says that the maximum encoding length for a UTF8String primitive
+ // is 1000.
+ //
+ // So we need 0C [1000] { 1000 anythings }
+ // 1000 => 0x3E8, so the length encoding is 82 03 E8.
+ // 1000 + 3 + 1 == 1004
+ byte[] input = new byte[1004];
+ input[0] = 0x0C;
+ input[1] = 0x82;
+ input[2] = 0x03;
+ input[3] = 0xE8;
+
+ // Content
+ input[4] = 0x65;
+ input[5] = 0x65;
+ input[1002] = 0x61;
+ input[1003] = 0x61;
+
+ byte[] output = new byte[1000];
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+
+ bool success = reader.TryCopyCharacterStringBytes(
+ UniversalTagNumber.UTF8String,
+ output,
+ out int bytesWritten);
+
+ Assert.True(success, "reader.TryCopyUTF8StringBytes");
+ Assert.Equal(1000, bytesWritten);
+
+ Assert.Equal(
+ input.AsReadOnlySpan().Slice(4).ByteArrayToHex(),
+ output.ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void TryCopyUTF8StringBytes_Success_CER_MinConstructedLength()
+ {
+ // CER says that the maximum encoding length for a UTF8String primitive
+ // is 1000, and that a constructed form must be used for values greater
+ // than 1000 bytes, with segments dividing up for each thousand
+ // [1000, 1000, ..., len%1000].
+ //
+ // So our smallest constructed form is 1001 bytes, [1000, 1]
+ //
+ // 2C 80 (indefinite constructed utf8 string)
+ // 04 82 03 E9 (primitive octet string, 1000 bytes)
+ // [1000 content bytes]
+ // 04 01 (primitive octet string, 1 byte)
+ // pp
+ // 00 00 (end of contents, 0 bytes)
+ // 1011 total.
+ byte[] input = new byte[1011];
+ int offset = 0;
+ // CONSTRUCTED UTF8 STRING (Indefinite)
+ input[offset++] = 0x2C;
+ input[offset++] = 0x80;
+ // OCTET STRING (1000)
+ input[offset++] = 0x04;
+ input[offset++] = 0x82;
+ input[offset++] = 0x03;
+ input[offset++] = 0xE8;
+
+ // Primitive 1: (65 65 :: 61 61) (1000)
+ input[offset++] = 0x65;
+ input[offset] = 0x65;
+ offset += 997;
+ input[offset++] = 0x61;
+ input[offset++] = 0x61;
+
+ // OCTET STRING (1)
+ input[offset++] = 0x04;
+ input[offset++] = 0x01;
+
+ // Primitive 2: One more byte
+ input[offset] = 0x2E;
+
+ byte[] expected = new byte[1001];
+ offset = 0;
+ expected[offset++] = 0x65;
+ expected[offset] = 0x65;
+ offset += 997;
+ expected[offset++] = 0x61;
+ expected[offset++] = 0x61;
+ expected[offset] = 0x2E;
+
+ byte[] output = new byte[1001];
+
+ AsnReader reader = new AsnReader(input, AsnEncodingRules.CER);
+
+ bool success = reader.TryCopyCharacterStringBytes(
+ UniversalTagNumber.UTF8String,
+ output,
+ out int bytesWritten);
+
+ Assert.True(success, "reader.TryCopyUTF8StringBytes");
+ Assert.Equal(1001, bytesWritten);
+
+ Assert.Equal(
+ expected.ByteArrayToHex(),
+ output.ByteArrayToHex());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x0C, 2, (byte)'e', (byte)'l' };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ const UniversalTagNumber EncodingType = UniversalTagNumber.UTF8String;
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.TryGetPrimitiveCharacterStringBytes(Asn1Tag.Null, EncodingType, out _));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetPrimitiveCharacterStringBytes(new Asn1Tag(TagClass.ContextSpecific, 0), EncodingType, out _));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ Assert.True(reader.TryGetPrimitiveCharacterStringBytes(EncodingType, out ReadOnlyMemory<byte> value));
+ Assert.Equal("656C", value.ByteArrayToHex());
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = { 0x87, 2, (byte)'h', (byte)'i' };
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ const UniversalTagNumber EncodingType = UniversalTagNumber.UTF8String;
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.TryGetPrimitiveCharacterStringBytes(Asn1Tag.Null, EncodingType, out _));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetPrimitiveCharacterStringBytes(EncodingType, out _));
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetPrimitiveCharacterStringBytes(new Asn1Tag(TagClass.Application, 0), EncodingType, out _));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.TryGetPrimitiveCharacterStringBytes(new Asn1Tag(TagClass.ContextSpecific, 1), EncodingType, out _));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ Assert.True(
+ reader.TryGetPrimitiveCharacterStringBytes(
+ new Asn1Tag(TagClass.ContextSpecific, 7),
+ EncodingType,
+ out ReadOnlyMemory<byte> value));
+
+ Assert.Equal("6869", value.ByteArrayToHex());
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "0C026869", PublicTagClass.Universal, 12)]
+ [InlineData(PublicEncodingRules.CER, "0C026869", PublicTagClass.Universal, 12)]
+ [InlineData(PublicEncodingRules.DER, "0C026869", PublicTagClass.Universal, 12)]
+ [InlineData(PublicEncodingRules.BER, "80023132", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.CER, "4C023132", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "DF8A46023132", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.True(
+ reader.TryGetPrimitiveCharacterStringBytes(
+ new Asn1Tag((TagClass)tagClass, tagValue, true),
+ UniversalTagNumber.UTF8String,
+ out ReadOnlyMemory<byte> val1));
+
+ Assert.False(reader.HasData);
+
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.True(
+ reader.TryGetPrimitiveCharacterStringBytes(
+ new Asn1Tag((TagClass)tagClass, tagValue, false),
+ UniversalTagNumber.UTF8String,
+ out ReadOnlyMemory<byte> val2));
+
+ Assert.False(reader.HasData);
+
+ Assert.Equal(val1.ByteArrayToHex(), val2.ByteArrayToHex());
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadUtcTime.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadUtcTime.cs
new file mode 100644
index 0000000000..290c7b13fa
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadUtcTime.cs
@@ -0,0 +1,238 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public sealed class ReadUtcTime : Asn1ReaderTests
+ {
+ [Theory]
+ // A, B2, C2
+ [InlineData(PublicEncodingRules.BER, "17113137303930383130333530332D30373030", 2017, 9, 8, 10, 35, 3, -7, 0)]
+ [InlineData(PublicEncodingRules.BER, "17113137303930383130333530332D30303530", 2017, 9, 8, 10, 35, 3, 0, -50)]
+ [InlineData(PublicEncodingRules.BER, "17113137303930383130333530332B30373030", 2017, 9, 8, 10, 35, 3, 7, 0)]
+ [InlineData(PublicEncodingRules.BER, "17113030303130313030303030302B30303030", 2000, 1, 1, 0, 0, 0, 0, 0)]
+ [InlineData(PublicEncodingRules.BER, "17113030303130313030303030302D31343030", 2000, 1, 1, 0, 0, 0, -14, 0)]
+ // A, B2, C1 (only legal form for CER or DER)
+ [InlineData(PublicEncodingRules.BER, "170D3132303130323233353935395A", 2012, 1, 2, 23, 59, 59, 0, 0)]
+ [InlineData(PublicEncodingRules.CER, "170D3439313233313233353935395A", 2049, 12, 31, 23, 59, 59, 0, 0)]
+ [InlineData(PublicEncodingRules.DER, "170D3530303130323132333435365A", 1950, 1, 2, 12, 34, 56, 0, 0)]
+ // A, B1, C2
+ [InlineData(PublicEncodingRules.BER, "170F313730393038313033352D30373030", 2017, 9, 8, 10, 35, 0, -7, 0)]
+ [InlineData(PublicEncodingRules.BER, "170F323730393038313033352B30393132", 2027, 9, 8, 10, 35, 0, 9, 12)]
+ // A, B1, C1
+ [InlineData(PublicEncodingRules.BER, "170B313230313032323335395A", 2012, 1, 2, 23, 59, 0, 0, 0)]
+ [InlineData(PublicEncodingRules.BER, "170B343931323331323335395A", 2049, 12, 31, 23, 59, 0, 0, 0)]
+ // BER Constructed form
+ [InlineData(
+ PublicEncodingRules.BER,
+ "3780" +
+ "04023132" +
+ "04023031" +
+ "2480" + "040130" + "040132" + "0000" +
+ "040432333539" +
+ "04830000015A" +
+ "0000",
+ 2012, 1, 2, 23, 59, 0, 0, 0)]
+ public static void ParseTime_Valid(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ int year,
+ int month,
+ int day,
+ int hour,
+ int minute,
+ int second,
+ int offsetHour,
+ int offsetMinute)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ DateTimeOffset value = reader.GetUtcTime();
+
+ Assert.Equal(year, value.Year);
+ Assert.Equal(month, value.Month);
+ Assert.Equal(day, value.Day);
+ Assert.Equal(hour, value.Hour);
+ Assert.Equal(minute, value.Minute);
+ Assert.Equal(second, value.Second);
+ Assert.Equal(0, value.Millisecond);
+ Assert.Equal(new TimeSpan(offsetHour, offsetMinute, 0), value.Offset);
+ }
+
+ [Fact]
+ public static void ParseTime_InvalidValue_LegalString()
+ {
+ byte[] inputData = "17113030303030303030303030302D31353030".HexToByteArray();
+
+ var exception = Assert.Throws<CryptographicException>(
+ () =>
+ {
+ AsnReader reader = new AsnReader(inputData, AsnEncodingRules.BER);
+ reader.GetUtcTime();
+ });
+
+ Assert.NotNull(exception.InnerException);
+ Assert.IsType<ArgumentOutOfRangeException>(exception.InnerException);
+ }
+
+ [Theory]
+ [InlineData(2011, 1912)]
+ [InlineData(2012, 2012)]
+ [InlineData(2013, 2012)]
+ [InlineData(2111, 2012)]
+ [InlineData(2112, 2112)]
+ [InlineData(2113, 2112)]
+ [InlineData(12, 12)]
+ [InlineData(99, 12)]
+ [InlineData(111, 12)]
+ public static void ReadUtcTime_TwoYearMaximum(int maximum, int interpretedYear)
+ {
+ byte[] inputData = "170D3132303130323233353935395A".HexToByteArray();
+
+ AsnReader reader = new AsnReader(inputData, AsnEncodingRules.BER);
+ DateTimeOffset value = reader.GetUtcTime(maximum);
+
+ Assert.Equal(interpretedYear, value.Year);
+ }
+
+ [Theory]
+ [InlineData("A,B2,C2", PublicEncodingRules.CER, "17113137303930383130333530332D30373030")]
+ [InlineData("A,B2,C2", PublicEncodingRules.DER, "17113137303930383130333530332D30373030")]
+ [InlineData("A,B1,C2", PublicEncodingRules.CER, "170F313730393038313033352D30373030")]
+ [InlineData("A,B1,C2", PublicEncodingRules.DER, "170F313730393038313033352D30373030")]
+ [InlineData("A,B1,C1", PublicEncodingRules.CER, "170B313230313032323335395A")]
+ [InlineData("A,B1,C1", PublicEncodingRules.DER, "170B313230313032323335395A")]
+ [InlineData("A,B1,C1-NotZ", PublicEncodingRules.BER, "170B313230313032323335392B")]
+ [InlineData("A,B1,C2-NotPlusMinus", PublicEncodingRules.BER, "170F313730393038313033352C30373030")]
+ [InlineData("A,B2,C2-NotPlusMinus", PublicEncodingRules.BER, "17113137303930383130333530332C30373030")]
+ [InlineData("A,B2,C2-MinuteOutOfRange", PublicEncodingRules.BER, "17113030303030303030303030302D31353630")]
+ [InlineData("A,B1,C2-MinuteOutOfRange", PublicEncodingRules.BER, "170F303030303030303030302D31353630")]
+ [InlineData("A1,B2,C1-NotZ", PublicEncodingRules.DER, "170D3530303130323132333435365B")]
+ [InlineData("A,B2,C2-MissingDigit", PublicEncodingRules.BER, "17103137303930383130333530332C303730")]
+ [InlineData("A,B2,C2-TooLong", PublicEncodingRules.BER, "17123137303930383130333530332B3037303030")]
+ [InlineData("WrongTag", PublicEncodingRules.BER, "1A0D3132303130323233353935395A")]
+ public static void ReadUtcTime_Throws(
+ string description,
+ PublicEncodingRules ruleSet,
+ string inputHex)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(() => reader.GetUtcTime());
+ }
+
+ [Fact]
+ public static void ReadUtcTime_WayTooBig_Throws()
+ {
+ // Need to exceed the length that the shared pool will return for 17:
+ byte[] inputData = new byte[4097+4];
+ inputData[0] = 0x17;
+ inputData[1] = 0x82;
+ inputData[2] = 0x10;
+ inputData[3] = 0x01;
+
+ AsnReader reader = new AsnReader(inputData, AsnEncodingRules.BER);
+
+ Assert.Throws<CryptographicException>(() => reader.GetUtcTime());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "170D3530303130323132333435365A".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.GetUtcTime(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetUtcTime(new Asn1Tag(TagClass.ContextSpecific, 0)));
+
+ Assert.True(reader.HasData, "HasData after wrong tag");
+
+ Assert.Equal(
+ new DateTimeOffset(1950, 1, 2, 12, 34, 56, TimeSpan.Zero),
+ reader.GetUtcTime());
+
+ Assert.False(reader.HasData, "HasData after read");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void TagMustBeCorrect_Custom(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = "850D3530303130323132333435365A".HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "expectedTag",
+ () => reader.GetUtcTime(Asn1Tag.Null));
+
+ Assert.True(reader.HasData, "HasData after bad universal tag");
+
+ Assert.Throws<CryptographicException>(() => reader.GetUtcTime());
+
+ Assert.True(reader.HasData, "HasData after default tag");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetUtcTime(new Asn1Tag(TagClass.Application, 5)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom class");
+
+ Assert.Throws<CryptographicException>(
+ () => reader.GetUtcTime(new Asn1Tag(TagClass.ContextSpecific, 7)));
+
+ Assert.True(reader.HasData, "HasData after wrong custom tag value");
+
+ Assert.Equal(
+ new DateTimeOffset(1950, 1, 2, 12, 34, 56, TimeSpan.Zero),
+ reader.GetUtcTime(new Asn1Tag(TagClass.ContextSpecific, 5)));
+
+ Assert.False(reader.HasData, "HasData after reading value");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "170D3530303130323132333435365A", PublicTagClass.Universal, 23)]
+ [InlineData(PublicEncodingRules.CER, "170D3530303130323132333435365A", PublicTagClass.Universal, 23)]
+ [InlineData(PublicEncodingRules.DER, "170D3530303130323132333435365A", PublicTagClass.Universal, 23)]
+ [InlineData(PublicEncodingRules.BER, "800D3530303130323132333435365A", PublicTagClass.ContextSpecific, 0)]
+ [InlineData(PublicEncodingRules.CER, "4C0D3530303130323132333435365A", PublicTagClass.Application, 12)]
+ [InlineData(PublicEncodingRules.DER, "DF8A460D3530303130323132333435365A", PublicTagClass.Private, 1350)]
+ public static void ExpectedTag_IgnoresConstructed(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ PublicTagClass tagClass,
+ int tagValue)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ DateTimeOffset val1 = reader.GetUtcTime(new Asn1Tag((TagClass)tagClass, tagValue, true));
+
+ Assert.False(reader.HasData);
+
+ reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+
+ DateTimeOffset val2 = reader.GetUtcTime(new Asn1Tag((TagClass)tagClass, tagValue, false));
+
+ Assert.False(reader.HasData);
+
+ Assert.Equal(val1, val2);
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReaderStateTests.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReaderStateTests.cs
new file mode 100644
index 0000000000..2cbfb57018
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReaderStateTests.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public static class ReaderStateTests
+ {
+ [Fact]
+ public static void HasDataAndThrowIfNotEmpty()
+ {
+ AsnReader reader = new AsnReader(new byte[] { 0x01, 0x01, 0x00 }, AsnEncodingRules.BER);
+ Assert.True(reader.HasData);
+ Assert.Throws<CryptographicException>(() => reader.ThrowIfNotEmpty());
+
+ // Consume the current value and move on.
+ reader.GetEncodedValue();
+
+ Assert.False(reader.HasData);
+ // Assert.NoThrow
+ reader.ThrowIfNotEmpty();
+ }
+
+ [Fact]
+ public static void HasDataAndThrowIfNotEmpty_StartsEmpty()
+ {
+ AsnReader reader = new AsnReader(ReadOnlyMemory<byte>.Empty, AsnEncodingRules.BER);
+ Assert.False(reader.HasData);
+ // Assert.NoThrow
+ reader.ThrowIfNotEmpty();
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/Asn1WriterTests.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/Asn1WriterTests.cs
new file mode 100644
index 0000000000..316eb28b59
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/Asn1WriterTests.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public abstract partial class Asn1WriterTests : Asn1ReaderTests
+ {
+ internal static void Verify(AsnWriter writer, string expectedHex)
+ {
+ byte[] encoded = writer.Encode();
+ Assert.Equal(expectedHex, encoded.ByteArrayToHex());
+
+ // Now verify TryEncode's boundary conditions.
+ byte[] encoded2 = new byte[encoded.Length + 3];
+ encoded2[0] = 255;
+ encoded2[encoded.Length] = 254;
+
+ Span<byte> dest = encoded2.AsSpan().Slice(0, encoded.Length - 1);
+ Assert.False(writer.TryEncode(dest, out int bytesWritten), "writer.TryEncode (too small)");
+ Assert.Equal(0, bytesWritten);
+ Assert.Equal(255, encoded2[0]);
+ Assert.Equal(254, encoded2[encoded.Length]);
+
+ dest = encoded2.AsSpan().Slice(0, encoded.Length);
+ Assert.True(writer.TryEncode(dest, out bytesWritten), "writer.TryEncode (exact length)");
+ Assert.Equal(encoded.Length, bytesWritten);
+ Assert.True(dest.SequenceEqual(encoded), "dest.SequenceEqual(encoded2) (exact length)");
+ Assert.Equal(254, encoded2[encoded.Length]);
+
+ // Start marker was obliterated, but the stop marker is still intact. Keep it there.
+ Array.Clear(encoded2, 0, bytesWritten);
+
+ dest = encoded2.AsSpan();
+ Assert.True(writer.TryEncode(dest, out bytesWritten), "writer.TryEncode (overly big)");
+ Assert.Equal(encoded.Length, bytesWritten);
+ Assert.True(dest.Slice(0, bytesWritten).SequenceEqual(encoded), "dest.SequenceEqual(encoded2) (overly big)");
+ Assert.Equal(254, encoded2[encoded.Length]);
+ }
+
+ internal static unsafe string Stringify(Asn1Tag tag)
+ {
+ byte* stackspace = stackalloc byte[10];
+ Span<byte> dest = new Span<byte>(stackspace, 10);
+
+ Assert.True(tag.TryWrite(dest, out int size));
+ return dest.Slice(0, size).ByteArrayToHex();
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/ComprehensiveWriteTest.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/ComprehensiveWriteTest.cs
new file mode 100644
index 0000000000..626d7f59ae
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/ComprehensiveWriteTest.cs
@@ -0,0 +1,272 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Numerics;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+using X509KeyUsageCSharpStyle = System.Security.Cryptography.Tests.Asn1.ReadNamedBitList.X509KeyUsageCSharpStyle;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public static class ComprehensiveWriteTest
+ {
+ [Fact]
+ public static void WriteMicrosoftDotComCert()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ // Certificate
+ writer.PushSequence();
+
+ // tbsCertificate
+ writer.PushSequence();
+
+ // version ([0] EXPLICIT INTEGER)
+ Asn1Tag context0 = new Asn1Tag(TagClass.ContextSpecific, 0, true);
+ writer.PushSequence(context0);
+ writer.WriteInteger(2);
+ writer.PopSequence(context0);
+
+ BigInteger serialValue = BigInteger.Parse("82365655871428336739211871484630851433");
+ writer.WriteInteger(serialValue);
+
+ // signature (algorithm)
+ writer.PushSequence();
+ writer.WriteObjectIdentifier("1.2.840.113549.1.1.11");
+ writer.WriteNull();
+ writer.PopSequence();
+
+ // issuer
+ writer.PushSequence();
+ WriteRdn(writer, "2.5.4.6", "US", UniversalTagNumber.PrintableString);
+ WriteRdn(writer, "2.5.4.10", "Symantec Corporation", UniversalTagNumber.PrintableString);
+ WriteRdn(writer, "2.5.4.11", "Symantec Trust Network", UniversalTagNumber.PrintableString);
+ WriteRdn(writer, "2.5.4.3", "Symantec Class 3 EV SSL CA - G3", UniversalTagNumber.PrintableString);
+ writer.PopSequence();
+
+ // validity
+ writer.PushSequence();
+ writer.WriteUtcTime(new DateTimeOffset(2014, 10, 15, 0, 0, 0, TimeSpan.Zero));
+ writer.WriteUtcTime(new DateTimeOffset(2016, 10, 15, 23, 59, 59, TimeSpan.Zero));
+ writer.PopSequence();
+
+ // subject
+ writer.PushSequence();
+ WriteRdn(writer, "1.3.6.1.4.1.311.60.2.1.3", "US", UniversalTagNumber.PrintableString);
+ WriteRdn(writer, "1.3.6.1.4.1.311.60.2.1.2", "Washington", UniversalTagNumber.UTF8String);
+ WriteRdn(writer, "2.5.4.15", "Private Organization", UniversalTagNumber.PrintableString);
+ WriteRdn(writer, "2.5.4.5", "600413485", UniversalTagNumber.PrintableString);
+ WriteRdn(writer, "2.5.4.6", "US", UniversalTagNumber.PrintableString);
+ WriteRdn(writer, "2.5.4.17", "98052", UniversalTagNumber.UTF8String);
+ WriteRdn(writer, "2.5.4.8", "Washington", UniversalTagNumber.UTF8String);
+ WriteRdn(writer, "2.5.4.7", "Redmond", UniversalTagNumber.UTF8String);
+ WriteRdn(writer, "2.5.4.9", "1 Microsoft Way", UniversalTagNumber.UTF8String);
+ WriteRdn(writer, "2.5.4.10", "Microsoft Corporation", UniversalTagNumber.UTF8String);
+ WriteRdn(writer, "2.5.4.11", "MSCOM", UniversalTagNumber.UTF8String);
+ WriteRdn(writer, "2.5.4.3", "www.microsoft.com", UniversalTagNumber.UTF8String);
+ writer.PopSequence();
+
+ // subjectPublicKeyInfo
+ writer.PushSequence();
+ // subjectPublicKeyInfo.algorithm
+ writer.PushSequence();
+ writer.WriteObjectIdentifier("1.2.840.113549.1.1.1");
+ writer.WriteNull();
+ writer.PopSequence();
+
+ AsnWriter publicKeyWriter = new AsnWriter(AsnEncodingRules.DER);
+ publicKeyWriter.PushSequence();
+ BigInteger modulus = BigInteger.Parse(
+ "207545550571844404676608632512851454930111394466749205318948660756381" +
+ "523214360115124048083611193260260272384440199925180817531535965931647" +
+ "037093368608713442955529617501657176146245891571745113402870077189045" +
+ "116705181899983704226178882882602868159586789723579670915035003754974" +
+ "985730226756711782751711104985926458681071638525996766798322809764200" +
+ "941677343791419428587801897366593842552727222686457866144928124161967" +
+ "521735393182823375650694786333059783380738262856873316471830589717911" +
+ "537307419734834201104082715701367336140572971505716740825623220507359" +
+ "42929758463490933054115079473593821332264673455059897928082590541");
+ publicKeyWriter.WriteInteger(modulus);
+ publicKeyWriter.WriteInteger(65537);
+ publicKeyWriter.PopSequence();
+
+ // subjectPublicKeyInfo.subjectPublicKey
+ writer.WriteBitString(publicKeyWriter.Encode());
+ writer.PopSequence();
+
+ // extensions ([3] EXPLICIT Extensions)
+ Asn1Tag context3 = new Asn1Tag(TagClass.ContextSpecific, 3);
+ writer.PushSequence(context3);
+ writer.PushSequence();
+
+ Asn1Tag dnsName = new Asn1Tag(TagClass.ContextSpecific, 2);
+ AsnWriter extensionValueWriter = new AsnWriter(AsnEncodingRules.DER);
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.WriteCharacterString(dnsName, UniversalTagNumber.IA5String, "www.microsoft.com");
+ extensionValueWriter.WriteCharacterString(dnsName, UniversalTagNumber.IA5String, "wwwqa.microsoft.com");
+ extensionValueWriter.PopSequence();
+
+ writer.PushSequence();
+ writer.WriteObjectIdentifier("2.5.29.17");
+ writer.WriteOctetString(extensionValueWriter.Encode());
+ writer.PopSequence();
+
+ writer.PushSequence();
+ writer.WriteObjectIdentifier("2.5.29.19");
+ // Empty sequence as the payload for a non-CA basic constraint.
+ writer.WriteOctetString(new byte[] { 0x30, 0x00 });
+ writer.PopSequence();
+
+ extensionValueWriter = new AsnWriter(AsnEncodingRules.DER);
+ // This extension doesn't use a sequence at all, just Named Bit List.
+ extensionValueWriter.WriteNamedBitList(
+ X509KeyUsageCSharpStyle.DigitalSignature | X509KeyUsageCSharpStyle.KeyEncipherment);
+
+ writer.PushSequence();
+ writer.WriteObjectIdentifier("2.5.29.15");
+ // critical: true
+ writer.WriteBoolean(true);
+ writer.WriteOctetString(extensionValueWriter.Encode());
+ writer.PopSequence();
+
+ extensionValueWriter = new AsnWriter(AsnEncodingRules.DER);
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.WriteObjectIdentifier("1.3.6.1.5.5.7.3.1");
+ extensionValueWriter.WriteObjectIdentifier("1.3.6.1.5.5.7.3.2");
+ extensionValueWriter.PopSequence();
+
+ writer.PushSequence();
+ writer.WriteObjectIdentifier("2.5.29.37");
+ writer.WriteOctetString(extensionValueWriter.Encode());
+ writer.PopSequence();
+
+ extensionValueWriter = new AsnWriter(AsnEncodingRules.DER);
+
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.WriteObjectIdentifier("2.16.840.1.113733.1.7.23.6");
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.WriteObjectIdentifier("1.3.6.1.5.5.7.2.1");
+ extensionValueWriter.WriteCharacterString(UniversalTagNumber.IA5String, "https://d.symcb.com/cps");
+ extensionValueWriter.PopSequence();
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.WriteObjectIdentifier("1.3.6.1.5.5.7.2.2");
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.WriteCharacterString(UniversalTagNumber.VisibleString, "https://d.symcb.com/rpa");
+ extensionValueWriter.PopSequence();
+ extensionValueWriter.PopSequence();
+ extensionValueWriter.PopSequence();
+ extensionValueWriter.PopSequence();
+ extensionValueWriter.PopSequence();
+
+ writer.PushSequence();
+ writer.WriteObjectIdentifier("2.5.29.32");
+ writer.WriteOctetString(extensionValueWriter.Encode());
+ writer.PopSequence();
+
+ byte[] authorityKeyIdentifier = "0159ABE7DD3A0B59A66463D6CF200757D591E76A".HexToByteArray();
+ Asn1Tag keyIdentifier = context0;
+ extensionValueWriter = new AsnWriter(AsnEncodingRules.DER);
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.WriteOctetString(keyIdentifier, authorityKeyIdentifier);
+ extensionValueWriter.PopSequence();
+
+ writer.PushSequence();
+ writer.WriteObjectIdentifier("2.5.29.35");
+ writer.WriteOctetString(extensionValueWriter.Encode());
+ writer.PopSequence();
+
+ Asn1Tag distributionPointChoice = context0;
+ Asn1Tag fullNameChoice = context0;
+ Asn1Tag generalNameUriChoice = new Asn1Tag(TagClass.ContextSpecific, 6);
+ extensionValueWriter = new AsnWriter(AsnEncodingRules.DER);
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.PushSequence(distributionPointChoice);
+ extensionValueWriter.PushSequence(fullNameChoice);
+ extensionValueWriter.WriteCharacterString(
+ generalNameUriChoice,
+ UniversalTagNumber.IA5String,
+ "http://sr.symcb.com/sr.crl");
+ extensionValueWriter.PopSequence(fullNameChoice);
+ extensionValueWriter.PopSequence(distributionPointChoice);
+ extensionValueWriter.PopSequence();
+ extensionValueWriter.PopSequence();
+
+ writer.PushSequence();
+ writer.WriteObjectIdentifier("2.5.29.31");
+ writer.WriteOctetString(extensionValueWriter.Encode());
+ writer.PopSequence();
+
+ extensionValueWriter = new AsnWriter(AsnEncodingRules.DER);
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.WriteObjectIdentifier("1.3.6.1.5.5.7.48.1");
+ extensionValueWriter.WriteCharacterString(
+ generalNameUriChoice,
+ UniversalTagNumber.IA5String,
+ "http://sr.symcd.com");
+ extensionValueWriter.PopSequence();
+ extensionValueWriter.PushSequence();
+ extensionValueWriter.WriteObjectIdentifier("1.3.6.1.5.5.7.48.2");
+ extensionValueWriter.WriteCharacterString(
+ generalNameUriChoice,
+ UniversalTagNumber.IA5String,
+ "http://sr.symcb.com/sr.crt");
+ extensionValueWriter.PopSequence();
+ extensionValueWriter.PopSequence();
+
+ writer.PushSequence();
+ writer.WriteObjectIdentifier("1.3.6.1.5.5.7.1.1");
+ writer.WriteOctetString(extensionValueWriter.Encode());
+ writer.PopSequence();
+
+ writer.PopSequence();
+ writer.PopSequence(context3);
+
+ // tbsCertificate
+ writer.PopSequence();
+
+ // signatureAlgorithm
+ writer.PushSequence();
+ writer.WriteObjectIdentifier("1.2.840.113549.1.1.11");
+ writer.WriteNull();
+ writer.PopSequence();
+
+ // signature
+ byte[] containsSignature = (
+ "010203040506070809" +
+ "15F8505B627ED7F9F96707097E93A51E7A7E05A3D420A5C258EC7A1CFE1843EC" +
+ "20ACF728AAFA7A1A1BC222A7CDBF4AF90AA26DEEB3909C0B3FB5C78070DAE3D6" +
+ "45BFCF840A4A3FDD988C7B3308BFE4EB3FD66C45641E96CA3352DBE2AEB4488A" +
+ "64A9C5FB96932BA70059CE92BD278B41299FD213471BD8165F924285AE3ECD66" +
+ "6C703885DCA65D24DA66D3AFAE39968521995A4C398C7DF38DFA82A20372F13D" +
+ "4A56ADB21B5822549918015647B5F8AC131CC5EB24534D172BC60218A88B65BC" +
+ "F71C7F388CE3E0EF697B4203720483BB5794455B597D80D48CD3A1D73CBBC609" +
+ "C058767D1FF060A609D7E3D4317079AF0CD0A8A49251AB129157F9894A036487" +
+ "090807060504030201").HexToByteArray();
+
+ writer.WriteBitString(containsSignature.AsReadOnlySpan().Slice(9, 256));
+
+ // certificate
+ writer.PopSequence();
+
+ Assert.Equal(
+ ComprehensiveReadTests.MicrosoftDotComSslCertBytes.ByteArrayToHex(),
+ writer.Encode().ByteArrayToHex());
+ }
+
+ private static void WriteRdn(AsnWriter writer, string oid, string value, UniversalTagNumber valueType)
+ {
+ writer.PushSetOf();
+ writer.PushSequence();
+ writer.WriteObjectIdentifier(oid);
+ writer.WriteCharacterString(valueType, value);
+ writer.PopSequence();
+ writer.PopSetOf();
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/PushPopSequence.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/PushPopSequence.cs
new file mode 100644
index 0000000000..50ef39b5d0
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/PushPopSequence.cs
@@ -0,0 +1,514 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Numerics;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class PushPopSequence : Asn1WriterTests
+ {
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PopNewWriter(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ // Maybe ArgumentException isn't right for this, since no argument was provided.
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PopSequence());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PopNewWriter_CustomTag(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PopSequence(new Asn1Tag(TagClass.ContextSpecific, (int)ruleSet, true)));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PopBalancedWriter(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSequence();
+ writer.PopSequence();
+
+ // Maybe ArgumentException isn't right for this, since no argument was provided.
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PopSequence());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PopBalancedWriter_CustomTag(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSequence();
+ writer.PopSequence();
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PopSequence(new Asn1Tag(TagClass.ContextSpecific, (int)ruleSet, true)));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushCustom_PopStandard(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)ruleSet, true));
+
+ // Maybe ArgumentException isn't right for this, since no argument was provided.
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PopSequence());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushStandard_PopCustom(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSequence();
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PopSequence(new Asn1Tag(TagClass.ContextSpecific, (int)ruleSet, true)));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushPrimitive_PopStandard(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSequence(new Asn1Tag(UniversalTagNumber.Sequence));
+ writer.PopSequence();
+
+ if (ruleSet == PublicEncodingRules.CER)
+ {
+ Verify(writer, "30800000");
+ }
+ else
+ {
+ Verify(writer, "3000");
+ }
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushCustomPrimitive_PopConstructed(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSequence(new Asn1Tag(TagClass.Private, 5));
+ writer.PopSequence(new Asn1Tag(TagClass.Private, 5, true));
+
+ if (ruleSet == PublicEncodingRules.CER)
+ {
+ Verify(writer, "E5800000");
+ }
+ else
+ {
+ Verify(writer, "E500");
+ }
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushStandard_PopPrimitive(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSequence();
+ writer.PopSequence(new Asn1Tag(UniversalTagNumber.Sequence, isConstructed: false));
+
+ if (ruleSet == PublicEncodingRules.CER)
+ {
+ Verify(writer, "30800000");
+ }
+ else
+ {
+ Verify(writer, "3000");
+ }
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushCustomConstructed_PopPrimitive(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSequence(new Asn1Tag(TagClass.Private, (int)ruleSet, true));
+ writer.PopSequence(new Asn1Tag(TagClass.Private, (int)ruleSet));
+
+ byte tag = (byte)((int)ruleSet | 0b1110_0000);
+ string tagHex = tag.ToString("X2");
+ string rest = ruleSet == PublicEncodingRules.CER ? "800000" : "00";
+
+ Verify(writer, tagHex + rest);
+ }
+
+ [Fact]
+ public static void BER_WritesDefinite_Empty()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ writer.PushSequence();
+ writer.PopSequence();
+
+ Verify(writer, "3000");
+ }
+
+ [Fact]
+ public static void CER_WritesIndefinite_Empty()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ writer.PushSequence();
+ writer.PopSequence();
+
+ Verify(writer, "30800000");
+ }
+
+ [Fact]
+ public static void DER_WritesDefinite_CustomTag_Empty()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ writer.PushSequence();
+ writer.PopSequence();
+
+ Verify(writer, "3000");
+ }
+
+ [Fact]
+ public static void BER_WritesDefinite_CustomTag__Empty()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Private, 15, true);
+ writer.PushSequence(tag);
+ writer.PopSequence(tag);
+
+ Verify(writer, "EF00");
+ }
+
+ [Fact]
+ public static void CER_WritesIndefinite_CustomTag__Empty()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Application, 91, true);
+ writer.PushSequence(tag);
+ writer.PopSequence(tag);
+
+ Verify(writer, "7F5B800000");
+ }
+
+ [Fact]
+ public static void DER_WritesDefinite_CustomTag__Empty()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ Asn1Tag tag = new Asn1Tag(TagClass.ContextSpecific, 30, true);
+ writer.PushSequence(tag);
+ writer.PopSequence(tag);
+
+ Verify(writer, "BE00");
+ }
+
+ private static void TestNested(AsnWriter writer, Asn1Tag alt, string expectedHex)
+ {
+ writer.PushSequence();
+ {
+ writer.PushSequence(alt);
+ writer.PopSequence(alt);
+
+ writer.PushSequence();
+ {
+ writer.PushSequence(alt);
+ {
+ writer.PushSequence();
+ writer.PopSequence();
+ }
+
+ writer.PopSequence(alt);
+ }
+
+ writer.PopSequence();
+ }
+
+ writer.PopSequence();
+
+ Verify(writer, expectedHex);
+ }
+
+ [Fact]
+ public static void BER_Nested()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag alt = new Asn1Tag(TagClass.Private, 127, true);
+
+ TestNested(writer, alt, "300AFF7F003005FF7F023000");
+ }
+
+ [Fact]
+ public static void CER_Nested()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ Asn1Tag alt = new Asn1Tag(TagClass.ContextSpecific, 12, true);
+
+ TestNested(writer, alt, "3080AC8000003080AC8030800000000000000000");
+ }
+
+ [Fact]
+ public static void DER_Nested()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ Asn1Tag alt = new Asn1Tag(TagClass.Application, 5, true);
+
+ TestNested(writer, alt, "30086500300465023000");
+ }
+
+ private static void SimpleContentShift(AsnWriter writer, string expectedHex)
+ {
+ writer.PushSequence();
+
+ // F00DF00D...F00DF00D
+ byte[] contentBytes = new byte[126];
+
+ for (int i = 0; i < contentBytes.Length; i += 2)
+ {
+ contentBytes[i] = 0xF0;
+ contentBytes[i + 1] = 0x0D;
+ }
+
+ writer.WriteOctetString(contentBytes);
+ writer.PopSequence();
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void SimpleContentShift(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ const string ExpectedHex =
+ "308180" +
+ "047E" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D";
+
+ SimpleContentShift(writer, ExpectedHex);
+ }
+
+ [Fact]
+ public static void SimpleContentShift_CER()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+
+ const string ExpectedHex =
+ "3080" +
+ "047E" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "0000";
+
+ SimpleContentShift(writer, ExpectedHex);
+ }
+
+ private static void WriteRSAPublicKey(AsnEncodingRules ruleSet, string expectedHex)
+ {
+ AsnWriter innerWriter = new AsnWriter(ruleSet);
+
+ byte[] paddedBigEndianN = (
+ "00" +
+ "AF81C1CBD8203F624A539ED6608175372393A2837D4890E48A19DED369731156" +
+ "20968D6BE0D3DAA38AA777BE02EE0B6B93B724E8DCC12B632B4FA80BBC925BCE" +
+ "624F4CA7CC606306B39403E28C932D24DD546FFE4EF6A37F10770B2215EA8CBB" +
+ "5BF427E8C4D89B79EB338375100C5F83E55DE9B4466DDFBEEE42539AEF33EF18" +
+ "7B7760C3B1A1B2103C2D8144564A0C1039A09C85CF6B5974EB516FC8D6623C94" +
+ "AE3A5A0BB3B4C792957D432391566CF3E2A52AFB0C142B9E0681B8972671AF2B" +
+ "82DD390A39B939CF719568687E4990A63050CA7768DCD6B378842F18FDB1F6D9" +
+ "FF096BAF7BEB98DCF930D66FCFD503F58D41BFF46212E24E3AFC45EA42BD8847").HexToByteArray();
+
+ // Now it's padded little-endian.
+ Array.Reverse(paddedBigEndianN);
+ BigInteger n = new BigInteger(paddedBigEndianN);
+ const long e = 8589935681;
+
+ innerWriter.PushSequence();
+ innerWriter.WriteInteger(n);
+ innerWriter.WriteInteger(e);
+ innerWriter.PopSequence();
+
+ AsnWriter outerWriter = new AsnWriter(ruleSet);
+ // RSAPublicKey
+ outerWriter.PushSequence();
+
+ // AlgorithmIdentifier
+ outerWriter.PushSequence();
+ outerWriter.WriteObjectIdentifier("1.2.840.113549.1.1.1");
+ outerWriter.WriteNull();
+ outerWriter.PopSequence();
+
+ outerWriter.WriteBitString(innerWriter.Encode());
+ outerWriter.PopSequence();
+
+ Verify(outerWriter, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void WriteRSAPublicKey(PublicEncodingRules ruleSet)
+ {
+ const string ExpectedHex =
+ // CONSTRUCTED SEQUENCE
+ "30820124" +
+ // CONSTRUCTED SEQUENCE
+ "300D" +
+ // OBJECT IDENTIFIER (1.2.840.113549.1.1.1, rsaEncryption)
+ "06092A864886F70D010101" +
+ // NULL
+ "0500" +
+ // BIT STRING
+ "03820111" +
+ // 0 unused bits
+ "00" +
+ // sneaky inspection of the payload bytes
+ // CONSTRUCTED SEQUENCE
+ "3082010C" +
+ // INTEGER (n)
+ "02820101" +
+ "00AF81C1CBD8203F624A539ED6608175372393A2837D4890E48A19DED3697311" +
+ "5620968D6BE0D3DAA38AA777BE02EE0B6B93B724E8DCC12B632B4FA80BBC925B" +
+ "CE624F4CA7CC606306B39403E28C932D24DD546FFE4EF6A37F10770B2215EA8C" +
+ "BB5BF427E8C4D89B79EB338375100C5F83E55DE9B4466DDFBEEE42539AEF33EF" +
+ "187B7760C3B1A1B2103C2D8144564A0C1039A09C85CF6B5974EB516FC8D6623C" +
+ "94AE3A5A0BB3B4C792957D432391566CF3E2A52AFB0C142B9E0681B8972671AF" +
+ "2B82DD390A39B939CF719568687E4990A63050CA7768DCD6B378842F18FDB1F6" +
+ "D9FF096BAF7BEB98DCF930D66FCFD503F58D41BFF46212E24E3AFC45EA42BD88" +
+ "47" +
+ // INTEGER (e)
+ "02050200000441";
+
+ WriteRSAPublicKey((AsnEncodingRules)ruleSet, ExpectedHex);
+ }
+
+ [Fact]
+ public static void WriteRSAPublicKey_CER()
+ {
+ const string ExpectedHex =
+ // CONSTRUCTED SEQUENCE
+ "3080" +
+ // CONSTRUCTED SEQUENCE
+ "3080" +
+ // OBJECT IDENTIFIER (1.2.840.113549.1.1.1, rsaEncryption)
+ "06092A864886F70D010101" +
+ // NULL
+ "0500" +
+ // End-of-Contents
+ "0000" +
+ // BIT STRING
+ "03820111" +
+ // 0 unused bits
+ "00" +
+ // sneaky inspection of the payload bytes
+ // CONSTRUCTED SEQUENCE
+ "3080" +
+ // INTEGER (n)
+ "02820101" +
+ "00AF81C1CBD8203F624A539ED6608175372393A2837D4890E48A19DED3697311" +
+ "5620968D6BE0D3DAA38AA777BE02EE0B6B93B724E8DCC12B632B4FA80BBC925B" +
+ "CE624F4CA7CC606306B39403E28C932D24DD546FFE4EF6A37F10770B2215EA8C" +
+ "BB5BF427E8C4D89B79EB338375100C5F83E55DE9B4466DDFBEEE42539AEF33EF" +
+ "187B7760C3B1A1B2103C2D8144564A0C1039A09C85CF6B5974EB516FC8D6623C" +
+ "94AE3A5A0BB3B4C792957D432391566CF3E2A52AFB0C142B9E0681B8972671AF" +
+ "2B82DD390A39B939CF719568687E4990A63050CA7768DCD6B378842F18FDB1F6" +
+ "D9FF096BAF7BEB98DCF930D66FCFD503F58D41BFF46212E24E3AFC45EA42BD88" +
+ "47" +
+ // INTEGER (e)
+ "02050200000441" +
+ // End-of-Contents
+ "0000" +
+ // (no EoC for the BIT STRING)
+ // End-of-Contents
+ "0000";
+
+ WriteRSAPublicKey(AsnEncodingRules.CER, ExpectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, false)]
+ [InlineData(PublicEncodingRules.CER, false)]
+ [InlineData(PublicEncodingRules.DER, false)]
+ [InlineData(PublicEncodingRules.BER, true)]
+ [InlineData(PublicEncodingRules.CER, true)]
+ [InlineData(PublicEncodingRules.DER, true)]
+ public static void CannotEncodeWhileUnbalanced(PublicEncodingRules ruleSet, bool customTag)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ if (customTag)
+ {
+ writer.PushSequence(new Asn1Tag(TagClass.ContextSpecific, (int)ruleSet, true));
+ }
+ else
+ {
+ writer.PushSequence();
+ }
+
+ int written = -5;
+
+ Assert.Throws<InvalidOperationException>(() => writer.Encode());
+ Assert.Throws<InvalidOperationException>(() => writer.TryEncode(Span<byte>.Empty, out written));
+ Assert.Equal(-5, written);
+
+ byte[] buf = new byte[10];
+ Assert.Throws<InvalidOperationException>(() => writer.TryEncode(buf, out written));
+ Assert.Equal(-5, written);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushSequence_EndOfContents(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PushSequence(Asn1Tag.EndOfContents));
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/PushPopSetOf.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/PushPopSetOf.cs
new file mode 100644
index 0000000000..9ed7155c6a
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/PushPopSetOf.cs
@@ -0,0 +1,497 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Numerics;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class PushPopSetOf : Asn1WriterTests
+ {
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PopNewWriter(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ // Maybe ArgumentException isn't right for this, since no argument was provided.
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PopSetOf());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PopNewWriter_CustomTag(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PopSetOf(new Asn1Tag(TagClass.ContextSpecific, (int)ruleSet, true)));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PopBalancedWriter(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSetOf();
+ writer.PopSetOf();
+
+ // Maybe ArgumentException isn't right for this, since no argument was provided.
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PopSetOf());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PopBalancedWriter_CustomTag(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSetOf();
+ writer.PopSetOf();
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PopSetOf(new Asn1Tag(TagClass.ContextSpecific, (int)ruleSet, true)));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushCustom_PopStandard(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSetOf(new Asn1Tag(TagClass.ContextSpecific, (int)ruleSet, true));
+
+ // Maybe ArgumentException isn't right for this, since no argument was provided.
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PopSetOf());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushStandard_PopCustom(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSetOf();
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PopSetOf(new Asn1Tag(TagClass.ContextSpecific, (int)ruleSet, true)));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushPrimitive_PopStandard(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSetOf(new Asn1Tag(UniversalTagNumber.SetOf));
+ writer.PopSetOf();
+
+ if (ruleSet == PublicEncodingRules.CER)
+ {
+ Verify(writer, "31800000");
+ }
+ else
+ {
+ Verify(writer, "3100");
+ }
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushCustomPrimitive_PopConstructed(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSetOf(new Asn1Tag(TagClass.Private, 5));
+ writer.PopSetOf(new Asn1Tag(TagClass.Private, 5, true));
+
+ if (ruleSet == PublicEncodingRules.CER)
+ {
+ Verify(writer, "E5800000");
+ }
+ else
+ {
+ Verify(writer, "E500");
+ }
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushStandard_PopPrimitive(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSetOf();
+ writer.PopSetOf(new Asn1Tag(UniversalTagNumber.SetOf));
+
+ if (ruleSet == PublicEncodingRules.CER)
+ {
+ Verify(writer, "31800000");
+ }
+ else
+ {
+ Verify(writer, "3100");
+ }
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushCustomConstructed_PopPrimitive(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.PushSetOf(new Asn1Tag(TagClass.Private, (int)ruleSet, true));
+ writer.PopSetOf(new Asn1Tag(TagClass.Private, (int)ruleSet));
+
+ byte tag = (byte)((int)ruleSet | 0b1110_0000);
+ string tagHex = tag.ToString("X2");
+ string rest = ruleSet == PublicEncodingRules.CER ? "800000" : "00";
+
+ Verify(writer, tagHex + rest);
+ }
+
+ [Fact]
+ public static void BER_WritesDefinite_Empty()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ writer.PushSetOf();
+ writer.PopSetOf();
+
+ Verify(writer, "3100");
+ }
+
+ [Fact]
+ public static void CER_WritesIndefinite_Empty()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ writer.PushSetOf();
+ writer.PopSetOf();
+
+ Verify(writer, "31800000");
+ }
+
+ [Fact]
+ public static void DER_WritesDefinite_CustomTag_Empty()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ writer.PushSetOf();
+ writer.PopSetOf();
+
+ Verify(writer, "3100");
+ }
+
+ [Fact]
+ public static void BER_WritesDefinite_CustomTag__Empty()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Private, 15, true);
+ writer.PushSetOf(tag);
+ writer.PopSetOf(tag);
+
+ Verify(writer, "EF00");
+ }
+
+ [Fact]
+ public static void CER_WritesIndefinite_CustomTag__Empty()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Application, 91, true);
+ writer.PushSetOf(tag);
+ writer.PopSetOf(tag);
+
+ Verify(writer, "7F5B800000");
+ }
+
+ [Fact]
+ public static void DER_WritesDefinite_CustomTag__Empty()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ Asn1Tag tag = new Asn1Tag(TagClass.ContextSpecific, 30, true);
+ writer.PushSetOf(tag);
+ writer.PopSetOf(tag);
+
+ Verify(writer, "BE00");
+ }
+
+ private static void TestNested(AsnWriter writer, Asn1Tag alt, string expectedHex)
+ {
+ // Written in pre-sorted order, since sorting is a different test.
+ writer.PushSetOf();
+ {
+ writer.PushSetOf();
+ {
+ writer.PushSetOf(alt);
+ {
+ writer.PushSetOf();
+ writer.PopSetOf();
+ }
+
+ writer.PopSetOf(alt);
+ }
+
+ writer.PopSetOf();
+
+ writer.PushSetOf(alt);
+ writer.PopSetOf(alt);
+ }
+
+ writer.PopSetOf();
+
+ Verify(writer, expectedHex);
+ }
+
+ [Fact]
+ public static void BER_Nested()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag alt = new Asn1Tag(TagClass.Private, 127, true);
+
+ TestNested(writer, alt, "310A3105FF7F023100FF7F00");
+ }
+
+ [Fact]
+ public static void CER_Nested()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ Asn1Tag alt = new Asn1Tag(TagClass.ContextSpecific, 12, true);
+
+ TestNested(writer, alt, "31803180AC803180000000000000AC8000000000");
+ }
+
+ [Fact]
+ public static void DER_Nested()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ Asn1Tag alt = new Asn1Tag(TagClass.Application, 5, true);
+
+ TestNested(writer, alt, "31083104650231006500");
+ }
+
+ private static void SimpleContentShift(AsnWriter writer, string expectedHex)
+ {
+ writer.PushSetOf();
+
+ // F00DF00D...F00DF00D
+ byte[] contentBytes = new byte[126];
+
+ for (int i = 0; i < contentBytes.Length; i += 2)
+ {
+ contentBytes[i] = 0xF0;
+ contentBytes[i + 1] = 0x0D;
+ }
+
+ writer.WriteOctetString(contentBytes);
+ writer.PopSetOf();
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void SimpleContentShift(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ const string ExpectedHex =
+ "318180" +
+ "047E" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D";
+
+ SimpleContentShift(writer, ExpectedHex);
+ }
+
+ [Fact]
+ public static void SimpleContentShift_CER()
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+
+ const string ExpectedHex =
+ "3180" +
+ "047E" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "F00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00DF00D" +
+ "0000";
+
+ SimpleContentShift(writer, ExpectedHex);
+ }
+
+ private static void ValidateDataSorting(AsnWriter writer, string expectedHex)
+ {
+ writer.PushSetOf();
+
+ // 02 01 FF
+ writer.WriteInteger(-1);
+ // 02 01 00
+ writer.WriteInteger(0);
+ // 02 02 00 FF
+ writer.WriteInteger(255);
+ // 01 01 FF
+ writer.WriteBoolean(true);
+ // 45 01 00
+ writer.WriteBoolean(new Asn1Tag(TagClass.Application, 5), false);
+ // 02 01 7F
+ writer.WriteInteger(127);
+ // 02 01 80
+ writer.WriteInteger(sbyte.MinValue);
+ // 02 02 00 FE
+ writer.WriteInteger(254);
+ // 02 01 00
+ writer.WriteInteger(0);
+
+ writer.PopSetOf();
+
+ // The correct sort order (CER, DER) is
+ // Universal Boolean: true
+ // Universal Integer: 0
+ // Universal Integer: 0
+ // Universal Integer: 127
+ // Universal Integer: -128
+ // Universal Integer: -1
+ // Universal Integer: 254
+ // Universal Integer: 255
+ // Application 5 (Boolean): false
+
+ // This test would be
+ //
+ // GrabBag ::= SET OF GrabBagItem
+ //
+ // GrabBagItem ::= CHOICE (
+ // value INTEGER
+ // bool BOOLEAN
+ // grr [APPLICATION 5] IMPLICIT BOOLEAN
+ // )
+
+ Verify(writer, expectedHex);
+ }
+
+ [Fact]
+ public static void BER_DoesNotSort()
+ {
+ const string ExpectedHex =
+ "311D" +
+ "0201FF" +
+ "020100" +
+ "020200FF" +
+ "0101FF" +
+ "450100" +
+ "02017F" +
+ "020180" +
+ "020200FE" +
+ "020100";
+
+ ValidateDataSorting(new AsnWriter(AsnEncodingRules.BER), ExpectedHex);
+ }
+
+ [Fact]
+ public static void CER_SortsData()
+ {
+ const string ExpectedHex =
+ "3180" +
+ "0101FF" +
+ "020100" +
+ "020100" +
+ "02017F" +
+ "020180" +
+ "0201FF" +
+ "020200FE" +
+ "020200FF" +
+ "450100" +
+ "0000";
+
+ ValidateDataSorting(new AsnWriter(AsnEncodingRules.CER), ExpectedHex);
+ }
+
+ [Fact]
+ public static void DER_SortsData()
+ {
+ const string ExpectedHex =
+ "311D" +
+ "0101FF" +
+ "020100" +
+ "020100" +
+ "02017F" +
+ "020180" +
+ "0201FF" +
+ "020200FE" +
+ "020200FF" +
+ "450100";
+
+ ValidateDataSorting(new AsnWriter(AsnEncodingRules.DER), ExpectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, false)]
+ [InlineData(PublicEncodingRules.CER, false)]
+ [InlineData(PublicEncodingRules.DER, false)]
+ [InlineData(PublicEncodingRules.BER, true)]
+ [InlineData(PublicEncodingRules.CER, true)]
+ [InlineData(PublicEncodingRules.DER, true)]
+ public static void CannotEncodeWhileUnbalanced(PublicEncodingRules ruleSet, bool customTag)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ if (customTag)
+ {
+ writer.PushSetOf(new Asn1Tag(TagClass.ContextSpecific, (int)ruleSet, true));
+ }
+ else
+ {
+ writer.PushSetOf();
+ }
+
+ int written = -5;
+
+ Assert.Throws<InvalidOperationException>(() => writer.Encode());
+ Assert.Throws<InvalidOperationException>(() => writer.TryEncode(Span<byte>.Empty, out written));
+ Assert.Equal(-5, written);
+
+ byte[] buf = new byte[10];
+ Assert.Throws<InvalidOperationException>(() => writer.TryEncode(buf, out written));
+ Assert.Equal(-5, written);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void PushSetOf_EndOfContents(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.PushSetOf(Asn1Tag.EndOfContents));
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteBMPString.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteBMPString.cs
new file mode 100644
index 0000000000..12c44833e2
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteBMPString.cs
@@ -0,0 +1,294 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Security.Cryptography.Asn1;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class WriteBMPString : WriteCharacterString
+ {
+ public static IEnumerable<object[]> ShortValidCases { get; } = new object[][]
+ {
+ new object[]
+ {
+ string.Empty,
+ "00",
+ },
+ new object[]
+ {
+ "hi",
+ "0400680069",
+ },
+ new object[]
+ {
+ "Dr. & Mrs. Smith\u2010Jones \uFE60 children",
+ "42" +
+ "00440072002E002000260020004D00720073002E00200053006D0069" +
+ "007400682010004A006F006E006500730020FE600020006300680069" +
+ "006C006400720065006E",
+ },
+ };
+
+ public static IEnumerable<object[]> LongValidCases { get; } = new object[][]
+ {
+ new object[]
+ {
+ // 498 Han-fragrant, 497 Han-dark, 23 Han-rich
+ new string('\u9999', 498) + new string('\u6666', 497) + new string('\u4444', 23),
+ "8207F4" + new string('9', 498 * 4) + new string('6', 497 * 4) + new string('4', 23 * 4),
+ },
+ };
+
+ public static IEnumerable<object[]> CERSegmentedCases { get; } = new object[][]
+ {
+ new object[]
+ {
+ GettysburgAddress,
+ 1458 * 2,
+ },
+ new object[]
+ {
+ // 498 Han-fragrant, 497 Han-dark, 5 Han-rich
+ new string('\u9999', 498) + new string('\u6666', 497) + new string('\u4444', 5),
+ 2000,
+ },
+ };
+
+ public static IEnumerable<object[]> InvalidInputs { get; } = new object[][]
+ {
+ // Surrogate pair for "Deseret Small Letter Yee" (U+10437)
+ new object[] { "\uD801\uDC37" },
+ };
+
+ internal override void WriteString(AsnWriter writer, string s) =>
+ writer.WriteCharacterString(UniversalTagNumber.BMPString, s);
+
+ internal override void WriteString(AsnWriter writer, Asn1Tag tag, string s) =>
+ writer.WriteCharacterString(tag, UniversalTagNumber.BMPString, s);
+
+ internal override void WriteSpan(AsnWriter writer, ReadOnlySpan<char> s) =>
+ writer.WriteCharacterString(UniversalTagNumber.BMPString, s);
+
+ internal override void WriteSpan(AsnWriter writer, Asn1Tag tag, ReadOnlySpan<char> s) =>
+ writer.WriteCharacterString(tag, UniversalTagNumber.BMPString, s);
+
+ internal override Asn1Tag StandardTag => new Asn1Tag(UniversalTagNumber.BMPString);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_String(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_String(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_String_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_String_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_String(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_String(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_String_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_String_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_String(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_String(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_String_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_String_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_Span(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_Span(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_Span_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_Span_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_Span(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_Span(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_Span_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_Span_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_Span(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_Span(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_Span_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_Span_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_String_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_String_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_String_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_String_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_Span_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_Span_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_Span_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_Span_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_String_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_String_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_String_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_String_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_Span_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_Span_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_Span_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_Span_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_String_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_String_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_String_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_String_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_Span_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_Span_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_Span_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_Span_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public new void VerifyWrite_String_Null(PublicEncodingRules ruleSet) =>
+ base.VerifyWrite_String_Null(ruleSet);
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public new void VerifyWrite_String_Null_CustomTag(PublicEncodingRules ruleSet) =>
+ base.VerifyWrite_String_Null_CustomTag(ruleSet);
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public new void VerifyWrite_EndOfContents_String(PublicEncodingRules ruleSet) =>
+ base.VerifyWrite_EndOfContents_String(ruleSet);
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public new void VerifyWrite_EndOfContents_Span(PublicEncodingRules ruleSet) =>
+ base.VerifyWrite_EndOfContents_Span(ruleSet);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_String(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_String(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_String_CustomTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_String_CustomTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_String_ConstructedTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_String_ConstructedTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_String_CustomPrimitiveTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_String_CustomPrimitiveTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_Span(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_Span(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_Span_CustomTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_Span_CustomTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_Span_ConstructedTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_Span_ConstructedTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_Span_CustomPrimitiveTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_Span_CustomPrimitiveTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(InvalidInputs))]
+ public new void VerifyWrite_String_NonEncodable(string input) =>
+ base.VerifyWrite_String_NonEncodable(input);
+
+ [Theory]
+ [MemberData(nameof(InvalidInputs))]
+ public new void VerifyWrite_Span_NonEncodable(string input) =>
+ base.VerifyWrite_Span_NonEncodable(input);
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteBitString.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteBitString.cs
new file mode 100644
index 0000000000..2a0c4dd008
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteBitString.cs
@@ -0,0 +1,305 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class WriteBitString : Asn1WriterTests
+ {
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void WriteEmpty(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteBitString(ReadOnlySpan<byte>.Empty);
+
+ Verify(writer, "030100");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, 1, 1, "030201")]
+ [InlineData(PublicEncodingRules.CER, 2, 1, "030301")]
+ [InlineData(PublicEncodingRules.DER, 3, 1, "030401")]
+ [InlineData(PublicEncodingRules.BER, 126, 0, "037F00")]
+ [InlineData(PublicEncodingRules.CER, 127, 3, "03818003")]
+ [InlineData(PublicEncodingRules.BER, 999, 0, "038203E800")]
+ [InlineData(PublicEncodingRules.CER, 999, 0, "038203E800")]
+ [InlineData(PublicEncodingRules.DER, 999, 0, "038203E800")]
+ [InlineData(PublicEncodingRules.BER, 1000, 0, "038203E900")]
+ [InlineData(PublicEncodingRules.DER, 1000, 0, "038203E900")]
+ [InlineData(PublicEncodingRules.BER, 2000, 0, "038207D100")]
+ [InlineData(PublicEncodingRules.DER, 2000, 0, "038207D100")]
+ public void WritePrimitive(PublicEncodingRules ruleSet, int length, int unusedBitCount, string hexStart)
+ {
+ string payloadHex = new string('0', 2 * length);
+ string expectedHex = hexStart + payloadHex;
+ byte[] data = new byte[length];
+
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteBitString(data, unusedBitCount);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(1000, 1, "2380038203E800", "030201")]
+ [InlineData(999*2, 3, "2380038203E800", "038203E803")]
+ public void WriteSegmentedCER(int length, int unusedBitCount, string hexStart, string hexStart2)
+ {
+ string payload1Hex = new string('8', 999 * 2);
+ string payload2Hex = new string('8', (length - 999) * 2);
+ string expectedHex = hexStart + payload1Hex + hexStart2 + payload2Hex + "0000";
+ byte[] data = new byte[length];
+
+ for (int i = 0; i < data.Length; i++)
+ {
+ data[i] = 0x88;
+ }
+
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ writer.WriteBitString(data, unusedBitCount);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, 0, false)]
+ [InlineData(PublicEncodingRules.CER, 0, false)]
+ [InlineData(PublicEncodingRules.DER, 0, false)]
+ [InlineData(PublicEncodingRules.BER, 999, false)]
+ [InlineData(PublicEncodingRules.CER, 999, false)]
+ [InlineData(PublicEncodingRules.DER, 999, false)]
+ [InlineData(PublicEncodingRules.BER, 1000, false)]
+ [InlineData(PublicEncodingRules.CER, 1000, true)]
+ [InlineData(PublicEncodingRules.DER, 1000, false)]
+ [InlineData(PublicEncodingRules.BER, 1998, false)]
+ [InlineData(PublicEncodingRules.CER, 1998, true)]
+ [InlineData(PublicEncodingRules.BER, 4096, false)]
+ [InlineData(PublicEncodingRules.CER, 4096, true)]
+ [InlineData(PublicEncodingRules.DER, 4096, false)]
+ public void VerifyWriteBitString_PrimitiveOrConstructed(
+ PublicEncodingRules ruleSet,
+ int payloadLength,
+ bool expectConstructed)
+ {
+ byte[] data = new byte[payloadLength];
+
+ Asn1Tag[] tagsToTry =
+ {
+ new Asn1Tag(UniversalTagNumber.BitString),
+ new Asn1Tag(UniversalTagNumber.BitString, isConstructed: true),
+ new Asn1Tag(TagClass.Private, 87),
+ new Asn1Tag(TagClass.ContextSpecific, 13, isConstructed: true),
+ };
+
+ byte[] answerBuf = new byte[payloadLength + 100];
+
+ foreach (Asn1Tag toTry in tagsToTry)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteBitString(toTry, data);
+
+ Assert.True(writer.TryEncode(answerBuf, out _));
+ Assert.True(Asn1Tag.TryParse(answerBuf, out Asn1Tag writtenTag, out _));
+
+ if (expectConstructed)
+ {
+ Assert.True(writtenTag.IsConstructed, $"writtenTag.IsConstructed ({toTry})");
+ }
+ else
+ {
+ Assert.False(writtenTag.IsConstructed, $"writtenTag.IsConstructed ({toTry})");
+ }
+
+ Assert.Equal(toTry.TagClass, writtenTag.TagClass);
+ Assert.Equal(toTry.TagValue, writtenTag.TagValue);
+ }
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, 0, "FF", false)]
+ [InlineData(PublicEncodingRules.BER, 1, "FE", false)]
+ [InlineData(PublicEncodingRules.CER, 1, "FE", false)]
+ [InlineData(PublicEncodingRules.DER, 1, "FE", false)]
+ [InlineData(PublicEncodingRules.BER, 1, "FF", true)]
+ [InlineData(PublicEncodingRules.CER, 1, "FF", true)]
+ [InlineData(PublicEncodingRules.DER, 1, "FF", true)]
+ [InlineData(PublicEncodingRules.BER, 7, "C0", true)]
+ [InlineData(PublicEncodingRules.CER, 7, "C0", true)]
+ [InlineData(PublicEncodingRules.DER, 7, "C0", true)]
+ [InlineData(PublicEncodingRules.BER, 7, "80", false)]
+ [InlineData(PublicEncodingRules.CER, 7, "80", false)]
+ [InlineData(PublicEncodingRules.DER, 7, "80", false)]
+ [InlineData(PublicEncodingRules.DER, 7, "40", true)]
+ [InlineData(PublicEncodingRules.DER, 6, "40", false)]
+ [InlineData(PublicEncodingRules.DER, 6, "C0", false)]
+ [InlineData(PublicEncodingRules.DER, 6, "20", true)]
+ [InlineData(PublicEncodingRules.DER, 5, "20", false)]
+ [InlineData(PublicEncodingRules.DER, 5, "A0", false)]
+ [InlineData(PublicEncodingRules.DER, 5, "10", true)]
+ [InlineData(PublicEncodingRules.DER, 4, "10", false)]
+ [InlineData(PublicEncodingRules.DER, 4, "90", false)]
+ [InlineData(PublicEncodingRules.DER, 4, "30", false)]
+ [InlineData(PublicEncodingRules.DER, 4, "08", true)]
+ [InlineData(PublicEncodingRules.DER, 4, "88", true)]
+ [InlineData(PublicEncodingRules.DER, 3, "08", false)]
+ [InlineData(PublicEncodingRules.DER, 3, "A8", false)]
+ [InlineData(PublicEncodingRules.DER, 3, "04", true)]
+ [InlineData(PublicEncodingRules.DER, 3, "14", true)]
+ [InlineData(PublicEncodingRules.DER, 2, "04", false)]
+ [InlineData(PublicEncodingRules.DER, 2, "0C", false)]
+ [InlineData(PublicEncodingRules.DER, 2, "FC", false)]
+ [InlineData(PublicEncodingRules.DER, 2, "02", true)]
+ [InlineData(PublicEncodingRules.DER, 2, "82", true)]
+ [InlineData(PublicEncodingRules.DER, 2, "FE", true)]
+ [InlineData(PublicEncodingRules.DER, 1, "02", false)]
+ [InlineData(PublicEncodingRules.DER, 1, "82", false)]
+ [InlineData(PublicEncodingRules.DER, 1, "FE", false)]
+ [InlineData(PublicEncodingRules.DER, 1, "80", false)]
+ public static void WriteBitString_UnusedBitCount_MustBeValid(
+ PublicEncodingRules ruleSet,
+ int unusedBitCount,
+ string inputHex,
+ bool expectThrow)
+ {
+ byte[] inputBytes = inputHex.HexToByteArray();
+
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ if (expectThrow)
+ {
+ Assert.Throws<CryptographicException>(
+ () => writer.WriteBitString(inputBytes, unusedBitCount));
+
+ Assert.Throws<CryptographicException>(
+ () => writer.WriteBitString(new Asn1Tag(TagClass.ContextSpecific, 3), inputBytes, unusedBitCount));
+
+ return;
+ }
+
+ byte[] output = new byte[512];
+ writer.WriteBitString(inputBytes, unusedBitCount);
+ Assert.True(writer.TryEncode(output, out int bytesWritten));
+
+ // This assumes that inputBytes is never more than 999 (and avoids CER constructed forms)
+ Assert.Equal(unusedBitCount, output[bytesWritten - inputBytes.Length - 1]);
+
+ writer.WriteBitString(new Asn1Tag(TagClass.ContextSpecific, 9), inputBytes, unusedBitCount);
+ Assert.True(writer.TryEncode(output, out bytesWritten));
+
+ Assert.Equal(unusedBitCount, output[bytesWritten - inputBytes.Length - 1]);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, -1)]
+ [InlineData(PublicEncodingRules.CER, -1)]
+ [InlineData(PublicEncodingRules.DER, -1)]
+ [InlineData(PublicEncodingRules.BER, -2)]
+ [InlineData(PublicEncodingRules.CER, -2)]
+ [InlineData(PublicEncodingRules.DER, -2)]
+ [InlineData(PublicEncodingRules.BER, 8)]
+ [InlineData(PublicEncodingRules.CER, 8)]
+ [InlineData(PublicEncodingRules.DER, 8)]
+ [InlineData(PublicEncodingRules.BER, 9)]
+ [InlineData(PublicEncodingRules.CER, 9)]
+ [InlineData(PublicEncodingRules.DER, 9)]
+ [InlineData(PublicEncodingRules.BER, 1048576)]
+ [InlineData(PublicEncodingRules.CER, 1048576)]
+ [InlineData(PublicEncodingRules.DER, 1048576)]
+ public static void UnusedBitCounts_Bounds(PublicEncodingRules ruleSet, int unusedBitCount)
+ {
+ byte[] data = new byte[5];
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ ArgumentOutOfRangeException exception = AssertExtensions.Throws<ArgumentOutOfRangeException>(
+ nameof(unusedBitCount),
+ () => writer.WriteBitString(data, unusedBitCount));
+
+ Assert.Equal(unusedBitCount, exception.ActualValue);
+
+ exception = AssertExtensions.Throws<ArgumentOutOfRangeException>(
+ nameof(unusedBitCount),
+ () => writer.WriteBitString(new Asn1Tag(TagClass.ContextSpecific, 5), data, unusedBitCount));
+
+ Assert.Equal(unusedBitCount, exception.ActualValue);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void EmptyData_Requires0UnusedBits(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => writer.WriteBitString(ReadOnlySpan<byte>.Empty, 1));
+
+ Assert.Throws<CryptographicException>(
+ () => writer.WriteBitString(ReadOnlySpan<byte>.Empty, 7));
+
+ Asn1Tag contextTag = new Asn1Tag(TagClass.ContextSpecific, 19);
+
+ Assert.Throws<CryptographicException>(
+ () => writer.WriteBitString(contextTag, ReadOnlySpan<byte>.Empty, 1));
+
+ Assert.Throws<CryptographicException>(
+ () => writer.WriteBitString(contextTag, ReadOnlySpan<byte>.Empty, 7));
+
+ writer.WriteBitString(ReadOnlySpan<byte>.Empty, 0);
+ writer.WriteBitString(contextTag, ReadOnlySpan<byte>.Empty, 0);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, PublicTagClass.Universal, 3, "030100")]
+ [InlineData(PublicEncodingRules.CER, PublicTagClass.Universal, 3, "030100")]
+ [InlineData(PublicEncodingRules.DER, PublicTagClass.Universal, 3, "030100")]
+ [InlineData(PublicEncodingRules.BER, PublicTagClass.Private, 1, "C10100")]
+ [InlineData(PublicEncodingRules.CER, PublicTagClass.Application, 5, "450100")]
+ [InlineData(PublicEncodingRules.DER, PublicTagClass.ContextSpecific, 32, "9F200100")]
+ public static void EmptyData_Allows0UnusedBits(
+ PublicEncodingRules ruleSet,
+ PublicTagClass tagClass,
+ int tagValue,
+ string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ if (tagClass == PublicTagClass.Universal)
+ {
+ Debug.Assert(tagValue == (int)UniversalTagNumber.BitString);
+ writer.WriteBitString(ReadOnlySpan<byte>.Empty, 0);
+ }
+ else
+ {
+ writer.WriteBitString(new Asn1Tag((TagClass)tagClass, tagValue), ReadOnlySpan<byte>.Empty, 0);
+ }
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void VerifyWriteBitString_EndOfContents(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteBitString(Asn1Tag.EndOfContents, ReadOnlySpan<byte>.Empty));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteBitString(Asn1Tag.EndOfContents, new byte[1]));
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteBoolean.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteBoolean.cs
new file mode 100644
index 0000000000..d52251dd4f
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteBoolean.cs
@@ -0,0 +1,81 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class WriteBoolean : Asn1WriterTests
+ {
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, false, "010100")]
+ [InlineData(PublicEncodingRules.BER, true, "0101FF")]
+ [InlineData(PublicEncodingRules.CER, false, "010100")]
+ [InlineData(PublicEncodingRules.CER, true, "0101FF")]
+ [InlineData(PublicEncodingRules.DER, false, "010100")]
+ [InlineData(PublicEncodingRules.DER, true, "0101FF")]
+ public void VerifyWriteBoolean(PublicEncodingRules ruleSet, bool value, string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteBoolean(value);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, false, "830100")]
+ [InlineData(PublicEncodingRules.BER, true, "8301FF")]
+ [InlineData(PublicEncodingRules.CER, false, "830100")]
+ [InlineData(PublicEncodingRules.CER, true, "8301FF")]
+ [InlineData(PublicEncodingRules.DER, false, "830100")]
+ [InlineData(PublicEncodingRules.DER, true, "8301FF")]
+ public void VerifyWriteBoolean_Context3(PublicEncodingRules ruleSet, bool value, string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteBoolean(new Asn1Tag(TagClass.ContextSpecific, 3), value);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, false)]
+ [InlineData(PublicEncodingRules.BER, true)]
+ [InlineData(PublicEncodingRules.CER, false)]
+ [InlineData(PublicEncodingRules.CER, true)]
+ [InlineData(PublicEncodingRules.DER, false)]
+ [InlineData(PublicEncodingRules.DER, true)]
+ public void VerifyWriteBoolean_EndOfContents(PublicEncodingRules ruleSet, bool value)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteBoolean(Asn1Tag.EndOfContents, value));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, false)]
+ [InlineData(PublicEncodingRules.BER, true)]
+ [InlineData(PublicEncodingRules.CER, false)]
+ [InlineData(PublicEncodingRules.CER, true)]
+ [InlineData(PublicEncodingRules.DER, false)]
+ [InlineData(PublicEncodingRules.DER, true)]
+ public void VerifyWriteBoolean_ConstructedIgnored(PublicEncodingRules ruleSet, bool value)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteBoolean(new Asn1Tag(TagClass.ContextSpecific, 7, true), value);
+ writer.WriteBoolean(new Asn1Tag(UniversalTagNumber.Boolean, true), value);
+
+ if (value)
+ {
+ Verify(writer, "8701FF0101FF");
+ }
+ else
+ {
+ Verify(writer, "870100010100");
+ }
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteCharacterString.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteCharacterString.cs
new file mode 100644
index 0000000000..fd0c579368
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteCharacterString.cs
@@ -0,0 +1,437 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using System.Text;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public abstract class WriteCharacterString : Asn1WriterTests
+ {
+ internal abstract void WriteString(AsnWriter writer, string s);
+ internal abstract void WriteString(AsnWriter writer, Asn1Tag tag, string s);
+
+ internal abstract void WriteSpan(AsnWriter writer, ReadOnlySpan<char> s);
+ internal abstract void WriteSpan(AsnWriter writer, Asn1Tag tag, ReadOnlySpan<char> s);
+
+ internal abstract Asn1Tag StandardTag { get; }
+
+ protected const string GettysburgAddress =
+ "Four score and seven years ago our fathers brought forth on this continent, a new nation, " +
+ "conceived in Liberty, and dedicated to the proposition that all men are created equal.\r\n" +
+ "\r\n" +
+ "Now we are engaged in a great civil war, testing whether that nation, or any nation so " +
+ "conceived and so dedicated, can long endure. We are met on a great battle-field of that " +
+ "war. We have come to dedicate a portion of that field, as a final resting place for those " +
+ "who here gave their lives that that nation might live. It is altogether fitting and proper " +
+ "that we should do this.\r\n" +
+ "\r\n" +
+ "But, in a larger sense, we can not dedicate-we can not consecrate-we can not hallow-this " +
+ "ground. The brave men, living and dead, who struggled here, have consecrated it, far above " +
+ "our poor power to add or detract. The world will little note, nor long remember what we say " +
+ "here, but it can never forget what they did here. It is for us the living, rather, to be " +
+ "dedicated here to the unfinished work which they who fought here have thus far so nobly " +
+ "advanced. It is rather for us to be here dedicated to the great task remaining before " +
+ "us-that from these honored dead we take increased devotion to that cause for which they " +
+ "gave the last full measure of devotion-that we here highly resolve that these dead shall " +
+ "not have died in vain-that this nation, under God, shall have a new birth of freedom-and " +
+ "that government of the people, by the people, for the people, shall not perish from the earth.";
+
+ protected void VerifyWrite_BER_String(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ WriteString(writer, input);
+
+ Verify(writer, Stringify(StandardTag) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_BER_String_CustomTag(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag tag = new Asn1Tag(TagClass.ContextSpecific, 14);
+ WriteString(writer, tag, input);
+
+ Verify(writer, Stringify(tag) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_CER_String(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ WriteString(writer, input);
+
+ Verify(writer, Stringify(StandardTag) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_CER_String_CustomTag(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Private, 19);
+ WriteString(writer, tag, input);
+
+ Verify(writer, Stringify(tag) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_DER_String(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ WriteString(writer, input);
+
+ Verify(writer, Stringify(StandardTag) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_DER_String_CustomTag(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Application, 2);
+ WriteString(writer, tag, input);
+
+ Verify(writer, Stringify(tag) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_BER_Span(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ WriteSpan(writer, input.AsReadOnlySpan());
+
+ Verify(writer, Stringify(StandardTag) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_BER_Span_CustomTag(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Private, int.MaxValue >> 1);
+ WriteSpan(writer, tag, input.AsReadOnlySpan());
+
+ Verify(writer, Stringify(tag) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_CER_Span(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ WriteSpan(writer, input.AsReadOnlySpan());
+
+ Verify(writer, Stringify(StandardTag) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_CER_Span_CustomTag(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Application, 30);
+ WriteSpan(writer, tag, input.AsReadOnlySpan());
+
+ Verify(writer, Stringify(tag) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_DER_Span(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ WriteSpan(writer, input.AsReadOnlySpan());
+
+ Verify(writer, Stringify(StandardTag) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_DER_Span_CustomTag(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag tag = new Asn1Tag(TagClass.ContextSpecific, 31);
+ WriteSpan(writer, tag, input.AsReadOnlySpan());
+
+ Verify(writer, Stringify(tag) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_BER_String_ClearsConstructed(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag standard = StandardTag;
+ Asn1Tag tag = new Asn1Tag(standard.TagClass, standard.TagValue, isConstructed: true);
+ WriteString(writer, tag, input);
+
+ Verify(writer, Stringify(standard) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_BER_String_CustomTag_ClearsConstructed(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Application, 19, isConstructed: true);
+ Asn1Tag expected = new Asn1Tag(tag.TagClass, tag.TagValue);
+ WriteString(writer, tag, input);
+
+ Verify(writer, Stringify(expected) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_BER_Span_ClearsConstructed(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag standard = StandardTag;
+ Asn1Tag tag = new Asn1Tag(standard.TagClass, standard.TagValue, isConstructed: true);
+ WriteSpan(writer, tag, input.AsReadOnlySpan());
+
+ Verify(writer, Stringify(standard) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_BER_Span_CustomTag_ClearsConstructed(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Private, 24601, isConstructed: true);
+ Asn1Tag expected = new Asn1Tag(tag.TagClass, tag.TagValue);
+ WriteSpan(writer, tag, input.AsReadOnlySpan());
+
+ Verify(writer, Stringify(expected) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_CER_String_ClearsConstructed(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ Asn1Tag standard = StandardTag;
+ Asn1Tag tag = new Asn1Tag(standard.TagClass, standard.TagValue, isConstructed: true);
+ WriteString(writer, tag, input);
+
+ Verify(writer, Stringify(standard) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_CER_String_CustomTag_ClearsConstructed(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ Asn1Tag tag = new Asn1Tag(TagClass.ContextSpecific, 1701, isConstructed: true);
+ Asn1Tag expected = new Asn1Tag(tag.TagClass, tag.TagValue);
+ WriteString(writer, tag, input);
+
+ Verify(writer, Stringify(expected) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_CER_Span_ClearsConstructed(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ Asn1Tag standard = StandardTag;
+ Asn1Tag tag = new Asn1Tag(standard.TagClass, standard.TagValue, isConstructed: true);
+ WriteSpan(writer, tag, input.AsReadOnlySpan());
+
+ Verify(writer, Stringify(standard) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_CER_Span_CustomTag_ClearsConstructed(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Application, 11, isConstructed: true);
+ Asn1Tag expected = new Asn1Tag(tag.TagClass, tag.TagValue);
+ WriteSpan(writer, tag, input.AsReadOnlySpan());
+
+ Verify(writer, Stringify(expected) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_DER_String_ClearsConstructed(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ Asn1Tag standard = StandardTag;
+ Asn1Tag tag = new Asn1Tag(standard.TagClass, standard.TagValue, isConstructed: true);
+ WriteString(writer, tag, input);
+
+ Verify(writer, Stringify(standard) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_DER_String_CustomTag_ClearsConstructed(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Application, 19, isConstructed: true);
+ Asn1Tag expected = new Asn1Tag(tag.TagClass, tag.TagValue);
+ WriteString(writer, tag, input);
+
+ Verify(writer, Stringify(expected) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_DER_Span_ClearsConstructed(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ Asn1Tag standard = StandardTag;
+ Asn1Tag tag = new Asn1Tag(standard.TagClass, standard.TagValue, isConstructed: true);
+ WriteSpan(writer, tag, input.AsReadOnlySpan());
+
+ Verify(writer, Stringify(standard) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_DER_Span_CustomTag_ClearsConstructed(string input, string expectedPayloadHex)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Private, 24601, isConstructed: true);
+ Asn1Tag expected = new Asn1Tag(tag.TagClass, tag.TagValue);
+ WriteSpan(writer, tag, input.AsReadOnlySpan());
+
+ Verify(writer, Stringify(expected) + expectedPayloadHex);
+ }
+
+ protected void VerifyWrite_String_Null(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentNullException>(
+ "str",
+ () => WriteString(writer, null));
+
+ }
+
+ protected void VerifyWrite_String_Null_CustomTag(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentNullException>(
+ "str",
+ () => WriteString(writer, new Asn1Tag(TagClass.ContextSpecific, 3), null));
+ }
+
+ protected void VerifyWrite_EndOfContents_String(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => WriteString(writer, Asn1Tag.EndOfContents, "hi"));
+ }
+
+ protected void VerifyWrite_EndOfContents_Span(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => WriteSpan(writer, Asn1Tag.EndOfContents, "hi".AsReadOnlySpan()));
+ }
+
+ private void VerifyWrite_CERSegmented(AsnWriter writer, string tagHex, int contentByteCount)
+ {
+ int div = Math.DivRem(contentByteCount, 1000, out int rem);
+
+ // tag, length(80), div full segments at 1004 bytes each, and the end of contents.
+ int encodedSize = (tagHex.Length / 2) + 1 + 1004 * div + 2;
+
+ if (rem != 0)
+ {
+ // tag, contents (length TBD)
+ encodedSize += 1 + rem;
+
+ if (encodedSize < 0x80)
+ encodedSize++;
+ else if (encodedSize <= 0xFF)
+ encodedSize += 2;
+ else
+ encodedSize += 3;
+ }
+
+ byte[] encoded = writer.Encode();
+
+ Assert.Equal(tagHex, encoded.AsReadOnlySpan().Slice(0, tagHex.Length / 2).ByteArrayToHex());
+ Assert.Equal("0000", encoded.AsReadOnlySpan().Slice(encoded.Length - 2).ByteArrayToHex());
+ Assert.Equal(encodedSize, encoded.Length);
+ }
+
+ protected void VerifyWrite_CERSegmented_String(string input, int contentByteCount)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+
+ Asn1Tag standard = StandardTag;
+ Asn1Tag tag = new Asn1Tag(standard.TagClass, standard.TagValue, true);
+ string tagHex = Stringify(tag);
+
+ WriteString(writer, input);
+ VerifyWrite_CERSegmented(writer, tagHex, contentByteCount);
+ }
+
+ protected void VerifyWrite_CERSegmented_String_CustomTag(string input, int contentByteCount)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+
+ Asn1Tag tag = new Asn1Tag(TagClass.Private, 7, true);
+ string tagHex = Stringify(tag);
+
+ WriteString(writer, tag, input);
+ VerifyWrite_CERSegmented(writer, tagHex, contentByteCount);
+ }
+
+ protected void VerifyWrite_CERSegmented_String_ConstructedTag(string input, int contentByteCount)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+
+ Asn1Tag standard = StandardTag;
+ Asn1Tag tag = new Asn1Tag(standard.TagClass, standard.TagValue, true);
+ string tagHex = Stringify(tag);
+
+ WriteString(writer, tag, input);
+ VerifyWrite_CERSegmented(writer, tagHex, contentByteCount);
+ }
+
+ protected void VerifyWrite_CERSegmented_String_CustomPrimitiveTag(string input, int contentByteCount)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+
+ Asn1Tag prim = new Asn1Tag(TagClass.Application, 42);
+ Asn1Tag constr = new Asn1Tag(prim.TagClass, prim.TagValue, true);
+ string tagHex = Stringify(constr);
+
+ WriteString(writer, prim, input);
+ VerifyWrite_CERSegmented(writer, tagHex, contentByteCount);
+ }
+
+ protected void VerifyWrite_CERSegmented_Span(string input, int contentByteCount)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+
+ Asn1Tag standard = StandardTag;
+ Asn1Tag tag = new Asn1Tag(standard.TagClass, standard.TagValue, true);
+ string tagHex = Stringify(tag);
+
+ WriteSpan(writer, input.AsReadOnlySpan());
+ VerifyWrite_CERSegmented(writer, tagHex, contentByteCount);
+ }
+
+ protected void VerifyWrite_CERSegmented_Span_CustomTag(string input, int contentByteCount)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+
+ Asn1Tag tag = new Asn1Tag(TagClass.Private, 7, true);
+ string tagHex = Stringify(tag);
+
+ WriteSpan(writer, tag, input.AsReadOnlySpan());
+ VerifyWrite_CERSegmented(writer, tagHex, contentByteCount);
+ }
+
+ protected void VerifyWrite_CERSegmented_Span_ConstructedTag(string input, int contentByteCount)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+
+ Asn1Tag standard = StandardTag;
+ Asn1Tag tag = new Asn1Tag(standard.TagClass, standard.TagValue, true);
+ string tagHex = Stringify(tag);
+
+ WriteSpan(writer, tag, input.AsReadOnlySpan());
+ VerifyWrite_CERSegmented(writer, tagHex, contentByteCount);
+ }
+
+ protected void VerifyWrite_CERSegmented_Span_CustomPrimitiveTag(string input, int contentByteCount)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+
+ Asn1Tag prim = new Asn1Tag(TagClass.Application, 42);
+ Asn1Tag constr = new Asn1Tag(prim.TagClass, prim.TagValue, true);
+ string tagHex = Stringify(constr);
+
+ WriteSpan(writer, prim, input.AsReadOnlySpan());
+ VerifyWrite_CERSegmented(writer, tagHex, contentByteCount);
+ }
+
+ protected void VerifyWrite_String_NonEncodable(string input)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+
+ Assert.Throws<EncoderFallbackException>(() => WriteString(writer, input));
+ }
+
+ protected void VerifyWrite_Span_NonEncodable(string input)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+
+ Assert.Throws<EncoderFallbackException>(() => WriteSpan(writer, input.AsReadOnlySpan()));
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteEnumerated.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteEnumerated.cs
new file mode 100644
index 0000000000..dead5a4a42
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteEnumerated.cs
@@ -0,0 +1,402 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Security.Cryptography.Asn1;
+using System.Security.Cryptography.X509Certificates;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class WriteEnumerated : Asn1WriterTests
+ {
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.SByteBacked.Zero, false, "0A0100")]
+ [InlineData(PublicEncodingRules.CER, ReadEnumerated.SByteBacked.Pillow, true, "9E01EF")]
+ [InlineData(PublicEncodingRules.DER, ReadEnumerated.SByteBacked.Fluff, false, "0A0153")]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.SByteBacked.Fluff, true, "9E0153")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.SByteBacked)(-127), true, "9E0181")]
+ public void VerifyWriteEnumerated_SByte(
+ PublicEncodingRules ruleSet,
+ ReadEnumerated.SByteBacked value,
+ bool customTag,
+ string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ if (customTag)
+ {
+ writer.WriteEnumeratedValue(new Asn1Tag(TagClass.ContextSpecific, 30), value);
+ }
+ else
+ {
+ writer.WriteEnumeratedValue(value);
+ }
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.ByteBacked.Zero, false, "0A0100")]
+ [InlineData(PublicEncodingRules.CER, ReadEnumerated.ByteBacked.NotFluffy, true, "9A010B")]
+ [InlineData(PublicEncodingRules.DER, ReadEnumerated.ByteBacked.Fluff, false, "0A010C")]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.ByteBacked.Fluff, true, "9A010C")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.ByteBacked)253, false, "0A0200FD")]
+ public void VerifyWriteEnumerated_Byte(
+ PublicEncodingRules ruleSet,
+ ReadEnumerated.ByteBacked value,
+ bool customTag,
+ string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ if (customTag)
+ {
+ writer.WriteEnumeratedValue(new Asn1Tag(TagClass.ContextSpecific, 26), value);
+ }
+ else
+ {
+ writer.WriteEnumeratedValue(value);
+ }
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.ShortBacked.Zero, true, "DF81540100")]
+ [InlineData(PublicEncodingRules.CER, ReadEnumerated.ShortBacked.Pillow, true, "DF815402FC00")]
+ [InlineData(PublicEncodingRules.DER, ReadEnumerated.ShortBacked.Fluff, false, "0A020209")]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.ShortBacked.Fluff, true, "DF8154020209")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.ShortBacked)25321, false, "0A0262E9")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.ShortBacked)(-12345), false, "0A02CFC7")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.ShortBacked)(-1), true, "DF815401FF")]
+ public void VerifyWriteEnumerated_Short(
+ PublicEncodingRules ruleSet,
+ ReadEnumerated.ShortBacked value,
+ bool customTag,
+ string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ if (customTag)
+ {
+ writer.WriteEnumeratedValue(new Asn1Tag(TagClass.Private, 212), value);
+ }
+ else
+ {
+ writer.WriteEnumeratedValue(value);
+ }
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.UShortBacked.Zero, false, "0A0100")]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.UShortBacked.Zero, true, "4D0100")]
+ [InlineData(PublicEncodingRules.DER, ReadEnumerated.UShortBacked.Fluff, false, "0A03008000")]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.UShortBacked.Fluff, true, "4D03008000")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.UShortBacked)11, false, "0A010B")]
+ [InlineData(PublicEncodingRules.DER, (ReadEnumerated.UShortBacked)short.MaxValue, false, "0A027FFF")]
+ [InlineData(PublicEncodingRules.BER, (ReadEnumerated.UShortBacked)ushort.MaxValue, true, "4D0300FFFF")]
+ public void VerifyWriteEnumerated_UShort(
+ PublicEncodingRules ruleSet,
+ ReadEnumerated.UShortBacked value,
+ bool customTag,
+ string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ if (customTag)
+ {
+ writer.WriteEnumeratedValue(new Asn1Tag(TagClass.Application, 13), value);
+ }
+ else
+ {
+ writer.WriteEnumeratedValue(value);
+ }
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.IntBacked.Zero, true, "5F81FF7F0100")]
+ [InlineData(PublicEncodingRules.CER, ReadEnumerated.IntBacked.Pillow, true, "5F81FF7F03FEFFFF")]
+ [InlineData(PublicEncodingRules.DER, ReadEnumerated.IntBacked.Fluff, false, "0A03010001")]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.IntBacked.Fluff, true, "5F81FF7F03010001")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.IntBacked)25321, false, "0A0262E9")]
+ [InlineData(PublicEncodingRules.DER, (ReadEnumerated.IntBacked)(-12345), false, "0A02CFC7")]
+ [InlineData(PublicEncodingRules.BER, (ReadEnumerated.IntBacked)(-1), true, "5F81FF7F01FF")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.IntBacked)int.MinValue, true, "5F81FF7F0480000000")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.IntBacked)int.MaxValue, false, "0A047FFFFFFF")]
+ public void VerifyWriteEnumerated_Int(
+ PublicEncodingRules ruleSet,
+ ReadEnumerated.IntBacked value,
+ bool customTag,
+ string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ if (customTag)
+ {
+ writer.WriteEnumeratedValue(new Asn1Tag(TagClass.Application, short.MaxValue), value);
+ }
+ else
+ {
+ writer.WriteEnumeratedValue(value);
+ }
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.UIntBacked.Zero, false, "0A0100")]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.UIntBacked.Zero, true, "9F610100")]
+ [InlineData(PublicEncodingRules.DER, ReadEnumerated.UIntBacked.Fluff, false, "0A050080000005")]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.UIntBacked.Fluff, true, "9F61050080000005")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.UIntBacked)11, false, "0A010B")]
+ [InlineData(PublicEncodingRules.DER, (ReadEnumerated.UIntBacked)short.MaxValue, false, "0A027FFF")]
+ [InlineData(PublicEncodingRules.BER, (ReadEnumerated.UIntBacked)ushort.MaxValue, true, "9F610300FFFF")]
+ public void VerifyWriteEnumerated_UInt(
+ PublicEncodingRules ruleSet,
+ ReadEnumerated.UIntBacked value,
+ bool customTag,
+ string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ if (customTag)
+ {
+ writer.WriteEnumeratedValue(new Asn1Tag(TagClass.ContextSpecific, 97), value);
+ }
+ else
+ {
+ writer.WriteEnumeratedValue(value);
+ }
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.LongBacked.Zero, true, "800100")]
+ [InlineData(PublicEncodingRules.CER, ReadEnumerated.LongBacked.Pillow, true, "8005FF00000000")]
+ [InlineData(PublicEncodingRules.DER, ReadEnumerated.LongBacked.Fluff, false, "0A050200000441")]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.LongBacked.Fluff, true, "80050200000441")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.LongBacked)25321, false, "0A0262E9")]
+ [InlineData(PublicEncodingRules.DER, (ReadEnumerated.LongBacked)(-12345), false, "0A02CFC7")]
+ [InlineData(PublicEncodingRules.BER, (ReadEnumerated.LongBacked)(-1), true, "8001FF")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.LongBacked)int.MinValue, true, "800480000000")]
+ [InlineData(PublicEncodingRules.DER, (ReadEnumerated.LongBacked)int.MaxValue, false, "0A047FFFFFFF")]
+ [InlineData(PublicEncodingRules.BER, (ReadEnumerated.LongBacked)long.MinValue, false, "0A088000000000000000")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.LongBacked)long.MaxValue, true, "80087FFFFFFFFFFFFFFF")]
+ public void VerifyWriteEnumerated_Long(
+ PublicEncodingRules ruleSet,
+ ReadEnumerated.LongBacked value,
+ bool customTag,
+ string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ if (customTag)
+ {
+ writer.WriteEnumeratedValue(new Asn1Tag(TagClass.ContextSpecific, 0), value);
+ }
+ else
+ {
+ writer.WriteEnumeratedValue(value);
+ }
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.ULongBacked.Zero, false, "0A0100")]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.ULongBacked.Zero, true, "C10100")]
+ [InlineData(PublicEncodingRules.DER, ReadEnumerated.ULongBacked.Fluff, false, "0A0900FACEF00DCAFEBEEF")]
+ [InlineData(PublicEncodingRules.BER, ReadEnumerated.ULongBacked.Fluff, true, "C10900FACEF00DCAFEBEEF")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.ULongBacked)11, false, "0A010B")]
+ [InlineData(PublicEncodingRules.DER, (ReadEnumerated.ULongBacked)short.MaxValue, false, "0A027FFF")]
+ [InlineData(PublicEncodingRules.BER, (ReadEnumerated.ULongBacked)ushort.MaxValue, true, "C10300FFFF")]
+ [InlineData(PublicEncodingRules.CER, (ReadEnumerated.ULongBacked)long.MaxValue, true, "C1087FFFFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.DER, (ReadEnumerated.ULongBacked)ulong.MaxValue, false, "0A0900FFFFFFFFFFFFFFFF")]
+ public void VerifyWriteEnumerated_ULong(
+ PublicEncodingRules ruleSet,
+ ReadEnumerated.ULongBacked value,
+ bool customTag,
+ string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ if (customTag)
+ {
+ writer.WriteEnumeratedValue(new Asn1Tag(TagClass.Private, 1), value);
+ }
+ else
+ {
+ writer.WriteEnumeratedValue(value);
+ }
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void VerifyFlagsBased(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tEnum",
+ () => writer.WriteEnumeratedValue(OpenFlags.IncludeArchived));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tEnum",
+ () => writer.WriteEnumeratedValue(
+ new Asn1Tag(TagClass.ContextSpecific, 13),
+ OpenFlags.IncludeArchived));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tEnum",
+ () => writer.WriteEnumeratedValue((object)OpenFlags.IncludeArchived));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tEnum",
+ () => writer.WriteEnumeratedValue(
+ new Asn1Tag(TagClass.ContextSpecific, 13),
+ (object)OpenFlags.IncludeArchived));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void VerifyNonEnum(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ Assert.Throws<ArgumentException>(
+ () => writer.WriteEnumeratedValue(5));
+
+ Assert.Throws<ArgumentException>(
+ () => writer.WriteEnumeratedValue((object)"hi"));
+
+ Assert.Throws<ArgumentException>(
+ () => writer.WriteEnumeratedValue((object)5));
+
+ Assert.Throws<ArgumentException>(
+ () => writer.WriteEnumeratedValue(new Asn1Tag(TagClass.ContextSpecific, 3), 5));
+
+ Assert.Throws<ArgumentException>(
+ () => writer.WriteEnumeratedValue(new Asn1Tag(TagClass.ContextSpecific, 3), (object)"hi"));
+
+ Assert.Throws<ArgumentException>(
+ () => writer.WriteEnumeratedValue(new Asn1Tag(TagClass.ContextSpecific, 3), (object)5));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void VerifyEndOfContents(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteEnumeratedValue(Asn1Tag.EndOfContents, ReadEnumerated.IntBacked.Pillow));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteEnumeratedValue(Asn1Tag.EndOfContents, (object)ReadEnumerated.IntBacked.Pillow));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void VerifyWriteEnumeratedValue_NonNull(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentNullException>(
+ "enumValue",
+ () => writer.WriteEnumeratedValue(null));
+
+ AssertExtensions.Throws<ArgumentNullException>(
+ "enumValue",
+ () => writer.WriteEnumeratedValue(
+ new Asn1Tag(TagClass.ContextSpecific, 1),
+ null));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void VerifyWriteEnumeratedValue_Object(PublicEncodingRules ruleSet)
+ {
+ AsnWriter objWriter = new AsnWriter((AsnEncodingRules)ruleSet);
+ AsnWriter genWriter = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ genWriter.WriteEnumeratedValue(ReadEnumerated.UIntBacked.Fluff);
+ objWriter.WriteEnumeratedValue((object)ReadEnumerated.UIntBacked.Fluff);
+
+ genWriter.WriteEnumeratedValue(ReadEnumerated.SByteBacked.Fluff);
+ objWriter.WriteEnumeratedValue((object)ReadEnumerated.SByteBacked.Fluff);
+
+ genWriter.WriteEnumeratedValue(ReadEnumerated.ULongBacked.Fluff);
+ objWriter.WriteEnumeratedValue((object)ReadEnumerated.ULongBacked.Fluff);
+
+ Verify(objWriter, genWriter.Encode().ByteArrayToHex());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void VerifyWriteEnumeratedValue_Object_WithTag(PublicEncodingRules ruleSet)
+ {
+ AsnWriter objWriter = new AsnWriter((AsnEncodingRules)ruleSet);
+ AsnWriter genWriter = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ Asn1Tag tag = new Asn1Tag(TagClass.ContextSpecific, 52);
+
+ genWriter.WriteEnumeratedValue(tag, ReadEnumerated.UIntBacked.Fluff);
+ objWriter.WriteEnumeratedValue(tag, (object)ReadEnumerated.UIntBacked.Fluff);
+
+ tag = new Asn1Tag(TagClass.Private, 4);
+
+ genWriter.WriteEnumeratedValue(tag, ReadEnumerated.SByteBacked.Fluff);
+ objWriter.WriteEnumeratedValue(tag, (object)ReadEnumerated.SByteBacked.Fluff);
+
+ tag = new Asn1Tag(TagClass.Application, 75);
+
+ genWriter.WriteEnumeratedValue(tag, ReadEnumerated.ULongBacked.Fluff);
+ objWriter.WriteEnumeratedValue(tag, (object)ReadEnumerated.ULongBacked.Fluff);
+
+ Verify(objWriter, genWriter.Encode().ByteArrayToHex());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void VerifyWriteEnumeratedValue_ConstructedIgnored(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ writer.WriteEnumeratedValue(
+ new Asn1Tag(UniversalTagNumber.Enumerated, isConstructed: true),
+ ReadEnumerated.ULongBacked.Fluff);
+
+ writer.WriteEnumeratedValue(
+ new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true),
+ (object)ReadEnumerated.SByteBacked.Fluff);
+
+ Verify(writer, "0A0900FACEF00DCAFEBEEF" + "800153");
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteGeneralizedTime.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteGeneralizedTime.cs
new file mode 100644
index 0000000000..29fd0b980b
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteGeneralizedTime.cs
@@ -0,0 +1,244 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Security.Cryptography.Asn1;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class WriteGeneralizedTime : Asn1WriterTests
+ {
+ public static IEnumerable<object[]> TestCases { get; } = new object[][]
+ {
+ new object[]
+ {
+ new DateTimeOffset(2017, 10, 16, 8, 24, 3, TimeSpan.FromHours(-7)),
+ false,
+ "0F32303137313031363135323430335A",
+ },
+ new object[]
+ {
+ new DateTimeOffset(1817, 10, 16, 21, 24, 3, TimeSpan.FromHours(6)),
+ false,
+ "0F31383137313031363135323430335A",
+ },
+ new object[]
+ {
+ new DateTimeOffset(3000, 1, 1, 0, 0, 0, TimeSpan.Zero),
+ false,
+ "0F33303030303130313030303030305A",
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 999, TimeSpan.Zero),
+ false,
+ "1331393939313233313233353935392E3939395A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 999, TimeSpan.Zero),
+ true,
+ "0F31393939313233313233353935395A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 880, TimeSpan.Zero),
+ false,
+ "1231393939313233313233353935392E38385A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 880, TimeSpan.Zero),
+ true,
+ "0F31393939313233313233353935395A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 700, TimeSpan.Zero),
+ false,
+ "1131393939313233313233353935392E375A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 700, TimeSpan.Zero),
+ true,
+ "0F31393939313233313233353935395A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 123, TimeSpan.Zero) + TimeSpan.FromTicks(4567),
+ false,
+ "1731393939313233313233353935392E313233343536375A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 123, TimeSpan.Zero) + TimeSpan.FromTicks(4567),
+ true,
+ "0F31393939313233313233353935395A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 12, TimeSpan.Zero) + TimeSpan.FromTicks(3450),
+ false,
+ "1631393939313233313233353935392E3031323334355A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 12, TimeSpan.Zero) + TimeSpan.FromTicks(3450),
+ true,
+ "0F31393939313233313233353935395A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 1, TimeSpan.Zero) + TimeSpan.FromTicks(2300),
+ false,
+ "1531393939313233313233353935392E30303132335A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 1, TimeSpan.Zero) + TimeSpan.FromTicks(2300),
+ true,
+ "0F31393939313233313233353935395A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 0, TimeSpan.Zero) + TimeSpan.FromTicks(1000),
+ false,
+ "1431393939313233313233353935392E303030315A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, 0, TimeSpan.Zero) + TimeSpan.FromTicks(1000),
+ true,
+ "0F31393939313233313233353935395A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, TimeSpan.Zero) + TimeSpan.FromTicks(1),
+ false,
+ "1731393939313233313233353935392E303030303030315A"
+ },
+ new object[]
+ {
+ new DateTimeOffset(1999, 12, 31, 23, 59, 59, TimeSpan.Zero) + TimeSpan.FromTicks(1),
+ true,
+ "0F31393939313233313233353935395A"
+ },
+ };
+
+ [Theory]
+ [MemberData(nameof(TestCases))]
+ public void VerifyWriteGeneralizedTime_BER(
+ DateTimeOffset input,
+ bool omitFractionalSeconds,
+ string expectedHexPayload)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ writer.WriteGeneralizedTime(input, omitFractionalSeconds);
+
+ Verify(writer, "18" + expectedHexPayload);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestCases))]
+ public void VerifyWriteGeneralizedTime_BER_CustomTag(
+ DateTimeOffset input,
+ bool omitFractionalSeconds,
+ string expectedHexPayload)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Application, 11);
+ writer.WriteGeneralizedTime(tag, input, omitFractionalSeconds);
+
+ Verify(writer, Stringify(tag) + expectedHexPayload);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestCases))]
+ public void VerifyWriteGeneralizedTime_CER(
+ DateTimeOffset input,
+ bool omitFractionalSeconds,
+ string expectedHexPayload)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ writer.WriteGeneralizedTime(input, omitFractionalSeconds);
+
+ Verify(writer, "18" + expectedHexPayload);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestCases))]
+ public void VerifyWriteGeneralizedTime_CER_CustomTag(
+ DateTimeOffset input,
+ bool omitFractionalSeconds,
+ string expectedHexPayload)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Private, 95);
+ writer.WriteGeneralizedTime(tag, input, omitFractionalSeconds);
+
+ Verify(writer, Stringify(tag) + expectedHexPayload);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestCases))]
+ public void VerifyWriteGeneralizedTime_DER(
+ DateTimeOffset input,
+ bool omitFractionalSeconds,
+ string expectedHexPayload)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ writer.WriteGeneralizedTime(input, omitFractionalSeconds);
+
+ Verify(writer, "18" + expectedHexPayload);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestCases))]
+ public void VerifyWriteGeneralizedTime_DER_CustomTag(
+ DateTimeOffset input,
+ bool omitFractionalSeconds,
+ string expectedHexPayload)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ Asn1Tag tag = new Asn1Tag(TagClass.ContextSpecific, 3);
+ writer.WriteGeneralizedTime(tag, input, omitFractionalSeconds);
+
+ Verify(writer, Stringify(tag) + expectedHexPayload);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, false)]
+ [InlineData(PublicEncodingRules.CER, false)]
+ [InlineData(PublicEncodingRules.DER, false)]
+ [InlineData(PublicEncodingRules.BER, true)]
+ [InlineData(PublicEncodingRules.CER, true)]
+ [InlineData(PublicEncodingRules.DER, true)]
+ public void VerifyWriteGeneralizedTime_EndOfContents(
+ PublicEncodingRules ruleSet,
+ bool omitFractionalSeconds)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteGeneralizedTime(Asn1Tag.EndOfContents, DateTimeOffset.Now, omitFractionalSeconds));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void VerifyWriteGeneralizedTime_IgnoresConstructed(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ DateTimeOffset value = new DateTimeOffset(2017, 11, 16, 17, 35, 1, TimeSpan.Zero);
+
+ writer.WriteGeneralizedTime(new Asn1Tag(UniversalTagNumber.GeneralizedTime, true), value);
+ writer.WriteGeneralizedTime(new Asn1Tag(TagClass.ContextSpecific, 3, true), value);
+ Verify(writer, "180F32303137313131363137333530315A" + "830F32303137313131363137333530315A");
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteIA5String.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteIA5String.cs
new file mode 100644
index 0000000000..8bcda287a5
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteIA5String.cs
@@ -0,0 +1,288 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Security.Cryptography.Asn1;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class WriteIA5String : WriteCharacterString
+ {
+ public static IEnumerable<object[]> ShortValidCases { get; } = new object[][]
+ {
+ new object[]
+ {
+ string.Empty,
+ "00",
+ },
+ new object[]
+ {
+ "hi",
+ "026869",
+ },
+ new object[]
+ {
+ "Dr. & Mrs. Smith-Jones & children",
+ "2144722E2026204D72732E20536D6974682D4A6F6E65732026206368696C6472656E",
+ },
+ };
+
+ public static IEnumerable<object[]> LongValidCases { get; } = new object[][]
+ {
+ new object[]
+ {
+ new string('f', 957) + new string('w', 182),
+ "820473" + new string('6', 957 * 2) + new string('7', 182 * 2),
+ },
+ };
+
+ public static IEnumerable<object[]> CERSegmentedCases { get; } = new object[][]
+ {
+ new object[]
+ {
+ GettysburgAddress,
+ 1458,
+ },
+ new object[]
+ {
+ new string ('Q', 2000),
+ 2000,
+ },
+ };
+
+ public static IEnumerable<object[]> InvalidInputs { get; } = new object[][]
+ {
+ new object[] { "Dr. & Mrs. Smith\u2010Jones \uFE60 children", },
+ };
+
+ internal override void WriteString(AsnWriter writer, string s) =>
+ writer.WriteCharacterString(UniversalTagNumber.IA5String, s);
+
+ internal override void WriteString(AsnWriter writer, Asn1Tag tag, string s) =>
+ writer.WriteCharacterString(tag, UniversalTagNumber.IA5String, s);
+
+ internal override void WriteSpan(AsnWriter writer, ReadOnlySpan<char> s) =>
+ writer.WriteCharacterString(UniversalTagNumber.IA5String, s);
+
+ internal override void WriteSpan(AsnWriter writer, Asn1Tag tag, ReadOnlySpan<char> s) =>
+ writer.WriteCharacterString(tag, UniversalTagNumber.IA5String, s);
+
+ internal override Asn1Tag StandardTag => new Asn1Tag(UniversalTagNumber.IA5String);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_String(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_String(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_String_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_String_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_String(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_String(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_String_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_String_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_String(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_String(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_String_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_String_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_Span(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_Span(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_Span_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_Span_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_Span(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_Span(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_Span_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_Span_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_Span(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_Span(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_Span_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_Span_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_String_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_String_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_String_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_String_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_Span_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_Span_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_Span_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_Span_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_String_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_String_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_String_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_String_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_Span_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_Span_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_Span_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_Span_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_String_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_String_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_String_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_String_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_Span_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_Span_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_Span_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_Span_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public new void VerifyWrite_String_Null(PublicEncodingRules ruleSet) =>
+ base.VerifyWrite_String_Null(ruleSet);
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public new void VerifyWrite_String_Null_CustomTag(PublicEncodingRules ruleSet) =>
+ base.VerifyWrite_String_Null_CustomTag(ruleSet);
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public new void VerifyWrite_EndOfContents_String(PublicEncodingRules ruleSet) =>
+ base.VerifyWrite_EndOfContents_String(ruleSet);
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public new void VerifyWrite_EndOfContents_Span(PublicEncodingRules ruleSet) =>
+ base.VerifyWrite_EndOfContents_Span(ruleSet);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_String(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_String(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_String_CustomTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_String_CustomTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_String_ConstructedTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_String_ConstructedTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_String_CustomPrimitiveTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_String_CustomPrimitiveTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_Span(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_Span(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_Span_CustomTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_Span_CustomTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_Span_ConstructedTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_Span_ConstructedTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_Span_CustomPrimitiveTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_Span_CustomPrimitiveTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(InvalidInputs))]
+ public new void VerifyWrite_String_NonEncodable(string input) =>
+ base.VerifyWrite_String_NonEncodable(input);
+
+ [Theory]
+ [MemberData(nameof(InvalidInputs))]
+ public new void VerifyWrite_Span_NonEncodable(string input) =>
+ base.VerifyWrite_Span_NonEncodable(input);
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteInteger.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteInteger.cs
new file mode 100644
index 0000000000..fd06aa8710
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteInteger.cs
@@ -0,0 +1,306 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Numerics;
+using System.Security.Cryptography.Asn1;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class WriteInteger : Asn1WriterTests
+ {
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, 0, "020100")]
+ [InlineData(PublicEncodingRules.CER, 0, "020100")]
+ [InlineData(PublicEncodingRules.DER, 0, "020100")]
+ [InlineData(PublicEncodingRules.BER, -1, "0201FF")]
+ [InlineData(PublicEncodingRules.CER, -1, "0201FF")]
+ [InlineData(PublicEncodingRules.DER, -1, "0201FF")]
+ [InlineData(PublicEncodingRules.BER, -2, "0201FE")]
+ [InlineData(PublicEncodingRules.DER, sbyte.MinValue, "020180")]
+ [InlineData(PublicEncodingRules.BER, sbyte.MinValue + 1, "020181")]
+ [InlineData(PublicEncodingRules.CER, sbyte.MinValue - 1, "0202FF7F")]
+ [InlineData(PublicEncodingRules.DER, sbyte.MinValue - 2, "0202FF7E")]
+ [InlineData(PublicEncodingRules.BER, -256, "0202FF00")]
+ [InlineData(PublicEncodingRules.CER, -257, "0202FEFF")]
+ [InlineData(PublicEncodingRules.DER, short.MinValue, "02028000")]
+ [InlineData(PublicEncodingRules.BER, short.MinValue + 1, "02028001")]
+ [InlineData(PublicEncodingRules.CER, short.MinValue + byte.MaxValue, "020280FF")]
+ [InlineData(PublicEncodingRules.DER, short.MinValue - 1, "0203FF7FFF")]
+ [InlineData(PublicEncodingRules.BER, short.MinValue - 2, "0203FF7FFE")]
+ [InlineData(PublicEncodingRules.CER, -65281, "0203FF00FF")]
+ [InlineData(PublicEncodingRules.DER, -8388608, "0203800000")]
+ [InlineData(PublicEncodingRules.BER, -8388607, "0203800001")]
+ [InlineData(PublicEncodingRules.CER, -8388609, "0204FF7FFFFF")]
+ [InlineData(PublicEncodingRules.DER, -16777216, "0204FF000000")]
+ [InlineData(PublicEncodingRules.BER, -16777217, "0204FEFFFFFF")]
+ [InlineData(PublicEncodingRules.CER, int.MinValue, "020480000000")]
+ [InlineData(PublicEncodingRules.DER, int.MinValue + 1, "020480000001")]
+ [InlineData(PublicEncodingRules.BER, (long)int.MinValue - 1, "0205FF7FFFFFFF")]
+ [InlineData(PublicEncodingRules.CER, (long)int.MinValue - 2, "0205FF7FFFFFFE")]
+ [InlineData(PublicEncodingRules.DER, -4294967296, "0205FF00000000")]
+ [InlineData(PublicEncodingRules.BER, -4294967295, "0205FF00000001")]
+ [InlineData(PublicEncodingRules.CER, -4294967294, "0205FF00000002")]
+ [InlineData(PublicEncodingRules.DER, -4294967297, "0205FEFFFFFFFF")]
+ [InlineData(PublicEncodingRules.BER, -549755813888, "02058000000000")]
+ [InlineData(PublicEncodingRules.CER, -549755813887, "02058000000001")]
+ [InlineData(PublicEncodingRules.DER, -549755813889, "0206FF7FFFFFFFFF")]
+ [InlineData(PublicEncodingRules.BER, -549755813890, "0206FF7FFFFFFFFE")]
+ [InlineData(PublicEncodingRules.CER, -140737488355328, "0206800000000000")]
+ [InlineData(PublicEncodingRules.DER, -140737488355327, "0206800000000001")]
+ [InlineData(PublicEncodingRules.BER, -140737488355329, "0207FF7FFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.CER, -281474976710656, "0207FF000000000000")]
+ [InlineData(PublicEncodingRules.DER, -281474976710655, "0207FF000000000001")]
+ [InlineData(PublicEncodingRules.BER, -281474976710657, "0207FEFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.CER, -36028797018963968, "020780000000000000")]
+ [InlineData(PublicEncodingRules.DER, -36028797018963967, "020780000000000001")]
+ [InlineData(PublicEncodingRules.DER, -36028797018963969, "0208FF7FFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.BER, -36028797018963970, "0208FF7FFFFFFFFFFFFE")]
+ [InlineData(PublicEncodingRules.CER, -72057594037927936, "0208FF00000000000000")]
+ [InlineData(PublicEncodingRules.DER, -72057594037927935, "0208FF00000000000001")]
+ [InlineData(PublicEncodingRules.BER, -72057594037927937, "0208FEFFFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.CER, long.MinValue + 1, "02088000000000000001")]
+ [InlineData(PublicEncodingRules.DER, long.MinValue, "02088000000000000000")]
+ [InlineData(PublicEncodingRules.BER, 1, "020101")]
+ [InlineData(PublicEncodingRules.CER, 127, "02017F")]
+ [InlineData(PublicEncodingRules.DER, 126, "02017E")]
+ [InlineData(PublicEncodingRules.BER, 128, "02020080")]
+ [InlineData(PublicEncodingRules.CER, 129, "02020081")]
+ [InlineData(PublicEncodingRules.DER, 254, "020200FE")]
+ [InlineData(PublicEncodingRules.BER, 255, "020200FF")]
+ [InlineData(PublicEncodingRules.CER, 256, "02020100")]
+ [InlineData(PublicEncodingRules.DER, 32767, "02027FFF")]
+ [InlineData(PublicEncodingRules.BER, 32766, "02027FFE")]
+ [InlineData(PublicEncodingRules.CER, 32768, "0203008000")]
+ [InlineData(PublicEncodingRules.DER, 32769, "0203008001")]
+ [InlineData(PublicEncodingRules.BER, 65535, "020300FFFF")]
+ [InlineData(PublicEncodingRules.CER, 65534, "020300FFFE")]
+ [InlineData(PublicEncodingRules.DER, 65536, "0203010000")]
+ [InlineData(PublicEncodingRules.BER, 65537, "0203010001")]
+ [InlineData(PublicEncodingRules.CER, 8388607, "02037FFFFF")]
+ [InlineData(PublicEncodingRules.DER, 8388606, "02037FFFFE")]
+ [InlineData(PublicEncodingRules.BER, 8388608, "020400800000")]
+ [InlineData(PublicEncodingRules.CER, 8388609, "020400800001")]
+ [InlineData(PublicEncodingRules.DER, 16777215, "020400FFFFFF")]
+ [InlineData(PublicEncodingRules.BER, 16777214, "020400FFFFFE")]
+ [InlineData(PublicEncodingRules.CER, 16777216, "020401000000")]
+ [InlineData(PublicEncodingRules.DER, 16777217, "020401000001")]
+ [InlineData(PublicEncodingRules.BER, 2147483647, "02047FFFFFFF")]
+ [InlineData(PublicEncodingRules.CER, 2147483646, "02047FFFFFFE")]
+ [InlineData(PublicEncodingRules.DER, 2147483648, "02050080000000")]
+ [InlineData(PublicEncodingRules.BER, 2147483649, "02050080000001")]
+ [InlineData(PublicEncodingRules.BER, 4294967295, "020500FFFFFFFF")]
+ [InlineData(PublicEncodingRules.CER, 4294967294, "020500FFFFFFFE")]
+ [InlineData(PublicEncodingRules.DER, 4294967296, "02050100000000")]
+ [InlineData(PublicEncodingRules.BER, 4294967297, "02050100000001")]
+ [InlineData(PublicEncodingRules.CER, 549755813887, "02057FFFFFFFFF")]
+ [InlineData(PublicEncodingRules.DER, 549755813886, "02057FFFFFFFFE")]
+ [InlineData(PublicEncodingRules.BER, 549755813888, "0206008000000000")]
+ [InlineData(PublicEncodingRules.CER, 549755813889, "0206008000000001")]
+ [InlineData(PublicEncodingRules.DER, 1099511627775, "020600FFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.BER, 1099511627774, "020600FFFFFFFFFE")]
+ [InlineData(PublicEncodingRules.CER, 1099511627776, "0206010000000000")]
+ [InlineData(PublicEncodingRules.DER, 1099511627777, "0206010000000001")]
+ [InlineData(PublicEncodingRules.BER, 140737488355327, "02067FFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.CER, 140737488355326, "02067FFFFFFFFFFE")]
+ [InlineData(PublicEncodingRules.DER, 140737488355328, "020700800000000000")]
+ [InlineData(PublicEncodingRules.BER, 140737488355329, "020700800000000001")]
+ [InlineData(PublicEncodingRules.CER, 281474976710655, "020700FFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.DER, 281474976710654, "020700FFFFFFFFFFFE")]
+ [InlineData(PublicEncodingRules.BER, 281474976710656, "020701000000000000")]
+ [InlineData(PublicEncodingRules.CER, 281474976710657, "020701000000000001")]
+ [InlineData(PublicEncodingRules.DER, 36028797018963967, "02077FFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.BER, 36028797018963966, "02077FFFFFFFFFFFFE")]
+ [InlineData(PublicEncodingRules.CER, 36028797018963968, "02080080000000000000")]
+ [InlineData(PublicEncodingRules.DER, 36028797018963969, "02080080000000000001")]
+ [InlineData(PublicEncodingRules.BER, 72057594037927935, "020800FFFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.CER, 72057594037927934, "020800FFFFFFFFFFFFFE")]
+ [InlineData(PublicEncodingRules.DER, 72057594037927936, "02080100000000000000")]
+ [InlineData(PublicEncodingRules.BER, 72057594037927937, "02080100000000000001")]
+ [InlineData(PublicEncodingRules.CER, 9223372036854775807, "02087FFFFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.DER, 9223372036854775806, "02087FFFFFFFFFFFFFFE")]
+ public void VerifyWriteInteger_Long(PublicEncodingRules ruleSet, long value, string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteInteger(value);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, 0, "020100")]
+ [InlineData(PublicEncodingRules.CER, 0, "020100")]
+ [InlineData(PublicEncodingRules.DER, 0, "020100")]
+ [InlineData(PublicEncodingRules.BER, 1, "020101")]
+ [InlineData(PublicEncodingRules.CER, 127, "02017F")]
+ [InlineData(PublicEncodingRules.DER, 126, "02017E")]
+ [InlineData(PublicEncodingRules.BER, 128, "02020080")]
+ [InlineData(PublicEncodingRules.CER, 129, "02020081")]
+ [InlineData(PublicEncodingRules.DER, 254, "020200FE")]
+ [InlineData(PublicEncodingRules.BER, 255, "020200FF")]
+ [InlineData(PublicEncodingRules.CER, 256, "02020100")]
+ [InlineData(PublicEncodingRules.DER, 32767, "02027FFF")]
+ [InlineData(PublicEncodingRules.BER, 32766, "02027FFE")]
+ [InlineData(PublicEncodingRules.CER, 32768, "0203008000")]
+ [InlineData(PublicEncodingRules.DER, 32769, "0203008001")]
+ [InlineData(PublicEncodingRules.BER, 65535, "020300FFFF")]
+ [InlineData(PublicEncodingRules.CER, 65534, "020300FFFE")]
+ [InlineData(PublicEncodingRules.DER, 65536, "0203010000")]
+ [InlineData(PublicEncodingRules.BER, 65537, "0203010001")]
+ [InlineData(PublicEncodingRules.CER, 8388607, "02037FFFFF")]
+ [InlineData(PublicEncodingRules.DER, 8388606, "02037FFFFE")]
+ [InlineData(PublicEncodingRules.BER, 8388608, "020400800000")]
+ [InlineData(PublicEncodingRules.CER, 8388609, "020400800001")]
+ [InlineData(PublicEncodingRules.DER, 16777215, "020400FFFFFF")]
+ [InlineData(PublicEncodingRules.BER, 16777214, "020400FFFFFE")]
+ [InlineData(PublicEncodingRules.CER, 16777216, "020401000000")]
+ [InlineData(PublicEncodingRules.DER, 16777217, "020401000001")]
+ [InlineData(PublicEncodingRules.BER, 2147483647, "02047FFFFFFF")]
+ [InlineData(PublicEncodingRules.CER, 2147483646, "02047FFFFFFE")]
+ [InlineData(PublicEncodingRules.DER, 2147483648, "02050080000000")]
+ [InlineData(PublicEncodingRules.BER, 2147483649, "02050080000001")]
+ [InlineData(PublicEncodingRules.BER, 4294967295, "020500FFFFFFFF")]
+ [InlineData(PublicEncodingRules.CER, 4294967294, "020500FFFFFFFE")]
+ [InlineData(PublicEncodingRules.DER, 4294967296, "02050100000000")]
+ [InlineData(PublicEncodingRules.BER, 4294967297, "02050100000001")]
+ [InlineData(PublicEncodingRules.CER, 549755813887, "02057FFFFFFFFF")]
+ [InlineData(PublicEncodingRules.DER, 549755813886, "02057FFFFFFFFE")]
+ [InlineData(PublicEncodingRules.BER, 549755813888, "0206008000000000")]
+ [InlineData(PublicEncodingRules.CER, 549755813889, "0206008000000001")]
+ [InlineData(PublicEncodingRules.DER, 1099511627775, "020600FFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.BER, 1099511627774, "020600FFFFFFFFFE")]
+ [InlineData(PublicEncodingRules.CER, 1099511627776, "0206010000000000")]
+ [InlineData(PublicEncodingRules.DER, 1099511627777, "0206010000000001")]
+ [InlineData(PublicEncodingRules.BER, 140737488355327, "02067FFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.CER, 140737488355326, "02067FFFFFFFFFFE")]
+ [InlineData(PublicEncodingRules.DER, 140737488355328, "020700800000000000")]
+ [InlineData(PublicEncodingRules.BER, 140737488355329, "020700800000000001")]
+ [InlineData(PublicEncodingRules.CER, 281474976710655, "020700FFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.DER, 281474976710654, "020700FFFFFFFFFFFE")]
+ [InlineData(PublicEncodingRules.BER, 281474976710656, "020701000000000000")]
+ [InlineData(PublicEncodingRules.CER, 281474976710657, "020701000000000001")]
+ [InlineData(PublicEncodingRules.DER, 36028797018963967, "02077FFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.BER, 36028797018963966, "02077FFFFFFFFFFFFE")]
+ [InlineData(PublicEncodingRules.CER, 36028797018963968, "02080080000000000000")]
+ [InlineData(PublicEncodingRules.DER, 36028797018963969, "02080080000000000001")]
+ [InlineData(PublicEncodingRules.BER, 72057594037927935, "020800FFFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.CER, 72057594037927934, "020800FFFFFFFFFFFFFE")]
+ [InlineData(PublicEncodingRules.DER, 72057594037927936, "02080100000000000000")]
+ [InlineData(PublicEncodingRules.BER, 72057594037927937, "02080100000000000001")]
+ [InlineData(PublicEncodingRules.CER, 9223372036854775807, "02087FFFFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.DER, 9223372036854775806, "02087FFFFFFFFFFFFFFE")]
+ [InlineData(PublicEncodingRules.BER, 9223372036854775808, "0209008000000000000000")]
+ [InlineData(PublicEncodingRules.CER, 9223372036854775809, "0209008000000000000001")]
+ [InlineData(PublicEncodingRules.DER, ulong.MaxValue, "020900FFFFFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.BER, ulong.MaxValue-1, "020900FFFFFFFFFFFFFFFE")]
+ public void VerifyWriteInteger_ULong(PublicEncodingRules ruleSet, ulong value, string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteInteger(value);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, "0", "020100")]
+ [InlineData(PublicEncodingRules.CER, "127", "02017F")]
+ [InlineData(PublicEncodingRules.DER, "128", "02020080")]
+ [InlineData(PublicEncodingRules.BER, "32767", "02027FFF")]
+ [InlineData(PublicEncodingRules.CER, "32768", "0203008000")]
+ [InlineData(PublicEncodingRules.DER, "9223372036854775807", "02087FFFFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.BER, "9223372036854775808", "0209008000000000000000")]
+ [InlineData(PublicEncodingRules.CER, "18446744073709551615", "020900FFFFFFFFFFFFFFFF")]
+ [InlineData(PublicEncodingRules.DER, "18446744073709551616", "0209010000000000000000")]
+ [InlineData(PublicEncodingRules.BER, "1339673755198158349044581307228491520", "02100102030405060708090A0B0C0D0E0F00")]
+ [InlineData(PublicEncodingRules.CER, "320182027492359845421654932427609477120", "021100F0E0D0C0B0A090807060504030201000")]
+ [InlineData(PublicEncodingRules.DER, "-1339673755198158349044581307228491520", "0210FEFDFCFBFAF9F8F7F6F5F4F3F2F1F100")]
+ public void VerifyWriteInteger_BigInteger(PublicEncodingRules ruleSet, string decimalValue, string expectedHex)
+ {
+ BigInteger value = BigInteger.Parse(decimalValue);
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteInteger(value);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, 0, "470100")]
+ [InlineData(PublicEncodingRules.CER, long.MinValue + 1, "47088000000000000001")]
+ [InlineData(PublicEncodingRules.DER, 9223372036854775806, "47087FFFFFFFFFFFFFFE")]
+ public void VerifyWriteInteger_Application7_Long(PublicEncodingRules ruleSet, long value, string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteInteger(new Asn1Tag(TagClass.Application, 7), value);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, 0, "890100")]
+ [InlineData(PublicEncodingRules.CER, 9223372036854775809, "8909008000000000000001")]
+ [InlineData(PublicEncodingRules.DER, 9223372036854775806, "89087FFFFFFFFFFFFFFE")]
+ public void VerifyWriteInteger_Context9_ULong(PublicEncodingRules ruleSet, ulong value, string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteInteger(new Asn1Tag(TagClass.ContextSpecific, 9), value);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, 0, "D00100")]
+ [InlineData(PublicEncodingRules.BER, "1339673755198158349044581307228491520", "D0100102030405060708090A0B0C0D0E0F00")]
+ [InlineData(PublicEncodingRules.CER, "320182027492359845421654932427609477120", "D01100F0E0D0C0B0A090807060504030201000")]
+ [InlineData(PublicEncodingRules.DER, "-1339673755198158349044581307228491520", "D010FEFDFCFBFAF9F8F7F6F5F4F3F2F1F100")]
+ public void VerifyWriteInteger_Private16_BigInteger(
+ PublicEncodingRules ruleSet,
+ string decimalValue,
+ string expectedHex)
+ {
+ BigInteger value = BigInteger.Parse(decimalValue);
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteInteger(new Asn1Tag(TagClass.Private, 16), value);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void VerifyWriteInteger_EndOfContents(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteInteger(Asn1Tag.EndOfContents, 0L));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteInteger(Asn1Tag.EndOfContents, 0UL));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteInteger(Asn1Tag.EndOfContents, BigInteger.Zero));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void VerifyWriteInteger_ConstructedIgnored(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteInteger(new Asn1Tag(UniversalTagNumber.Integer, isConstructed: true), 0L);
+ writer.WriteInteger(new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true), 0L);
+ writer.WriteInteger(new Asn1Tag(UniversalTagNumber.Integer, isConstructed: true), 0UL);
+ writer.WriteInteger(new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true), 0UL);
+ writer.WriteInteger(new Asn1Tag(UniversalTagNumber.Integer, isConstructed: true), BigInteger.Zero);
+ writer.WriteInteger(new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true), BigInteger.Zero);
+
+ Verify(writer, "020100800100020100800100020100800100");
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteNamedBitList.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteNamedBitList.cs
new file mode 100644
index 0000000000..a30f752e1a
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteNamedBitList.cs
@@ -0,0 +1,227 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class WriteNamedBitList : Asn1WriterTests
+ {
+ [Theory]
+ [InlineData(
+ PublicEncodingRules.BER,
+ "030100",
+ ReadNamedBitList.ULongFlags.None)]
+ [InlineData(
+ PublicEncodingRules.CER,
+ "030100",
+ ReadNamedBitList.ULongFlags.None)]
+ [InlineData(
+ PublicEncodingRules.DER,
+ "030100",
+ ReadNamedBitList.ULongFlags.None)]
+ [InlineData(
+ PublicEncodingRules.BER,
+ "0309000000000000000003",
+ ReadNamedBitList.ULongFlags.Max | ReadNamedBitList.ULongFlags.AlmostMax)]
+ [InlineData(
+ PublicEncodingRules.CER,
+ "0309010000000080000002",
+ ReadNamedBitList.LongFlags.Max | ReadNamedBitList.LongFlags.Mid)]
+ [InlineData(
+ PublicEncodingRules.DER,
+ "030204B0",
+ ReadNamedBitList.X509KeyUsageCSharpStyle.DigitalSignature |
+ ReadNamedBitList.X509KeyUsageCSharpStyle.KeyEncipherment |
+ ReadNamedBitList.X509KeyUsageCSharpStyle.DataEncipherment)]
+ public static void VerifyWriteNamedBitList(
+ PublicEncodingRules ruleSet,
+ string expectedHex,
+ object value)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteNamedBitList(value);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(
+ PublicEncodingRules.BER,
+ "C00100",
+ ReadNamedBitList.ULongFlags.None)]
+ [InlineData(
+ PublicEncodingRules.CER,
+ "410100",
+ ReadNamedBitList.ULongFlags.None)]
+ [InlineData(
+ PublicEncodingRules.DER,
+ "820100",
+ ReadNamedBitList.ULongFlags.None)]
+ [InlineData(
+ PublicEncodingRules.BER,
+ "C009000000000000000003",
+ ReadNamedBitList.ULongFlags.Max | ReadNamedBitList.ULongFlags.AlmostMax)]
+ [InlineData(
+ PublicEncodingRules.CER,
+ "4109010000000080000002",
+ ReadNamedBitList.LongFlags.Max | ReadNamedBitList.LongFlags.Mid)]
+ [InlineData(
+ PublicEncodingRules.DER,
+ "820204B0",
+ ReadNamedBitList.X509KeyUsageCSharpStyle.DigitalSignature |
+ ReadNamedBitList.X509KeyUsageCSharpStyle.KeyEncipherment |
+ ReadNamedBitList.X509KeyUsageCSharpStyle.DataEncipherment)]
+ public static void VerifyWriteNamedBitList_WithTag(
+ PublicEncodingRules ruleSet,
+ string expectedHex,
+ object value)
+ {
+ int ruleSetVal = (int)ruleSet;
+ TagClass tagClass = (TagClass)(byte)(ruleSetVal << 6);
+
+ if (tagClass == TagClass.Universal)
+ tagClass = TagClass.Private;
+
+ Asn1Tag tag = new Asn1Tag(tagClass, ruleSetVal);
+
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteNamedBitList(tag, value);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void VerifyWriteNamedBitList_Generic(PublicEncodingRules ruleSet)
+ {
+ AsnWriter objWriter = new AsnWriter((AsnEncodingRules)ruleSet);
+ AsnWriter genWriter = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ var flagsValue =
+ ReadNamedBitList.X509KeyUsageCSharpStyle.DigitalSignature |
+ ReadNamedBitList.X509KeyUsageCSharpStyle.KeyEncipherment |
+ ReadNamedBitList.X509KeyUsageCSharpStyle.DataEncipherment;
+
+ genWriter.WriteNamedBitList(flagsValue);
+ objWriter.WriteNamedBitList((object)flagsValue);
+
+ Verify(genWriter, objWriter.Encode().ByteArrayToHex());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void VerifyWriteNamedBitList_Generic_WithTag(PublicEncodingRules ruleSet)
+ {
+ AsnWriter objWriter = new AsnWriter((AsnEncodingRules)ruleSet);
+ AsnWriter genWriter = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ Asn1Tag tag = new Asn1Tag(TagClass.ContextSpecific, 52);
+
+ var flagsValue =
+ ReadNamedBitList.X509KeyUsageCSharpStyle.DigitalSignature |
+ ReadNamedBitList.X509KeyUsageCSharpStyle.KeyEncipherment |
+ ReadNamedBitList.X509KeyUsageCSharpStyle.DataEncipherment;
+
+ genWriter.WriteNamedBitList(tag, flagsValue);
+ objWriter.WriteNamedBitList(tag, (object)flagsValue);
+
+ Verify(genWriter, objWriter.Encode().ByteArrayToHex());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void VerifyWriteNamedBitList_NonNull(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentNullException>(
+ "enumValue",
+ () => writer.WriteNamedBitList(null));
+
+ AssertExtensions.Throws<ArgumentNullException>(
+ "enumValue",
+ () => writer.WriteNamedBitList(new Asn1Tag(TagClass.ContextSpecific, 1), null));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void VerifyWriteNamedBitList_EnumRequired(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ Assert.Throws<ArgumentException>(
+ () => writer.WriteNamedBitList(3));
+
+ Assert.Throws<ArgumentException>(
+ () => writer.WriteNamedBitList(new Asn1Tag(TagClass.ContextSpecific, 1), 3));
+
+ Assert.Throws<ArgumentException>(
+ () => writer.WriteNamedBitList((object)3));
+
+ Assert.Throws<ArgumentException>(
+ () => writer.WriteNamedBitList(new Asn1Tag(TagClass.ContextSpecific, 1), (object)3));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void VerifyWriteNamedBitList_FlagsEnumRequired(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tEnum",
+ () => writer.WriteNamedBitList(AsnEncodingRules.BER));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tEnum",
+ () => writer.WriteNamedBitList(
+ new Asn1Tag(TagClass.ContextSpecific, 1),
+ AsnEncodingRules.BER));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tEnum",
+ () => writer.WriteNamedBitList((object)AsnEncodingRules.BER));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tEnum",
+ () => writer.WriteNamedBitList(
+ new Asn1Tag(TagClass.ContextSpecific, 1),
+ (object)AsnEncodingRules.BER));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void VerifyWriteNamedBitList_EndOfContents(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteNamedBitList(
+ Asn1Tag.EndOfContents,
+ StringSplitOptions.RemoveEmptyEntries));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteNamedBitList(
+ Asn1Tag.EndOfContents,
+ (object)StringSplitOptions.RemoveEmptyEntries));
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteNull.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteNull.cs
new file mode 100644
index 0000000000..dc1c7117b2
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteNull.cs
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class WriteNull : Asn1WriterTests
+ {
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.DER)]
+ [InlineData(PublicEncodingRules.CER)]
+ public void VerifyWriteNull(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteNull();
+
+ Verify(writer, "0500");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.DER)]
+ [InlineData(PublicEncodingRules.CER)]
+ public void VerifyWriteNull_EndOfContents(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteNull(Asn1Tag.EndOfContents));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.DER)]
+ [InlineData(PublicEncodingRules.CER)]
+ public void VerifyWriteNull_ConstructedIgnored(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteNull(new Asn1Tag(TagClass.ContextSpecific, 7, true));
+ writer.WriteNull(new Asn1Tag(UniversalTagNumber.Null, true));
+
+ Verify(writer, "87000500");
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteObjectIdentifier.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteObjectIdentifier.cs
new file mode 100644
index 0000000000..ca568825b8
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteObjectIdentifier.cs
@@ -0,0 +1,289 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Security.Cryptography.Asn1;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class WriteObjectIdentifier : Asn1WriterTests
+ {
+ [Theory]
+ [MemberData(nameof(ValidOidData))]
+ public void VerifyWriteObjectIdentifier_String(
+ PublicEncodingRules ruleSet,
+ string oidValue,
+ string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteObjectIdentifier(oidValue);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidOidData))]
+ public void VerifyWriteObjectIdentifier_Span(
+ PublicEncodingRules ruleSet,
+ string oidValue,
+ string expectedHex)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteObjectIdentifier(oidValue.AsReadOnlySpan());
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidOidData))]
+ public void VerifyWriteObjectIdentifier_Oid(
+ PublicEncodingRules ruleSet,
+ string oidValue,
+ string expectedHex)
+ {
+ Oid oidObj = new Oid(oidValue, "FriendlyName does not matter");
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteObjectIdentifier(oidObj);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [MemberData(nameof(InvalidOidData))]
+ public void VerifyWriteOid_InvalidValue_String(
+ string description,
+ PublicEncodingRules ruleSet,
+ string nonOidValue)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => writer.WriteObjectIdentifier(nonOidValue));
+ }
+
+ [Theory]
+ [MemberData(nameof(InvalidOidData))]
+ public void VerifyWriteOid_InvalidValue_Span(
+ string description,
+ PublicEncodingRules ruleSet,
+ string nonOidValue)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ Assert.Throws<CryptographicException>(
+ () => writer.WriteObjectIdentifier(nonOidValue.AsReadOnlySpan()));
+ }
+
+ [Theory]
+ [MemberData(nameof(InvalidOidData))]
+ public void VerifyWriteOid_InvalidValue_Oid(
+ string description,
+ PublicEncodingRules ruleSet,
+ string nonOidValue)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ Oid nonOidObj = new Oid(nonOidValue, "FriendlyName does not matter");
+
+ Assert.Throws<CryptographicException>(
+ () => writer.WriteObjectIdentifier(nonOidObj));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void WriteObjectIdentifier_CustomTag_String(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteObjectIdentifier(new Asn1Tag(TagClass.ContextSpecific, 3), "1.3.14.3.2.26");
+
+ Verify(writer, "83052B0E03021A");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void WriteObjectIdentifier_CustomTag_Span(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteObjectIdentifier(new Asn1Tag(TagClass.Application, 2), "1.3.14.3.2.26".AsReadOnlySpan());
+
+ Verify(writer, "42052B0E03021A");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void WriteObjectIdentifier_CustomTag_Oid(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteObjectIdentifier(new Asn1Tag(TagClass.Private, 36), Oid.FromFriendlyName("SHA1", OidGroup.HashAlgorithm));
+
+ Verify(writer, "DF24052B0E03021A");
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void WriteObjectIdentifier_NullString(bool defaultTag)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+
+ AssertExtensions.Throws<ArgumentNullException>(
+ "oidValue",
+ () =>
+ {
+ if (defaultTag)
+ {
+ writer.WriteObjectIdentifier((string)null);
+ }
+ else
+ {
+ writer.WriteObjectIdentifier(new Asn1Tag(TagClass.ContextSpecific, 6), (string)null);
+ }
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void WriteObjectIdentifier_NullOid(bool defaultTag)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+
+ AssertExtensions.Throws<ArgumentNullException>(
+ "oid",
+ () =>
+ {
+ if (defaultTag)
+ {
+ writer.WriteObjectIdentifier((Oid)null);
+ }
+ else
+ {
+ writer.WriteObjectIdentifier(new Asn1Tag(TagClass.Application, 2), (Oid)null);
+ }
+ });
+ }
+
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void VerifyWriteObjectIdentifier_EndOfContents(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteObjectIdentifier(Asn1Tag.EndOfContents, "1.1"));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteObjectIdentifier(Asn1Tag.EndOfContents, "1.1".AsReadOnlySpan()));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteObjectIdentifier(Asn1Tag.EndOfContents, new Oid("1.1", "1.1")));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void VerifyWriteObjectIdentifier_ConstructedIgnored(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ const string OidValue = "1.1";
+ Asn1Tag constructedOid = new Asn1Tag(UniversalTagNumber.ObjectIdentifier, isConstructed: true);
+ Asn1Tag constructedContext0 = new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true);
+
+ writer.WriteObjectIdentifier(constructedOid, OidValue);
+ writer.WriteObjectIdentifier(constructedContext0, OidValue);
+ writer.WriteObjectIdentifier(constructedOid, OidValue.AsReadOnlySpan());
+ writer.WriteObjectIdentifier(constructedContext0, OidValue.AsReadOnlySpan());
+ writer.WriteObjectIdentifier(constructedOid, new Oid(OidValue, OidValue));
+ writer.WriteObjectIdentifier(constructedContext0, new Oid(OidValue, OidValue));
+
+ Verify(writer, "060129800129060129800129060129800129");
+ }
+
+ public static IEnumerable<object[]> ValidOidData { get; } =
+ new object[][]
+ {
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "0.0",
+ "060100",
+ },
+ new object[]
+ {
+ PublicEncodingRules.CER,
+ "1.0",
+ "060128",
+ },
+ new object[]
+ {
+ PublicEncodingRules.DER,
+ "2.0",
+ "060150",
+ },
+ new object[]
+ {
+ PublicEncodingRules.BER,
+ "1.3.14.3.2.26",
+ "06052B0E03021A",
+ },
+ new object[]
+ {
+ PublicEncodingRules.CER,
+ "2.999.19427512891.25",
+ "06088837C8AFE1A43B19",
+ },
+ new object[]
+ {
+ PublicEncodingRules.DER,
+ "1.2.840.113549.1.1.10",
+ "06092A864886F70D01010A",
+ },
+ new object[]
+ {
+ // Using the rules of ITU-T-REC-X.667-201210 for 2.25.{UUID} unregistered arcs, and
+ // their sample value of f81d4fae-7dec-11d0-a765-00a0c91e6bf6
+ // this is
+ // { joint-iso-itu-t(2) uuid(255) thatuuid(329800735698586629295641978511506172918) three(3) }
+ PublicEncodingRules.DER,
+ "2.25.329800735698586629295641978511506172918.3",
+ "06156983F09DA7EBCFDEE0C7A1A7B2C0948CC8F9D77603",
+ },
+ };
+
+ public static IEnumerable<object[]> InvalidOidData { get; } =
+ new object[][]
+ {
+ new object[] { "Empty string", PublicEncodingRules.BER, "" },
+ new object[] { "No period", PublicEncodingRules.CER, "1" },
+ new object[] { "No second RID", PublicEncodingRules.DER, "1." },
+ new object[] { "Invalid first RID", PublicEncodingRules.BER, "3.0" },
+ new object[] { "Invalid first RID - multichar", PublicEncodingRules.CER, "27.0" },
+ new object[] { "Double zero - First RID", PublicEncodingRules.DER, "00.0" },
+ new object[] { "Leading zero - First RID", PublicEncodingRules.BER, "01.0" },
+ new object[] { "Double zero - second RID", PublicEncodingRules.CER, "0.00" },
+ new object[] { "Leading zero - second RID", PublicEncodingRules.DER, "0.01" },
+ new object[] { "Ends with period - second RID", PublicEncodingRules.BER, "0.0." },
+ new object[] { "Ends with period - third RID", PublicEncodingRules.BER, "0.1.30." },
+ new object[] { "Double zero - third RID", PublicEncodingRules.CER, "0.1.00" },
+ new object[] { "Leading zero - third RID", PublicEncodingRules.DER, "0.1.023" },
+ new object[] { "Invalid character first position", PublicEncodingRules.BER, "a.1.23" },
+ new object[] { "Invalid character second position", PublicEncodingRules.CER, "0,1.23" },
+ new object[] { "Invalid character second rid", PublicEncodingRules.DER, "0.1q.23" },
+ new object[] { "Invalid character third rid", PublicEncodingRules.BER, "0.1.23q" },
+ };
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteOctetString.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteOctetString.cs
new file mode 100644
index 0000000000..18825af8c4
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteOctetString.cs
@@ -0,0 +1,156 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Security.Cryptography.Asn1;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class WriteOctetString : Asn1WriterTests
+ {
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void WriteEmpty(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteOctetString(ReadOnlySpan<byte>.Empty);
+
+ Verify(writer, "0400");
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, 1, "0401")]
+ [InlineData(PublicEncodingRules.CER, 2, "0402")]
+ [InlineData(PublicEncodingRules.DER, 3, "0403")]
+ [InlineData(PublicEncodingRules.BER, 126, "047E")]
+ [InlineData(PublicEncodingRules.CER, 127, "047F")]
+ [InlineData(PublicEncodingRules.DER, 128, "048180")]
+ [InlineData(PublicEncodingRules.BER, 1000, "048203E8")]
+ [InlineData(PublicEncodingRules.CER, 1000, "048203E8")]
+ [InlineData(PublicEncodingRules.DER, 1000, "048203E8")]
+ [InlineData(PublicEncodingRules.BER, 1001, "048203E9")]
+ [InlineData(PublicEncodingRules.DER, 1001, "048203E9")]
+ [InlineData(PublicEncodingRules.BER, 2001, "048207D1")]
+ [InlineData(PublicEncodingRules.DER, 2001, "048207D1")]
+ public void WritePrimitive(PublicEncodingRules ruleSet, int length, string hexStart)
+ {
+ string payloadHex = new string('0', 2 * length);
+ string expectedHex = hexStart + payloadHex;
+ byte[] data = new byte[length];
+
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteOctetString(data);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(1001, "2480048203E8", "0401")]
+ [InlineData(1999, "2480048203E8", "048203E7")]
+ [InlineData(2000, "2480048203E8", "048203E8")]
+ public void WriteSegmentedCER(int length, string hexStart, string hexStart2)
+ {
+ string payload1Hex = new string('8', 2000);
+ string payload2Hex = new string('8', (length - 1000) * 2);
+ string expectedHex = hexStart + payload1Hex + hexStart2 + payload2Hex + "0000";
+ byte[] data = new byte[length];
+
+ for (int i = 0; i < data.Length; i++)
+ {
+ data[i] = 0x88;
+ }
+
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ writer.WriteOctetString(data);
+
+ Verify(writer, expectedHex);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER, 0, false)]
+ [InlineData(PublicEncodingRules.CER, 0, false)]
+ [InlineData(PublicEncodingRules.DER, 0, false)]
+ [InlineData(PublicEncodingRules.BER, 999, false)]
+ [InlineData(PublicEncodingRules.CER, 999, false)]
+ [InlineData(PublicEncodingRules.DER, 999, false)]
+ [InlineData(PublicEncodingRules.BER, 1000, false)]
+ [InlineData(PublicEncodingRules.CER, 1000, false)]
+ [InlineData(PublicEncodingRules.DER, 1000, false)]
+ [InlineData(PublicEncodingRules.BER, 1001, false)]
+ [InlineData(PublicEncodingRules.CER, 1001, true)]
+ [InlineData(PublicEncodingRules.DER, 1001, false)]
+ [InlineData(PublicEncodingRules.BER, 1998, false)]
+ [InlineData(PublicEncodingRules.CER, 1998, true)]
+ [InlineData(PublicEncodingRules.DER, 1998, false)]
+ [InlineData(PublicEncodingRules.BER, 1999, false)]
+ [InlineData(PublicEncodingRules.CER, 1999, true)]
+ [InlineData(PublicEncodingRules.DER, 1999, false)]
+ [InlineData(PublicEncodingRules.BER, 2000, false)]
+ [InlineData(PublicEncodingRules.CER, 2000, true)]
+ [InlineData(PublicEncodingRules.DER, 2000, false)]
+ [InlineData(PublicEncodingRules.BER, 2001, false)]
+ [InlineData(PublicEncodingRules.CER, 2001, true)]
+ [InlineData(PublicEncodingRules.DER, 2001, false)]
+ [InlineData(PublicEncodingRules.BER, 4096, false)]
+ [InlineData(PublicEncodingRules.CER, 4096, true)]
+ [InlineData(PublicEncodingRules.DER, 4096, false)]
+ public void VerifyWriteOctetString_PrimitiveOrConstructed(
+ PublicEncodingRules ruleSet,
+ int payloadLength,
+ bool expectConstructed)
+ {
+ byte[] data = new byte[payloadLength];
+
+ Asn1Tag[] tagsToTry =
+ {
+ new Asn1Tag(UniversalTagNumber.OctetString),
+ new Asn1Tag(UniversalTagNumber.OctetString, isConstructed: true),
+ new Asn1Tag(TagClass.Private, 87),
+ new Asn1Tag(TagClass.ContextSpecific, 13, isConstructed: true),
+ };
+
+ byte[] answerBuf = new byte[payloadLength + 100];
+
+ foreach (Asn1Tag toTry in tagsToTry)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ writer.WriteOctetString(toTry, data);
+
+ Assert.True(writer.TryEncode(answerBuf, out _));
+ Assert.True(Asn1Tag.TryParse(answerBuf, out Asn1Tag writtenTag, out _));
+
+ if (expectConstructed)
+ {
+ Assert.True(writtenTag.IsConstructed, $"writtenTag.IsConstructed ({toTry})");
+ }
+ else
+ {
+ Assert.False(writtenTag.IsConstructed, $"writtenTag.IsConstructed ({toTry})");
+ }
+
+ Assert.Equal(toTry.TagClass, writtenTag.TagClass);
+ Assert.Equal(toTry.TagValue, writtenTag.TagValue);
+ }
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void VerifyWriteOctetString_EndOfContents(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteOctetString(Asn1Tag.EndOfContents, ReadOnlySpan<byte>.Empty));
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteOctetString(Asn1Tag.EndOfContents, new byte[1]));
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteUtcTime.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteUtcTime.cs
new file mode 100644
index 0000000000..1d52882447
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteUtcTime.cs
@@ -0,0 +1,122 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Security.Cryptography.Asn1;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class WriteUtcTime : Asn1WriterTests
+ {
+ public static IEnumerable<object[]> TestCases { get; } = new object[][]
+ {
+ new object[]
+ {
+ new DateTimeOffset(2017, 10, 16, 8, 24, 3, TimeSpan.FromHours(-7)),
+ "0D3137313031363135323430335A",
+ },
+ new object[]
+ {
+ new DateTimeOffset(1817, 10, 16, 21, 24, 3, TimeSpan.FromHours(6)),
+ "0D3137313031363135323430335A",
+ },
+ new object[]
+ {
+ new DateTimeOffset(3000, 1, 1, 0, 0, 0, TimeSpan.Zero),
+ "0D3030303130313030303030305A",
+ },
+ };
+
+ [Theory]
+ [MemberData(nameof(TestCases))]
+ public void VerifyWriteUtcTime_BER(DateTimeOffset input, string expectedHexPayload)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ writer.WriteUtcTime(input);
+
+ Verify(writer, "17" + expectedHexPayload);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestCases))]
+ public void VerifyWriteUtcTime_BER_CustomTag(DateTimeOffset input, string expectedHexPayload)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.BER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Application, 11);
+ writer.WriteUtcTime(tag, input);
+
+ Verify(writer, Stringify(tag) + expectedHexPayload);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestCases))]
+ public void VerifyWriteUtcTime_CER(DateTimeOffset input, string expectedHexPayload)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ writer.WriteUtcTime(input);
+
+ Verify(writer, "17" + expectedHexPayload);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestCases))]
+ public void VerifyWriteUtcTime_CER_CustomTag(DateTimeOffset input, string expectedHexPayload)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.CER);
+ Asn1Tag tag = new Asn1Tag(TagClass.Private, 95);
+ writer.WriteUtcTime(tag, input);
+
+ Verify(writer, Stringify(tag) + expectedHexPayload);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestCases))]
+ public void VerifyWriteUtcTime_DER(DateTimeOffset input, string expectedHexPayload)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ writer.WriteUtcTime(input);
+
+ Verify(writer, "17" + expectedHexPayload);
+ }
+
+ [Theory]
+ [MemberData(nameof(TestCases))]
+ public void VerifyWriteUtcTime_DER_CustomTag(DateTimeOffset input, string expectedHexPayload)
+ {
+ AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
+ Asn1Tag tag = new Asn1Tag(TagClass.ContextSpecific, 3);
+ writer.WriteUtcTime(tag, input);
+
+ Verify(writer, Stringify(tag) + expectedHexPayload);
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void VerifyWriteUtcTime_EndOfContents(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+
+ AssertExtensions.Throws<ArgumentException>(
+ "tag",
+ () => writer.WriteUtcTime(Asn1Tag.EndOfContents, DateTimeOffset.Now));
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public void VerifyWriteUtcTime_IgnoresConstructed(PublicEncodingRules ruleSet)
+ {
+ AsnWriter writer = new AsnWriter((AsnEncodingRules)ruleSet);
+ DateTimeOffset value = new DateTimeOffset(2017, 11, 16, 17, 35, 1, TimeSpan.Zero);
+
+ writer.WriteUtcTime(new Asn1Tag(UniversalTagNumber.UtcTime, true), value);
+ writer.WriteUtcTime(new Asn1Tag(TagClass.ContextSpecific, 3, true), value);
+ Verify(writer, "170D3137313131363137333530315A" + "830D3137313131363137333530315A");
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteUtf8String.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteUtf8String.cs
new file mode 100644
index 0000000000..c8dce7a0d0
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Writer/WriteUtf8String.cs
@@ -0,0 +1,287 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Security.Cryptography.Asn1;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public class WriteUtf8String : WriteCharacterString
+ {
+ public static IEnumerable<object[]> ShortValidCases { get; } = new object[][]
+ {
+ new object[]
+ {
+ string.Empty,
+ "00",
+ },
+ new object[]
+ {
+ "hi",
+ "026869",
+ },
+ new object[]
+ {
+ "Dr. & Mrs. Smith\u2010Jones \uFE60 children",
+ "2544722E2026204D72732E20536D697468E280904A6F6E657320EFB9A0206368696C6472656E",
+ },
+ };
+
+ public static IEnumerable<object[]> LongValidCases { get; } = new object[][]
+ {
+ new object[]
+ {
+ new string('f', 957) + new string('w', 182),
+ "820473" + new string('6', 957 * 2) + new string('7', 182 * 2),
+ },
+ };
+
+ public static IEnumerable<object[]> CERSegmentedCases { get; } = new object[][]
+ {
+ new object[]
+ {
+ GettysburgAddress,
+ 1458,
+ },
+ new object[]
+ {
+ // A whole bunch of "small ampersand" values (3 bytes each UTF-8),
+ // then one inverted exclamation (2 bytes UTF-8)
+ new string('\uFE60', 2000 / 3) + '\u00A1',
+ 2000,
+ },
+ };
+
+ public static IEnumerable<object[]> InvalidInputs => Array.Empty<object[]>();
+
+ internal override void WriteString(AsnWriter writer, string s) =>
+ writer.WriteCharacterString(UniversalTagNumber.UTF8String, s);
+
+ internal override void WriteString(AsnWriter writer, Asn1Tag tag, string s) =>
+ writer.WriteCharacterString(tag, UniversalTagNumber.UTF8String, s);
+
+ internal override void WriteSpan(AsnWriter writer, ReadOnlySpan<char> s) =>
+ writer.WriteCharacterString(UniversalTagNumber.UTF8String, s);
+
+ internal override void WriteSpan(AsnWriter writer, Asn1Tag tag, ReadOnlySpan<char> s) =>
+ writer.WriteCharacterString(tag, UniversalTagNumber.UTF8String, s);
+
+ internal override Asn1Tag StandardTag => new Asn1Tag(UniversalTagNumber.UTF8String);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_String(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_String(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_String_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_String_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_String(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_String(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_String_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_String_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_String(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_String(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_String_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_String_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_Span(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_Span(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_Span_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_Span_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_Span(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_Span(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_Span_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_Span_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_Span(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_Span(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_Span_CustomTag(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_Span_CustomTag(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_String_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_String_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_String_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_String_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_Span_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_Span_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_BER_Span_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_BER_Span_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_String_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_String_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_String_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_String_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_Span_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_Span_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ public new void VerifyWrite_CER_Span_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_CER_Span_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_String_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_String_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_String_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_String_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_Span_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_Span_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [MemberData(nameof(ShortValidCases))]
+ [MemberData(nameof(LongValidCases))]
+ public new void VerifyWrite_DER_Span_CustomTag_ClearsConstructed(string input, string expectedPayloadHex) =>
+ base.VerifyWrite_DER_Span_CustomTag_ClearsConstructed(input, expectedPayloadHex);
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public new void VerifyWrite_String_Null(PublicEncodingRules ruleSet) =>
+ base.VerifyWrite_String_Null(ruleSet);
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public new void VerifyWrite_String_Null_CustomTag(PublicEncodingRules ruleSet) =>
+ base.VerifyWrite_String_Null_CustomTag(ruleSet);
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public new void VerifyWrite_EndOfContents_String(PublicEncodingRules ruleSet) =>
+ base.VerifyWrite_EndOfContents_String(ruleSet);
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public new void VerifyWrite_EndOfContents_Span(PublicEncodingRules ruleSet) =>
+ base.VerifyWrite_EndOfContents_Span(ruleSet);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_String(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_String(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_String_CustomTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_String_CustomTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_String_ConstructedTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_String_ConstructedTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_String_CustomPrimitiveTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_String_CustomPrimitiveTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_Span(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_Span(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_Span_CustomTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_Span_CustomTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_Span_ConstructedTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_Span_ConstructedTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(CERSegmentedCases))]
+ public new void VerifyWrite_CERSegmented_Span_CustomPrimitiveTag(string input, int contentByteCount) =>
+ base.VerifyWrite_CERSegmented_Span_CustomPrimitiveTag(input, contentByteCount);
+
+ [Theory]
+ [MemberData(nameof(InvalidInputs))]
+ public new void VerifyWrite_String_NonEncodable(string input) =>
+ base.VerifyWrite_String_NonEncodable(input);
+
+ [Theory]
+ [MemberData(nameof(InvalidInputs))]
+ public new void VerifyWrite_Span_NonEncodable(string input) =>
+ base.VerifyWrite_Span_NonEncodable(input);
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Resources/Strings.resx b/src/System.Security.Cryptography.Encoding/tests/Resources/Strings.resx
index 59c5bc4f97..797443823e 100644
--- a/src/System.Security.Cryptography.Encoding/tests/Resources/Strings.resx
+++ b/src/System.Security.Cryptography.Encoding/tests/Resources/Strings.resx
@@ -61,10 +61,34 @@
<data name="Argument_InvalidOidValue" xml:space="preserve">
<value>The OID value was invalid.</value>
</data>
+ <data name="Cryptography_Asn_EnumeratedValueRequiresNonFlagsEnum" xml:space="preserve">
+ <value>ASN.1 Enumerated values only apply to enum types without the [Flags] attribute.</value>
+ </data>
+ <data name="Cryptography_Asn_NamedBitListRequiresFlagsEnum" xml:space="preserve">
+ <value>Named bit list operations require an enum with the [Flags] attribute.</value>
+ </data>
+ <data name="Cryptography_Asn_NamedBitListValueTooBig" xml:space="preserve">
+ <value>The encoded named bit list value is larger than the value size of the '{0}' enum.</value>
+ </data>
+ <data name="Cryptography_Asn_UniversalValueIsFixed" xml:space="preserve">
+ <value>Tags with TagClass Universal must have the appropriate TagValue value for the data type being read or written.</value>
+ </data>
+ <data name="Cryptography_Asn_UnusedBitCountRange" xml:space="preserve">
+ <value>Unused bit count must be between 0 and 7, inclusive.</value>
+ </data>
+ <data name="Cryptography_AsnWriter_EncodeUnbalancedStack" xml:space="preserve">
+ <value>Encode cannot be called while a Sequence or SetOf is still open.</value>
+ </data>
+ <data name="Cryptography_AsnWriter_PopWrongTag" xml:space="preserve">
+ <value>Cannot pop the requested tag as it is not currently in progress.</value>
+ </data>
<data name="Cryptography_Der_Invalid_Encoding" xml:space="preserve">
<value>ASN1 corrupted data.</value>
</data>
<data name="Cryptography_Invalid_IA5String" xml:space="preserve">
<value>The string contains a character not in the 7 bit ASCII character set.</value>
</data>
+ <data name="Cryptography_WriteEncodedValue_OneValueAtATime" xml:space="preserve">
+ <value>The input to WriteEncodedValue must represent a single encoded value with no trailing data.</value>
+ </data>
</root>
diff --git a/src/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj b/src/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj
index 8026270d7c..e32a65c910 100644
--- a/src/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj
+++ b/src/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj
@@ -10,6 +10,45 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Windows_NT-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Windows_NT-Release|AnyCPU'" />
<ItemGroup>
+ <Compile Include="Asn1\Reader\Asn1ReaderTests.cs" />
+ <Compile Include="Asn1\Reader\ComprehensiveReadTests.cs" />
+ <Compile Include="Asn1\Reader\ParseTag.cs" />
+ <Compile Include="Asn1\Reader\PeekTests.cs" />
+ <Compile Include="Asn1\Reader\ReadBitString.cs" />
+ <Compile Include="Asn1\Reader\ReadBMPString.cs" />
+ <Compile Include="Asn1\Reader\ReadBoolean.cs" />
+ <Compile Include="Asn1\Reader\ReadEnumerated.cs" />
+ <Compile Include="Asn1\Reader\ReadGeneralizedTime.cs" />
+ <Compile Include="Asn1\Reader\ReadIA5String.cs" />
+ <Compile Include="Asn1\Reader\ReadInteger.cs" />
+ <Compile Include="Asn1\Reader\ReadLength.cs" />
+ <Compile Include="Asn1\Reader\ReadNull.cs" />
+ <Compile Include="Asn1\Reader\ReadNamedBitList.cs" />
+ <Compile Include="Asn1\Reader\ReadObjectIdentifier.cs" />
+ <Compile Include="Asn1\Reader\ReadOctetString.cs" />
+ <Compile Include="Asn1\Reader\ReadSequence.cs" />
+ <Compile Include="Asn1\Reader\ReadSetOf.cs" />
+ <Compile Include="Asn1\Reader\ReadUtcTime.cs" />
+ <Compile Include="Asn1\Reader\ReadUTF8String.cs" />
+ <Compile Include="Asn1\Reader\ReaderStateTests.cs" />
+ <Compile Include="Asn1\Writer\Asn1WriterTests.cs" />
+ <Compile Include="Asn1\Writer\ComprehensiveWriteTest.cs" />
+ <Compile Include="Asn1\Writer\PushPopSequence.cs" />
+ <Compile Include="Asn1\Writer\PushPopSetOf.cs" />
+ <Compile Include="Asn1\Writer\WriteBitString.cs" />
+ <Compile Include="Asn1\Writer\WriteBMPString.cs" />
+ <Compile Include="Asn1\Writer\WriteBoolean.cs" />
+ <Compile Include="Asn1\Writer\WriteCharacterString.cs" />
+ <Compile Include="Asn1\Writer\WriteEnumerated.cs" />
+ <Compile Include="Asn1\Writer\WriteGeneralizedTime.cs" />
+ <Compile Include="Asn1\Writer\WriteIA5String.cs" />
+ <Compile Include="Asn1\Writer\WriteInteger.cs" />
+ <Compile Include="Asn1\Writer\WriteNamedBitList.cs" />
+ <Compile Include="Asn1\Writer\WriteNull.cs" />
+ <Compile Include="Asn1\Writer\WriteOctetString.cs" />
+ <Compile Include="Asn1\Writer\WriteObjectIdentifier.cs" />
+ <Compile Include="Asn1\Writer\WriteUtcTime.cs" />
+ <Compile Include="Asn1\Writer\WriteUtf8String.cs" />
<Compile Include="AsnEncodedData.cs" />
<Compile Include="AsnEncodedDataCollectionTests.cs" />
<Compile Include="Base64TransformsTests.cs" />
@@ -17,6 +56,15 @@
<Compile Include="DerSequenceReaderTests.cs" />
<Compile Include="Oid.cs" />
<Compile Include="OidCollectionTests.cs" />
+ <Compile Include="$(CommonPath)\System\Security\Cryptography\Asn1V2.cs">
+ <Link>Common\System\Security\Cryptography\Asn1V2.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\Security\Cryptography\AsnReader.cs">
+ <Link>Common\System\Security\Cryptography\AsnReader.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonPath)\System\Security\Cryptography\AsnWriter.cs">
+ <Link>Common\System\Security\Cryptography\AsnWriter.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\System\Security\Cryptography\DerEncoder.cs">
<Link>Common\System\Security\Cryptography\DerEncoder.cs</Link>
</Compile>