Skip to content

Instantly share code, notes, and snippets.

@gabriel-samfira
Last active December 15, 2019 22:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gabriel-samfira/61663ec3c07652d4deeeccfdec319d64 to your computer and use it in GitHub Desktop.
Save gabriel-samfira/61663ec3c07652d4deeeccfdec319d64 to your computer and use it in GitHub Desktop.
Generate certificates for quick testing. This generates a CA, server cert and client cert. The CA allows client certificate verification. By default local hostname and IPs are added to the certificate. Use -certificate-hosts to add a comma separated list of aditional hosts/ips.
package main
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"flag"
"fmt"
"log"
"math/big"
"net"
"os"
"path/filepath"
"strings"
"time"
)
var certHostsHelpMsg = `Extra hosts to set as DNS names/IP addresses in the certificate.`
var (
outDir = flag.String("output-dir", "", "output dir for certificates")
hosts = flag.String("certificate-hosts", "", certHostsHelpMsg)
)
func getLocalIPAddresses() ([]net.IP, error) {
ifaces, err := net.Interfaces()
if err != nil {
return nil, err
}
ret := []net.IP{}
for _, i := range ifaces {
addrs, err := i.Addrs()
if err != nil {
return nil, err
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ret = append(ret, ip)
}
}
return ret, nil
}
// shamelessly copied from https://golang.org/src/crypto/tls/generate_cert.go
func publicKey(priv interface{}) interface{} {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &k.PublicKey
case *ecdsa.PrivateKey:
return &k.PublicKey
default:
return nil
}
}
// shamelessly copied from https://golang.org/src/crypto/tls/generate_cert.go
func pemBlockForKey(priv interface{}) *pem.Block {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
case *ecdsa.PrivateKey:
b, err := x509.MarshalECPrivateKey(k)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err)
os.Exit(2)
}
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
default:
return nil
}
}
func writeKeyFile(path string, key interface{}) error {
keyOut, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("failed to open %s for writing:", path, err)
}
if err := pem.Encode(keyOut, pemBlockForKey(key)); err != nil {
return fmt.Errorf("failed to write data to %s: %s", path, err)
}
if err := keyOut.Close(); err != nil {
return fmt.Errorf("error closing %s: %s", path, err)
}
return nil
}
func writeCertFile(path string, certBytes []byte) error {
certOut, err := os.Create(path)
if err != nil {
return fmt.Errorf("failed to open %s for writing: %s", path, err)
}
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}); err != nil {
return fmt.Errorf("failed to write data to %s: %s", path, err)
}
if err := certOut.Close(); err != nil {
return fmt.Errorf("error closing %s: %s", path, err)
}
return nil
}
func generateCertificates(rootKey, rootCert, srvKey, srvCert, cliKey, cliCert string, extraHosts []string) (err error) {
defer func() {
if err != nil {
os.Remove(rootKey)
os.Remove(rootCert)
os.Remove(srvKey)
os.Remove(srvCert)
os.Remove(cliKey)
os.Remove(cliCert)
}
}()
localIps, err := getLocalIPAddresses()
if err != nil {
return err
}
hostname, err := os.Hostname()
if err != nil {
return err
}
var root interface{}
root, err = rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
if err := writeKeyFile(rootKey, root); err != nil {
return err
}
now := time.Now()
notBefore := now.Add(time.Duration(-24) * time.Hour)
notAfter := notBefore.Add(87600 * time.Hour) // 10 years
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return fmt.Errorf("failed to generate serial number: %s", err)
}
rootTemplate := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Cloudbase Solutions"},
CommonName: "Root CA",
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &rootTemplate, &rootTemplate, publicKey(root), root)
if err != nil {
return err
}
if err := writeCertFile(rootCert, derBytes); err != nil {
return err
}
var priv interface{}
priv, err = rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
if err := writeKeyFile(srvKey, priv); err != nil {
return err
}
leafSerialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return fmt.Errorf("failed to generate serial number: %s", err)
}
dnsNames := []string{hostname, fmt.Sprintf("%s.local", hostname), "localhost"}
for _, val := range extraHosts {
if ip := net.ParseIP(val); ip == nil {
dnsNames = append(dnsNames, val)
} else {
localIps = append(localIps, ip)
}
}
leafTemplate := x509.Certificate{
SerialNumber: leafSerialNumber,
Subject: pkix.Name{
Organization: []string{"Cloudbase Solutions"},
CommonName: "replicator",
},
NotBefore: notBefore,
NotAfter: notAfter,
IPAddresses: localIps,
DNSNames: dnsNames,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: false,
}
derBytes, err = x509.CreateCertificate(rand.Reader, &leafTemplate, &rootTemplate, publicKey(priv), root)
if err != nil {
return fmt.Errorf("Failed to create certificate: %s", err)
}
if err := writeCertFile(srvCert, derBytes); err != nil {
return err
}
var clientKey interface{}
clientKey, err = rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
if err := writeKeyFile(cliKey, clientKey); err != nil {
return err
}
clientTemplate := x509.Certificate{
SerialNumber: new(big.Int).SetInt64(4),
Subject: pkix.Name{
Organization: []string{"Cloudbase Solutions"},
CommonName: "replicator_client",
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
IsCA: false,
}
derBytes, err = x509.CreateCertificate(rand.Reader, &clientTemplate, &rootTemplate, publicKey(clientKey), root)
if err != nil {
return err
}
if err := writeCertFile(cliCert, derBytes); err != nil {
return err
}
return nil
}
func main() {
flag.Parse()
var err error
if *outDir == "" {
*outDir, err = os.Getwd()
if err != nil {
log.Fatalf("failed to get cwd: %q", err)
}
}
var certHosts []string
if *hosts != "" {
certHosts = strings.Split(
strings.Replace(*hosts, " ", "", -1), ",")
}
caPrivKey := filepath.Join(*outDir, "ca-key.pem")
caPubKey := filepath.Join(*outDir, "ca-pub.pem")
srvPrivKey := filepath.Join(*outDir, "srv-key.pem")
srvPubKey := filepath.Join(*outDir, "srv-pub.pem")
clientPrivKey := filepath.Join(*outDir, "client-key.pem")
clientPubKey := filepath.Join(*outDir, "client-pub.pem")
if err := generateCertificates(caPrivKey, caPubKey, srvPrivKey, srvPubKey, clientPrivKey, clientPubKey, certHosts); err != nil {
log.Fatalf("error generating certificates: %q", err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment