diff --git a/src/libraries/Common/src/System/Security/Cryptography/Oids.cs b/src/libraries/Common/src/System/Security/Cryptography/Oids.cs index 740d464b96e060..39fbb266abdd43 100644 --- a/src/libraries/Common/src/System/Security/Cryptography/Oids.cs +++ b/src/libraries/Common/src/System/Security/Cryptography/Oids.cs @@ -105,11 +105,17 @@ internal static partial class Oids // X500 Names internal const string CommonName = "2.5.4.3"; + internal const string Surname = "2.5.4.4"; internal const string CountryOrRegionName = "2.5.4.6"; internal const string LocalityName = "2.5.4.7"; internal const string StateOrProvinceName = "2.5.4.8"; + internal const string Street = "2.5.4.9"; internal const string Organization = "2.5.4.10"; internal const string OrganizationalUnit = "2.5.4.11"; + internal const string Title = "2.5.4.12"; + internal const string GivenName = "2.5.4.42"; + internal const string Initials = "2.5.4.43"; + internal const string EmailAddress = "1.2.840.113549.1.9.1"; // Cert Extensions diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X500NameEncoder.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X500NameEncoder.cs index 865b18e0951888..b8c66c6dd262fd 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X500NameEncoder.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X500NameEncoder.cs @@ -522,7 +522,7 @@ private static byte[] ParseRdn(ReadOnlySpan tagOid, ReadOnlySpan cha throw new CryptographicException(SR.Cryptography_Invalid_IA5String); } } - else if (forceUtf8Encoding) + else if (forceUtf8Encoding && IsForceUtf8Eligible(tagOid)) { writer.WriteCharacterString(UniversalTagNumber.UTF8String, data); } @@ -542,6 +542,26 @@ private static byte[] ParseRdn(ReadOnlySpan tagOid, ReadOnlySpan cha return writer.Encode(); } + private static bool IsForceUtf8Eligible(ReadOnlySpan oid) + { + switch (oid) + { + case Oids.CommonName: + case Oids.GivenName: + case Oids.LocalityName: + case Oids.Organization: + case Oids.OrganizationalUnit: + case Oids.StateOrProvinceName: + case Oids.Surname: + case Oids.Street: + case Oids.Title: + case Oids.Initials: + return true; + default: + return false; + } + } + private static int ExtractValue(ReadOnlySpan chars, Span destination) { Debug.Assert(destination.Length >= chars.Length); diff --git a/src/libraries/System.Security.Cryptography/tests/X509Certificates/NameTests.cs b/src/libraries/System.Security.Cryptography/tests/X509Certificates/NameTests.cs index 16e546db20c4c2..1e187177f2084d 100644 --- a/src/libraries/System.Security.Cryptography/tests/X509Certificates/NameTests.cs +++ b/src/libraries/System.Security.Cryptography/tests/X509Certificates/NameTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Asn1; using Test.Cryptography; using Xunit; @@ -82,8 +83,8 @@ public static void ForceUtf8EncodingWithFlagWhenEncoding() "CN=potato, O=jicama", X500DistinguishedNameFlags.ForceUTF8Encoding); - ReadOnlySpan expectedDer = new byte[] - { + ReadOnlySpan expectedDer = + [ 0x30, 0x22, 0x31, 0x0F, 0x30, 0x0D, @@ -91,13 +92,46 @@ public static void ForceUtf8EncodingWithFlagWhenEncoding() 0x0C, 0x06, 0x70, 0x6F, 0x74, 0x61, 0x74, 0x6F, // 0x0C is UTF8String 0x31, 0x0F, 0x30, 0x0D, - 0x06, 0x03, 0x55, 0x04, 0x0A, // id-at-organizatioName OID + 0x06, 0x03, 0x55, 0x04, 0x0A, // id-at-organizationName OID 0x0C, 0x06, 0x6A, 0x69, 0x63, 0x61, 0x6D, 0x61, // 0x0C is UTF8String - }; + ]; AssertExtensions.SequenceEqual(expectedDer, name.RawData); } + [Theory] + [InlineData("G=DotNet", UniversalTagNumber.UTF8String)] + [InlineData("L=Alexandria", UniversalTagNumber.UTF8String)] + [InlineData("O=GitHub", UniversalTagNumber.UTF8String)] + [InlineData("OU=ProdSec", UniversalTagNumber.UTF8String)] + [InlineData("S=Virginia", UniversalTagNumber.UTF8String)] + [InlineData("SN=Doe", UniversalTagNumber.UTF8String)] + [InlineData("ST=Main", UniversalTagNumber.UTF8String)] + [InlineData("T=Pancake", UniversalTagNumber.UTF8String)] + [InlineData("CN=Foo", UniversalTagNumber.UTF8String)] + [InlineData("I=DD", UniversalTagNumber.UTF8String)] + [InlineData("E=noone@example.com", UniversalTagNumber.IA5String)] + [InlineData("OID.2.5.4.11=ProdSec", UniversalTagNumber.UTF8String)] + [InlineData("OID.2.5.4.43=DD", UniversalTagNumber.UTF8String)] + [InlineData("OID.1.2.3.4=sample", UniversalTagNumber.PrintableString)] + [InlineData("C=US", UniversalTagNumber.PrintableString)] + public static void ForceUtf8EncodingForEligibleComponents(string distinguishedName, UniversalTagNumber tagNumber) + { + X500DistinguishedName name = new(distinguishedName, X500DistinguishedNameFlags.ForceUTF8Encoding); + byte[] encoded = name.RawData; + + AsnValueReader reader = new(encoded, AsnEncodingRules.DER); + AsnValueReader component = reader.ReadSequence(); + reader.ThrowIfNotEmpty(); + AsnValueReader rdn = component.ReadSetOf(); + component.ThrowIfNotEmpty(); + AsnValueReader value = rdn.ReadSequence(); + rdn.ThrowIfNotEmpty(); + + value.ReadObjectIdentifier(); + Assert.Equal(new Asn1Tag(tagNumber), value.PeekTag()); + } + [Theory] [InlineData(false)] [InlineData(true)]