Skip to content

Commit

Permalink
Support displaying X.500 AttributeValue in binary form
Browse files Browse the repository at this point in the history
  • Loading branch information
vcsjones authored Jun 29, 2023
1 parent 205ac76 commit d3d537f
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@
<Compile Include="System\Security\Cryptography\X509Certificates\StoreName.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\StorePal.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\SubjectAlternativeNameBuilder.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\X500DictionaryStringHelper.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\X500DirectoryStringHelper.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\X500DistinguishedName.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\X500DistinguishedNameBuilder.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\X500DistinguishedNameFlags.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace System.Security.Cryptography.X509Certificates
{
internal static class X500DictionaryStringHelper
internal static class X500DirectoryStringHelper
{
internal static string ReadAnyAsnString(this AsnReader tavReader)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Formats.Asn1;
using System.Text;

Expand Down Expand Up @@ -92,7 +93,7 @@ private static string X500DistinguishedNameDecode(
{
AsnReader tavReader = rdnReader.ReadSequence();
string oid = tavReader.ReadObjectIdentifier();
string attributeValue = tavReader.ReadAnyAsnString();
string attributeValue = ReadAttributeValue(tavReader, out bool fallback);

tavReader.ThrowIfNotEmpty();

Expand All @@ -110,7 +111,7 @@ private static string X500DistinguishedNameDecode(
AppendOid(ref decodedName, oid);
}

bool quote = quoteIfNeeded && NeedsQuoting(attributeValue);
bool quote = quoteIfNeeded && NeedsQuoting(attributeValue) && !fallback;

if (quote)
{
Expand Down Expand Up @@ -144,5 +145,52 @@ private static string X500DistinguishedNameDecode(
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
}
}

private static string ReadAttributeValue(AsnReader tavReader, out bool binaryFallback)
{
Debug.Assert(tavReader.RuleSet == AsnEncodingRules.DER);

Asn1Tag tag = tavReader.PeekTag();

if (tag.TagClass == TagClass.Universal)
{
switch ((UniversalTagNumber)tag.TagValue)
{
case UniversalTagNumber.BMPString:
case UniversalTagNumber.IA5String:
case UniversalTagNumber.NumericString:
case UniversalTagNumber.PrintableString:
case UniversalTagNumber.UTF8String:
case UniversalTagNumber.T61String:
// .NET's string comparisons start by checking the length, so a trailing
// NULL character which was literally embedded in the DER would cause a
// failure in .NET whereas it wouldn't have with strcmp.
binaryFallback = false;
return tavReader.ReadCharacterString((UniversalTagNumber)tag.TagValue).TrimEnd('\0');
case UniversalTagNumber.OctetString:
// Windows will implicitly unwrap one OCTET STRING and display only the contents.
if (tavReader.TryReadPrimitiveOctetString(out ReadOnlyMemory<byte> contents))
{
binaryFallback = true;
return BinaryEncode(contents);
}

Debug.Fail("TryReadPrimitiveOctetString should either succeed or throw with DER.");
throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
}
}

binaryFallback = true;
return BinaryEncode(tavReader.ReadEncodedValue());

static string BinaryEncode(ReadOnlyMemory<byte> data)
{
return string.Create(1 + data.Length * 2, data, static (buff, state) =>
{
buff[0] = '#';
HexConverter.EncodeToUtf16(state.Span, buff.Slice(1));
});
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,84 @@ public static void CheckCachedOids()
Assert.Equal("1.2.840.113549.1.9.1", rdns[2].GetSingleElementType().Value);
}

[Theory]
[InlineData(new [] { "2.5.4.3" }, new [] { "3000" }, "CN=#3000")]
[InlineData(new [] { "2.5.4.5" }, new[] { "0603550406" }, "SERIALNUMBER=#0603550406")]
[InlineData(new [] { "0.0" }, new[] { "31020500" }, "OID.0.0=#31020500")]
[InlineData(new [] { "2.5.4.3" }, new [] { "04023000" }, "CN=#3000")] // OCTET STRING is implicitly stripped
[InlineData(new [] { "2.5.4.3" }, new [] { "040404023000" }, "CN=#04023000")] // Only one OCTET STRING is stripped
[InlineData(new [] { "2.5.4.3" }, new [] { "0303003000" }, "CN=#0303003000")] // BIT STRING is not implicitly stripped
[InlineData(new [] { "2.5.4.8" }, new [] { "0500" }, "S=#0500")]
[InlineData(new [] { "2.5.4.8" }, new [] { "0101FF" }, "S=#0101FF")]
[InlineData(new [] { "2.5.4.3", "2.5.4.8" }, new [] { "0101FF", "3000" }, "CN=#0101FF, S=#3000")]
[InlineData(new [] { "2.5.4.3", "2.5.4.8", "0.0" }, new [] { "0C02504A", "3000", "0C024141" }, "CN=PJ, S=#3000, OID.0.0=AA")]
[InlineData(new [] { "2.5.4.3", "2.5.4.8" }, new [] { "0C03233030", "3000" }, "CN=\"#00\", S=#3000")]
[SkipOnPlatform(TestPlatforms.Browser, "Browser doesn't support an X.509 PAL")]
public static void Format_ComponentWithNonStringContent(string[] oids, string[] attributeValues, string expected)
{
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);

using (writer.PushSequence())
{
for (int i = 0; i < oids.Length; i++)
{
using (writer.PushSetOf())
using (writer.PushSequence())
{
writer.WriteObjectIdentifier(oids[i]);
writer.WriteEncodedValue(Convert.FromHexString(attributeValues[i]));
}
}
}

X500DistinguishedName distinguishedName = new X500DistinguishedName(writer.Encode());
string dnString = distinguishedName.Format(false);
Assert.Equal(expected, dnString);

string decode = distinguishedName.Decode(X500DistinguishedNameFlags.None);
Assert.Equal(expected, decode);
}

[Fact]
[SkipOnPlatform(TestPlatforms.Browser, "Browser doesn't support an X.509 PAL")]
public static void Format_MultiValueComponentWithNonStringContent()
{
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);

using (writer.PushSequence())
{
using (writer.PushSetOf())
{
WriteRDNComponent(writer, "2.5.4.3", "3000");
WriteRDNComponent(writer, "2.5.4.8", "3100");
}

using (writer.PushSetOf())
{
WriteRDNComponent(writer, "2.5.4.5", "0C0430313233");
WriteRDNComponent(writer, "2.5.4.10", "31055050505050");
WriteRDNComponent(writer, "2.5.4.9", "0C075441434F434154");
}
}

const string Expected = "CN=#3000 + S=#3100, SERIALNUMBER=0123 + O=#31055050505050 + STREET=TACOCAT";
X500DistinguishedName distinguishedName = new X500DistinguishedName(writer.Encode());
string dnString = distinguishedName.Format(false);
Assert.Equal(Expected, dnString);

string decode = distinguishedName.Decode(X500DistinguishedNameFlags.None);
Assert.Equal(Expected, decode);

static void WriteRDNComponent(AsnWriter writer, string oid, string value)
{
using (writer.PushSequence())
{
writer.WriteObjectIdentifier(oid);
writer.WriteEncodedValue(Convert.FromHexString(value));
}
}
}

public static readonly object[][] WhitespaceBeforeCases =
{
// Regular space.
Expand Down

0 comments on commit d3d537f

Please sign in to comment.