diff options
author | Jeremy Barton <jbarton@microsoft.com> | 2017-12-14 07:50:45 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-14 07:50:45 +0300 |
commit | 163cfff64e956ca091a38bdb2b4b60b59dc667fd (patch) | |
tree | f90fd476acb93e6104b927ad93e8180c04adba81 /src/System.Security.Cryptography.Encoding | |
parent | 0555aa9370128a92ca8303c860a985b030d3f7ae (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')
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> |