Day 40 — Certificate Generation in Go
Learning Objectives
- Generate a CA, server cert, and client cert entirely in Go
- Understand certificate fields: SANs, KeyUsage, ExtKeyUsage
- Build automated cert provisioning for dev/test environments
Key Concepts
Certificate Chain:
Root CA (self-signed, trust anchor)
└── Server Cert (signed by CA, presented to clients)
└── Client Cert (signed by CA, presented to server in mTLS)
Key fields:
IsCA: true — can sign other certs
KeyUsage: KeyCertSign — allowed to sign certificates
ExtKeyUsage: ServerAuth — valid for server authentication
ExtKeyUsage: ClientAuth — valid for client authentication
DNSNames: ["example.com"] — Subject Alternative Names (SANs)
IPAddresses: [127.0.0.1] — IP SANs
Exercise: Full PKI Generator
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"os"
"time"
)
func generateKey() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
}
func savePEM(path, pemType string, data []byte) error {
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil { return err }
defer f.Close()
return pem.Encode(f, &pem.Block{Type: pemType, Bytes: data})
}
// generateCA creates a self-signed root CA certificate
func generateCA(commonName string) (*x509.Certificate, *ecdsa.PrivateKey, error) {
key, err := generateKey()
if err != nil { return nil, nil, err }
serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
template := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{CommonName: commonName, Organization: []string{"SecProxy CA"}},
NotBefore: time.Now().Add(-time.Minute), // backdate 1 min for clock skew
NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour),
IsCA: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
}
certDER, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil { return nil, nil, err }
cert, _ := x509.ParseCertificate(certDER)
// Save CA cert and key
savePEM("ca.crt", "CERTIFICATE", certDER)
keyDER, _ := x509.MarshalECPrivateKey(key)
savePEM("ca.key", "EC PRIVATE KEY", keyDER)
fmt.Printf("Generated CA: %s (valid until %s)\n", commonName, template.NotAfter.Format("2006-01-02"))
return cert, key, nil
}
// generateCert creates a certificate signed by the given CA
func generateCert(commonName string, isServer bool, sans []string, ips []net.IP, caCert *x509.Certificate, caKey *ecdsa.PrivateKey) error {
key, err := generateKey()
if err != nil { return err }
serial, _ := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
extKeyUsage := []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
if isServer {
extKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
}
template := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{CommonName: commonName},
NotBefore: time.Now().Add(-time.Minute),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: extKeyUsage,
DNSNames: sans,
IPAddresses: ips,
}
certDER, err := x509.CreateCertificate(rand.Reader, template, caCert, &key.PublicKey, caKey)
if err != nil { return err }
// Save cert and key
prefix := "client"
if isServer { prefix = "server" }
savePEM(prefix+".crt", "CERTIFICATE", certDER)
keyDER, _ := x509.MarshalECPrivateKey(key)
savePEM(prefix+".key", "EC PRIVATE KEY", keyDER)
kind := "client"
if isServer { kind = "server" }
fmt.Printf("Generated %s cert: %s (signed by CA)\n", kind, commonName)
return nil
}
func main() {
// Step 1: Generate root CA
caCert, caKey, err := generateCA("SecProxy Root CA")
if err != nil { fmt.Println("CA error:", err); return }
// Step 2: Generate server cert with SANs
err = generateCert("proxy.internal", true,
[]string{"proxy.internal", "localhost"},
[]net.IP{net.ParseIP("127.0.0.1")},
caCert, caKey,
)
if err != nil { fmt.Println("server cert error:", err); return }
// Step 3: Generate client cert (for mTLS)
err = generateCert("service-backend", false,
nil, nil, caCert, caKey,
)
if err != nil { fmt.Println("client cert error:", err); return }
fmt.Println("\nGenerated files: ca.crt, ca.key, server.crt, server.key, client.crt, client.key")
fmt.Println("Verify: openssl verify -CAfile ca.crt server.crt")
}
Real-World Context
Internal service certificates are often managed manually with openssl scripts — fragile and audit-unfriendly. Go-based cert generation lets you automate PKI for dev/test, generate short-lived certs for ephemeral services, and build your own internal CA without depending on external tooling.