Skip to content

Instantly share code, notes, and snippets.

@pgaskin
Created June 25, 2018 01:55
Show Gist options
  • Save pgaskin/58f6faae71f45404c7660b60819eb6cd to your computer and use it in GitHub Desktop.
Save pgaskin/58f6faae71f45404c7660b60819eb6cd to your computer and use it in GitHub Desktop.
Generates self-signed certificates on-the-fly.
package main
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net"
"net/http"
"os"
"sync"
"time"
)
func generateCA() (keyDerBytes, certPemBytes []byte, err error) {
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return nil, nil, fmt.Errorf("failed to generate serial number: %s", err)
}
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}
var keyBuf bytes.Buffer
keyDer, err := x509.MarshalECPrivateKey(key)
if err != nil {
return nil, nil, err
}
err = pem.Encode(&keyBuf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDer})
if err != nil {
return nil, nil, err
}
tmpl := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Patrick G"},
CommonName: "Root CA",
OrganizationalUnit: []string{"Certificate Authority"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10),
KeyUsage: x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
}
cert, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &key.PublicKey, key)
if err != nil {
return nil, nil, err
}
var pemBuf bytes.Buffer
err = pem.Encode(&pemBuf, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
if err != nil {
return nil, nil, err
}
return keyBuf.Bytes(), pemBuf.Bytes(), nil
}
func genCert(caKeyDer, caCertPem []byte, host string) (keyBytes, certPemBytes []byte, err error) {
b, _ := pem.Decode(caKeyDer)
if b.Type != "EC PRIVATE KEY" {
return nil, nil, fmt.Errorf("unexpected block type: %s", b.Type)
}
caKey, err := x509.ParseECPrivateKey(b.Bytes)
if err != nil {
return nil, nil, err
}
b, _ = pem.Decode(caCertPem)
if b.Type != "CERTIFICATE" {
return nil, nil, fmt.Errorf("unexpected block type: %s", b.Type)
}
caCert, err := x509.ParseCertificate(b.Bytes)
if err != nil {
return nil, nil, err
}
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return nil, nil, fmt.Errorf("failed to generate serial number: %s", err)
}
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}
var keyBuf bytes.Buffer
keyDer, err := x509.MarshalECPrivateKey(key)
if err != nil {
return nil, nil, err
}
err = pem.Encode(&keyBuf, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDer})
if err != nil {
return nil, nil, err
}
tmpl := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Patrick G"},
CommonName: host,
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: false,
}
if ip := net.ParseIP(host); ip != nil {
tmpl.IPAddresses = append(tmpl.IPAddresses, ip)
} else {
tmpl.DNSNames = append(tmpl.DNSNames, host)
}
cert, err := x509.CreateCertificate(rand.Reader, &tmpl, caCert, &key.PublicKey, caKey)
if err != nil {
return nil, nil, err
}
var pemBuf bytes.Buffer
err = pem.Encode(&pemBuf, &pem.Block{Type: "CERTIFICATE", Bytes: cert})
if err != nil {
return nil, nil, err
}
return keyBuf.Bytes(), pemBuf.Bytes(), nil
}
func exists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
type ww struct{}
func (s *ww) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("It worked!\n"))
}
func main() {
if !exists("ca.key") || !exists("ca.crt") || !exists("ca.pem") {
fmt.Printf("Could not access ca.key, ca.crt, and ca.pem. Generating new CA.\n")
caKeyDer, caCertPem, err := generateCA()
if err != nil {
fmt.Printf("Could not generate CA certificate: %v\n", err)
os.Exit(1)
}
erra := ioutil.WriteFile("ca.key", caKeyDer, os.ModePerm)
errb := ioutil.WriteFile("ca.crt", caCertPem, os.ModePerm)
errc := ioutil.WriteFile("ca.pem", caCertPem, os.ModePerm)
if erra != nil || errb != nil || errc != nil {
fmt.Printf("Could not save certificates to disk.\n")
os.Exit(1)
}
}
caKeyDer, erra := ioutil.ReadFile("ca.key")
caCertPem, errb := ioutil.ReadFile("ca.pem")
if erra != nil || errb != nil {
fmt.Printf("Could not read CA certificates.\n")
os.Exit(1)
}
sa := &http.Server{
Handler: &ww{},
Addr: ":80",
}
var certificates sync.Map
sb := &http.Server{
Handler: &ww{},
Addr: ":443",
TLSConfig: &tls.Config{
GetCertificate: func(h *tls.ClientHelloInfo) (*tls.Certificate, error) {
// TODO: cache in sync.Map
var cert tls.Certificate
var err error
val, ok := certificates.Load(h.ServerName)
if !ok {
var leafKeyDer, leafCertPem []byte
leafKeyDer, leafCertPem, err = genCert(caKeyDer, caCertPem, h.ServerName)
if err != nil {
fmt.Printf("Error generating certificate for %s: %v\n", h.ServerName, err)
return &cert, err
}
cert, err = tls.X509KeyPair(leafCertPem, leafKeyDer)
if err != nil {
fmt.Printf("Error generating certificate for %s: %v\n", h.ServerName, err)
return &cert, err
}
certificates.Store(h.ServerName, cert)
} else {
cert, ok = val.(tls.Certificate)
if !ok {
err = fmt.Errorf("could not load certificate from cache")
}
if err != nil {
fmt.Printf("Error generating certificate for %s: %v\n", h.ServerName, err)
return &cert, err
}
}
return &cert, nil
},
},
}
go func() {
err := sa.ListenAndServe()
if err != nil {
panic(err)
}
}()
err := sb.ListenAndServeTLS("", "")
if err != nil {
panic(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment