@@ -11,6 +11,16 @@ namespace System.Security.Cryptography.X509Certificates
1111{
1212 internal static partial class X500NameEncoder
1313 {
14+ private enum EncodingRules
15+ {
16+ Unknown ,
17+ IA5String ,
18+ DirectoryString ,
19+ PrintableString ,
20+ UTF8String ,
21+ NumericString ,
22+ }
23+
1424 private const string OidTagPrefix = "OID." ;
1525 private const string UseSemicolonSeparators = ";" ;
1626 private const string UseCommaSeparators = "," ;
@@ -20,6 +30,8 @@ internal static partial class X500NameEncoder
2030 private static readonly SearchValues < char > s_needsQuotingChars =
2131 SearchValues . Create ( ",+=\" \n <>#;" ) ; // \r is NOT in this list, because it isn't in Windows.
2232
33+ private static readonly Lazy < Dictionary < string , EncodingRules > > s_lazyEncodingRulesLookup = new ( CreateEncodingRulesLookup ) ;
34+
2335 internal static string X500DistinguishedNameDecode (
2436 byte [ ] encodedName ,
2537 bool printOid ,
@@ -510,32 +522,35 @@ private static byte[] ParseRdn(ReadOnlySpan<char> tagOid, ReadOnlySpan<char> cha
510522 throw new CryptographicException ( SR . Cryptography_Invalid_X500Name , e ) ;
511523 }
512524
513- if ( tagOid . SequenceEqual ( Oids . EmailAddress ) )
514- {
515- try
516- {
517- // An email address with an invalid value will throw.
518- writer . WriteCharacterString ( UniversalTagNumber . IA5String , data ) ;
519- }
520- catch ( EncoderFallbackException )
521- {
522- throw new CryptographicException ( SR . Cryptography_Invalid_IA5String ) ;
523- }
524- }
525- else if ( forceUtf8Encoding )
525+ switch ( LookupEncodingRules ( tagOid ) )
526526 {
527- writer . WriteCharacterString ( UniversalTagNumber . UTF8String , data ) ;
528- }
529- else
530- {
531- try
532- {
533- writer . WriteCharacterString ( UniversalTagNumber . PrintableString , data ) ;
534- }
535- catch ( EncoderFallbackException )
536- {
537- writer . WriteCharacterString ( UniversalTagNumber . UTF8String , data ) ;
538- }
527+ case EncodingRules . IA5String :
528+ WriteCryptoCharacterString ( writer , UniversalTagNumber . IA5String , data ) ;
529+ break ;
530+ case EncodingRules . UTF8String :
531+ case EncodingRules . DirectoryString or EncodingRules . Unknown when forceUtf8Encoding :
532+ WriteCryptoCharacterString ( writer , UniversalTagNumber . UTF8String , data ) ;
533+ break ;
534+ case EncodingRules . NumericString :
535+ WriteCryptoCharacterString ( writer , UniversalTagNumber . NumericString , data ) ;
536+ break ;
537+ case EncodingRules . PrintableString :
538+ WriteCryptoCharacterString ( writer , UniversalTagNumber . PrintableString , data ) ;
539+ break ;
540+ case EncodingRules . DirectoryString :
541+ case EncodingRules . Unknown :
542+ try
543+ {
544+ writer . WriteCharacterString ( UniversalTagNumber . PrintableString , data ) ;
545+ }
546+ catch ( EncoderFallbackException )
547+ {
548+ WriteCryptoCharacterString ( writer , UniversalTagNumber . UTF8String , data ) ;
549+ }
550+ break ;
551+ default :
552+ Debug . Fail ( "Encoding rule was not handled." ) ;
553+ goto case EncodingRules . Unknown ;
539554 }
540555 }
541556
@@ -567,5 +582,91 @@ private static int ExtractValue(ReadOnlySpan<char> chars, Span<char> destination
567582
568583 return written ;
569584 }
585+
586+ private static Dictionary < string , EncodingRules > CreateEncodingRulesLookup ( )
587+ {
588+ // Attributes that are not "obsolete" from ITU T-REC X.520-2019.
589+ // Attributes that are included are attributes that are string-like and can be represented by a String.
590+ // Windows does not have any restrictions on encoding non-string encodable types, it will encode them
591+ // anyway, such as OID.2.5.4.14=test will encode test as a PrintableString, even though the OID is a SET.
592+ // To maintain similar behavior as Windows, those types will remain treated as unknown.
593+ const int LookupDictionarySize = 43 ;
594+ Dictionary < string , EncodingRules > lookup = new ( LookupDictionarySize , StringComparer . Ordinal )
595+ {
596+ { Oids . KnowledgeInformation , EncodingRules . DirectoryString } ,
597+ { Oids . CommonName , EncodingRules . DirectoryString } ,
598+ { Oids . Surname , EncodingRules . DirectoryString } ,
599+ { Oids . SerialNumber , EncodingRules . PrintableString } ,
600+ { Oids . CountryOrRegionName , EncodingRules . PrintableString } ,
601+ { Oids . LocalityName , EncodingRules . DirectoryString } ,
602+ { Oids . StateOrProvinceName , EncodingRules . DirectoryString } ,
603+ { Oids . StreetAddress , EncodingRules . DirectoryString } ,
604+ { Oids . Organization , EncodingRules . DirectoryString } ,
605+ { Oids . OrganizationalUnit , EncodingRules . DirectoryString } ,
606+ { Oids . Title , EncodingRules . DirectoryString } ,
607+ { Oids . Description , EncodingRules . DirectoryString } ,
608+ { Oids . BusinessCategory , EncodingRules . DirectoryString } ,
609+ { Oids . PostalCode , EncodingRules . DirectoryString } ,
610+ { Oids . PostOfficeBox , EncodingRules . DirectoryString } ,
611+ { Oids . PhysicalDeliveryOfficeName , EncodingRules . DirectoryString } ,
612+ { Oids . TelephoneNumber , EncodingRules . PrintableString } ,
613+ { Oids . X121Address , EncodingRules . NumericString } ,
614+ { Oids . InternationalISDNNumber , EncodingRules . NumericString } ,
615+ { Oids . DestinationIndicator , EncodingRules . PrintableString } ,
616+ { Oids . Name , EncodingRules . DirectoryString } ,
617+ { Oids . GivenName , EncodingRules . DirectoryString } ,
618+ { Oids . Initials , EncodingRules . DirectoryString } ,
619+ { Oids . GenerationQualifier , EncodingRules . DirectoryString } ,
620+ { Oids . DnQualifier , EncodingRules . PrintableString } ,
621+ { Oids . HouseIdentifier , EncodingRules . DirectoryString } ,
622+ { Oids . DmdName , EncodingRules . DirectoryString } ,
623+ { Oids . Pseudonym , EncodingRules . DirectoryString } ,
624+ { Oids . UiiInUrn , EncodingRules . UTF8String } ,
625+ { Oids . ContentUrl , EncodingRules . UTF8String } ,
626+ { Oids . Uri , EncodingRules . UTF8String } ,
627+ { Oids . Urn , EncodingRules . UTF8String } ,
628+ { Oids . Url , EncodingRules . UTF8String } ,
629+ { Oids . UrnC , EncodingRules . PrintableString } ,
630+ { Oids . EpcInUrn , EncodingRules . DirectoryString } ,
631+ { Oids . LdapUrl , EncodingRules . UTF8String } ,
632+ { Oids . OrganizationIdentifier , EncodingRules . DirectoryString } ,
633+ { Oids . CountryOrRegionName3C , EncodingRules . PrintableString } ,
634+ { Oids . CountryOrRegionName3N , EncodingRules . NumericString } ,
635+ { Oids . DnsName , EncodingRules . UTF8String } ,
636+ { Oids . IntEmail , EncodingRules . UTF8String } ,
637+ { Oids . JabberId , EncodingRules . UTF8String } ,
638+ { Oids . EmailAddress , EncodingRules . IA5String } ,
639+ } ;
640+
641+ Debug . Assert ( lookup . Count == LookupDictionarySize ) ;
642+ return lookup ;
643+ }
644+
645+ private static void WriteCryptoCharacterString ( AsnWriter writer , UniversalTagNumber tagNumber , ReadOnlySpan < char > data )
646+ {
647+ try
648+ {
649+ writer . WriteCharacterString ( tagNumber , data ) ;
650+ }
651+ catch ( EncoderFallbackException )
652+ {
653+ if ( tagNumber == UniversalTagNumber . IA5String )
654+ {
655+ throw new CryptographicException ( SR . Cryptography_Invalid_IA5String ) ;
656+ }
657+ else
658+ {
659+ throw new CryptographicException ( SR . Cryptography_Invalid_X500Name ) ;
660+ }
661+ }
662+ }
663+
664+ private static EncodingRules LookupEncodingRules ( ReadOnlySpan < char > oid )
665+ {
666+ Dictionary < string , EncodingRules > lookup = s_lazyEncodingRulesLookup . Value ;
667+ Dictionary < string , EncodingRules > . AlternateLookup < ReadOnlySpan < char > > alternateLookup =
668+ lookup . GetAlternateLookup < ReadOnlySpan < char > > ( ) ;
669+ return alternateLookup . TryGetValue ( oid , out EncodingRules rules ) ? rules : EncodingRules . Unknown ;
670+ }
570671 }
571672}
0 commit comments