Created
May 16, 2022 15:10
-
-
Save korylprince/1d4437a8ce00bc5583f45677c2cc73cf to your computer and use it in GitHub Desktop.
Lightspeed Smart Agent localhost certificate generation
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
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