Skip to content

Instantly share code, notes, and snippets.

@kohenkatz
Created March 12, 2023 17:58
Show Gist options
  • Save kohenkatz/9bd4a382cc27208d35e3eaa9686afa68 to your computer and use it in GitHub Desktop.
Save kohenkatz/9bd4a382cc27208d35e3eaa9686afa68 to your computer and use it in GitHub Desktop.
Generate a self-signed certificate on demand for a Go server for http/2 (gRPC/connect)
package main
import (
"crypto/tls"
"net/http"
"os"
"strings"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
// NOTE: Error handling removed for brevity. You should put it back in your own code.
func main() {
mux := setUpRoutes()
handler := h2c.NewHandler(mux, &http2.Server{})
server := &http.Server{
Addr: listenAddr,
Handler: handler,
ReadHeaderTimeout: time.Second,
ReadTimeout: 5 * time.Minute,
WriteTimeout: 5 * time.Minute,
MaxHeaderBytes: 8 * 1024, // 8KiB
}
if selfSigned, ok := os.LookupEnv("SELFSIGNED_CERT_HOSTS"); ok {
hosts := strings.Split(selfSigned, ",")
cert, _ := genCert(hosts, KeyTypeEC384, time.Hour*24*365)
server.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
}
server.ListenAndServeTLS("", "")
} else {
server.ListenAndServe()
}
}
package main
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"time"
)
// Based on https://go.dev/src/crypto/tls/generate_cert.go
// and https://gist.github.com/samuel/8b500ddd3f6118d052b5e6bc16bc4c09
type KeyType int
// NOTE: Not all of these types are supported by browsers right now,
// but they are here as examples of how to use them.
const (
KeyTypeRSA2048 KeyType = iota
KeyTypeRSA4096
KeyTypeEC224
KeyTypeEC256
KeyTypeEC384
KeyTypeEC521
KeyTypeED25519
)
type PrivateKey interface {
Public() crypto.PublicKey
}
// Generate a self-signed certificate, and return it
func genCert(hosts []string, keyType KeyType, validFor time.Duration) (tls.Certificate, error) {
fail := func(err error) (tls.Certificate, error) { return tls.Certificate{}, err }
var priv PrivateKey
var pub crypto.PublicKey
var err error
// ECDSA, ED25519 and RSA subject keys should have the DigitalSignature
// KeyUsage bits set in the x509.Certificate template
keyUsage := x509.KeyUsageDigitalSignature
switch keyType {
case KeyTypeRSA2048:
priv, err = rsa.GenerateKey(rand.Reader, 2048)
// Only RSA subject keys should have the KeyEncipherment KeyUsage bits set. In
// the context of TLS this KeyUsage is particular to RSA key exchange and
// authentication.
keyUsage |= x509.KeyUsageKeyEncipherment
pub = priv.Public()
case KeyTypeRSA4096:
priv, err = rsa.GenerateKey(rand.Reader, 4096)
keyUsage |= x509.KeyUsageKeyEncipherment
pub = priv.Public()
case KeyTypeEC224:
priv, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
pub = priv.Public()
case KeyTypeEC256:
priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
pub = priv.Public()
case KeyTypeEC384:
priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
pub = priv.Public()
case KeyTypeEC521:
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
pub = priv.Public()
case KeyTypeED25519:
pub, priv, err = ed25519.GenerateKey(rand.Reader)
}
if err != nil {
return fail(fmt.Errorf("Failed to generate private key: %w", err))
}
notBefore := time.Now().Add(time.Hour * -1)
notAfter := time.Now().Add(validFor)
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"My Company"},
OrganizationalUnit: []string{"Server Development"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: keyUsage,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, h)
}
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, pub, priv)
if err != nil {
return fail(fmt.Errorf("Failed to create certificate: %w", err))
}
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return fail(fmt.Errorf("Failed to encode private key PKCS#8: %w", err))
}
certOut := &bytes.Buffer{}
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
keyOut := &bytes.Buffer{}
pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
return tls.X509KeyPair(certOut.Bytes(), keyOut.Bytes())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment