Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a Certificate Signing Request (CSR) with an email address in Go

Tags:

ssl

go

x509

csr

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/[email protected]
        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:[email protected]
    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).
like image 394
mephist Avatar asked Sep 25 '14 16:09

mephist


People also ask

Is email address required for CSR?

Thus the private key is needed to produce, but it is not part of, the CSR. CSR for personal ID certificates and signing certificates must have the email address of the ID holder or name of organisation in case of business ID.

How do I send a certificate signing request?

In the Server Manager dashboard, in the top right corner, choose Tools, Certification Authority. In the Certification Authority window, choose your computer name. From the Action menu, choose All Tasks, Submit new request. Select your CSR file, and then choose Open.


2 Answers

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 := "[email protected]"
    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.)

like image 67
Joe Shaw Avatar answered Oct 10 '22 05:10

Joe Shaw


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 := "[email protected]"
    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})

}
like image 28
Jeremy Jay Avatar answered Oct 10 '22 04:10

Jeremy Jay