Respect ForceUTF8Encoding when encoding an X500DN
authorKevin Jones <kevin@vcsjones.com>
Fri, 22 Jul 2022 23:14:44 +0000 (19:14 -0400)
committerGitHub <noreply@github.com>
Fri, 22 Jul 2022 23:14:44 +0000 (19:14 -0400)
The managed implementation did not respect the ForceUTF8Encoding flag when encoding a distinguished name. This changes the encoding to start respecting the flag, as Windows does.

src/libraries/System.Security.Cryptography.X509Certificates/tests/NameTests.cs
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/X509Certificates/X500NameEncoder.cs

index 58f0455..9f0002f 100644 (file)
@@ -72,6 +72,31 @@ namespace System.Security.Cryptography.X509Certificates.Tests
             Assert.Equal(expected, actual);
         }
 
+        [Fact]
+        public static void ForceUtf8EncodingWithFlagWhenEncoding()
+        {
+            // Even though these values are encodable as a PrintableString, the flag should force them to be encoded
+            // as UTF8String.
+            X500DistinguishedName name = new X500DistinguishedName(
+                "CN=potato, O=jicama",
+                X500DistinguishedNameFlags.ForceUTF8Encoding);
+
+            ReadOnlySpan<byte> expectedDer = new byte[]
+            {
+                0x30, 0x22,
+                0x31, 0x0F,
+                0x30, 0x0D,
+                    0x06, 0x03, 0x55, 0x04, 0x03, // id-at-commonName OID
+                    0x0C, 0x06, 0x70, 0x6F, 0x74, 0x61, 0x74, 0x6F, // 0x0C is UTF8String
+                0x31, 0x0F,
+                0x30, 0x0D,
+                    0x06, 0x03, 0x55, 0x04, 0x0A,  // id-at-organizatioName OID
+                    0x0C, 0x06, 0x6A, 0x69, 0x63, 0x61, 0x6D, 0x61, // 0x0C is UTF8String
+            };
+
+            AssertExtensions.SequenceEqual(expectedDer, name.RawData);
+        }
+
         [Theory]
         [InlineData(false)]
         [InlineData(true)]
index 7b90d14..7dc8d03 100644 (file)
@@ -84,6 +84,7 @@ namespace System.Security.Cryptography.X509Certificates
         {
             bool reverse = (flags & X500DistinguishedNameFlags.Reversed) == X500DistinguishedNameFlags.Reversed;
             bool noQuotes = (flags & X500DistinguishedNameFlags.DoNotUseQuotes) == X500DistinguishedNameFlags.DoNotUseQuotes;
+            bool forceUtf8Encoding = (flags & X500DistinguishedNameFlags.ForceUTF8Encoding) == X500DistinguishedNameFlags.ForceUTF8Encoding;
 
             List<char> dnSeparators;
 
@@ -111,7 +112,7 @@ namespace System.Security.Cryptography.X509Certificates
 
             Debug.Assert(dnSeparators.Count != 0);
 
-            List<byte[]> encodedSets = ParseDistinguishedName(stringForm, dnSeparators, noQuotes);
+            List<byte[]> encodedSets = ParseDistinguishedName(stringForm, dnSeparators, noQuotes, forceUtf8Encoding);
 
             if (reverse)
             {
@@ -197,7 +198,8 @@ namespace System.Security.Cryptography.X509Certificates
         private static List<byte[]> ParseDistinguishedName(
             string stringForm,
             List<char> dnSeparators,
-            bool noQuotes)
+            bool noQuotes,
+            bool forceUtf8Encoding)
         {
             // 16 is way more RDNs than we should ever need. A fairly standard set of values is
             // { E, CN, O, OU, L, S, C } = 7;
@@ -400,7 +402,7 @@ namespace System.Security.Cryptography.X509Certificates
                             Debug.Assert(valueEnd != -1);
                             Debug.Assert(valueStart != -1);
 
-                            encodedSets.Add(ParseRdn(tagOid, chars[valueStart..valueEnd], hadEscapedQuote));
+                            encodedSets.Add(ParseRdn(tagOid, chars[valueStart..valueEnd], hadEscapedQuote, forceUtf8Encoding));
                             hasTagOid = false;
                             valueStart = -1;
                             valueEnd = -1;
@@ -469,7 +471,7 @@ namespace System.Security.Cryptography.X509Certificates
                     Debug.Assert(valueStart != -1);
                     Debug.Assert(valueEnd != -1);
 
-                    encodedSets.Add(ParseRdn(tagOid, chars[valueStart..valueEnd], hadEscapedQuote));
+                    encodedSets.Add(ParseRdn(tagOid, chars[valueStart..valueEnd], hadEscapedQuote, forceUtf8Encoding));
                     break;
 
                 // If the entire string was empty, or ended in a dnSeparator.
@@ -499,7 +501,7 @@ namespace System.Security.Cryptography.X509Certificates
             return new Oid(str.ToString()).Value; // Value can be null, but permit the null-to-empty conversion.
         }
 
-        private static byte[] ParseRdn(ReadOnlySpan<char> tagOid, ReadOnlySpan<char> chars, bool hadEscapedQuote)
+        private static byte[] ParseRdn(ReadOnlySpan<char> tagOid, ReadOnlySpan<char> chars, bool hadEscapedQuote, bool forceUtf8Encoding)
         {
             scoped ReadOnlySpan<char> data;
 
@@ -544,6 +546,10 @@ namespace System.Security.Cryptography.X509Certificates
                         throw new CryptographicException(SR.Cryptography_Invalid_IA5String);
                     }
                 }
+                else if (forceUtf8Encoding)
+                {
+                    writer.WriteCharacterString(UniversalTagNumber.UTF8String, data);
+                }
                 else
                 {
                     try