Created
March 12, 2023 17:58
-
-
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)
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/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() | |
} | |
} |
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" | |
"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