Skip to content

Instantly share code, notes, and snippets.

@jedisct1
Last active February 13, 2025 09:34
Show Gist options
  • Save jedisct1/168019d9b7864e98ffd1b207d0755f30 to your computer and use it in GitHub Desktop.
Save jedisct1/168019d9b7864e98ffd1b207d0755f30 to your computer and use it in GitHub Desktop.
SIgning a CSR in Go, for mTLS (using ECDSA keys)
// Create a key pair for the app and a CSR:
// $ openssl ecparam -genkey -name prime256v1 -out application.key
// $ openssl req -new -sha256 -key application.key -out request.csr
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"os"
"time"
)
// Create a self-signed root CA
func createCa() {
// 1. Generate an ECDSA private key using the P-256 curve.
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
fmt.Fprintf(os.Stderr, "Error generating ECDSA private key: %v\n", err)
os.Exit(1)
}
// 2. Create a certificate template for the self-signed root CA.
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
fmt.Fprintf(os.Stderr, "Error generating serial number: %v\n", err)
os.Exit(1)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Country: []string{"US"},
Organization: []string{"Fastly"},
OrganizationalUnit: []string{"Compute"},
CommonName: "self-Signed CA",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour), // valid for 10 years
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
BasicConstraintsValid: true,
IsCA: true,
// Optionally, set MaxPathLen, if needed:
MaxPathLen: 2,
}
// 3. Self-sign the certificate by using the template as both the certificate and the parent.
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating certificate: %v\n", err)
os.Exit(1)
}
// 4. Encode the certificate to PEM format.
certPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certDER,
})
// 5. Marshal the private key to DER format and encode it as PEM.
keyBytes, err := x509.MarshalECPrivateKey(priv)
if err != nil {
fmt.Fprintf(os.Stderr, "Error marshaling private key: %v\n", err)
os.Exit(1)
}
keyPEM := pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: keyBytes,
})
// 6. Write the certificate and key to files.
if err := os.WriteFile("rootCA.crt", certPEM, 0644); err != nil {
fmt.Fprintf(os.Stderr, "Error writing certificate file: %v\n", err)
os.Exit(1)
}
if err := os.WriteFile("rootCA.key", keyPEM, 0600); err != nil {
fmt.Fprintf(os.Stderr, "Error writing key file: %v\n", err)
os.Exit(1)
}
}
func main() {
// Create a self-signed root CA
createCa()
// Read the CSR file
csrPEM, err := ioutil.ReadFile("request.csr")
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading CSR file: %v\n", err)
os.Exit(1)
}
// Decode the PEM block containing the CSR
csrBlock, _ := pem.Decode(csrPEM)
if csrBlock == nil || (csrBlock.Type != "CERTIFICATE REQUEST" && csrBlock.Type != "NEW CERTIFICATE REQUEST") {
fmt.Fprintf(os.Stderr, "Failed to decode PEM block containing CSR\n")
os.Exit(1)
}
// Parse the CSR
csr, err := x509.ParseCertificateRequest(csrBlock.Bytes)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing CSR: %v\n", err)
os.Exit(1)
}
// Verify the CSR signature
if err := csr.CheckSignature(); err != nil {
fmt.Fprintf(os.Stderr, "CSR signature verification failed: %v\n", err)
os.Exit(1)
}
// Create a certificate template based on the CSR
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
fmt.Fprintf(os.Stderr, "Error generating serial number: %v\n", err)
os.Exit(1)
}
certTemplate := x509.Certificate{
SerialNumber: serialNumber,
Subject: csr.Subject,
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0), // Valid for 1 year
// Set key usage as needed
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
// If the CSR includes SANs, include them:
DNSNames: csr.DNSNames,
EmailAddresses: csr.EmailAddresses,
IPAddresses: csr.IPAddresses,
}
// Load the CA certificate
caCertPEM, err := ioutil.ReadFile("rootCA.crt")
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading CA certificate: %v\n", err)
os.Exit(1)
}
caCertBlock, _ := pem.Decode(caCertPEM)
if caCertBlock == nil || caCertBlock.Type != "CERTIFICATE" {
fmt.Fprintf(os.Stderr, "Failed to decode CA certificate PEM block\n")
os.Exit(1)
}
caCert, err := x509.ParseCertificate(caCertBlock.Bytes)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing CA certificate: %v\n", err)
os.Exit(1)
}
// Load the CA private key
caKeyPEM, err := ioutil.ReadFile("rootCA.key")
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading CA private key: %v\n", err)
os.Exit(1)
}
caKeyBlock, _ := pem.Decode(caKeyPEM)
if caKeyBlock == nil {
fmt.Fprintf(os.Stderr, "Failed to decode CA private key PEM block\n")
os.Exit(1)
}
var caKey *ecdsa.PrivateKey
// Parse the private key
caKey, err = x509.ParseECPrivateKey(caKeyBlock.Bytes)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing EC private key: %v\n", err)
os.Exit(1)
}
// Use the public key from the CSR
certTemplate.PublicKey = csr.PublicKey
// Sign the certificate using the CA's key.
// Note: The parent certificate is the CA certificate since we’re using a self–signed CA.
certDER, err := x509.CreateCertificate(rand.Reader, &certTemplate, caCert, csr.PublicKey, caKey)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating certificate: %v\n", err)
os.Exit(1)
}
// Encode the signed certificate to PEM format
certPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certDER,
})
fmt.Printf("Signed Certificate:\n%s\n", certPEM)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment