Created
June 25, 2018 01:55
-
-
Save pgaskin/58f6faae71f45404c7660b60819eb6cd to your computer and use it in GitHub Desktop.
Generates self-signed certificates on-the-fly.
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 ( | |
"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