|
// Copyright 2009 The Go Authors. All rights reserved. |
|
// Use of this source code is governed by a BSD-style |
|
// license that can be found in the LICENSE file. |
|
|
|
// A dummy HTTPS server which generates a self-signed TLS certificate for itself. |
|
|
|
// Adapted from https://golang.org/src/crypto/tls/generate_cert.go. |
|
|
|
package main |
|
|
|
import ( |
|
"context" |
|
"crypto/ecdsa" |
|
"crypto/ed25519" |
|
"crypto/elliptic" |
|
"crypto/rand" |
|
"crypto/rsa" |
|
"crypto/x509" |
|
"crypto/x509/pkix" |
|
"encoding/pem" |
|
"flag" |
|
"fmt" |
|
"io/ioutil" |
|
"log" |
|
"math/big" |
|
"net" |
|
"net/http" |
|
"os" |
|
"os/signal" |
|
"time" |
|
) |
|
|
|
var ( |
|
address = flag.String("address", "", "Address to listen on") |
|
port = flag.Int("port", 8443, "Port to listen on") |
|
validFrom = flag.String("start-date", "", "Creation date formatted as Jan 1 15:04:05 2011") |
|
validFor = flag.Duration("duration", 365*24*time.Hour, "Duration that certificate is valid for") |
|
isCA = flag.Bool("ca", false, "whether this cert should be its own Certificate Authority") |
|
rsaBits = flag.Int("rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set") |
|
ecdsaCurve = flag.String("ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256 (recommended), P384, P521") |
|
ed25519Key = flag.Bool("ed25519", false, "Generate an Ed25519 key") |
|
) |
|
|
|
func publicKey(priv interface{}) interface{} { |
|
switch k := priv.(type) { |
|
case *rsa.PrivateKey: |
|
return &k.PublicKey |
|
case *ecdsa.PrivateKey: |
|
return &k.PublicKey |
|
case ed25519.PrivateKey: |
|
return k.Public().(ed25519.PublicKey) |
|
default: |
|
return nil |
|
} |
|
} |
|
|
|
func generate() (string, string, error) { |
|
var priv interface{} |
|
var err error |
|
switch *ecdsaCurve { |
|
case "": |
|
if *ed25519Key { |
|
_, priv, err = ed25519.GenerateKey(rand.Reader) |
|
} else { |
|
priv, err = rsa.GenerateKey(rand.Reader, *rsaBits) |
|
} |
|
case "P224": |
|
priv, err = ecdsa.GenerateKey(elliptic.P224(), rand.Reader) |
|
case "P256": |
|
priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) |
|
case "P384": |
|
priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) |
|
case "P521": |
|
priv, err = ecdsa.GenerateKey(elliptic.P521(), rand.Reader) |
|
default: |
|
return "", "", fmt.Errorf("Unrecognized elliptic curve: %q", *ecdsaCurve) |
|
} |
|
if err != nil { |
|
return "", "", fmt.Errorf("Failed to generate private key: %v", err) |
|
} |
|
|
|
// ECDSA, ED25519 and RSA subject keys should have the DigitalSignature |
|
// KeyUsage bits set in the x509.Certificate template |
|
keyUsage := x509.KeyUsageDigitalSignature |
|
// 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. |
|
if _, isRSA := priv.(*rsa.PrivateKey); isRSA { |
|
keyUsage |= x509.KeyUsageKeyEncipherment |
|
} |
|
|
|
var notBefore time.Time |
|
if len(*validFrom) == 0 { |
|
notBefore = time.Now() |
|
} else { |
|
notBefore, err = time.Parse("Jan 2 15:04:05 2006", *validFrom) |
|
if err != nil { |
|
return "", "", fmt.Errorf("Failed to parse creation date: %v", err) |
|
} |
|
} |
|
|
|
notAfter := notBefore.Add(*validFor) |
|
|
|
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: %v", err) |
|
} |
|
|
|
template := x509.Certificate{ |
|
SerialNumber: serialNumber, |
|
Subject: pkix.Name{ |
|
Organization: []string{"Acme Co"}, |
|
}, |
|
NotBefore: notBefore, |
|
NotAfter: notAfter, |
|
|
|
KeyUsage: keyUsage, |
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, |
|
BasicConstraintsValid: true, |
|
} |
|
|
|
if ip := net.ParseIP(*address); ip != nil { |
|
template.IPAddresses = []net.IP{ip} |
|
} |
|
template.DNSNames = []string{"localhost"} |
|
|
|
if *isCA { |
|
template.IsCA = true |
|
template.KeyUsage |= x509.KeyUsageCertSign |
|
} |
|
|
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) |
|
if err != nil { |
|
return "", "", fmt.Errorf("Failed to create certificate: %v", err) |
|
} |
|
|
|
certOut, err := ioutil.TempFile("", "cert.pem") |
|
if err != nil { |
|
return "", "", fmt.Errorf("Failed to open cert.pem for writing: %v", err) |
|
} |
|
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { |
|
return "", "", fmt.Errorf("Failed to write data to cert.pem: %v", err) |
|
} |
|
if err := certOut.Close(); err != nil { |
|
return "", "", fmt.Errorf("Error closing cert.pem: %v", err) |
|
} |
|
log.Printf("Generated cert file %s\n", certOut.Name()) |
|
|
|
keyOut, err := ioutil.TempFile("", "key.pem") |
|
if err != nil { |
|
return "", "", fmt.Errorf("Failed to create temporary file for key: %v", err) |
|
} |
|
privBytes, err := x509.MarshalPKCS8PrivateKey(priv) |
|
if err != nil { |
|
return "", "", fmt.Errorf("Unable to marshal private key: %v", err) |
|
} |
|
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { |
|
return "", "", fmt.Errorf("Failed to write data to key.pem: %v", err) |
|
} |
|
if err := keyOut.Close(); err != nil { |
|
return "", "", fmt.Errorf("Error closing key.pem: %v", err) |
|
} |
|
log.Printf("Generated key file %s\n", keyOut.Name()) |
|
|
|
return certOut.Name(), keyOut.Name(), nil |
|
} |
|
|
|
func HelloServer(w http.ResponseWriter, req *http.Request) { |
|
w.Header().Set("Content-Type", "text/plain") |
|
w.Write([]byte("Howdy!\n")) |
|
} |
|
|
|
func main() { |
|
flag.Parse() |
|
|
|
certPath, keyPath, err := generate() |
|
if err != nil { |
|
log.Fatalf("Error generating cert: %v", err) |
|
} |
|
defer func() { |
|
log.Println("Cleaning up generated files") |
|
if err := os.Remove(certPath); err != nil { |
|
log.Printf("Could not clean up file %s\n", certPath) |
|
} |
|
log.Printf("Removed %s\n", certPath) |
|
|
|
if err := os.Remove(keyPath); err != nil { |
|
log.Printf("Could not clean up file %s\n", keyPath) |
|
} |
|
log.Printf("Removed %s\n", keyPath) |
|
}() |
|
|
|
addr := fmt.Sprintf("%s:%d", *address, *port) |
|
srv := http.Server{ |
|
Addr: addr, |
|
} |
|
|
|
idleConnsClosed := make(chan struct{}) |
|
go func() { |
|
sigint := make(chan os.Signal, 1) |
|
signal.Notify(sigint, os.Interrupt) |
|
<-sigint |
|
|
|
if err := srv.Shutdown(context.Background()); err != nil { |
|
log.Printf("HTTP server Shutdown: %v", err) |
|
} |
|
close(idleConnsClosed) |
|
}() |
|
|
|
http.HandleFunc("/", HelloServer) |
|
log.Printf("Listening on %s\n", addr) |
|
err = srv.ListenAndServeTLS(certPath, keyPath) |
|
if err != http.ErrServerClosed { |
|
log.Println("ListenAndServe: ", err) |
|
return |
|
} |
|
|
|
<-idleConnsClosed |
|
} |