Last active
February 13, 2025 09:34
-
-
Save jedisct1/168019d9b7864e98ffd1b207d0755f30 to your computer and use it in GitHub Desktop.
SIgning a CSR in Go, for mTLS (using ECDSA keys)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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