Skip to content

crypto/x509: pkix.Name.String() does not preserve correct order  #52462

Description

@bjanders

What version of Go are you using (go version)?

$ go version
go version go1.18.1 linux/amd64

Does this issue reproduce with the latest release?

Yes, produces with go version go1.18.1

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GOARCH="amd64"
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOVERSION="go1.18.1"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build281192732=/tmp/go-build -gno-record-gcc-switches"

What did you do?

Create three ceritificates with openssl:

openssl req -new -x509 -subj '/CN=foo/O=bar/C=FI/' -nodes -outform DER -out cert1.crt
openssl req -new -x509 -subj '/O=bar/CN=foo/C=FI/' -nodes -outform DER -out cert2.crt
openssl req -new -x509 -multivalue-rdn -subj '/CN=foo+UID=1234/O=bar/C=FI/' -nodes -outform DER -out cert3.crt

Read in and print the subject name of each certificates with the following program:

package main

import (
	"crypto/x509"
	"fmt"
	"os"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("need path to cert")
		os.Exit(1)
	}
	f, err := os.Open(os.Args[1])
	if err != nil {
		panic(err)
	}
	certBytes := make([]byte, 2000)
	n, err := f.Read(certBytes)
	if err != nil {
		panic(err)
	}
	f.Close()
	cert, err := x509.ParseCertificate(certBytes[:n])
	if err != nil {
		panic(err)
	}

	fmt.Println(cert.Subject.String())
}

What did you expect to see?

cert1.crt: CN=foo,O=bar,C=FI

cert2.crt: O=bar,CN=foo,C=FI

cert3.crt: CN=foo+0.9.2342.19200300.100.1.1=#130431323334,O=bar,C=FI

(Or actually for cert3.crt: CN=foo+0.9.2342.19200300.100.1.1=1234,O=bar,C=FI, but that is another issue, see #33093.)

What did you see instead?

cert1.crt: CN=foo,O=bar,C=FI (as expected)

cert2.crt: CN=foo,O=bar,C=FI (order is not maintained)

cert3.crt : CN=foo,O=bar,C=FI,0.9.2342.19200300.100.1.1=#130431323334 (order is not mainainted, multi value RDN lost)

To be fair, the documentation for pkix.Name.String() says:

String returns the string form of n, roughly following the RFC 2253 Distinguished Names syntax.

But I think the "roughly" is a little too liberally interpretted.

RFC2253 2.1 says:

the output consists of the string encodings of each
RelativeDistinguishedName in the RDNSequence (according to 2.2),
starting with the last element of the sequence and moving backwards
toward the first.

So the ordering should be mainainted.

Looking at #39924 I suspect there is a misinterpretation where @FiloSottile says:

RFC 2253 lets us pick any order we like.

However, the quoted text is related to the individual RDN parts, so both the following would be correct for cert3.crt:

CN=foo+0.9.2342.19200300.100.1.1=#130431323334,O=bar,C=FI
0.9.2342.19200300.100.1.1=#130431323334+CN=foo,O=bar,C=FI

But not:

CN=foo,O=bar,C=FI,0.9.2342.19200300.100.1.1=#130431323334

Further and for clarity, looking at the ASN.1 structure of the Subject DN of cert3.crt:

SEQUENCE (3 elem)
      SET (2 elem)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 2.5.4.3 commonName
          UTF8String foo
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 0.9.2342.19200300.100.1.1 userID
          UTF8String 1234
      SET (1 elem)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 2.5.4.10 organizationName
          UTF8String bar
      SET (1 elem)
        SEQUENCE (2 elem)
          OBJECT IDENTIFIER 2.5.4.6 countryName
          PrintableString FI

You can see that each RDN is in a sequence (order should be maintained) but the multivalue RDN is a set (order does not matter. you can pick any order). The same goes for cert2.crt where the String() method throws the RDNs around in a fixed orderd and places unrecognized RDNs last.

In addition to this, it looks the String() method prints the sequence in reverse to what RFC 2253 says "starting with the last element of the sequence and moving backwards toward the first", which would suggest cert1.crt should actually print as (contrary to what I said in "What did you expect to see?"):

C=FI,O=bar,CN=foo

countryName is the last element and commonName is the first. RFC 2253 shows examples where the countryName is last, but it does not show the corresponding ASN.1 structure, so that cannot be used as a reference. The ASN.1 sequence in RFC 2253 is probably in reverse to what this example shows.

Metadata

Metadata

Assignees

No one assigned

    Labels

    NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions