21

I tried to generate a CSR using "crypto/x509" package and didn't find the way to add a "emailAddress" field into its Subject.

According to the documentation CertificateRequest structure has a "EmailAddresses []string" field but it's serialized into SAN extension. Here is a test code i used: http://play.golang.org/p/OtObaTyuTM

Also I created a CSR using "openssl req" program and compared results:

% openssl req -in openssl.csr -noout -text
Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: C=AU, ST=Some-State, L=MyCity, O=Company Ltd, OU=IT, CN=domain.com/emailAddress=test@email.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (512 bit)
                Modulus:
                    00:a3:05:e3:37:63:f9:8b:d0:37:46:2d:a8:d9:26:
                    4e:be:83:1d:b9:30:88:2b:80:4b:53:cc:7c:01:86:
                    b0:9b:1d:3b:0a:05:c4:56:47:4e:5d:90:f9:5a:29:
                    8b:9a:7f:fa:4b:5e:e4:5d:dd:c6:8b:87:33:c4:b4:
                    fa:6b:b4:67:bd
                Exponent: 65537 (0x10001)
        Attributes:
            a0:00
    Signature Algorithm: sha1WithRSAEncryption
         0b:24:6e:0a:f9:bf:23:d7:41:5f:96:da:78:d1:99:18:fb:d6:
         71:7e:79:f0:02:e9:8a:50:a9:00:32:df:26:14:2f:f4:3e:c4:
         22:c9:5c:4e:79:c1:c2:22:1b:2a:da:79:6f:51:ba:8a:12:63:
         27:02:4a:b3:22:97:59:f7:6e:d6
===============================================================
 % openssl req -in golang.csr -noout -text
Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: C=AU, O=Company Ltd, OU=IT, L=MyCity, ST=Some-State, CN=domain.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (512 bit)
                Modulus:
                    00:ac:b6:51:5b:53:44:44:20:91:da:01:45:72:49:
                    95:83:78:74:7c:05:f9:a7:77:88:02:3a:23:5f:04:
                    c3:69:45:b9:5a:bb:fd:e7:d3:24:5f:46:14:b8:7d:
                    30:ce:a0:c6:ea:e3:3b:ec:4c:75:24:cc:ce:60:1d:
                    e9:33:57:ae:21
                Exponent: 65537 (0x10001)
        Attributes:
        Requested Extensions:
            X509v3 Subject Alternative Name:
                email:test@email.com
    Signature Algorithm: sha256WithRSAEncryption
         a1:c1:b7:80:a0:f0:c3:b6:44:06:f4:ad:12:3a:67:19:fa:84:
         34:22:2a:d9:56:d9:8b:c9:a4:d0:cf:8d:a1:36:87:fa:75:b7:
         05:40:0a:15:1f:72:61:85:a8:09:bc:f4:13:e6:24:5e:2e:b7:
         99:e3:93:53:4e:2d:d5:0c:22:fc

To my mind I should build RawSubject field myself with emainAddress oid but I didn't find any code samples. UPD: I've found the solution. As I mentioned above, the RawSubject field must be prepared manually:

subj := pkix.Name{
                CommonName:         cn,
                Country:            []string{c},
                Organization:       []string{o},
                OrganizationalUnit: []string{ou},
                Locality:           []string{l},
                Province:           []string{s},
}
rawSubj := subj.ToRDNSequence()
rawSubj = appendRDNs(rawSubj, []string{e}, oidEmailAddress)
asn1Subj, err := asn1.Marshal(rawSubj)
template := x509.CertificateRequest{
            RawSubject: asn1Subj,
            SignatureAlgorithm: x509.SHA1WithRSA,
}

where:

  • var oidEmailAddress = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}
  • appendRDNs() is defined in crypto/x509/pkix (because its name doesn't start with an uppercase letter, it's not exported by default. You can just define it again as your own function with copy&paste).
Joe Shaw
  • 22,066
  • 16
  • 70
  • 92
mephist
  • 363
  • 1
  • 3
  • 9

3 Answers3

27

This is a variation on Jeremy's answer which takes advantage of some new additions in Go since his answer, and also fixes what I believe to be a bug. (For more info on that, see my comments on his post.)

Here's a runnable playground link for the code below.

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "crypto/x509/pkix"
    "encoding/asn1"
    "encoding/pem"
    "os"
)

var oidEmailAddress = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}

func main() {
    keyBytes, _ := rsa.GenerateKey(rand.Reader, 1024)

    emailAddress := "test@example.com"
    subj := pkix.Name{
        CommonName:         "example.com",
        Country:            []string{"AU"},
        Province:           []string{"Some-State"},
        Locality:           []string{"MyCity"},
        Organization:       []string{"Company Ltd"},
        OrganizationalUnit: []string{"IT"},
        ExtraNames: []pkix.AttributeTypeAndValue{
            {
                Type:  oidEmailAddress, 
                Value: asn1.RawValue{
                    Tag:   asn1.TagIA5String, 
                    Bytes: []byte(emailAddress),
                },
            },
        },
    }

    template := x509.CertificateRequest{
        Subject:            subj,
        SignatureAlgorithm: x509.SHA256WithRSA,
    }

    csrBytes, _ := x509.CreateCertificateRequest(rand.Reader, &template, keyBytes)
    pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})

}

The main differences are:

  • Rather than serializing the subject and using the RawSubject field, add the email address field to the pkix.Name ExtraNames slice (added in Go 1.5).
  • The email address needs to be encoded as an ASN.1 IA5String, not as a PrintableString or UTF8String. This is why we need to use asn1.RawValue.
  • Don't add email addresses to the CertificateRequest EmailAddresses field, which sets a SubjectAltName (SAN). Those are designed more for things like signed emails. In the context of TLS certificates, SANs should be used for alternatively valid hostnames and IP addresses.

(Update 2018-06-14: The Value has been changed from a string to an asn1.RawValue. OpenSSL rejects CSRs generated otherwise because the ASN.1 serialized encoding of emailAddress needs to be IA5String rather than PrintableString or UTF8String.)

Joe Shaw
  • 22,066
  • 16
  • 70
  • 92
22

I know mephist answered his own question, but he left a few things to piece together. So, for the sake of completeness (and because I've landed here twice in the past 2 years...) Here's a complete working example: https://play.golang.org/p/YL_qfPe4Zz

package main

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "crypto/x509/pkix"
    "encoding/asn1"
    "encoding/pem"
    "os"
)

var oidEmailAddress = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}

func main() {
    keyBytes, _ := rsa.GenerateKey(rand.Reader, 1024)

    emailAddress := "test@example.com"
    subj := pkix.Name{
        CommonName:         "example.com",
        Country:            []string{"AU"},
        Province:           []string{"Some-State"},
        Locality:           []string{"MyCity"},
        Organization:       []string{"Company Ltd"},
        OrganizationalUnit: []string{"IT"},
    }
    rawSubj := subj.ToRDNSequence()
    rawSubj = append(rawSubj, []pkix.AttributeTypeAndValue{
        {Type: oidEmailAddress, Value: emailAddress},
    })

    asn1Subj, _ := asn1.Marshal(rawSubj)
    template := x509.CertificateRequest{
        RawSubject:         asn1Subj,
        EmailAddresses:     []string{emailAddress},
        SignatureAlgorithm: x509.SHA256WithRSA,
    }

    csrBytes, _ := x509.CreateCertificateRequest(rand.Reader, &template, keyBytes)
    pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})

}
Jeremy Jay
  • 1,323
  • 13
  • 15
  • Today there is an easier way to add items to `pkix.Name` without needing to marshal the ASN.1 and replace `RawSubject`: Add the `pkix.AttributeTypeAndValue` to the `ExtraNames` field. This was added in Go 1.5. Updated working example: https://play.golang.org/p/znBDXC7C978 – Joe Shaw May 16 '18 at 15:49
  • 1
    Also, I think putting the email address into the `EmailAddresses` field is not the right thing to do. That will add it as a `SubjectAltName` -- those are for specifying the DNS names and IP addresses that the certificate will be valid for. In my experience, I've not seen CSRs that put email addresses there. (It's designed more for signed emails like S/MIME.) – Joe Shaw May 17 '18 at 15:01
0

Just put it in the CommonName?

CommonName:         "domain.com/emailAddress=test@email.com",

Subject: C=AU, O=Company Ltd, OU=IT, L=MyCity, ST=Some-State, CN=domain.com/emailAddress=test@email.com

pram
  • 642
  • 1
  • 6
  • 13
  • 2
    I've checked your solution. According to openssl source, "CN" and "emailAddress" fields have their dedicated oid's ('2.5.4.3' and '1.2.840.113549.1.9.1' respectively), so just appending email to CommonName is the wrong way. – mephist Sep 29 '14 at 18:20