Frontend Labs
⋯

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.


Generated from week6.md · 8 Oct 2026 16:36