From 3f7fc8bb4f7d5cebacb087146f8448a3f44176f1 Mon Sep 17 00:00:00 2001 From: Michal Witkowski Date: Mon, 31 Oct 2016 22:29:33 +0000 Subject: [PATCH] Initial commit --- .gitignore | 163 ++++++++++++++++++++++++++++++++++++ example/certs/gen_cert.sh | 5 ++ example/certs/localhost.crt | 21 +++++ example/certs/localhost.key | 28 +++++++ example/server.go | 108 ++++++++++++++++++++++++ 5 files changed, 325 insertions(+) create mode 100644 .gitignore create mode 100644 example/certs/gen_cert.sh create mode 100644 example/certs/localhost.crt create mode 100644 example/certs/localhost.key create mode 100644 example/server.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..406e493 --- /dev/null +++ b/.gitignore @@ -0,0 +1,163 @@ +# Created by .ignore support plugin (hsz.mobi) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea +.idea/workspace.xml +.idea/tasks.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Go template +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + diff --git a/example/certs/gen_cert.sh b/example/certs/gen_cert.sh new file mode 100644 index 0000000..b79c0e4 --- /dev/null +++ b/example/certs/gen_cert.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Regenerate the self-signed certificate for local host. + +openssl req -x509 -sha256 -nodes -newkey rsa:2048 -days 365 -keyout localhost.key -out localhost.crt + diff --git a/example/certs/localhost.crt b/example/certs/localhost.crt new file mode 100644 index 0000000..c2b6b18 --- /dev/null +++ b/example/certs/localhost.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDhTCCAm2gAwIBAgIJALjSEF7Y2tBMMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV +BAYTAlVLMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNjA4MTYwNzQz +MTFaFw0xNzA4MTYwNzQzMTFaMFkxCzAJBgNVBAYTAlVLMRMwEQYDVQQIDApTb21l +LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV +BAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANnu +F3N5F3dqmshGs1jMDWI/FNWMNn5T9l9L+X/bw0FylPr6z46I6baFvuOR0CFnOVRj +VnL4XGnHuf5BF00r3yaZTik7E/XAP3I2SHjwIRMO6v+dZqkFB1dqGSumBP2Vxh9F +HdqVj5Q7No7HAdbef7QMlngW/K8+dZ4I5vR95ZedIhmrVzEGsndB83xqJeq8Jpnf +Xwq2Pzmwa5zy0P2cdMFebYqzyOpu5nt3+IE1GFSbQ+xDSAAtejirIARGxIwFMVBg +wpy43fbU5zQ/HeMjs5l3WrQ9KPVlR+mw8gtrv0JnpxgwBaPTA9W8jr6Z6Qdb0/BR +L/3j9LjtLdB2U6cR2FUCAwEAAaNQME4wHQYDVR0OBBYEFFJ4XhqFff7nv/gLMObh +DaIQ8MMUMB8GA1UdIwQYMBaAFFJ4XhqFff7nv/gLMObhDaIQ8MMUMAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAB0YmklAhFPY4X5/uVem53Gcr+SB02Gs +VHDHKkMFlc74lFoRd/ZFnzI9y4oSBvK5ryjDiA+A7msyc5X2Ii+96wdfRUbyzeUi +oYC66GlYitRHJhJIp/alvU8qHEkWXQD5s2rk5RuLNDZKKHEHcZRsQcK11p1otAQa +r1L1V+O3OMZJmAdlWuehW0IN161vMlR0oCEpmL9VLrRNPcOL5lGV/GlNCMzLP2XM +T9EWt4i6mtGELZ94hea6GxaYfK00K+3t0eVUsMs2IbpoJinLHcJ4fPQdAjzvaAud +FCrJIOkJPk0rNeBIXnRRtBL87MV+/rhG1I/eLqLgz5QVjpNG9QDjx8A= +-----END CERTIFICATE----- diff --git a/example/certs/localhost.key b/example/certs/localhost.key new file mode 100644 index 0000000..5115f7e --- /dev/null +++ b/example/certs/localhost.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDZ7hdzeRd3aprI +RrNYzA1iPxTVjDZ+U/ZfS/l/28NBcpT6+s+OiOm2hb7jkdAhZzlUY1Zy+Fxpx7n+ +QRdNK98mmU4pOxP1wD9yNkh48CETDur/nWapBQdXahkrpgT9lcYfRR3alY+UOzaO +xwHW3n+0DJZ4FvyvPnWeCOb0feWXnSIZq1cxBrJ3QfN8aiXqvCaZ318Ktj85sGuc +8tD9nHTBXm2Ks8jqbuZ7d/iBNRhUm0PsQ0gALXo4qyAERsSMBTFQYMKcuN321Oc0 +Px3jI7OZd1q0PSj1ZUfpsPILa79CZ6cYMAWj0wPVvI6+mekHW9PwUS/94/S47S3Q +dlOnEdhVAgMBAAECggEAYurqJBS7rQ1rUiqdL1n3XTfKyh9JgM+1jY3boshqeSN8 +Met0GHtWse1FNuAxe2fyIrawP6ExuSXZ62k6HWIjeM6vJpHVPn/TjJDNFm/QY2kr +C3kzZtaMfYYABMrniv7XncvugA/QyvSRj/8Oe6wkhOINzlaIvTZ5hpD9283lT4pB +vT6qthNrr+6tBX4qCqKD/xhBQbCXTdXmQHVMksesSTOELuSKtnK2QJiAp7L502Hw +tWm0obt4T9AbK8QDkLWMUAUsYzSRgRhKB3FGSa4TT0uI2TE9UpNPiOeb47YcZodd +eHjbmToL1NK2OXGRhxeYY7bDa24lkKT8rKSXkiTngQKBgQDxg0GajHqtKR2S1UMt +UStHwQ+0mEYrNPtT4ipwEIbGbc5Z38E7xUcHimV5NcAgDno4oUvo/lolDYb3E64u +Nie+YsK84np0RsqHxKKF+JumRB1HT7MYwdV1CsKJP3g0SnQYV0VYW/C56LOf4Z1I +l4HGKrPGYRUyGjIo223pcOigtQKBgQDnALFJa6KLX6kdkYPvpsVr3eC+2iPsr4qj +lMhr30UkWVSZG4Pfx3fZIUsUNa20PbtSTBKo+tHGTtf0aRPCwvGnyhaAaPy3w2w8 +9ohULe4IPn7zal+UgGyiGWpyt29SGF8zSpdalfxSNuup+Lm3TBE9Rh31N0djqgrl +jHxpf+I9IQKBgQCsYDqazFli7k2lV4Gy/pQdirZi96xdeltH68zOX31Sc10s2H9a +4dtojmcOtEaEmtCxSq6bha9hct45y1ousYh8YpELr7om87/qV3aImIC/ky4yj7gM +m4x3FU70FtD8wYdLOD7OahDPID/UhXt1LG37us7FcNVoBTp33uX8EBJ5YQKBgAH8 +64mqN3fjltz+R5hkYwaOnkSGNBDxYcwOl7r17O5nJmc66WOfn9RqiO7fl2MZtOb8 +aJyzq+J9AzbDQLxIWTQMdS0dui8Kq3/Kz1mKG6ZOg2Es5S2t/UFX3qamFXsrYoZa +efr5l3ZNqrGHxnFhYjSYyeE2XJLq/7UCBIAT7aqBAoGARyysROXvpI0VWd1m/x3X +8zTlZqy4jMxWm1XArDJWEYcLd8uWUsQ4mad+qopwBP8w2Irn9qFpokLp7qT82bKg +Hwha/6qRG6aTD/JHl2Q636EPXHmSPPTUhEjVKAwDIeo1W32pbn10L+1fIPl5Ig7v +31zo/OuHi+06gzcM8Pb+Oh0= +-----END PRIVATE KEY----- diff --git a/example/server.go b/example/server.go new file mode 100644 index 0000000..8a1d185 --- /dev/null +++ b/example/server.go @@ -0,0 +1,108 @@ +package main + +import ( + "flag" + "log" + "net" + "net/http" + + "crypto/tls" + "fmt" +) + +var ( + port = flag.Int("port", 9090, "whether to use tls or not") + useTls = flag.Bool("tls", true, "Whether to use TLS and HTTP2.") + tlsCertFilePath = flag.String("tls_cert_file", "certs/localhost.crt", "Path to the CRT/PEM file.") + tlsKeyFilePath = flag.String("tls_key_file", "certs/localhost.key", "Path to the private key file.") +) + +func main() { + flag.Parse() + + handler := func(resp http.ResponseWriter, req *http.Request) { + resp.WriteHeader(http.StatusOK) + resp.Header().Add("Content-Type", "application/json") + resp.Write([]byte(`{"msg": "hello"}`)) + log.Printf("Got request: %v", req) + } + + httpServer := http.Server{ + Handler: http.HandlerFunc(handler), + } + var httpListener net.Listener + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) + if err != nil { + log.Fatalf("Failed to listen: %v", err) + } + if !*useTls { + httpListener = listener + } else { + tlsConfig, err := tlsConfigForCert(*tlsCertFilePath, *tlsKeyFilePath) + if err != nil { + log.Fatalf("Failed configuring TLS: %v", err) + } + //httpServer.TLSConfig = tlsConfig + tlsListener := tls.NewListener(listener, tlsConfig) + httpListener = tlsListener + + } + //httpListener.Addr() + log.Printf("Listening on: %s", listener.Addr().String()) + if err := httpServer.Serve(httpListener); err != nil { + log.Fatalf("Failed listning: %v", err) + } +} + +// tlsConfigForCert is needed as it duplicates ListenAndServeTLS, but for a listener. +func tlsConfigForCert(certFile string, keyFile string) (*tls.Config, error) { + var err error + config := new(tls.Config) + config, err = tlsEnableHttp2(config) + if err != nil { + return nil, err + } + // Make sure http1.1 is *after* h2. + config.NextProtos = append(config.NextProtos, "http/1.1") + + config.Certificates = make([]tls.Certificate, 1) + config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil, err + } + return tlsEnableHttp2(config) +} + +//tlsEnableHttp2 performs what http2ConfigureServer does privately. +func tlsEnableHttp2(config *tls.Config) (*tls.Config, error) { + if config.CipherSuites != nil { + // If they already provided a CipherSuite list, return + // an error if it has a bad order or is missing + // ECDHE_RSA_WITH_AES_128_GCM_SHA256. + const requiredCipher = tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + haveRequired := false + for _, cs := range config.CipherSuites { + if cs == requiredCipher { + haveRequired = true + } + } + if !haveRequired { + return nil, fmt.Errorf("http2: TLSConfig.CipherSuites is missing HTTP/2-required TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256") + } + } + + config.PreferServerCipherSuites = true + + haveNPN := false + for _, p := range config.NextProtos { + if p == "h2" { + haveNPN = true + break + } + } + if !haveNPN { + config.NextProtos = append(config.NextProtos, "h2") + } + config.NextProtos = append(config.NextProtos, "h2-14") + return config, nil +}