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.
What version of Go are you using (
go version)?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 envOutputWhat did you do?
Create three ceritificates with openssl:
Read in and print the subject name of each certificates with the following program:
What did you expect to see?
cert1.crt:
CN=foo,O=bar,C=FIcert2.crt:
O=bar,CN=foo,C=FIcert3.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:
But I think the "roughly" is a little too liberally interpretted.
RFC2253 2.1 says:
So the ordering should be mainainted.
Looking at #39924 I suspect there is a misinterpretation where @FiloSottile says:
However, the quoted text is related to the individual RDN parts, so both the following would be correct for cert3.crt:
But not:
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 FIYou 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?"):
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.