diff --git a/examples/hello/cert.pem b/examples/hello/cert.pem new file mode 100644 index 0000000..d4782ca --- /dev/null +++ b/examples/hello/cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC+TCCAeGgAwIBAgIQSdfo419RR0AZFHX5acJU2DANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMB4XDTE3MDIxMjIzNDA1MFoXDTE4MDIxMjIzNDA1 +MFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBAM3Vef2/j4TfSWVLtML9O0gEq3HclkbdDpSztclbTMJx61MV/ifGezjx +6NRXBo8uUMN2t71dmltoVi3i4rXZQCfQPNILn7ENhiaEndioSBgCVeRFaio6m5LF +sOjrMTLG7rKFb8BGGuO9walMA7eD7RwrRkZ4SJalwbG3BzZEyBeoKWaSIGJh86Ip +fnF2g7WGLKzBfwaDSwhbDh4z6KeBZOfSLZgh/frg+5YvCYPFQr7VkWBscwMozfh4 +cH8w8Xyh07+48dFv737Qe+o8qmiRbGRl56JOCtVQSHtf+XugmJL5wL4yODbpT6Od +XyzEP6i6l+Q+ACquFPaUs8s+3mhL7JMCAwEAAaNLMEkwDgYDVR0PAQH/BAQDAgWg +MBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwFAYDVR0RBA0wC4IJ +bG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4IBAQBxIVCR1MiYJAmJNsrU2kFysput +1Ff7iqXg8biyOqMNhL0VjQ+aaL06P4qA33SGJRgN+gNlsPzoeSDzm1Fw8E70nsu7 +Vdc8AD4fESsLOoEgvJF97aT850bhW1kXWZKMSpdF8ZOjJvn6Ho0fZNhSLuDJb8l6 +kk/pTJt9dXsqkgQ9q3plx95uW6YDp+CGFStdmBcWCnMx8J/o5nNWxMQJyNpFhaUW +LXEpwoC+2gRwCHrIFZAAuN3WpnhkJwIRR5ChtArVu92eFPlt13e9lXeFCocvF4Wf +4L/JAXgwrFQVQZ04KO/MhuXMgdqvZj8NoUr786dHutvDcHyVlENbJZ6Oqr1D +-----END CERTIFICATE----- diff --git a/examples/hello/gencerts b/examples/hello/gencerts new file mode 100755 index 0000000..b38a7e5 Binary files /dev/null and b/examples/hello/gencerts differ diff --git a/examples/hello/gencerts.go b/examples/hello/gencerts.go new file mode 100644 index 0000000..05ada27 --- /dev/null +++ b/examples/hello/gencerts.go @@ -0,0 +1,161 @@ +// 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. + +// +build ignore + +// Generate a self-signed X.509 certificate for a TLS server. Outputs to +// 'cert.pem' and 'key.pem' and will overwrite existing files. + +package main + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "flag" + "fmt" + "log" + "math/big" + "net" + "os" + "strings" + "time" +) + +var ( + host = flag.String("host", "", "Comma-separated hostnames and IPs to generate a certificate for") + 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, P384, P521") +) + +func publicKey(priv interface{}) interface{} { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &k.PublicKey + case *ecdsa.PrivateKey: + return &k.PublicKey + default: + return nil + } +} + +func pemBlockForKey(priv interface{}) *pem.Block { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} + case *ecdsa.PrivateKey: + b, err := x509.MarshalECPrivateKey(k) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err) + os.Exit(2) + } + return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + default: + return nil + } +} + +func main() { + flag.Parse() + + if len(*host) == 0 { + log.Fatalf("Missing required --host parameter") + } + + var priv interface{} + var err error + switch *ecdsaCurve { + case "": + 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: + fmt.Fprintf(os.Stderr, "Unrecognized elliptic curve: %q", *ecdsaCurve) + os.Exit(1) + } + if err != nil { + log.Fatalf("failed to generate private key: %s", err) + } + + 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 { + fmt.Fprintf(os.Stderr, "Failed to parse creation date: %s\n", err) + os.Exit(1) + } + } + + notAfter := notBefore.Add(*validFor) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + log.Fatalf("failed to generate serial number: %s", err) + } + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + hosts := strings.Split(*host, ",") + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + if *isCA { + template.IsCA = true + template.KeyUsage |= x509.KeyUsageCertSign + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv) + if err != nil { + log.Fatalf("Failed to create certificate: %s", err) + } + + certOut, err := os.Create("cert.pem") + if err != nil { + log.Fatalf("failed to open cert.pem for writing: %s", err) + } + pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + certOut.Close() + log.Print("written cert.pem\n") + + keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + log.Print("failed to open key.pem for writing:", err) + return + } + pem.Encode(keyOut, pemBlockForKey(priv)) + keyOut.Close() + log.Print("written key.pem\n") +} \ No newline at end of file diff --git a/examples/hello/key.pem b/examples/hello/key.pem new file mode 100644 index 0000000..9d2e0fe --- /dev/null +++ b/examples/hello/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAzdV5/b+PhN9JZUu0wv07SASrcdyWRt0OlLO1yVtMwnHrUxX+ +J8Z7OPHo1FcGjy5Qw3a3vV2aW2hWLeLitdlAJ9A80gufsQ2GJoSd2KhIGAJV5EVq +KjqbksWw6OsxMsbusoVvwEYa473BqUwDt4PtHCtGRnhIlqXBsbcHNkTIF6gpZpIg +YmHzoil+cXaDtYYsrMF/BoNLCFsOHjPop4Fk59ItmCH9+uD7li8Jg8VCvtWRYGxz +AyjN+HhwfzDxfKHTv7jx0W/vftB76jyqaJFsZGXnok4K1VBIe1/5e6CYkvnAvjI4 +NulPo51fLMQ/qLqX5D4AKq4U9pSzyz7eaEvskwIDAQABAoIBAHjsn5ypu8FOBKqU +DYA4kWlABREBjO/Y0sYdxgCjuLbLgnrvgr8PZjkQNb752YqopR4QUuXXZeWpqGeQ +0awQFBbPycuEtK6pyVQ7uRgd1Sz1wkw3U6W3hMp3hfVwpxizsmwC5KJLDWUah/nU +BKL9yIEyEzk3VT3b561mX8BMLFlojfqQA+9DrOT04hrEvoXW4QBTCIvXVolcmQ0N +30+BX7i1hEWy16a6hmjdU46/kOx1arp+FHK6g042aSsPiiXUfjXpQrpqsp46fq9Y +wR/9B/aepf6wKiYSHhBW1QBqwAQKihc7iv+N0BjcIVGkJNoq1qm9IvWrShTpIqq2 +PW7ENMECgYEA7W9bW60QJSjFG9zCRo1z80liDmuSeESIhCtbomuJNjXv/ANNwM2t +z0aBsESrHfGiNtCTOcna7Sq55bWHTJ81R2NQUwIokcnHs84oDwpsnH1DyLBlXTKJ +lpcclCNZlafuqHOUvD/R1ZDZ8Yt32Ij5OrP+8IAF1xs+cAXpVr9RgxECgYEA3e2S +quo4vR23lWvmbifjlRwfMP660UwozOE7bAE23G7M2AZEWHQhXAdPpeVOddbn6AAJ +2R0ejOAH7LfCQ43lzJ1A13zqKq0xwpQqTl4JT+luBZcOSvMiXtew0Ph67IhGe6EN +/0aYWaX1IpB3fC+fNY56NMNBwWmk6XcOVNd4bWMCgYEA5+cdqfrsi/64FjCWJ+a8 +22BPP5pdos39MGpQw+CYUbR4wwJPDQNWwd4xiQJox4RcVYF+arD2/TIAA5Y7oeDW +XvVnXMv85/fW741pntcloerPK9LuDgrwS38oUJ/+PaeZ+Dl7iiojPp6N39fCpGGU +W3u2gxvnmGNppmx/rVSLvqECgYEAnC0cEftSgoPvNdABEXQwzDZGHQymG7Qk4kl+ +r7sLCIVQ0pVWc4kGEOV0P6LnXDYobTPZulirryfZa9TFeRTaquvfPcZCZFYNtoje +XNgAZaQ4MObkwAnqHiRb4jN7Dgm40De+ye68OBp6MCDKdTJuMBpcFdVy6NGXpFSM +WVho/1ECgYEAi3J/NwSVBwAT4YMzkCG58wW7ZkiXvcSwLKUeZ6JOb+zJXFy0Oml0 +4SWHO+iPkA0kVY9P1ouFgeSP6XA/UtayL/GE7WOVwI5GV1pe8DSpWBG5YaeMz5yg +BScaVi7PHuh9K6ySIIHAVSgo6EXTYnf8VQhFFYN4pn3aazLFqwgdjX0= +-----END RSA PRIVATE KEY----- diff --git a/examples/hello/main.go b/examples/hello/main.go index e95f379..58f5541 100644 --- a/examples/hello/main.go +++ b/examples/hello/main.go @@ -2,6 +2,7 @@ package main import ( "log" + "sync" "github.com/prologic/go-gopher" ) @@ -12,6 +13,11 @@ func index(w gopher.ResponseWriter, r *gopher.Request) { Type: gopher.DIRECTORY, Selector: "/hello", Description: "hello", + + // TLS Resource + Host: "localhost", + Port: 73, + Extras: []string{"TLS"}, }, ) w.WriteItem( @@ -41,8 +47,37 @@ func foo(w gopher.ResponseWriter, r *gopher.Request) { } func main() { - gopher.HandleFunc("/", index) - gopher.HandleFunc("/foo", foo) - gopher.HandleFunc("/hello", hello) - log.Fatal(gopher.ListenAndServe("localhost:70", nil)) + wg := &sync.WaitGroup{} + + // Standard Server + wg.Add(1) + go func() { + mux := gopher.NewServeMux() + + mux.HandleFunc("/", index) + mux.HandleFunc("/foo", foo) + mux.HandleFunc("/hello", hello) + + log.Fatal(gopher.ListenAndServe("localhost:70", mux)) + wg.Done() + }() + + // TLS server + wg.Add(1) + go func() { + mux := gopher.NewServeMux() + + mux.HandleFunc("/", index) + mux.HandleFunc("/foo", foo) + mux.HandleFunc("/hello", hello) + + log.Fatal( + gopher.ListenAndServeTLS( + "localhost:73", "cert.pem", "key.pem", mux, + ), + ) + wg.Done() + }() + + wg.Wait() } diff --git a/gopher.go b/gopher.go index 4a9d1fe..5d8811a 100644 --- a/gopher.go +++ b/gopher.go @@ -10,6 +10,7 @@ package gopher import ( "bufio" "bytes" + "crypto/rand" "crypto/tls" "encoding/json" "errors" @@ -571,6 +572,41 @@ func (s Server) ListenAndServe() error { return s.Serve(ln) } +// ListenAndServeTLS listens on the TCP network address srv.Addr and +// then calls Serve to handle requests on incoming TLS connections. +// Accepted connections are configured to enable TCP keep-alives. +// +// Filenames containing a certificate and matching private key for the +// server must be provided if neither the Server's TLSConfig.Certificates +// nor TLSConfig.GetCertificate are populated. If the certificate is +// signed by a certificate authority, the certFile should be the +// concatenation of the server's certificate, any intermediates, and +// the CA's certificate. +// +// If srv.Addr is blank, ":gophers" is used (port 73). +// +// ListenAndServeTLS always returns a non-nil error. +func (s *Server) ListenAndServeTLS(certFile, keyFile string) error { + addr := s.Addr + if addr == "" { + addr = ":73" + } + + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + log.Fatalf("server: loadkeys: %s", err) + } + config := tls.Config{Certificates: []tls.Certificate{cert}} + config.Rand = rand.Reader + + ln, err := tls.Listen("tcp", addr, &config) + if err != nil { + log.Fatalf("server: listen: %s", err) + } + + return s.Serve(ln) +} + // Serve ... func (s Server) Serve(l net.Listener) error { defer l.Close() @@ -582,6 +618,7 @@ func (s Server) Serve(l net.Listener) error { for { rw, err := l.Accept() if err != nil { + fmt.Errorf("error acceptig new client: %s", err) return err } @@ -745,6 +782,40 @@ func ListenAndServe(addr string, handler Handler) error { return server.ListenAndServe() } +// ListenAndServeTLS acts identically to ListenAndServe, except that it +// expects TLS connections. Additionally, files containing a certificate and +// matching private key for the server must be provided. If the certificate +// is signed by a certificate authority, the certFile should be the +// concatenation of the server's certificate, any intermediates, +// and the CA's certificate. +// +// A trivial example server is: +// +// import ( +// "log" +// +// "github.com/prologic/go-gopher", +// ) +// +// func HelloServer(w gopher.ResponseWriter, req *gopher.Request) { +// w.WriteInfo("hello, world!") +// } +// +// func main() { +// gopher.HandleFunc("/", handler) +// log.Printf("About to listen on 73. Go to gophers://127.0.0.1:73/") +// err := gopher.ListenAndServeTLS(":73", "cert.pem", "key.pem", nil) +// log.Fatal(err) +// } +// +// One can use generate_cert.go in crypto/tls to generate cert.pem and key.pem. +// +// ListenAndServeTLS always returns a non-nil error. +func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error { + server := &Server{Addr: addr, Handler: handler} + return server.ListenAndServeTLS(certFile, keyFile) +} + // ServeMux is a Gopher request multiplexer. // It matches the URL of each incoming request against a list of registered // patterns and calls the handler for the pattern that