Skip to content

Instantly share code, notes, and snippets.

@korylprince
Created May 16, 2022 15:10
Show Gist options
  • Save korylprince/1d4437a8ce00bc5583f45677c2cc73cf to your computer and use it in GitHub Desktop.
Save korylprince/1d4437a8ce00bc5583f45677c2cc73cf to your computer and use it in GitHub Desktop.
Lightspeed Smart Agent localhost certificate generation
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"math/big"
"os"
"time"
)
// GenerateLocalhost generates a new key pair for localhost signed by the given CA key pair
func GenerateLocalhost(ca *x509.Certificate, caKey *rsa.PrivateKey) ([]byte, *rsa.PrivateKey, error) {
// generate random serial
buf := make([]byte, 16)
if _, err := rand.Read(buf); err != nil {
return nil, nil, fmt.Errorf("could not generate serial: %w", err)
}
serial := new(big.Int)
serial.SetBytes(buf)
// generate key
key, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, nil, fmt.Errorf("could not generate key: %w", err)
}
// generate subject key id by hashing the public key
ski := sha512.Sum512(x509.MarshalPKCS1PublicKey(&key.PublicKey))
tmpl := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
Organization: []string{"Lightspeed Systems"},
},
SubjectKeyId: ski[:],
DNSNames: []string{"localhost"},
NotBefore: time.Now().Add(-time.Minute), // give a little buffer
NotAfter: time.Now().AddDate(1, 0, 0), // 1 year expiry
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
BasicConstraintsValid: true,
}
// marshal certificate
der, err := x509.CreateCertificate(rand.Reader, tmpl, ca, &key.PublicKey, caKey)
if err != nil {
return nil, nil, fmt.Errorf("could not generate certificate: %w", err)
}
return der, key, nil
}
// UpdateLocalhostCertificate checks if the given cert exists and is still valid and generates a new key pair with the given ca if not
func UpdateLocalhostCertificate(capath, cakeypath, certpath, certkeypath string) error {
var (
block *pem.Block
cert *x509.Certificate
)
// read cert
buf, err := os.ReadFile(certpath)
if err != nil {
// if cert doesn't exist, skip to generation
if errors.Is(err, os.ErrNotExist) {
goto gen
}
return fmt.Errorf("could not read %s: %w", certpath, err)
}
// parse cert PEM data
block, _ = pem.Decode(buf)
if block.Type != "CERTIFICATE" {
return fmt.Errorf("could not parse %s: expected CERTIFICATE block, got %s", certpath, block.Type)
}
// parse cert der to certificate
cert, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return fmt.Errorf("could not parse %s: %w", certpath, err)
}
// check if expired
if !time.Now().After(cert.NotAfter) {
// not expired, no work to do
return nil
}
gen:
// read ca cert
buf, err = os.ReadFile(capath)
if err != nil {
return fmt.Errorf("could not read CA %s: %w", capath, err)
}
// parse ca PEM data
block, _ = pem.Decode(buf)
if block.Type != "CERTIFICATE" {
return fmt.Errorf("could not parse CA %s: expected CERTIFICATE block, got %s", capath, block.Type)
}
// parse ca der to certificate
ca, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return fmt.Errorf("could not parse CA %s: %w", capath, err)
}
// read ca key
buf, err = os.ReadFile(cakeypath)
if err != nil {
return fmt.Errorf("could not read CA key %s: %w", cakeypath, err)
}
// parse ca key PEM data
block, _ = pem.Decode(buf)
if block.Type != "RSA PRIVATE KEY" {
return fmt.Errorf("could not parse CA key %s: expected RSA PRIVATE KEY block, got %s", cakeypath, block.Type)
}
// parse ca PKCS1 key
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return fmt.Errorf("could not parse CA key %s: %w", cakeypath, err)
}
// generate key pair
certbuf, key, err := GenerateLocalhost(ca, key)
if err != nil {
return fmt.Errorf("could not generate new certificate: %w", err)
}
// encode key pair to PEM
certpem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certbuf})
keypem := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
// write key pair to disk
if err := os.WriteFile(certpath, certpem, 0600); err != nil {
if err != nil {
return fmt.Errorf("could not write new certificate %s: %w", certpath, err)
}
}
if err := os.WriteFile(certkeypath, keypem, 0600); err != nil {
if err != nil {
return fmt.Errorf("could not write new certificate key %s: %w", certkeypath, err)
}
}
return nil
}
func main() {
err := UpdateLocalhostCertificate(
"/usr/local/etc/ca.pem",
"/usr/local/etc/ca_key.pem",
"/usr/local/etc/localhost.pem",
"/usr/local/etc/localhost_key.pem",
)
fmt.Println(err)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment