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:
authorJeremy Barton <jbarton@microsoft.com>2017-12-09 06:06:10 +0300
committerGitHub <noreply@github.com>2017-12-09 06:06:10 +0300
commitec6014e3b656747e75a9ad86f227a17551512ccb (patch)
tree6755aadfcd5cc03c0c9d9e96d88609d49441f920 /src/System.Security.Cryptography.Encoding
parentfc0e922bb1d5e92414acb6869d8a7aa3502f6b1c (diff)
Create a Memory/Span-based ASN.1 reader and writer (#25296)
The AsnReader type can read any BER-encoded value for the types that it supports (for tag values up to int.MaxValue). It also understands the CER and DER restrictions, and when reading in those modes it will enforce the restrictions that those encoding rulesets dictate. Callers who want minimal validation can just read in BER mode. The AsnWriter type mostly writes in DER (except where CER demands a different encoding). The only significant "BER-relaxation" it takes is that closing a SET OF value will not sort the contents. Reference materials: * ITU-T-REC-X.680-201508 (ASN.1 language, and some semantics behind the values) * ITU-T-REC-X.690-201508 (the BER encoding family (BER, CER, DER))
Diffstat (limited to 'src/System.Security.Cryptography.Encoding')
-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
41 files changed, 12777 insertions, 0 deletions
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>