-
Notifications
You must be signed in to change notification settings - Fork 18k
crypto/x509: ParseCertificate ignores all but first value from Subject's []string{} fields #18654
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I tried to reproduce this on go1.8rc1 running on darwin/amd64, seems to work fine for me: package main
import (
"crypto/x509"
"encoding/pem"
"fmt"
)
const testCertificate = `
-----BEGIN CERTIFICATE-----
MIIDTzCCAjegAwIBAgIJAPd2rUd3n9vNMA0GCSqGSIb3DQEBCwUAMDExDTALBgNV
BAMTBHRlc3QxDzANBgNVBAsTBnZhbHVlMTEPMA0GA1UECxMGdmFsdWUyMB4XDTE3
MDExNDA3MTMwNloXDTE5MTAxMTA3MTMwNlowMTENMAsGA1UEAxMEdGVzdDEPMA0G
A1UECxMGdmFsdWUxMQ8wDQYDVQQLEwZ2YWx1ZTIwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQDOm1c/1c/Ry5/H1WOG09bL/XuCbOhNT62yzyVfGk9dy/oB
HobuV+ow/ctUJ43dgFsJgkJ+w3f/+DLlmRFwoaSbHoVV+5hWdCtBmKCAwWYJ4m0d
bppYGU/IAfGuDSgsSTmQs/MEJ6T5jRdSNIBAQy3ci0stsZsZazGZ+i0TPTQByRvn
F3c1cU9QXKzB52BRU3qm0X+SFvn+uEoabI3Nf1AXWqXye5yybJtWffdc/N5kA7Bz
VqTU314DUxob1LoTbKsv1agGoBfIYMvhsbVkJpSRSKX/X+scYv7J4RrEhPDX+TWJ
3YYjE5dHNgZxqCf1kPKi6ms7iM6u1TBpqshwHaFTAgMBAAGjajBoMAwGA1UdEwEB
/wQCMAAwCwYDVR0PBAQDAgTwMEsGA1UdIwREMEKhNaQzMDExDTALBgNVBAMTBHRl
c3QxDzANBgNVBAsTBnZhbHVlMTEPMA0GA1UECxMGdmFsdWUyggkA93atR3ef280w
DQYJKoZIhvcNAQELBQADggEBAG8MH0plZthQBpdoGK3uyh2mErQ9IHLDi9r2ykLV
h24GdV0UExqL5LAr1l+Nd23KFfYMr3E8nm+PWkqZCOyQTVj7Jby2ETqL+DJWVcJp
ZO9HhgQ2sOvicg2GNIqSGUm1qH3BJwBo3NPGFF/QgBJeuZnJzPaWZrUoriUUDyJ6
/TRm5wRXFMlekt2g3/zpNqeHZSQm4zNijY+qJ+BNZumtPSE6i5093neNjOPhY4yI
n/GWK1XL4ET98qvNHM05gO0cWIe1ZnYEFzyylMPEwWLcluIrBy2aj9xbKR5FKWXb
LDSb0SqDg4UMbsQw3AidGXo5Vluc22KigTrJV9mf1OrUm7Q=
-----END CERTIFICATE-----`
func main() {
block, _ := pem.Decode([]byte(testCertificate))
cert, _ := x509.ParseCertificate(block.Bytes)
fmt.Printf("CN = %v\n", cert.Subject.CommonName)
fmt.Printf("OU = %v\n", cert.Subject.OrganizationalUnit)
} Parses and populates both OU values in the embedded certificate:
OpenSSL for comparison:
The linked foo.go program (from gist) also seems to parse multiple values correctly:
Used |
With your code I get identical results on 1.7 and 1.8rc:
Using my code from the gist, I get differing results between 1.7 and 1.8:
I'm a bit confused at this point, but I'll keep poking. Thanks for the feedback. |
Trying a slight variation: Using your code, but with a cert.pem created by the code in my (
I'm confused as to why the cert fixture in your code works as expected in both 1.7.4 and 1.8 for me, while the other cert only works as expected in 1.8. In any case, I'm getting better results from 1.8 in general at this time, so possibly a non-issue in 1.8+ which is good. |
Can confirm, seeing the same difference in behavior (using your test cert, but not mine) in 1.7.1 vs. 1.8rc1. |
It appears there's a difference in the way the subjects are encoded. From the working cert (output from dumpasn1):
From the broken cert:
For reference, my test cert was generated with OpenSSL 0.9.8zh (which ships in macOS Sierra). |
One of the sharp edges of X.509 is that the name structure isn't a mapping from OIDs to a list of values, it's a sequence of sets of pairs. I.e. something like [][]struct{key OID, value string}. (I think the original motivation was to try and identify which values of the multiset were alternatives, and which were needed for a unique identification. These days, nobody cares and that over-engineering is now just a source of confusion.) There was a bug, fixed in 809a1de, that meant that code previously only looked at first element of these sublists. So the difference between the certificates that work and fail with 1.7 is that the multiple values are either duplicated at the top-level, or the sublevel. You can see this with |
(Since this should be fixed in 1.8, I think this can be closed.) |
@agl Thanks for the clarification, it's much appreciated. Glad it's already fixed! |
@agl thank you! |
This defect report addresses the parsing behavior of
I'm not saying that Go is generating certificates incorrectly; I'm just noting that it's doing something different from openssl x509. If the certificate consumer is built with Go 1.7, the certificate producer can't use |
@seh, we don't track closed bugs, so if you're filing a bug report, please open a new bug. You can cross-reference the new one and this one. |
@seh Please do cross-reference if you file a new bug, I'd want to keep track of it. |
As I ponder whether there's anything here deserving a defect report against Go—given that this problem is solved in Go 1.8—here I'll share the rather distasteful workaround that I concocted, mostly by poring over the documentation for In my program that creates certificates that I need a consumer built with Go 1.7 to interpret properly, I am specifically interested in conveying only two RDNs in my certificate subject:
First, a couple of helper functions: func appendSingletonAVAStringSets(name *pkix.Name, oid asn1.ObjectIdentifier, s []string) *pkix.Name {
for _, v := range s {
name.ExtraNames = append(name.ExtraNames, pkix.AttributeTypeAndValue{
Type: oid,
Value: v,
})
}
return name
}
func forceOrganizationAsSingletonAVASets(name *pkix.Name) *pkix.Name {
// Per RFC 5328 appendix A.1:
oidOrganization := asn1.ObjectIdentifier{2, 5, 4, 10}
orgs := name.Organization
// Not strictly necessary:
name.Organization = nil
return appendSingletonAVAStringSets(name, oidOrganization, orgs)
} Then, while preparing for the coming call to signeeTemplate.Subject = csr.Subject
// Work around Go issue 18654 (https://github.com/golang/go/issues/18654):
//
// Force the ASN.1 marshaller to emit a sequence of singleton sets (sets with a single sequence
// with a single element), as opposed to the more compact sequence of a single set with multiple
// sequences, each of a single element.
//
// (Sample output from "openssl asn1parse -i" follows.)
//
// Compact form that Go 1.7 does not parse correctly:
// SEQUENCE
// SET
// SEQUENCE
// OBJECT :organizationName
// PRINTABLESTRING :org1
// SEQUENCE
// OBJECT :organizationName
// PRINTABLESTRING :org2
// SEQUENCE
// OBJECT :organizationName
// PRINTABLESTRING :org3
// SET
// SEQUENCE
// OBJECT :commonName
// UTF8STRING :someone@somewhere.org
//
// Expanded form that Go 1.7 parses well enough:
// SEQUENCE
// SET
// SEQUENCE
// OBJECT :organizationName
// PRINTABLESTRING :org1
// SET
// SEQUENCE
// OBJECT :organizationName
// PRINTABLESTRING :org2
// SET
// SEQUENCE
// OBJECT :organizationName
// PRINTABLESTRING :org3
// SET
// SEQUENCE
// OBJECT :commonName
// UTF8STRING :someone@somewhere.org
forceOrganizationAsSingletonAVASets(&signeeTemplate.Subject) I'm not sure if all of my X.509- and ANS.1-related terms are correct here. If there's a better way to achieve this goal, I'm open to suggestions. |
What version of Go are you using (
go version
)?go1.7.4, go1.8rc1
What operating system and processor architecture are you using (
go env
)?darwin/amd64
What did you do?
When parsing a certificate with the
x509.ParseCertificate()
function it ignores all but the first value from attributes in the Subject (pkix.Name) struct that support multiple values.A cert contains a
Subject
(pkix.Name) struct. Some of the attributes in this struct support multiple values, represented as[]string{}
, such asOrganizationalUnit
,Locality
, etc:When
x509.ParseCertificate()
parses a certificate that has, for example, twoOrganizationalUnit
(OU) attributes only the first one is parsed and set in the returnedx509.Certificate
Note that
x509.CreateCertificate()
will correctly create certs with multiple values.Example code and additional commentary demonstrating this issue in this gist: https://gist.github.com/joemiller/c97a52d46cae0a4b38df841db8307fc4
I can move the info from the gist into this issue if that is desirable. I don't know the golang issue etiquette.
What did you expect to see?
x509.ParseCertificate()
should return anx509.Certificate
containing all of the values for the fields that are of type[]string{}
What did you see instead?
I only see the first value for each field.
Discovered during this work: hashicorp/vault#2251
The text was updated successfully, but these errors were encountered: