crypto/x509/pkix: render string-typed attribute values as strings

RFC 2253 §2.4 says that an AttributeValue should be rendered as a string
when its ASN.1 type has a string representation, reserving the
prefixed hex form for types that don't.

RDNSequence.String previously hex-encoded any value whose OID wasn't in
attributeTypeNames, even when the value was already a Go string. Render
string-typed values directly; the existing escape logic handles RFC 2253
special characters.

Fixes #33093

Change-Id: Idb8acd907f9dac902790c9ac7d0bc0cb36b5b918
Reviewed-on: https://go-review.googlesource.com/c/go/+/773800
Reviewed-by: Cherry Mui <cherryyz@google.com>
Auto-Submit: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Emmanuel T Odeke 2026-05-04 11:15:19 -04:00 committed by Gopher Robot
parent 253aa2a12a
commit 978f00ab7f
3 changed files with 50 additions and 10 deletions

View file

@ -0,0 +1,4 @@
[RDNSequence.String] (and therefore [Name.String]) now renders string-typed
attribute values as strings even when the attribute's OID is unrecognized.
Previously such values were always hex-encoded in their DER form.
See [#33093](/issue/33093).

View file

@ -53,12 +53,17 @@ func (r RDNSequence) String() string {
oidString := tv.Type.String()
typeName, ok := attributeTypeNames[oidString]
if !ok {
derBytes, err := asn1.Marshal(tv.Value)
if err == nil {
buf.WriteString(oidString)
buf.WriteString("=#")
buf.WriteString(hex.EncodeToString(derBytes))
continue // No value escaping necessary.
// RFC 2253 §2.4: if the value's ASN.1 type has a string
// representation, render it as a string; otherwise hex-encode
// the DER.
if _, ok := tv.Value.(string); !ok {
derBytes, err := asn1.Marshal(tv.Value)
if err == nil {
buf.WriteString(oidString)
buf.WriteString("=#")
buf.WriteString(hex.EncodeToString(derBytes))
continue // No value escaping necessary.
}
}
typeName = oidString

View file

@ -2189,7 +2189,7 @@ func TestPKIXNameString(t *testing.T) {
dn pkix.Name
want string
}{
{nn, "L=Gophertown,1.2.3.4.5=#130a676f6c616e672e6f7267"},
{nn, "L=Gophertown,1.2.3.4.5=golang.org"},
{extraNotNil, "L=Gophertown"},
{pkix.Name{
CommonName: "Steve Kille",
@ -2218,13 +2218,13 @@ func TestPKIXNameString(t *testing.T) {
Locality: []string{"Gophertown"},
ExtraNames: []pkix.AttributeTypeAndValue{
{Type: asn1.ObjectIdentifier([]int{1, 2, 3, 4, 5}), Value: "golang.org"}},
}, "1.2.3.4.5=#130a676f6c616e672e6f7267,L=Gophertown"},
}, "1.2.3.4.5=golang.org,L=Gophertown"},
// If there are no ExtraNames, the Names are printed instead.
{pkix.Name{
Locality: []string{"Gophertown"},
Names: []pkix.AttributeTypeAndValue{
{Type: asn1.ObjectIdentifier([]int{1, 2, 3, 4, 5}), Value: "golang.org"}},
}, "L=Gophertown,1.2.3.4.5=#130a676f6c616e672e6f7267"},
}, "L=Gophertown,1.2.3.4.5=golang.org"},
// If there are both, print only the ExtraNames.
{pkix.Name{
Locality: []string{"Gophertown"},
@ -2232,7 +2232,38 @@ func TestPKIXNameString(t *testing.T) {
{Type: asn1.ObjectIdentifier([]int{1, 2, 3, 4, 5}), Value: "golang.org"}},
Names: []pkix.AttributeTypeAndValue{
{Type: asn1.ObjectIdentifier([]int{1, 2, 3, 4, 6}), Value: "example.com"}},
}, "1.2.3.4.5=#130a676f6c616e672e6f7267,L=Gophertown"},
}, "1.2.3.4.5=golang.org,L=Gophertown"},
// Non-string value falls back to hex-encoded DER (issue 33093).
{pkix.Name{
CommonName: "foobar",
ExtraNames: []pkix.AttributeTypeAndValue{
{Type: asn1.ObjectIdentifier([]int{1, 2, 3, 4}), Value: 42}},
}, "1.2.3.4=#02012a,CN=foobar"},
// String containing non-PrintableString chars (here, UTF-8) is still
// rendered as a string per RFC 2253 §2.4, not hex-encoded.
{pkix.Name{
CommonName: "foobar",
ExtraNames: []pkix.AttributeTypeAndValue{
{Type: asn1.ObjectIdentifier([]int{2, 3, 4, 5}), Value: "Lučić"}},
}, "2.3.4.5=Lučić,CN=foobar"},
// String beginning with '#' has the '#' escaped (RFC 2253 §2.4).
{pkix.Name{
CommonName: "foobar",
ExtraNames: []pkix.AttributeTypeAndValue{
{Type: asn1.ObjectIdentifier([]int{2, 3, 4, 5}), Value: "#abcdef"}},
}, "2.3.4.5=\\#abcdef,CN=foobar"},
// Printable string with an embedded RFC 2253 escapable character.
{pkix.Name{
CommonName: "foobar",
ExtraNames: []pkix.AttributeTypeAndValue{
{Type: asn1.ObjectIdentifier([]int{2, 3, 4, 5}), Value: "abcdef,GHI"}},
}, "2.3.4.5=abcdef\\,GHI,CN=foobar"},
// Printable string with leading and trailing space gets escaped.
{pkix.Name{
CommonName: "foobar",
ExtraNames: []pkix.AttributeTypeAndValue{
{Type: asn1.ObjectIdentifier([]int{2, 3, 4, 5}), Value: " abcdef "}},
}, "2.3.4.5=\\ abcdef\\ ,CN=foobar"},
}
for i, test := range tests {