Skip to content
ecdsa.com

Generate a CSR · Go

How to generate a CSR for an EC P-256 key with SANs in Go

Go generates a CSR with the standard library alone: x509.CreateCertificateRequest takes a CertificateRequest template — subject, DNSNames for the SANs, signature algorithm — and signs it with an ecdsa.PrivateKey. The run writes key.pem and csr.pem and decodes the CSR with OpenSSL.

Tested with Go 1.27.3 (crypto/x509, linux/arm64).

main.go — P-256 key and CSR with two SANs, standard library only
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"os"
)
func main() {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatal(err)
}
template := &x509.CertificateRequest{
Subject: pkix.Name{
Country: []string{"US"},
Organization: []string{"Example Corp"},
CommonName: "api.example.com",
},
DNSNames: []string{"api.example.com", "www.example.com"}, // becomes the SAN extension
SignatureAlgorithm: x509.ECDSAWithSHA256,
}
der, err := x509.CreateCertificateRequest(rand.Reader, template, key)
if err != nil {
log.Fatal(err)
}
keyDER, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("key.pem", pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), 0o600); err != nil {
log.Fatal(err)
}
if err := os.WriteFile("csr.pem", pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: der}), 0o644); err != nil {
log.Fatal(err)
}
}

How it works

  1. ecdsa.GenerateKey(elliptic.P256(), rand.Reader) creates the key.
  2. pkix.Name holds the subject; Country and Organization are slices because a name may repeat an attribute.
  3. DNSNames becomes the subjectAltName extension; IPAddresses, EmailAddresses and URIs cover the other SAN types.
  4. x509.CreateCertificateRequest returns DER; pem.EncodeToMemory with type CERTIFICATE REQUEST writes the usual PEM, and MarshalPKCS8PrivateKey the key.

Gotchas

  • Go does not copy the CommonName into the SANs: a template with only CommonName gives a CSR without any SAN (DNSNames=[]), and Go clients reject certificates like that with x509: certificate relies on legacy Common Name field.
  • SignatureAlgorithm may be left empty — Go picks ECDSA-SHA256 for a P-256 key — but setting it documents the choice and fails loudly if the key does not match.
  • The key file is written with mode 0o600; os.WriteFile with 0o644 would leave the private key readable by every user on the machine.
  • x509.ParseCertificateRequest plus CheckSignature() verifies a CSR you receive; the CA side should always do that before signing.

Related recipes