From ebcffd9688299d3eceeb16fe0c39a44fc96a59b4 Mon Sep 17 00:00:00 2001 From: acoshift Date: Thu, 2 Aug 2018 17:37:13 +0700 Subject: [PATCH 1/3] add self sign config --- config.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/config.go b/config.go index 116d3a3..8dfc4dd 100644 --- a/config.go +++ b/config.go @@ -26,6 +26,14 @@ type AppConfig struct { Wait string `yaml:"wait" json:"wait"` } `yaml:"gracefulShutdown" json:"gracefulShutdown"` TLS *struct { + SelfSign *struct { + Key struct { + Algo string `yaml:"algo" json:"algo"` + Size int `yaml:"size" json:"size"` + } `yaml:"key" json:"key"` + CN string `yaml:"cn" json:"cn"` + Hosts []string `yaml:"host" json:"host"` + } `yaml:"selfSign" json:"selfSign"` CertFile string `yaml:"certFile" json:"certFile"` KeyFile string `yaml:"keyFile" json:"keyFile"` Profile string `yaml:"profile" json:"profile"` From 44551e8be3f0d7ab12b906b066b8538855b08af6 Mon Sep 17 00:00:00 2001 From: acoshift Date: Thu, 2 Aug 2018 21:03:38 +0700 Subject: [PATCH 2/3] impl generate selfsign cert --- app.go | 35 +++++------- config.go | 108 +++++++++++++++++++++++++++++++++++--- examples/selfsign/main.go | 24 +++++++++ graceful.go | 9 ---- 4 files changed, 137 insertions(+), 39 deletions(-) create mode 100644 examples/selfsign/main.go diff --git a/app.go b/app.go index 2a1cfb5..8e78ec2 100644 --- a/app.go +++ b/app.go @@ -55,8 +55,6 @@ type App struct { templateFuncs []template.FuncMap gracefulShutdown *gracefulShutdown - - certFile, keyFile string } var ( @@ -91,8 +89,6 @@ func (app *App) Clone() *App { template: cloneTmpl(app.template), templateFuncs: cloneFuncMaps(app.templateFuncs), gracefulShutdown: &*app.gracefulShutdown, - certFile: app.certFile, - keyFile: app.keyFile, } if app.TLSConfig != nil { x.TLSConfig = app.TLSConfig.Clone() @@ -155,21 +151,15 @@ func (app *App) configServer() { func (app *App) listenAndServe() error { app.configServer() - return app.srv.ListenAndServe() -} - -func (app *App) listenAndServeTLS(certFile, keyFile string) error { - app.configServer() + if app.srv.TLSConfig != nil { + return app.srv.ListenAndServeTLS("", "") + } - return app.srv.ListenAndServeTLS(certFile, keyFile) + return app.srv.ListenAndServe() } // ListenAndServe starts web server func (app *App) ListenAndServe() error { - if app.certFile != "" && app.keyFile != "" { - return app.ListenAndServeTLS(app.certFile, app.keyFile) - } - if app.gracefulShutdown != nil { return app.GracefulShutdown().ListenAndServe() } @@ -179,17 +169,16 @@ func (app *App) ListenAndServe() error { // TLS sets cert and key file func (app *App) TLS(certFile, keyFile string) *App { - app.certFile, app.keyFile = certFile, keyFile - return app -} - -// ListenAndServeTLS starts web server in tls mode -func (app *App) ListenAndServeTLS(certFile, keyFile string) error { - if app.gracefulShutdown != nil { - return app.GracefulShutdown().ListenAndServeTLS(certFile, keyFile) + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + panic("hime: load key pair error; " + err.Error()) } - return app.listenAndServeTLS(certFile, keyFile) + if app.TLSConfig == nil { + app.TLSConfig = &tls.Config{} + } + app.TLSConfig.Certificates = append(app.TLSConfig.Certificates, cert) + return app } // GracefulShutdown returns graceful shutdown server diff --git a/config.go b/config.go index 8dfc4dd..a588927 100644 --- a/config.go +++ b/config.go @@ -1,9 +1,18 @@ package hime import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" "io/ioutil" "log" + "math/big" + "net" "strings" "time" @@ -162,15 +171,100 @@ func (app *App) Config(config AppConfig) *App { } } - // TODO: auto generate self-signed tls if cert file, key file empty - if t.CertFile != "" { - app.certFile = t.CertFile - } - if t.KeyFile != "" { - app.keyFile = t.KeyFile + if t.CertFile != "" && t.KeyFile != "" { + cert, err := tls.LoadX509KeyPair(t.CertFile, t.KeyFile) + if err != nil { + panic("hime: load key pair error; " + err.Error()) + } + tlsConfig.Certificates = append(tlsConfig.Certificates, cert) + } else if c := t.SelfSign; c != nil { + var priv interface{} + var pub interface{} + + switch c.Key.Algo { + case "ecdsa", "": + var curve elliptic.Curve + switch c.Key.Size { + case 224: + curve = elliptic.P224() + case 256, 0: + curve = elliptic.P256() + case 384: + curve = elliptic.P384() + case 521: + curve = elliptic.P521() + default: + panic("hime: invalid self-sign key size") + } + + pri, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + panic("hime: generate private key error;" + err.Error()) + } + priv, pub = pri, &pri.PublicKey + case "rsa": + // TODO: make rsa key size configurable + pri, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + panic("hime: generate private key error; " + err.Error()) + } + priv, pub = pri, &pri.PublicKey + default: + panic("hime: invalid self-sign key algo") + } + + sn, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + panic("hime: generate serial number error; " + err.Error()) + } + + cert := x509.Certificate{ + SerialNumber: sn, + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + }, + NotAfter: time.Now().AddDate(1, 0, 0), // TODO: make not before configurable + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + for _, h := range c.Hosts { + if ip := net.ParseIP(h); ip != nil { + cert.IPAddresses = append(cert.IPAddresses, ip) + } else { + cert.DNSNames = append(cert.DNSNames, h) + } + } + + certBytes, err := x509.CreateCertificate(rand.Reader, &cert, &cert, pub, priv) + if err != nil { + panic("hime: create cvertificate error; " + err.Error()) + } + + certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) + + var keyPemBlock pem.Block + switch k := priv.(type) { + case *ecdsa.PrivateKey: + b, err := x509.MarshalECPrivateKey(k) + if err != nil { + panic("hime: marshal ec private key error; " + err.Error()) + } + keyPemBlock = pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + case *rsa.PrivateKey: + keyPemBlock = pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} + } + keyPem := pem.EncodeToMemory(&keyPemBlock) + + tlsCert, err := tls.X509KeyPair(certPem, keyPem) + if err != nil { + panic("hime: load key pair error; " + err.Error()) + } + tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCert) } - app.srv.TLSConfig = tlsConfig + app.TLSConfig = tlsConfig } if gs := server.GracefulShutdown; gs != nil { diff --git a/examples/selfsign/main.go b/examples/selfsign/main.go new file mode 100644 index 0000000..8d66673 --- /dev/null +++ b/examples/selfsign/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "log" + "net/http" + + "github.com/acoshift/hime" +) + +var config = []byte(` +server: + addr: :8080 + tls: + selfSign: {} +`) + +func main() { + app := hime.New() + app.ParseConfig(config) + app.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("ok")) + })) + log.Fatal(app.ListenAndServe()) +} diff --git a/graceful.go b/graceful.go index 7ec7b8d..5e318f7 100644 --- a/graceful.go +++ b/graceful.go @@ -93,14 +93,5 @@ func (gs *GracefulShutdownApp) start(listenAndServe func() error) (err error) { // ListenAndServe starts web server in graceful shutdown mode func (gs *GracefulShutdownApp) ListenAndServe() error { - if gs.App.certFile != "" && gs.App.keyFile != "" { - return gs.ListenAndServeTLS(gs.App.certFile, gs.App.keyFile) - } - return gs.start(gs.App.listenAndServe) } - -// ListenAndServeTLS starts web server in graceful shutdown and tls mode -func (gs *GracefulShutdownApp) ListenAndServeTLS(certFile, keyFile string) error { - return gs.start(func() error { return gs.App.listenAndServeTLS(certFile, keyFile) }) -} From 246f47837a885fe7662bdaae5c2749be22062a06 Mon Sep 17 00:00:00 2001 From: acoshift Date: Thu, 2 Aug 2018 21:07:47 +0700 Subject: [PATCH 3/3] fix test --- config_internal_test.go | 3 +-- testdata/config1.yaml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/config_internal_test.go b/config_internal_test.go index 6f44762..6bd6df8 100644 --- a/config_internal_test.go +++ b/config_internal_test.go @@ -35,8 +35,7 @@ func TestConfig(t *testing.T) { assert.Equal(t, 5*time.Second, app.ReadHeaderTimeout) assert.Equal(t, 6*time.Second, app.WriteTimeout) assert.Equal(t, 30*time.Second, app.IdleTimeout) - assert.Equal(t, "tls.crt", app.certFile) - assert.Equal(t, "tls.key", app.keyFile) + assert.Len(t, app.TLSConfig.Certificates, 1) // graceful assert.NotNil(t, app.gracefulShutdown) diff --git a/testdata/config1.yaml b/testdata/config1.yaml index dea4291..e8a8e82 100644 --- a/testdata/config1.yaml +++ b/testdata/config1.yaml @@ -26,6 +26,5 @@ server: timeout: 1m wait: 5s tls: - certFile: tls.crt - keyFile: tls.key + selfSign: {} profile: modern