package main import ( "crypto/rand" "crypto/tls" "crypto/x509" "encoding/base64" "encoding/pem" "fmt" "io/ioutil" "net/http" "os" "os/signal" "strings" "syscall" "time" "github.com/BurntSushi/toml" "github.com/gorilla/csrf" "github.com/nicksnyder/go-i18n/v2/i18n" log "github.com/sirupsen/logrus" "golang.org/x/text/language" "git.dittberner.info/jan/browser_csr_generation/handlers" ) func main() { log.SetFormatter(&log.TextFormatter{ FullTimestamp: true, }) bundle := loadI18nBundle() mux := http.NewServeMux() csrfKey := initCSRFKey() signingRequestRegistry := handlers.NewSigningRequestRegistry(loadCACertificates()) mux.Handle("/sign/", handlers.NewCertificateSigningHandler(signingRequestRegistry)) mux.Handle("/", handlers.NewIndexHandler(bundle)) fileServer := http.FileServer(http.Dir("./public")) mux.Handle("/css/", fileServer) mux.Handle("/js/", fileServer) mux.Handle("/locales/", handlers.NewJSLocalesHandler(bundle)) mux.Handle("/ws/", handlers.NewWebSocketHandler(signingRequestRegistry)) tlsConfig := &tls.Config{ CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, }, NextProtos: []string{"h2"}, PreferServerCipherSuites: true, MinVersion: tls.VersionTLS12, } server := http.Server{ Addr: ":8000", Handler: csrf.Protect(csrfKey, csrf.FieldName("csrfToken"), csrf.RequestHeader("X-CSRF-Token"))(mux), TLSConfig: tlsConfig, ReadTimeout: 20 * time.Second, ReadHeaderTimeout: 5 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 30 * time.Second, } go func() { err := server.ListenAndServeTLS("server.crt.pem", "server.key.pem") if err != nil { log.Fatal(err) } }() var hostPort string if strings.HasPrefix(server.Addr, ":") { hostPort = fmt.Sprintf("localhost%s", server.Addr) } else { hostPort = server.Addr } log.Infof("started web server on https://%s/", hostPort) c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) s := <-c log.Infof("received %s, shutting down", s) _ = server.Close() } func loadI18nBundle() *i18n.Bundle { bundle := i18n.NewBundle(language.English) bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) for _, lang := range []string{"en-US", "de-DE"} { if _, err := bundle.LoadMessageFile(fmt.Sprintf("active.%s.toml", lang)); err != nil { log.Panic(err) } } return bundle } func initCSRFKey() []byte { var csrfKey []byte = nil if csrfB64, exists := os.LookupEnv("CSRF_KEY"); exists { csrfKey, _ = base64.RawStdEncoding.DecodeString(csrfB64) log.Info("read CSRF key from environment variable") } if csrfKey == nil { csrfKey = generateRandomBytes(32) log.Infof( "generated new random CSRF key, set environment variable CSRF_KEY to %s to "+ "keep the same key for new sessions", base64.RawStdEncoding.EncodeToString(csrfKey)) } return csrfKey } func generateRandomBytes(count int) []byte { randomBytes := make([]byte, count) _, err := rand.Read(randomBytes) if err != nil { log.Fatalf("could not read random bytes: %v", err) return nil } return randomBytes } func loadCACertificates() (caCertificates []*x509.Certificate) { var err error caCertificates = make([]*x509.Certificate, 2) for index, certFile := range []string{"example_ca/sub/ca.crt.pem", "example_ca/root/ca.crt.pem"} { var certBytes []byte if certBytes, err = ioutil.ReadFile(certFile); err != nil { log.Panic(err) } var block *pem.Block if block, _ = pem.Decode(certBytes); block == nil { log.Panicf("no PEM data found in %s", certFile) return } if caCertificates[index], err = x509.ParseCertificate(block.Bytes); err != nil { log.Panic(err) } } log.Infof("read %d CA certificates", len(caCertificates)) return }