ASN.1 Field Ordering in Go
A few days ago I had to generate a Certificate Signing Request using Go; dealing with crypto in Go is always a breeze, but then I was notified by the Certification Authority that fields were out of order. Technically, considering the DER nature of the CSR, the order of Organizational Unit should not matter. The CSR was regenerated to no avail.
The solution was to write the CSR manually (sort of manually, see below),
defining all asn1
bits. That comprised of digging through the documentation,
implementation files and tests, so I could understand what was happening behind
the courtains. Personally, what most intriged me was Go checking for a prefix
in a type name through reflection to determine whether it was an ASN.1 sequence
or set.
So, first of all, let’s determine what we will need to generate our Subject
line: Object IDs for all the fields we plan on filling, a custom type so we
can write UTF8String
instead of PrintableString
(Only Country is
PrintableString
), and a custom type so we can get sets instead of sequences.
package generator
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
)
var (
oidCountry = asn1.ObjectIdentifier{2, 5, 4, 6}
oidOrganization = asn1.ObjectIdentifier{2, 5, 4, 10}
oidOrganizationalUnit = asn1.ObjectIdentifier{2, 5, 4, 11}
oidCommonName = asn1.ObjectIdentifier{2, 5, 4, 3}
oidLocality = asn1.ObjectIdentifier{2, 5, 4, 7}
oidProvince = asn1.ObjectIdentifier{2, 5, 4, 8}
)
type ASNUTF8String struct {
Type asn1.ObjectIdentifier
Value string `asn1:"utf8"`
}
// AnySET is an interface{} slice; SET is required by asn.1 to generate it
// as a set instead of a sequence.
type AnySET []interface{}
Then, to the implementation. The CSR requires a private key, which in my case was storad in a PKCS#12 container. Reading it is easy using Go’s crypto packages. One of the CSR’s Organizational Unit values and the Common Name is provided through arguments, together with two byte slices containing the private key, and its password. Error checks were elided to keep the example small.
func GenerateCSR(private, password []byte, commonName, OU string) ([]byte, error) {
block, _ := pem.Decode(private)
var rawBlock []byte
var key *rsa.PrivateKey
rawBlock, _ = x509.DecryptPEMBlock(block, password)
key, _ = x509.ParsePKCS1PrivateKey(rawBlock)
attributes := []AnySET{
{ASNUTF8String{Type: oidCommonName, Value: commonName}},
{ASNUTF8String{Type: oidOrganizationalUnit, Value: OU}},
{ASNUTF8String{Type: oidOrganizationalUnit, Value: "Another OU Value"}},
{ASNUTF8String{Type: oidOrganizationalUnit, Value: "Yet another OU Value"}},
{ASNUTF8String{Type: oidOrganization, Value: "Organization Name"}},
{ASNUTF8String{Type: oidLocality, Value: "Locality Name"}},
{ASNUTF8String{Type: oidProvince, Value: "Province"}},
{pkix.AttributeTypeAndValue{Type: oidCountry, Value: "Country Code"}},
}
attrBytes, _ := asn1.Marshal(attributes)
template := x509.CertificateRequest{
RawSubject: attrBytes,
}
csrBytes, _ := x509.CreateCertificateRequest(rand.Reader, &template, key)
return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}), nil
}
Without this, the order was based on the Subject
type field order, which was
considered invalid by our CA.