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-14 07:50:45 +0300
committerGitHub <noreply@github.com>2017-12-14 07:50:45 +0300
commit163cfff64e956ca091a38bdb2b4b60b59dc667fd (patch)
treef90fd476acb93e6104b927ad93e8180c04adba81 /src/System.Security.Cryptography.Encoding/tests
parent0555aa9370128a92ca8303c860a985b030d3f7ae (diff)
Create a serializer and deserializer for ASN.1 data
This enables serialization and deserialization of [StructLayout(LayoutKind.Sequential)] types to/from ASN.1 BER/CER/DER data streams. Ambiguous types (like string) require attributes to identify their ASN.1 representation, and other attributes exist to optionally control the serialization mechanism (such as [OptionalValue], [ExpectedTag], [AnyValue]). For background, see ITU-T-REC-X.680-201508 (ASN.1 language).
Diffstat (limited to 'src/System.Security.Cryptography.Encoding/tests')
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadInteger.cs107
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/SimpleDeserialize.cs826
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/SimpleSerialize.cs394
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/X509DeserializeTests.cs188
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/Resources/Strings.resx57
-rw-r--r--src/System.Security.Cryptography.Encoding/tests/System.Security.Cryptography.Encoding.Tests.csproj6
6 files changed, 1578 insertions, 0 deletions
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadInteger.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadInteger.cs
index 6cf2e754d6..148e7127f5 100644
--- a/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadInteger.cs
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Reader/ReadInteger.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Numerics;
using System.Security.Cryptography.Asn1;
using Test.Cryptography;
using Xunit;
@@ -419,6 +420,112 @@ namespace System.Security.Cryptography.Tests.Asn1
[InlineData(PublicEncodingRules.BER)]
[InlineData(PublicEncodingRules.CER)]
[InlineData(PublicEncodingRules.DER)]
+ public static void GetBigInteger(PublicEncodingRules ruleSet)
+ {
+ byte[] inputData = (
+ "0282010100A46861FA9D5DB763633BF5A64EF6E7C2C2367F48D2D46643A22DFC" +
+ "FCCB24E58A14D0F06BDC956437F2A56BA4BEF70BA361BF12964A0D665AFD84B0" +
+ "F7494C8FA4ABC5FCA2E017C06178AEF2CDAD1B5F18E997A14B965C074E8F5649" +
+ "70607276B00583932240FE6E2DD013026F9AE13D7C91CC07C4E1E8E87737DC06" +
+ "EF2B575B89D62EFE46859F8255A123692A706C68122D4DAFE11CB205A7B3DE06" +
+ "E553F7B95F978EF8601A8DF819BF32040BDF92A0DE0DF269B4514282E17AC699" +
+ "34E8440A48AB9D1F5DF89A502CEF6DFDBE790045BD45E0C94E5CA8ADD76A013E" +
+ "9C978440FC8A9E2A9A4940B2460819C3E302AA9C9F355AD754C86D3ED77DDAA3" +
+ "DA13810B4D").HexToByteArray();
+
+ BigInteger expected = BigInteger.Parse(
+ "2075455505718444046766086325128514549301113944667492053189486607" +
+ "5638152321436011512404808361119326026027238444019992518081753153" +
+ "5965931647037093368608713442955529617501657176146245891571745113" +
+ "4028700771890451167051818999837042261788828826028681595867897235" +
+ "7967091503500375497498573022675671178275171110498592645868107163" +
+ "8525996766798322809764200941677343791419428587801897366593842552" +
+ "7272226864578661449281241619675217353931828233756506947863330597" +
+ "8338073826285687331647183058971791153730741973483420110408271570" +
+ "1367336140572971505716740825623220507359429297584634909330541150" +
+ "79473593821332264673455059897928082590541");
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ Assert.Equal(expected, reader.GetInteger());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void GetNegativeBigInteger(PublicEncodingRules ruleSet)
+ {
+ // This uses a length that doesn't line up with the array pool size so
+ // the fill code gets tested on netstandard.
+ // It's the same data as above, minus the padding and lead byte (and the
+ // next byte changed from 0x68 to 0x88)
+ byte[] inputData = (
+ "0281FF8861FA9D5DB763633BF5A64EF6E7C2C2367F48D2D46643A22DFC" +
+ "FCCB24E58A14D0F06BDC956437F2A56BA4BEF70BA361BF12964A0D665AFD84B0" +
+ "F7494C8FA4ABC5FCA2E017C06178AEF2CDAD1B5F18E997A14B965C074E8F5649" +
+ "70607276B00583932240FE6E2DD013026F9AE13D7C91CC07C4E1E8E87737DC06" +
+ "EF2B575B89D62EFE46859F8255A123692A706C68122D4DAFE11CB205A7B3DE06" +
+ "E553F7B95F978EF8601A8DF819BF32040BDF92A0DE0DF269B4514282E17AC699" +
+ "34E8440A48AB9D1F5DF89A502CEF6DFDBE790045BD45E0C94E5CA8ADD76A013E" +
+ "9C978440FC8A9E2A9A4940B2460819C3E302AA9C9F355AD754C86D3ED77DDAA3" +
+ "DA13810B4D").HexToByteArray();
+
+ BigInteger expected = BigInteger.Parse(
+ "-" +
+ "5898547409447487884446992857601985651316300515844052200158198046" +
+ "7814538020408452501006415149581619776188797413593169277984980446" +
+ "1302361382932378450492052337986628823880000831383555853860116718" +
+ "5361729331647715885538858385106930514758305144777880150203212976" +
+ "6715081632226412951106013360243549075631850526067219857352295397" +
+ "2308328327377769665309386917336850273904442596855844458638806936" +
+ "6169824439111394938336579524651037239551388910737675470211780509" +
+ "8035477769907389338548451561341965157059382875181284370047601682" +
+ "6924486017215979427815833587119797658480104671279234402026468966" +
+ "86109928634475300812601680679147599027");
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ Assert.Equal(expected, reader.GetInteger());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
+ public static void GetDiminutiveBigInteger(PublicEncodingRules ruleSet)
+ {
+ // GetBigInteger with the last byte removed.
+ // Since it is no longer an ArrayPool alignment size the fill code gets tested on netstandard.
+ byte[] inputData = (
+ "0282010000A46861FA9D5DB763633BF5A64EF6E7C2C2367F48D2D46643A22DFC" +
+ "FCCB24E58A14D0F06BDC956437F2A56BA4BEF70BA361BF12964A0D665AFD84B0" +
+ "F7494C8FA4ABC5FCA2E017C06178AEF2CDAD1B5F18E997A14B965C074E8F5649" +
+ "70607276B00583932240FE6E2DD013026F9AE13D7C91CC07C4E1E8E87737DC06" +
+ "EF2B575B89D62EFE46859F8255A123692A706C68122D4DAFE11CB205A7B3DE06" +
+ "E553F7B95F978EF8601A8DF819BF32040BDF92A0DE0DF269B4514282E17AC699" +
+ "34E8440A48AB9D1F5DF89A502CEF6DFDBE790045BD45E0C94E5CA8ADD76A013E" +
+ "9C978440FC8A9E2A9A4940B2460819C3E302AA9C9F355AD754C86D3ED77DDAA3" +
+ "DA13810B").HexToByteArray();
+
+ BigInteger expected = BigInteger.Parse(
+ "2075455505718444046766086325128514549301113944667492053189486607" +
+ "5638152321436011512404808361119326026027238444019992518081753153" +
+ "5965931647037093368608713442955529617501657176146245891571745113" +
+ "4028700771890451167051818999837042261788828826028681595867897235" +
+ "7967091503500375497498573022675671178275171110498592645868107163" +
+ "8525996766798322809764200941677343791419428587801897366593842552" +
+ "7272226864578661449281241619675217353931828233756506947863330597" +
+ "8338073826285687331647183058971791153730741973483420110408271570" +
+ "1367336140572971505716740825623220507359429297584634909330541150" +
+ "79473593821332264673455059897928082590541") >> 8;
+
+ AsnReader reader = new AsnReader(inputData, (AsnEncodingRules)ruleSet);
+ Assert.Equal(expected, reader.GetInteger());
+ }
+
+ [Theory]
+ [InlineData(PublicEncodingRules.BER)]
+ [InlineData(PublicEncodingRules.CER)]
+ [InlineData(PublicEncodingRules.DER)]
public static void TagMustBeCorrect_Universal(PublicEncodingRules ruleSet)
{
byte[] inputData = { 2, 1, 0x7E };
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/SimpleDeserialize.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/SimpleDeserialize.cs
new file mode 100644
index 0000000000..2fe1e1acb4
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/SimpleDeserialize.cs
@@ -0,0 +1,826 @@
+// 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.Globalization;
+using System.IO;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+using PublicEncodingRules=System.Security.Cryptography.Tests.Asn1.Asn1ReaderTests.PublicEncodingRules;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public static class SimpleDeserialize
+ {
+ [Theory]
+ [InlineData(
+ PublicEncodingRules.BER,
+ "3080" + "06072A8648CE3D0201" + "06082A8648CE3D030107" + "0000",
+ "1.2.840.10045.3.1.7")]
+ // More!
+ public static void AlgorithmIdentifier_ECC_WithCurves(
+ PublicEncodingRules ruleSet,
+ string inputHex,
+ string curveOid)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+
+ var algorithmIdentifier = AsnSerializer.Deserialize<AlgorithmIdentifier>(
+ inputData,
+ (AsnEncodingRules)ruleSet);
+
+ Assert.Equal("1.2.840.10045.2.1", algorithmIdentifier.Algorithm.Value);
+
+ var reader = new AsnReader(algorithmIdentifier.Parameters, (AsnEncodingRules)ruleSet);
+ Oid curveId = reader.ReadObjectIdentifier(skipFriendlyName: true);
+ Assert.Equal(curveOid, curveId.Value);
+ }
+
+ [Fact]
+ public static void AllTheSimpleThings()
+ {
+ const string InputHex =
+ "3080" +
+ "0101FF" +
+ "0201FE" +
+ "020101" +
+ "0202FEFF" +
+ "02020101" +
+ "0204FEFFFFFF" +
+ "020401000001" +
+ "0208FEFFFFFFFFFFFFFF" +
+ "02080100000000000001" +
+ "0209010000000000000001" +
+ "0303000102" +
+ "0404FF0055AA" +
+ "0500" +
+ "06082A8648CE3D030107" +
+ "06072A8648CE3D0201" +
+ "06092A864886F70D010101" +
+ "0A011E" +
+ "0C2544722E2026204D72732E20536D697468E280904A6F6E657320EFB9A0206368696C6472656E" +
+ "162144722E2026204D72732E20536D6974682D4A6F6E65732026206368696C6472656E" +
+ "1E42" +
+ "00440072002E002000260020004D00720073002E00200053006D006900740068" +
+ "2010004A006F006E006500730020FE600020006300680069006C006400720065" +
+ "006E" +
+ "3080" +
+ "010100" +
+ "010100" +
+ "0101FF" +
+ "0101FF" +
+ "010100" +
+ "0000" +
+ "3180" +
+ "020100" +
+ "020101" +
+ "0201FE" +
+ "0201FF" +
+ "02020100" +
+ "0000" +
+ "3080" +
+ "020100" +
+ "020101" +
+ "020200FE" +
+ "02017F" +
+ "020200FF" +
+ "0000" +
+ "170D3530303130323132333435365A" +
+ "170D3530303130323132333435365A" +
+ "181432303136313130363031323334352E373635345A" +
+ "180F32303136313130363031323334355A" +
+ "020F0102030405060708090A0B0C0D0E0F" +
+ "0000";
+
+ byte[] inputData = InputHex.HexToByteArray();
+
+ var atst = AsnSerializer.Deserialize<AllTheSimpleThings>(
+ inputData,
+ AsnEncodingRules.BER);
+
+ const string UnicodeVerifier = "Dr. & Mrs. Smith\u2010Jones \uFE60 children";
+ const string AsciiVerifier = "Dr. & Mrs. Smith-Jones & children";
+
+ Assert.False(atst.NotBool, "atst.NotBool");
+ Assert.Equal(-2, atst.SByte);
+ Assert.Equal(1, atst.Byte);
+ Assert.Equal(unchecked((short)0xFEFF), atst.Short);
+ Assert.Equal(0x0101, atst.UShort);
+ Assert.Equal(unchecked((int)0xFEFFFFFF), atst.Int);
+ Assert.Equal((uint)0x01000001, atst.UInt);
+ Assert.Equal(unchecked((long)0xFEFFFFFFFFFFFFFF), atst.Long);
+ Assert.Equal(0x0100000000000001UL, atst.ULong);
+ Assert.Equal("010000000000000001", atst.BigIntBytes.ByteArrayToHex());
+ Assert.Equal("0102", atst.BitStringBytes.ByteArrayToHex());
+ Assert.Equal("FF0055AA", atst.OctetStringBytes.ByteArrayToHex());
+ Assert.Equal("0500", atst.Null.ByteArrayToHex());
+ Assert.Equal("1.2.840.10045.3.1.7", atst.UnattrOid.Value);
+ Assert.Equal("1.2.840.10045.3.1.7", atst.UnattrOid.FriendlyName);
+ Assert.Equal("1.2.840.10045.2.1", atst.WithName.Value);
+ Assert.Equal("ECC", atst.WithName.FriendlyName);
+ Assert.Equal("1.2.840.113549.1.1.1", atst.OidString);
+ Assert.Equal(UniversalTagNumber.BMPString, atst.LinearEnum);
+ Assert.Equal(UnicodeVerifier, atst.Utf8Encoded);
+ Assert.Equal(AsciiVerifier, atst.Ia5Encoded);
+ Assert.Equal(UnicodeVerifier, atst.BmpEncoded);
+ Assert.Equal(new[] { false, false, true, true, false }, atst.Bools);
+ Assert.Equal(new[] { 0, 1, -2, -1, 256 }, atst.Ints);
+ Assert.Equal(new byte[] { 0, 1, 254, 127, 255 }, atst.LittleUInts);
+ Assert.Equal(new DateTimeOffset(1950, 1, 2, 12, 34, 56, TimeSpan.Zero), atst.UtcTime2049);
+ Assert.Equal(new DateTimeOffset(2050, 1, 2, 12, 34, 56, TimeSpan.Zero), atst.UtcTime2099);
+ Assert.Equal(new DateTimeOffset(2016, 11, 6, 1, 23, 45, 765, TimeSpan.Zero), atst.GeneralizedTimeWithFractions);
+ Assert.Equal(new DateTimeOffset(2016, 11, 6, 1, 23, 45, TimeSpan.Zero), atst.GeneralizedTimeNoFractions);
+ Assert.Equal(BigInteger.Parse("0102030405060708090A0B0C0D0E0F", NumberStyles.HexNumber), atst.BigInteger);
+ }
+
+ [Fact]
+ public static void ReadEcPublicKey()
+ {
+ const string PublicKeyValue =
+ "04" +
+ "2363DD131DA65E899A2E63E9E05E50C830D4994662FFE883DB2B9A767DCCABA2" +
+ "F07081B5711BE1DEE90DFC8DE17970C2D937A16CD34581F52B8D59C9E9532D13";
+
+ const string InputHex =
+ "3059" +
+ "3013" +
+ "06072A8648CE3D0201" +
+ "06082A8648CE3D030107" +
+ "0342" +
+ "00" +
+ PublicKeyValue;
+
+ byte[] inputData = InputHex.HexToByteArray();
+
+ var spki = AsnSerializer.Deserialize<SubjectPublicKeyInfo>(
+ inputData,
+ AsnEncodingRules.DER);
+
+ Assert.Equal("1.2.840.10045.2.1", spki.AlgorithmIdentifier.Algorithm.Value);
+ Assert.Equal(PublicKeyValue, spki.PublicKey.ByteArrayToHex());
+
+ AsnReader reader = new AsnReader(spki.AlgorithmIdentifier.Parameters, AsnEncodingRules.DER);
+ string curveOid = reader.ReadObjectIdentifierAsString();
+ Assert.False(reader.HasData, "reader.HasData");
+ Assert.Equal("1.2.840.10045.3.1.7", curveOid);
+ }
+
+ [Fact]
+ public static void ReadDirectoryString()
+ {
+ const string BmpInputHex = "1E0400480069";
+ const string Utf8InputHex = "0C024869";
+
+ var ds1 = AsnSerializer.Deserialize<DirectoryString>(
+ BmpInputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ var ds2 = AsnSerializer.Deserialize<DirectoryString>(
+ Utf8InputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ Assert.NotNull(ds1);
+ Assert.NotNull(ds2);
+ Assert.Null(ds1.Utf8String);
+ Assert.Null(ds2.BmpString);
+ Assert.Equal("Hi", ds1.BmpString);
+ Assert.Equal("Hi", ds2.Utf8String);
+ }
+
+ [Fact]
+ public static void ReadFlexibleString()
+ {
+ const string BmpInputHex = "1E0400480069";
+ const string Utf8InputHex = "0C024869";
+ const string Ia5InputHex = "16024869";
+
+ var fs1 = AsnSerializer.Deserialize<FlexibleString>(
+ BmpInputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ var fs2 = AsnSerializer.Deserialize<FlexibleString>(
+ Utf8InputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ var fs3 = AsnSerializer.Deserialize<FlexibleString>(
+ Ia5InputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ Assert.Null(fs1.DirectoryString?.Utf8String);
+ Assert.Null(fs1.Ascii);
+ Assert.Null(fs2.DirectoryString?.BmpString);
+ Assert.Null(fs2.Ascii);
+ Assert.Null(fs3.DirectoryString?.BmpString);
+ Assert.Null(fs3.DirectoryString?.Utf8String);
+ Assert.False(fs3.DirectoryString.HasValue, "fs3.DirectoryString.HasValue");
+ Assert.Equal("Hi", fs1.DirectoryString?.BmpString);
+ Assert.Equal("Hi", fs2.DirectoryString?.Utf8String);
+ Assert.Equal("Hi", fs3.Ascii);
+ }
+
+ [Fact]
+ public static void ReadFlexibleString_Class()
+ {
+ const string BmpInputHex = "1E0400480069";
+ const string Utf8InputHex = "0C024869";
+ const string Ia5InputHex = "16024869";
+
+ var fs1 = AsnSerializer.Deserialize<FlexibleStringClass>(
+ BmpInputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ var fs2 = AsnSerializer.Deserialize<FlexibleStringClass>(
+ Utf8InputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ var fs3 = AsnSerializer.Deserialize<FlexibleStringClass>(
+ Ia5InputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ Assert.Null(fs1.DirectoryString?.Utf8String);
+ Assert.Null(fs1.Ascii);
+ Assert.Null(fs2.DirectoryString?.BmpString);
+ Assert.Null(fs2.Ascii);
+ Assert.Null(fs3.DirectoryString?.BmpString);
+ Assert.Null(fs3.DirectoryString?.Utf8String);
+ Assert.Null(fs3.DirectoryString);
+ Assert.Equal("Hi", fs1.DirectoryString?.BmpString);
+ Assert.Equal("Hi", fs2.DirectoryString?.Utf8String);
+ Assert.Equal("Hi", fs3.Ascii);
+ }
+
+ [Fact]
+ public static void ReadFlexibleString_ClassHybrid()
+ {
+ const string BmpInputHex = "1E0400480069";
+ const string Utf8InputHex = "0C024869";
+ const string Ia5InputHex = "16024869";
+
+ var fs1 = AsnSerializer.Deserialize<FlexibleStringClassHybrid>(
+ BmpInputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ var fs2 = AsnSerializer.Deserialize<FlexibleStringClassHybrid>(
+ Utf8InputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ var fs3 = AsnSerializer.Deserialize<FlexibleStringClassHybrid>(
+ Ia5InputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ Assert.Null(fs1.DirectoryString?.Utf8String);
+ Assert.Null(fs1.Ascii);
+ Assert.Null(fs2.DirectoryString?.BmpString);
+ Assert.Null(fs2.Ascii);
+ Assert.Null(fs3.DirectoryString?.BmpString);
+ Assert.Null(fs3.DirectoryString?.Utf8String);
+ Assert.False(fs3.DirectoryString.HasValue, "fs3.DirectoryString.HasValue");
+ Assert.Equal("Hi", fs1.DirectoryString?.BmpString);
+ Assert.Equal("Hi", fs2.DirectoryString?.Utf8String);
+ Assert.Equal("Hi", fs3.Ascii);
+ }
+
+ [Fact]
+ public static void ReadFlexibleString_StructHybrid()
+ {
+ const string BmpInputHex = "1E0400480069";
+ const string Utf8InputHex = "0C024869";
+ const string Ia5InputHex = "16024869";
+
+ var fs1 = AsnSerializer.Deserialize<FlexibleStringStructHybrid>(
+ BmpInputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ var fs2 = AsnSerializer.Deserialize<FlexibleStringStructHybrid>(
+ Utf8InputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ var fs3 = AsnSerializer.Deserialize<FlexibleStringStructHybrid>(
+ Ia5InputHex.HexToByteArray(),
+ AsnEncodingRules.DER);
+
+ Assert.Null(fs1.DirectoryString?.Utf8String);
+ Assert.Null(fs1.Ascii);
+ Assert.Null(fs2.DirectoryString?.BmpString);
+ Assert.Null(fs2.Ascii);
+ Assert.Null(fs3.DirectoryString?.BmpString);
+ Assert.Null(fs3.DirectoryString?.Utf8String);
+ Assert.Null(fs3.DirectoryString);
+ Assert.Equal("Hi", fs1.DirectoryString?.BmpString);
+ Assert.Equal("Hi", fs2.DirectoryString?.Utf8String);
+ Assert.Equal("Hi", fs3.Ascii);
+ }
+
+ [Fact]
+ public static void Choice_CycleRoot_Throws()
+ {
+ byte[] inputBytes = { 0x01, 0x01, 0x00 };
+
+ Assert.Throws<AsnSerializationConstraintException>(
+ () =>
+ AsnSerializer.Deserialize<CycleRoot>(
+ inputBytes,
+ AsnEncodingRules.DER)
+ );
+ }
+
+ [Fact]
+ public static void DirectoryStringClass_AsNull()
+ {
+ byte[] inputBytes = { 0x05, 0x00 };
+
+ DirectoryStringClass ds = AsnSerializer.Deserialize<DirectoryStringClass>(
+ inputBytes,
+ AsnEncodingRules.DER);
+
+ Assert.Null(ds);
+ }
+
+ [Fact]
+ public static void Deserialize_ContextSpecific_Choice()
+ {
+ byte[] inputBytes = { 0x82, 0x00 };
+
+ ContextSpecificChoice choice = AsnSerializer.Deserialize<ContextSpecificChoice>(
+ inputBytes,
+ AsnEncodingRules.DER);
+
+ Assert.Null(choice.Utf8String);
+ Assert.Equal(string.Empty, choice.IA5String);
+ }
+
+ [Fact]
+ public static void Deserialize_UtcTime_WithTwoYearMax()
+ {
+ const string UtcTimeValue = "170D3132303130323233353935395A";
+
+ const string InputHex =
+ "3080" + UtcTimeValue + UtcTimeValue + UtcTimeValue + "0000";
+
+ byte[] inputBytes = InputHex.HexToByteArray();
+
+ UtcTimeTwoDigitYears dates = AsnSerializer.Deserialize<UtcTimeTwoDigitYears>(
+ inputBytes,
+ AsnEncodingRules.BER);
+
+ Assert.Equal(new DateTimeOffset(1912, 1, 2, 23, 59, 59, TimeSpan.Zero), dates.ErnestoSabatoLifetime);
+ Assert.Equal(new DateTimeOffset(2012, 1, 2, 23, 59, 59, TimeSpan.Zero), dates.MayanPhenomenon);
+ Assert.Equal(new DateTimeOffset(2012, 1, 2, 23, 59, 59, TimeSpan.Zero), dates.ImplicitMax);
+ }
+
+ [Fact]
+ public static void Deserialize_NamedBitLists()
+ {
+ const string InputHex =
+ "3080" +
+ "0303000841" +
+ "0000";
+
+ byte[] inputBytes = InputHex.HexToByteArray();
+
+ var variants = AsnSerializer.Deserialize<NamedBitListModeVariants>(
+ inputBytes,
+ AsnEncodingRules.BER);
+
+ Assert.Equal(
+ SomeFlagsEnum.BitFour | SomeFlagsEnum.BitNine | SomeFlagsEnum.BitFifteen,
+ variants.DefaultMode);
+ }
+
+ [Fact]
+ public static void ReadAnyValueWithExpectedTag()
+ {
+ byte[] inputData = "308006010030030101000000".HexToByteArray();
+
+ var data = AsnSerializer.Deserialize<AnyWithExpectedTag>(
+ inputData,
+ AsnEncodingRules.BER);
+
+ Assert.Equal("0.0", data.Id);
+ Assert.Equal(5, data.Data.Length);
+ Assert.True(Unsafe.AreSame(ref data.Data.Span.DangerousGetPinnableReference(), ref inputData[5]));
+
+ // Change [Constructed] SEQUENCE to [Constructed] Context-Specific 0.
+ inputData[5] = 0xA0;
+
+ Assert.Throws<CryptographicException>(
+ () => AsnSerializer.Deserialize<AnyWithExpectedTag>(inputData, AsnEncodingRules.BER));
+ }
+
+ [Theory]
+ [InlineData("3000", false, false)]
+ [InlineData("30051603494135", false, true)]
+ [InlineData("30060C0455544638", true, false)]
+ [InlineData("300B0C04555446381603494135", true, true)]
+ public static void ReadOptionals(string inputHex, bool hasUtf8, bool hasIa5)
+ {
+ byte[] inputData = inputHex.HexToByteArray();
+ var data = AsnSerializer.Deserialize<OptionalValues>(inputData, AsnEncodingRules.BER);
+
+ if (hasUtf8)
+ {
+ Assert.Equal("UTF8", data.Utf8String);
+ }
+ else
+ {
+ Assert.Null(data.Utf8String);
+ }
+
+ if (hasIa5)
+ {
+ Assert.Equal("IA5", data.IA5String);
+ }
+ else
+ {
+ Assert.Null(data.IA5String);
+ }
+ }
+
+ [Fact]
+ public static void TooMuchData()
+ {
+ // This is { IA5String("IA5"), UTF8String("UTF8") }, which is the opposite
+ // of the field order of OptionalValues. SO it will see the UTF8String as null,
+ // then the IA5String as present, but then data remains.
+ byte[] inputData = "300B16034941350C0455544638".HexToByteArray();
+
+ Assert.Throws<CryptographicException>(
+ () => AsnSerializer.Deserialize<OptionalValues>(inputData, AsnEncodingRules.BER));
+ }
+ }
+
+ // RFC 3280 / ITU-T X.509
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct AlgorithmIdentifier
+ {
+ public Oid Algorithm;
+ [AnyValue]
+ public ReadOnlyMemory<byte> Parameters;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SubjectPublicKeyInfo
+ {
+ public AlgorithmIdentifier AlgorithmIdentifier;
+ [BitString]
+ public ReadOnlyMemory<byte> PublicKey;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal sealed class AllTheSimpleThings
+ {
+ private bool _bool;
+ private sbyte _sbyte;
+ private byte _byte;
+ private short _short;
+ private ushort _ushort;
+ private int _int;
+ private uint _uint;
+ private long _long;
+ private ulong _ulong;
+ [Integer]
+ private ReadOnlyMemory<byte> _bigInt;
+ [BitString]
+ private ReadOnlyMemory<byte> _bitString;
+ [OctetString]
+ private ReadOnlyMemory<byte> _octetString;
+ [AnyValue]
+ private ReadOnlyMemory<byte> _null;
+ private Oid _oidNoName;
+ [ObjectIdentifier(PopulateFriendlyName = true)]
+ private Oid _oid;
+ [ObjectIdentifier]
+ private string _oidString;
+ private UniversalTagNumber _nonFlagsEnum;
+ [UTF8String]
+ private string _utf8String;
+ [IA5String]
+ private string _ia5String;
+ [BMPString]
+ private string _bmpString;
+ private bool[] _bools;
+ [SetOf]
+ private int[] _ints;
+ [SequenceOf]
+ private byte[] _littleUInts;
+ [UtcTime]
+ public DateTimeOffset UtcTime2049;
+ [UtcTime(TwoDigitYearMax = 2099)]
+ public DateTimeOffset UtcTime2099;
+ [GeneralizedTime]
+ public DateTimeOffset GeneralizedTimeWithFractions;
+ [GeneralizedTime(DisallowFractions = true)]
+ public DateTimeOffset GeneralizedTimeNoFractions;
+ public BigInteger BigInteger;
+
+ public bool NotBool
+ {
+ get => !_bool;
+ set => _bool = !value;
+ }
+
+ public sbyte SByte
+ {
+ get => _sbyte;
+ set => _sbyte = value;
+ }
+
+ public byte Byte
+ {
+ get => _byte;
+ set => _byte = value;
+ }
+
+ public short Short
+ {
+ get => _short;
+ set => _short = value;
+ }
+
+ public ushort UShort
+ {
+ get => _ushort;
+ set => _ushort = value;
+ }
+
+ public int Int
+ {
+ get => _int;
+ set => _int = value;
+ }
+
+ public uint UInt
+ {
+ get => _uint;
+ set => _uint = value;
+ }
+
+ public long Long
+ {
+ get => _long;
+ set => _long = value;
+ }
+
+ public ulong ULong
+ {
+ get => _ulong;
+ set => _ulong = value;
+ }
+
+ public ReadOnlyMemory<byte> BigIntBytes
+ {
+ get => _bigInt;
+ set => _bigInt = value.ToArray();
+ }
+
+ public ReadOnlyMemory<byte> BitStringBytes
+ {
+ get => _bitString;
+ set => _bitString = value.ToArray();
+ }
+
+ public ReadOnlyMemory<byte> OctetStringBytes
+ {
+ get => _octetString;
+ set => _octetString = value.ToArray();
+ }
+
+ public ReadOnlyMemory<byte> Null
+ {
+ get => _null;
+ set => _null = value.ToArray();
+ }
+
+ public Oid UnattrOid
+ {
+ get => _oidNoName;
+ set => _oidNoName = value;
+ }
+
+ public Oid WithName
+ {
+ get => _oid;
+ set => _oid = value;
+ }
+
+ public string OidString
+ {
+ get => _oidString;
+ set => _oidString = value;
+ }
+
+ public UniversalTagNumber LinearEnum
+ {
+ get => _nonFlagsEnum;
+ set => _nonFlagsEnum = value;
+ }
+
+ public string Utf8Encoded
+ {
+ get => _utf8String;
+ set => _utf8String = value;
+ }
+
+ public string Ia5Encoded
+ {
+ get => _ia5String;
+ set => _ia5String = value;
+ }
+
+ public string BmpEncoded
+ {
+ get => _bmpString;
+ set => _bmpString = value;
+ }
+
+ public bool[] Bools
+ {
+ get => _bools;
+ set => _bools = value;
+ }
+
+ public int[] Ints
+ {
+ get => _ints;
+ set => _ints = value;
+ }
+
+ public byte[] LittleUInts
+ {
+ get => _littleUInts;
+ set => _littleUInts = value;
+ }
+ }
+
+ [Choice]
+ [StructLayout(LayoutKind.Sequential)]
+ public struct DirectoryString
+ {
+ [UTF8String]
+ public string Utf8String;
+ [BMPString]
+ public string BmpString;
+ }
+
+ [Choice]
+ [StructLayout(LayoutKind.Sequential)]
+ public struct FlexibleString
+ {
+ public DirectoryString? DirectoryString;
+
+ [IA5String]
+ public string Ascii;
+ }
+
+ [Choice(AllowNull = true)]
+ [StructLayout(LayoutKind.Sequential)]
+ public sealed class DirectoryStringClass
+ {
+ [UTF8String]
+ public string Utf8String;
+ [BMPString]
+ public string BmpString;
+ [PrintableString]
+ public string PrintableString;
+ }
+
+ [Choice]
+ [StructLayout(LayoutKind.Sequential)]
+ public sealed class FlexibleStringClass
+ {
+ public DirectoryStringClass DirectoryString;
+
+ [IA5String]
+ public string Ascii;
+ }
+
+ [Choice]
+ [StructLayout(LayoutKind.Sequential)]
+ public sealed class FlexibleStringClassHybrid
+ {
+ public DirectoryString? DirectoryString;
+
+ [IA5String]
+ public string Ascii;
+ }
+
+ [Choice]
+ [StructLayout(LayoutKind.Sequential)]
+ public struct FlexibleStringStructHybrid
+ {
+ public DirectoryStringClass DirectoryString;
+
+ [IA5String]
+ public string Ascii;
+ }
+
+ [Choice]
+ [StructLayout(LayoutKind.Sequential)]
+ public sealed class CycleRoot
+ {
+ public Cycle2 C2;
+ }
+
+ [Choice]
+ [StructLayout(LayoutKind.Sequential)]
+ public sealed class Cycle2
+ {
+ public Cycle3 C3;
+ }
+
+ [Choice]
+ [StructLayout(LayoutKind.Sequential)]
+ public sealed class Cycle3
+ {
+ public CycleRoot CycleRoot;
+ }
+
+ [Choice]
+ [StructLayout(LayoutKind.Sequential)]
+ public struct ContextSpecificChoice
+ {
+ [UTF8String]
+ [ExpectedTag(3)]
+ public string Utf8String;
+
+ [IA5String]
+ [ExpectedTag(2)]
+ public string IA5String;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct UtcTimeTwoDigitYears
+ {
+ [UtcTime(TwoDigitYearMax = 2011)]
+ public DateTimeOffset ErnestoSabatoLifetime;
+
+ [UtcTime(TwoDigitYearMax = 2012)]
+ public DateTimeOffset MayanPhenomenon;
+
+ [UtcTime]
+ public DateTimeOffset ImplicitMax;
+ }
+
+ [Flags]
+ public enum SomeFlagsEnum : short
+ {
+ None = 0,
+ BitZero = 1 << 0,
+ BitOne = 1 << 1,
+ BitTwo = 1 << 2,
+ BitThree = 1 << 3,
+ BitFour = 1 << 4,
+ BitFive = 1 << 5,
+ BitSix = 1 << 6,
+ BitSeven = 1 << 7,
+ BitEight = 1 << 8,
+ BitNine = 1 << 9,
+ BitTen = 1 << 10,
+ BitEleven = 1 << 11,
+ BitTwelve = 1 << 12,
+ BitThirteen = 1 << 13,
+ BitFourteen = 1 << 14,
+ BitFifteen = short.MinValue,
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct NamedBitListModeVariants
+ {
+ public SomeFlagsEnum DefaultMode;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct ExplicitValueStruct
+ {
+ [ExpectedTag(0, ExplicitTag = true)]
+ public int ExplicitInt;
+
+ public int ImplicitInt;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct AnyWithExpectedTag
+ {
+ [ObjectIdentifier]
+ public string Id;
+
+ [AnyValue]
+ [ExpectedTag(TagClass.Universal, (int)UniversalTagNumber.Sequence)]
+ public ReadOnlyMemory<byte> Data;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct OptionalValues
+ {
+ [UTF8String, OptionalValue]
+ public string Utf8String;
+
+ [IA5String, OptionalValue]
+ public string IA5String;
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/SimpleSerialize.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/SimpleSerialize.cs
new file mode 100644
index 0000000000..e8f821b00a
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/SimpleSerialize.cs
@@ -0,0 +1,394 @@
+// 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.Globalization;
+using System.Numerics;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public static class SimpleSerialize
+ {
+ [Fact]
+ public static void SerializeAlgorithmIdentifier()
+ {
+ AlgorithmIdentifier identifier = new AlgorithmIdentifier
+ {
+ Algorithm = new Oid("2.16.840.1.101.3.4.2.1", "SHA-2-256"),
+ Parameters = new byte[] { 5, 0 },
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(identifier, AsnEncodingRules.DER);
+
+ const string ExpectedHex =
+ "300D" +
+ "0609608648016503040201" +
+ "0500";
+
+ Assert.Equal(ExpectedHex, writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeAlgorithmIdentifier_CER()
+ {
+ AlgorithmIdentifier identifier = new AlgorithmIdentifier
+ {
+ Algorithm = new Oid("2.16.840.1.101.3.4.2.1", "SHA-2-256"),
+ Parameters = new byte[] { 5, 0 },
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(identifier, AsnEncodingRules.CER);
+
+ const string ExpectedHex =
+ "3080" +
+ "0609608648016503040201" +
+ "0500" +
+ "0000";
+
+ Assert.Equal(ExpectedHex, writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeAllTheSimpleThings_CER()
+ {
+ const string ExpectedHex =
+ "3080" +
+ "0101FF" +
+ "0201FE" +
+ "020101" +
+ "0202FEFF" +
+ "02020101" +
+ "0204FEFFFFFF" +
+ "020401000001" +
+ "0208FEFFFFFFFFFFFFFF" +
+ "02080100000000000001" +
+ "0209010000000000000001" +
+ "0303000102" +
+ "0404FF0055AA" +
+ "0500" +
+ "06082A8648CE3D030107" +
+ "06072A8648CE3D0201" +
+ "06092A864886F70D010101" +
+ "0A011E" +
+ "0C2544722E2026204D72732E20536D697468E280904A6F6E657320EFB9A0206368696C6472656E" +
+ "162144722E2026204D72732E20536D6974682D4A6F6E65732026206368696C6472656E" +
+ "1E42" +
+ "00440072002E002000260020004D00720073002E00200053006D006900740068" +
+ "2010004A006F006E006500730020FE600020006300680069006C006400720065" +
+ "006E" +
+ "3080" +
+ "010100" +
+ "010100" +
+ "0101FF" +
+ "0101FF" +
+ "010100" +
+ "0000" +
+ "3180" +
+ "020100" +
+ "020101" +
+ "0201FE" +
+ "0201FF" +
+ "02020100" +
+ "0000" +
+ "3080" +
+ "020100" +
+ "020101" +
+ "020200FE" +
+ "02017F" +
+ "020200FF" +
+ "0000" +
+ "170D3530303130323132333435365A" +
+ "170D3530303130323132333435365A" +
+ // This is different than what we read in deserialize,
+ // because we don't write back the .0004 second.
+ "181332303136313130363031323334352E3736355A" +
+ "180F32303136313130363031323334355A" +
+ "020F0102030405060708090A0B0C0D0E0F" +
+ "0000";
+
+ const string UnicodeVerifier = "Dr. & Mrs. Smith\u2010Jones \uFE60 children";
+ const string AsciiVerifier = "Dr. & Mrs. Smith-Jones & children";
+
+ var allTheThings = new AllTheSimpleThings
+ {
+ NotBool = false,
+ SByte = -2,
+ Byte = 1,
+ Short = unchecked((short)0xFEFF),
+ UShort = 0x0101,
+ Int = unchecked((int)0xFEFFFFFF),
+ UInt = 0x01000001U,
+ Long = unchecked((long)0xFEFFFFFFFFFFFFFF),
+ ULong = 0x0100000000000001UL,
+ BigIntBytes = "010000000000000001".HexToByteArray(),
+ BitStringBytes = new byte[] { 1, 2 },
+ OctetStringBytes = new byte[] { 0xFF, 0, 0x55, 0xAA },
+ Null = new byte[] { 5, 0 },
+ UnattrOid = new Oid("1.2.840.10045.3.1.7", "1.2.840.10045.3.1.7"),
+ WithName = new Oid("1.2.840.10045.2.1", "ECC"),
+ OidString = "1.2.840.113549.1.1.1",
+ LinearEnum = UniversalTagNumber.BMPString,
+ Utf8Encoded = UnicodeVerifier,
+ Ia5Encoded = AsciiVerifier,
+ BmpEncoded = UnicodeVerifier,
+ Bools = new[] { false, false, true, true, false },
+ Ints = new [] { 0, 1, -2, -1, 256 },
+ LittleUInts = new byte[] { 0, 1, 254, 127, 255 },
+ UtcTime2049 = new DateTimeOffset(1950, 1, 2, 12, 34, 56, TimeSpan.Zero),
+ // 1950 is out of range for the reader, but the writer just does mod 100.
+ UtcTime2099 = new DateTimeOffset(1950, 1, 2, 12, 34, 56, TimeSpan.Zero),
+ GeneralizedTimeWithFractions = new DateTimeOffset(2016, 11, 6, 1, 23, 45, 765, TimeSpan.Zero),
+ // The fractions will be dropped off by the serializer/writer, to simplify
+ // the cases where the time was computed and isn't an integer number of seconds.
+ GeneralizedTimeNoFractions = new DateTimeOffset(2016, 11, 6, 1, 23, 45, 765, TimeSpan.Zero),
+ BigInteger = BigInteger.Parse("0102030405060708090A0B0C0D0E0F", NumberStyles.HexNumber),
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(allTheThings, AsnEncodingRules.CER);
+ Assert.Equal(ExpectedHex, writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeChoice_Null()
+ {
+ DirectoryStringClass directoryString = default;
+ AsnWriter writer = AsnSerializer.Serialize(directoryString, AsnEncodingRules.DER);
+
+ Assert.Equal("0500", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeChoice_First()
+ {
+ DirectoryStringClass directoryString = new DirectoryStringClass
+ {
+ Utf8String = "UTF8",
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(directoryString, AsnEncodingRules.DER);
+ Assert.Equal("0C0455544638", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeChoice_Second()
+ {
+ DirectoryStringClass directoryString = new DirectoryStringClass
+ {
+ BmpString = "BMP",
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(directoryString, AsnEncodingRules.DER);
+ Assert.Equal("1E060042004D0050", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeChoice_Third()
+ {
+ DirectoryStringClass directoryString = new DirectoryStringClass
+ {
+ PrintableString = "Printable",
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(directoryString, AsnEncodingRules.DER);
+ Assert.Equal("13095072696E7461626C65", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeChoice_NoSelection()
+ {
+ DirectoryStringClass directoryString = new DirectoryStringClass();
+
+ Assert.ThrowsAny<CryptographicException>(
+ () => AsnSerializer.Serialize(directoryString, AsnEncodingRules.DER));
+ }
+
+ [Fact]
+ public static void SerializeChoice_MultipleSelections()
+ {
+ DirectoryStringClass directoryString = new DirectoryStringClass
+ {
+ BmpString = "BMP",
+ PrintableString = "Printable",
+ };
+
+ Assert.ThrowsAny<CryptographicException>(
+ () => AsnSerializer.Serialize(directoryString, AsnEncodingRules.DER));
+ }
+
+ [Fact]
+ public static void SerializeChoice_WithinChoice()
+ {
+ var hybrid = new FlexibleStringClassHybrid
+ {
+ Ascii = "IA5",
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(hybrid, AsnEncodingRules.DER);
+ Assert.Equal("1603494135", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeChoice_WithinChoice2()
+ {
+ var hybrid = new FlexibleStringClassHybrid
+ {
+ DirectoryString = new DirectoryString
+ {
+ Utf8String = "Marco",
+ },
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(hybrid, AsnEncodingRules.DER);
+ Assert.Equal("0C054D6172636F", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeChoice_WithinChoice3()
+ {
+ var hybrid = new FlexibleStringClassHybrid
+ {
+ DirectoryString = new DirectoryString
+ {
+ BmpString = "Polo",
+ },
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(hybrid, AsnEncodingRules.DER);
+ Assert.Equal("1E080050006F006C006F", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeChoice_WithinChoice4()
+ {
+ var hybrid = new FlexibleStringStructHybrid
+ {
+ DirectoryString = new DirectoryStringClass
+ {
+ BmpString = "Polo",
+ },
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(hybrid, AsnEncodingRules.DER);
+ Assert.Equal("1E080050006F006C006F", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeChoice_WithinChoice5()
+ {
+ var hybrid = new FlexibleStringStructHybrid
+ {
+ DirectoryString = new DirectoryStringClass
+ {
+ Utf8String = "Marco",
+ },
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(hybrid, AsnEncodingRules.DER);
+ Assert.Equal("0C054D6172636F", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeChoice_WithinChoice6()
+ {
+ var hybrid = new FlexibleStringStructHybrid
+ {
+ Ascii = "IA5",
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(hybrid, AsnEncodingRules.DER);
+ Assert.Equal("1603494135", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeNamedBitList()
+ {
+ var flagsContainer = new NamedBitListModeVariants
+ {
+ DefaultMode = SomeFlagsEnum.BitEleven | SomeFlagsEnum.BitTwo | SomeFlagsEnum.BitFourteen
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(flagsContainer, AsnEncodingRules.DER);
+ Assert.Equal("30050303012012", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeDefaultValue_AsDefault()
+ {
+ var extension = new X509DeserializeTests.Extension
+ {
+ ExtnId = "2.5.29.19",
+ Critical = false,
+ ExtnValue = new byte[] { 0x30, 0x00 },
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(extension, AsnEncodingRules.DER);
+ Assert.Equal("30090603551D1304023000", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeDefaultValue_AsNonDefault()
+ {
+ var extension = new X509DeserializeTests.Extension
+ {
+ ExtnId = "2.5.29.15",
+ Critical = true,
+ ExtnValue = new byte[] { 0x03, 0x02, 0x05, 0xA0 },
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(extension, AsnEncodingRules.DER);
+ Assert.Equal("300E0603551D0F0101FF0404030205A0", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void SerializeExplicitValue()
+ {
+ var data = new ExplicitValueStruct
+ {
+ ExplicitInt = 3,
+ ImplicitInt = 0x17,
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(data, AsnEncodingRules.DER);
+ Assert.Equal("3008A003020103020117", writer.Encode().ByteArrayToHex());
+ }
+
+ [Fact]
+ public static void WriteAnyValueWithExpectedTag()
+ {
+ byte[] anyValue = "3003010100".HexToByteArray();
+
+ var data = new AnyWithExpectedTag
+ {
+ Id = "0.0",
+ Data = anyValue,
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(data, AsnEncodingRules.DER);
+ Assert.Equal("30080601003003010100", writer.Encode().ByteArrayToHex());
+
+ anyValue[0] = 0xA0;
+
+ Assert.Throws<CryptographicException>(() => AsnSerializer.Serialize(data, AsnEncodingRules.DER));
+ }
+
+ [Theory]
+ [InlineData("3000", false, false)]
+ [InlineData("30051603494135", false, true)]
+ [InlineData("30060C0455544638", true, false)]
+ [InlineData("300B0C04555446381603494135", true, true)]
+ public static void WriteOptionals(string expectedHex, bool hasUtf8, bool hasIa5)
+ {
+ var data = new OptionalValues
+ {
+ Utf8String = hasUtf8 ? "UTF8" : null,
+ IA5String = hasIa5 ? "IA5" : null,
+ };
+
+ AsnWriter writer = AsnSerializer.Serialize(data, AsnEncodingRules.DER);
+ Assert.Equal(expectedHex, writer.Encode().ByteArrayToHex());
+ }
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/X509DeserializeTests.cs b/src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/X509DeserializeTests.cs
new file mode 100644
index 0000000000..f781f6329a
--- /dev/null
+++ b/src/System.Security.Cryptography.Encoding/tests/Asn1/Serializer/X509DeserializeTests.cs
@@ -0,0 +1,188 @@
+// 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.InteropServices;
+using System.Security.Cryptography.Asn1;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests.Asn1
+{
+ public static class X509DeserializeTests
+ {
+ [Fact]
+ public static void ReadMicrosoftDotCom()
+ {
+ byte[] buf = Convert.FromBase64String(MicrosoftDotComBase64);
+
+ Certificate cert = AsnSerializer.Deserialize<Certificate>(
+ buf,
+ AsnEncodingRules.DER);
+
+ ref TbsCertificate tbsCertificate = ref cert.TbsCertificate;
+ ref SubjectPublicKeyInfo spki = ref tbsCertificate.SubjectPublicKeyInfo;
+
+ Assert.Equal(2, tbsCertificate.Version);
+ Assert.Equal("3DF70C5D9903F8D8868B9B8CCF20DF69", tbsCertificate.SerialNumber.ByteArrayToHex());
+
+ Assert.Equal("1.2.840.113549.1.1.11", tbsCertificate.Signature.Algorithm.Value);
+ Assert.Equal("0500", tbsCertificate.Signature.Parameters.ByteArrayToHex());
+
+ // Issuer goes here
+
+ Assert.Equal(new DateTimeOffset(2014, 10, 15, 0, 0, 0, TimeSpan.Zero), tbsCertificate.Validity.NotBefore.Value);
+ Assert.Equal(new DateTimeOffset(2016, 10, 15, 23, 59, 59, TimeSpan.Zero), tbsCertificate.Validity.NotAfter.Value);
+
+ // Subject goes here
+
+ Assert.Equal("1.2.840.113549.1.1.1", spki.AlgorithmIdentifier.Algorithm.Value);
+ Assert.Equal("0500", spki.AlgorithmIdentifier.Parameters.ByteArrayToHex());
+ Assert.Equal(
+ "3082010A0282010100A46861FA9D5DB763633BF5A64EF6E7C2C2367F48D2D466" +
+ "43A22DFCFCCB24E58A14D0F06BDC956437F2A56BA4BEF70BA361BF12964A0D66" +
+ "5AFD84B0F7494C8FA4ABC5FCA2E017C06178AEF2CDAD1B5F18E997A14B965C07" +
+ "4E8F564970607276B00583932240FE6E2DD013026F9AE13D7C91CC07C4E1E8E8" +
+ "7737DC06EF2B575B89D62EFE46859F8255A123692A706C68122D4DAFE11CB205" +
+ "A7B3DE06E553F7B95F978EF8601A8DF819BF32040BDF92A0DE0DF269B4514282" +
+ "E17AC69934E8440A48AB9D1F5DF89A502CEF6DFDBE790045BD45E0C94E5CA8AD" +
+ "D76A013E9C978440FC8A9E2A9A4940B2460819C3E302AA9C9F355AD754C86D3E" +
+ "D77DDAA3DA13810B4D0203010001",
+ spki.PublicKey.ByteArrayToHex());
+
+ Assert.Null(tbsCertificate.IssuerUniqueId);
+ Assert.Null(tbsCertificate.SubjectUniqueId);
+
+ Assert.Equal(8, tbsCertificate.Extensions.Length);
+ Assert.Equal("2.5.29.17", tbsCertificate.Extensions[0].ExtnId);
+ Assert.Equal("2.5.29.19", tbsCertificate.Extensions[1].ExtnId);
+ Assert.Equal("2.5.29.15", tbsCertificate.Extensions[2].ExtnId);
+ Assert.Equal("2.5.29.37", tbsCertificate.Extensions[3].ExtnId);
+ Assert.Equal("2.5.29.32", tbsCertificate.Extensions[4].ExtnId);
+ Assert.Equal("2.5.29.35", tbsCertificate.Extensions[5].ExtnId);
+ Assert.Equal("2.5.29.31", tbsCertificate.Extensions[6].ExtnId);
+ Assert.Equal("1.3.6.1.5.5.7.1.1", tbsCertificate.Extensions[7].ExtnId);
+
+ Assert.Equal("1.2.840.113549.1.1.11", cert.SignatureAlgorithm.Algorithm.Value);
+ Assert.Equal("0500", cert.SignatureAlgorithm.Parameters.ByteArrayToHex());
+
+ Assert.Equal(
+ "15F8505B627ED7F9F96707097E93A51E7A7E05A3D420A5C258EC7A1CFE1843EC" +
+ "20ACF728AAFA7A1A1BC222A7CDBF4AF90AA26DEEB3909C0B3FB5C78070DAE3D6" +
+ "45BFCF840A4A3FDD988C7B3308BFE4EB3FD66C45641E96CA3352DBE2AEB4488A" +
+ "64A9C5FB96932BA70059CE92BD278B41299FD213471BD8165F924285AE3ECD66" +
+ "6C703885DCA65D24DA66D3AFAE39968521995A4C398C7DF38DFA82A20372F13D" +
+ "4A56ADB21B5822549918015647B5F8AC131CC5EB24534D172BC60218A88B65BC" +
+ "F71C7F388CE3E0EF697B4203720483BB5794455B597D80D48CD3A1D73CBBC609" +
+ "C058767D1FF060A609D7E3D4317079AF0CD0A8A49251AB129157F9894A036487",
+ cert.Signature.ByteArrayToHex());
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct Certificate
+ {
+ public TbsCertificate TbsCertificate;
+ public AlgorithmIdentifier SignatureAlgorithm;
+ [BitString]
+ public ReadOnlyMemory<byte> Signature;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct TbsCertificate
+ {
+ [ExpectedTag(0, ExplicitTag = true)]
+ [DefaultValue(0x02, 0x01, 0x01)]
+ public int Version;
+
+ [Integer]
+ public ReadOnlyMemory<byte> SerialNumber;
+
+ public AlgorithmIdentifier Signature;
+
+ [AnyValue]
+ public ReadOnlyMemory<byte> Issuer;
+
+ public Validity Validity;
+
+ [AnyValue]
+ public ReadOnlyMemory<byte> Subject;
+
+ public SubjectPublicKeyInfo SubjectPublicKeyInfo;
+
+ [ExpectedTag(1), BitString, OptionalValue]
+ public ReadOnlyMemory<byte>? IssuerUniqueId;
+
+ [ExpectedTag(2), BitString, OptionalValue]
+ public ReadOnlyMemory<byte>? SubjectUniqueId;
+
+ [ExpectedTag(3, ExplicitTag = true), OptionalValue]
+ public Extension[] Extensions;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct Extension
+ {
+ [ObjectIdentifier]
+ public string ExtnId;
+
+ [DefaultValue(0x01, 0x01, 0x00)]
+ public bool Critical;
+
+ [OctetString]
+ public ReadOnlyMemory<byte> ExtnValue;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct Validity
+ {
+ public Time NotBefore;
+ public Time NotAfter;
+ }
+
+ [Choice]
+ [StructLayout(LayoutKind.Sequential)]
+ public struct Time
+ {
+ [UtcTime]
+ public DateTimeOffset? UtcTime;
+ [GeneralizedTime(DisallowFractions = true)]
+ public DateTimeOffset? GeneralTime;
+
+ public DateTimeOffset Value => UtcTime ?? GeneralTime.Value;
+ }
+
+ private const string MicrosoftDotComBase64 =
+ @"
+MIIFlDCCBHygAwIBAgIQPfcMXZkD+NiGi5uMzyDfaTANBgkqhkiG9w0BAQsFADB3
+MQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAd
+BgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxKDAmBgNVBAMTH1N5bWFudGVj
+IENsYXNzIDMgRVYgU1NMIENBIC0gRzMwHhcNMTQxMDE1MDAwMDAwWhcNMTYxMDE1
+MjM1OTU5WjCCAQ8xEzARBgsrBgEEAYI3PAIBAxMCVVMxGzAZBgsrBgEEAYI3PAIB
+AgwKV2FzaGluZ3RvbjEdMBsGA1UEDxMUUHJpdmF0ZSBPcmdhbml6YXRpb24xEjAQ
+BgNVBAUTCTYwMDQxMzQ4NTELMAkGA1UEBhMCVVMxDjAMBgNVBBEMBTk4MDUyMRMw
+EQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdSZWRtb25kMRgwFgYDVQQJDA8x
+IE1pY3Jvc29mdCBXYXkxHjAcBgNVBAoMFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEO
+MAwGA1UECwwFTVNDT00xGjAYBgNVBAMMEXd3dy5taWNyb3NvZnQuY29tMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApGhh+p1dt2NjO/WmTvbnwsI2f0jS
+1GZDoi38/Msk5YoU0PBr3JVkN/Kla6S+9wujYb8SlkoNZlr9hLD3SUyPpKvF/KLg
+F8BheK7yza0bXxjpl6FLllwHTo9WSXBgcnawBYOTIkD+bi3QEwJvmuE9fJHMB8Th
+6Oh3N9wG7ytXW4nWLv5GhZ+CVaEjaSpwbGgSLU2v4RyyBaez3gblU/e5X5eO+GAa
+jfgZvzIEC9+SoN4N8mm0UUKC4XrGmTToRApIq50fXfiaUCzvbf2+eQBFvUXgyU5c
+qK3XagE+nJeEQPyKniqaSUCyRggZw+MCqpyfNVrXVMhtPtd92qPaE4ELTQIDAQAB
+o4IBgDCCAXwwMQYDVR0RBCowKIIRd3d3Lm1pY3Jvc29mdC5jb22CE3d3d3FhLm1p
+Y3Jvc29mdC5jb20wCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw
+FAYIKwYBBQUHAwEGCCsGAQUFBwMCMGYGA1UdIARfMF0wWwYLYIZIAYb4RQEHFwYw
+TDAjBggrBgEFBQcCARYXaHR0cHM6Ly9kLnN5bWNiLmNvbS9jcHMwJQYIKwYBBQUH
+AgIwGRoXaHR0cHM6Ly9kLnN5bWNiLmNvbS9ycGEwHwYDVR0jBBgwFoAUAVmr5906
+C1mmZGPWzyAHV9WR52owKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3NyLnN5bWNi
+LmNvbS9zci5jcmwwVwYIKwYBBQUHAQEESzBJMB8GCCsGAQUFBzABhhNodHRwOi8v
+c3Iuc3ltY2QuY29tMCYGCCsGAQUFBzAChhpodHRwOi8vc3Iuc3ltY2IuY29tL3Ny
+LmNydDANBgkqhkiG9w0BAQsFAAOCAQEAFfhQW2J+1/n5ZwcJfpOlHnp+BaPUIKXC
+WOx6HP4YQ+wgrPcoqvp6GhvCIqfNv0r5CqJt7rOQnAs/tceAcNrj1kW/z4QKSj/d
+mIx7Mwi/5Os/1mxFZB6WyjNS2+KutEiKZKnF+5aTK6cAWc6SvSeLQSmf0hNHG9gW
+X5JCha4+zWZscDiF3KZdJNpm06+uOZaFIZlaTDmMffON+oKiA3LxPUpWrbIbWCJU
+mRgBVke1+KwTHMXrJFNNFyvGAhioi2W89xx/OIzj4O9pe0IDcgSDu1eURVtZfYDU
+jNOh1zy7xgnAWHZ9H/BgpgnX49QxcHmvDNCopJJRqxKRV/mJSgNkhw==
+";
+ }
+}
diff --git a/src/System.Security.Cryptography.Encoding/tests/Resources/Strings.resx b/src/System.Security.Cryptography.Encoding/tests/Resources/Strings.resx
index 797443823e..f0515f179c 100644
--- a/src/System.Security.Cryptography.Encoding/tests/Resources/Strings.resx
+++ b/src/System.Security.Cryptography.Encoding/tests/Resources/Strings.resx
@@ -76,6 +76,63 @@
<data name="Cryptography_Asn_UnusedBitCountRange" xml:space="preserve">
<value>Unused bit count must be between 0 and 7, inclusive.</value>
</data>
+ <data name="Cryptography_AsnSerializer_AmbiguousFieldType" xml:space="preserve">
+ <value>Field '{0}' of type '{1}' has ambiguous type '{2}', an attribute derived from AsnTypeAttribute is required.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_Choice_AllowNullNonNullable" xml:space="preserve">
+ <value>[Choice].AllowNull=true is not valid because type '{0}' cannot have a null value.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_Choice_ConflictingTagMapping" xml:space="preserve">
+ <value>The tag ({0} {1}) for field '{2}' on type '{3}' already is associated in this context with field '{4}' on type '{5}'.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_Choice_DefaultValueDisallowed" xml:space="preserve">
+ <value>Field '{0}' on [Choice] type '{1}' has a default value, which is not permitted.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_Choice_NoChoiceWasMade" xml:space="preserve">
+ <value>An instance of [Choice] type '{0}' has no non-null fields.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_Choice_NonNullableField" xml:space="preserve">
+ <value>Field '{0}' on [Choice] type '{1}' can not be assigned a null value.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_Choice_TooManyValues" xml:space="preserve">
+ <value>Fields '{0}' and '{1}' on type '{2}' are both non-null when only one value is permitted.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_Choice_TypeCycle" xml:space="preserve">
+ <value>Field '{0}' on [Choice] type '{1}' has introduced a type chain cycle.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_MultipleAsnTypeAttributes" xml:space="preserve">
+ <value>Field '{0}' on type '{1}' has multiple attributes deriving from '{2}' when at most one is permitted.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_NoJaggedArrays" xml:space="preserve">
+ <value>Type '{0}' cannot be serialized or deserialized because it is an array of arrays.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_NoMultiDimensionalArrays" xml:space="preserve">
+ <value>Type '{0}' cannot be serialized or deserialized because it is a multi-dimensional array.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_NoOpenTypes" xml:space="preserve">
+ <value>Type '{0}' cannot be serialized or deserialized because it is not sealed or has unbound generic parameters.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_Optional_NonNullableField" xml:space="preserve">
+ <value>Field '{0}' on type '{1}' is declared [OptionalValue], but it can not be assigned a null value.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_PopulateFriendlyNameOnString" xml:space="preserve">
+ <value>Field '{0}' on type '{1}' has [ObjectIdentifier].PopulateFriendlyName set to true, which is not applicable to a string. Change the field to '{2}' or set PopulateFriendlyName to false.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_SetValueException" xml:space="preserve">
+ <value>Unable to set field {0} on type {1}.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_SpecificTagChoice" xml:space="preserve">
+ <value>Field '{0}' on type '{1}' has specified an implicit tag value via [ExpectedTag] for [Choice] type '{2}'. ExplicitTag must be true, or the [ExpectedTag] attribute removed.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_UnexpectedTypeForAttribute" xml:space="preserve">
+ <value>Field '{0}' of type '{1}' has an effective type of '{2}' when one of ({3}) was expected.</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_UtcTimeTwoDigitYearMaxTooSmall" xml:space="preserve">
+ <value>Field '{0}' on type '{1}' has a [UtcTime] TwoDigitYearMax value ({2}) smaller than the minimum (99).</value>
+ </data>
+ <data name="Cryptography_AsnSerializer_UnhandledType" xml:space="preserve">
+ <value>Could not determine how to serialize or deserialize type '{0}'.</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>
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 e32a65c910..e8d377c7e7 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
@@ -31,6 +31,9 @@
<Compile Include="Asn1\Reader\ReadUtcTime.cs" />
<Compile Include="Asn1\Reader\ReadUTF8String.cs" />
<Compile Include="Asn1\Reader\ReaderStateTests.cs" />
+ <Compile Include="Asn1\Serializer\SimpleDeserialize.cs" />
+ <Compile Include="Asn1\Serializer\SimpleSerialize.cs" />
+ <Compile Include="Asn1\Serializer\X509DeserializeTests.cs" />
<Compile Include="Asn1\Writer\Asn1WriterTests.cs" />
<Compile Include="Asn1\Writer\ComprehensiveWriteTest.cs" />
<Compile Include="Asn1\Writer\PushPopSequence.cs" />
@@ -59,6 +62,9 @@
<Compile Include="$(CommonPath)\System\Security\Cryptography\Asn1V2.cs">
<Link>Common\System\Security\Cryptography\Asn1V2.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)\System\Security\Cryptography\Asn1V2.Serializer.cs">
+ <Link>Common\System\Security\Cryptography\Asn1V2.Serializer.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\System\Security\Cryptography\AsnReader.cs">
<Link>Common\System\Security\Cryptography\AsnReader.cs</Link>
</Compile>