vcs-test: code for vcs-test.golang.org
Change-Id: I48e8648fc9f3e3b64a548743fd2c2480f881bfb1
Reviewed-on: https://go-review.googlesource.com/73332
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Ross Light <light@google.com>
diff --git a/vcs-test/README.md b/vcs-test/README.md
new file mode 100644
index 0000000..6a527fa
--- /dev/null
+++ b/vcs-test/README.md
@@ -0,0 +1,72 @@
+# vcs-test
+
+We run a version control server for testing at `vcs-test.golang.org`.
+
+## Machine initialization
+
+The machine should just run. You should not need these instructions very often.
+In particular you do not need them just to make a change to `vcweb`.
+Skip ahead to the next section.
+
+The VM runs in the builder project “symbolic-datum-552” in zone `us-central1-a`,
+where it has a reserved static IP address named `vcs-test`.
+
+To destroy the current VM (if any) and rebuild a fresh one in its place, run:
+
+ ./rebuild-server.sh && ./rebuild-systemd.sh && ./redeploy-vcweb.sh
+
+You should not need to do this unless you have changed rebuild-server.sh and want to test it.
+
+To delete the VM's current systemd configuration for `vcweb` and upload the configuration
+from the local directory (specifically, `vcweb.service` and `vcweb*.socket`), run:
+
+ ./rebuild-systemd.sh && ./redeploy-vcweb.sh
+
+You should not need to do this unless you have changed the systemd configuration files.
+
+## vcweb
+
+The Go program that runs the actual server is in the subdirectory `vcweb`.
+For local development:
+
+ go build -o vcweb.exe ./vcweb && ./vcweb.exe
+
+It maintains files in `/tmp/vcweb` and serves localhost:8088.
+
+Once you are happy with local testing, deploy to the VM by running `./redeploy-vcweb.sh`.
+
+## Repositories
+
+The server can serve Bazaar, Fossil, Git, Mercurial, and Subversion repositories.
+The root of each repository is `http://vcs-test.golang.org/VCS/REPONAME`,
+where `VCS` is the version control system's command name (`bzr` for Bazaar, and so on),
+and `REPONAME` is the repository name.
+
+To serve a particular repository, the server downloads
+`gs://vcs-test/VCS/REPONAME.zip` from Google Cloud Storage and unzips it
+into an empty directory.
+The result should be a valid repository directory for the given version control system.
+If the needed format of the zip file is unclear, download and inspect `gs://vcs-test/VCS/hello.zip`
+from `https://vcs-test.storage.googleapis.com/VCS/hello.zip`.
+
+Stale data may be served for up to five minutes after a zip file is updated in the
+Google Cloud Storage bucket. To force a rescan of Google Cloud Storage,
+fetch `http://vcs-test.golang.org/VCS/REPONAME?vcweb-force-reload=1`.
+
+## Static files
+
+The URL space `http://vcs-test.golang.org/go/NAME` is served by static files,
+fetched from `gs://vcs-test/go/NAME.zip`.
+The main use for static files is to write redirect HTML.
+See `gs://vcs-test/go/hello.zip` for examples.
+Note that because the server uses `http.DetectContentType` to deduce
+the content type from file data, it is not necessary to
+name HTML files with a `.html` suffix.
+
+## HTTPS
+
+The server fetches an HTTPS certificate on demand from Let's Encrypt,
+using `golang.org/x/crypto/acme/autocert`.
+It caches the certificates in `gs://vcs-test-autocert` using
+`golang.org/x/build/autocertcache`.
+
diff --git a/vcs-test/rebuild-server.sh b/vcs-test/rebuild-server.sh
new file mode 100755
index 0000000..7fe2f0c
--- /dev/null
+++ b/vcs-test/rebuild-server.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+# Copyright 2017 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.
+
+set -e
+
+gcloud compute instances delete vcs-test --zone=us-central1-a
+gcloud compute instances create vcs-test --zone=us-central1-a \
+ --address vcs-test \
+ --image-project debian-cloud --image-family debian-9 \
+ --machine-type n1-standard-1 \
+ --service-account=vcs-test@symbolic-datum-552.iam.gserviceaccount.com \
+ --tags=allow-ssh,http-server,https-server
+
+while sleep 5 && ! gcloud compute ssh vcs-test -- date; do
+ echo 'waiting for machine to respond to ssh...'
+done
+
+gcloud compute ssh vcs-test -- sudo -n bash -c \''
+ mkdir -p /home/vcweb/svn
+ chown -R uucp:uucp /home/vcweb
+ chown -R 777 /home/vcweb
+ apt-get update
+ apt-get install -y mercurial fossil bzr git apache2 ed subversion libapache2-mod-svn
+ perl -pie 's/80/8888/' /etc/apache2/ports.conf
+ echo "
+ <Location /svn>
+ DAV svn
+ SVNParentPath /home/vcweb/svn
+ <LimitExcept GET PROPFIND OPTIONS REPORT>
+ Require all denied
+ </LimitExcept>
+ </Location>
+ " >/etc/apache2/mods-enabled/dav_svn.conf
+ apache2ctl restart
+ systemctl enable apache2.service
+'\'
diff --git a/vcs-test/rebuild-systemd.sh b/vcs-test/rebuild-systemd.sh
new file mode 100755
index 0000000..e858698
--- /dev/null
+++ b/vcs-test/rebuild-systemd.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+# Copyright 2017 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.
+
+set -e
+
+gcloud beta compute scp vcweb.service vcweb-*.socket vcs-test:
+
+gcloud compute ssh vcs-test -- sudo -n bash -c \''
+ systemctl stop vcweb.service
+ systemctl disable vcweb.service
+ rm -f /lib/systemd/system/vcweb* /etc/systemd/system/*/vcweb*
+
+ mv vcweb.exe /usr/bin/vcweb
+ mv vcweb.service vcweb-*.socket /lib/systemd/system
+ systemctl enable vcweb.service
+'\'
diff --git a/vcs-test/redeploy-vcweb.sh b/vcs-test/redeploy-vcweb.sh
new file mode 100755
index 0000000..38f9675
--- /dev/null
+++ b/vcs-test/redeploy-vcweb.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+# Copyright 2017 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.
+
+set -e
+
+info="$USER $(date)"
+CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build "-ldflags=\"-X=main.buildInfo=$info\"" -o vcweb.exe ./vcweb
+trap "rm -f vcweb.exe" EXIT
+
+gcloud beta compute scp vcweb.exe vcs-test:
+
+gcloud compute ssh vcs-test -- sudo -n bash -c \''
+ mv vcweb.exe /usr/bin/vcweb
+ systemctl restart vcweb.service
+ systemctl status -l vcweb.service
+'\'
diff --git a/vcs-test/vcweb-http.socket b/vcs-test/vcweb-http.socket
new file mode 100644
index 0000000..28c6f4e
--- /dev/null
+++ b/vcs-test/vcweb-http.socket
@@ -0,0 +1,10 @@
+[Unit]
+Description=version control server HTTP socket
+PartOf=vcweb.service
+
+[Socket]
+ListenStream=80
+Service=vcweb.service
+
+[Install]
+WantedBy=sockets.target
diff --git a/vcs-test/vcweb-https.socket b/vcs-test/vcweb-https.socket
new file mode 100644
index 0000000..0c3738d
--- /dev/null
+++ b/vcs-test/vcweb-https.socket
@@ -0,0 +1,10 @@
+[Unit]
+Description=version control server HTTPS socket
+PartOf=vcweb.service
+
+[Socket]
+ListenStream=443
+Service=vcweb.service
+
+[Install]
+WantedBy=sockets.target
diff --git a/vcs-test/vcweb.service b/vcs-test/vcweb.service
new file mode 100644
index 0000000..9ef2651
--- /dev/null
+++ b/vcs-test/vcweb.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=vcs-test web server
+Requires=vcweb-http.socket vcweb-https.socket
+
+[Service]
+Type=notify
+ExecStart=/usr/bin/vcweb -d /home/vcweb
+WatchdogSec=30s
+Restart=always
+StandardOutput=syslog
+StandardError=syslog
+SyslogIdentifier=vcweb
+User=uucp
+
+[Install]
+WantedBy=multi-user.target
diff --git a/vcs-test/vcweb/bzr.go b/vcs-test/vcweb/bzr.go
new file mode 100644
index 0000000..4064fb3
--- /dev/null
+++ b/vcs-test/vcweb/bzr.go
@@ -0,0 +1,14 @@
+// Copyright 2017 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.
+
+package main
+
+import (
+ "net/http"
+ "path/filepath"
+)
+
+func bzrHandler() http.Handler {
+ return http.StripPrefix("/bzr/", http.FileServer(http.Dir(filepath.Join(*dir, "bzr"))))
+}
diff --git a/vcs-test/vcweb/fossil.go b/vcs-test/vcweb/fossil.go
new file mode 100644
index 0000000..ea7d9c4
--- /dev/null
+++ b/vcs-test/vcweb/fossil.go
@@ -0,0 +1,45 @@
+// Copyright 2017 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.
+
+package main
+
+import (
+ "io/ioutil"
+ "net/http"
+ "net/http/cgi"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+func fossilHandler() http.Handler {
+ return http.HandlerFunc(fossilDispatch)
+}
+
+func fossilDispatch(w http.ResponseWriter, r *http.Request) {
+ if !strings.HasPrefix(r.URL.Path, "/fossil/") {
+ w.WriteHeader(404)
+ return
+ }
+ name := strings.TrimPrefix(r.URL.Path, "/fossil/")
+ if i := strings.Index(name, "/"); i >= 0 {
+ name = name[:i]
+ }
+
+ db := filepath.Join(*dir, "fossil/"+name+"/"+name+".fossil")
+ _, err := os.Stat(db)
+ if err != nil {
+ w.WriteHeader(404)
+ }
+ if _, err := os.Stat(db + ".cgi"); err != nil {
+ ioutil.WriteFile(db+".cgi", []byte("#!/usr/bin/fossil\nrepository: "+db+"\n"), 0777)
+ }
+
+ h := &cgi.Handler{
+ Path: db + ".cgi",
+ Root: "/fossil/" + name,
+ Dir: filepath.Join(*dir, "fossil"),
+ }
+ h.ServeHTTP(w, r)
+}
diff --git a/vcs-test/vcweb/git.go b/vcs-test/vcweb/git.go
new file mode 100644
index 0000000..ef08c07
--- /dev/null
+++ b/vcs-test/vcweb/git.go
@@ -0,0 +1,31 @@
+// Copyright 2017 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.
+
+package main
+
+import (
+ "log"
+ "net/http"
+ "net/http/cgi"
+ "os"
+ "os/exec"
+ "path/filepath"
+)
+
+func gitHandler() http.Handler {
+ os.Mkdir(filepath.Join(*dir, "git"), 0777)
+ path, err := exec.LookPath("git")
+ if err != nil {
+ log.Fatal(err)
+ }
+ return &cgi.Handler{
+ Path: path,
+ Args: []string{"http-backend"},
+ Dir: filepath.Join(*dir, "git"),
+ Env: []string{
+ "GIT_PROJECT_ROOT=" + filepath.Join(*dir),
+ "GIT_HTTP_EXPORT_ALL=1",
+ },
+ }
+}
diff --git a/vcs-test/vcweb/hg.go b/vcs-test/vcweb/hg.go
new file mode 100644
index 0000000..4f2d5ce
--- /dev/null
+++ b/vcs-test/vcweb/hg.go
@@ -0,0 +1,42 @@
+// Copyright 2017 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.
+
+package main
+
+import (
+ "io/ioutil"
+ "log"
+ "net/http"
+ "net/http/cgi"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+var hgwebPy = `#!/usr/bin/python
+from mercurial import demandimport; demandimport.enable()
+from mercurial.hgweb import hgweb, wsgicgi
+wsgicgi.launch(hgweb("../hgweb.cfg"))
+`
+
+var hgwebCfg = `
+[paths]
+/hg/ = /DIR/hg/*
+`
+
+func hgHandler() http.Handler {
+ py := filepath.Join(*dir, "hgweb.py")
+ if err := ioutil.WriteFile(py, []byte(hgwebPy), 0777); err != nil {
+ log.Fatal(err)
+ }
+ if err := ioutil.WriteFile(filepath.Join(*dir, "hgweb.cfg"), []byte(strings.Replace(hgwebCfg, "DIR", *dir, -1)), 0777); err != nil {
+ log.Fatal(err)
+ }
+ os.Mkdir(filepath.Join(*dir, "hg"), 0777)
+
+ return &cgi.Handler{
+ Path: py,
+ Dir: filepath.Join(*dir, "hg"),
+ }
+}
diff --git a/vcs-test/vcweb/load.go b/vcs-test/vcweb/load.go
new file mode 100644
index 0000000..630f5df
--- /dev/null
+++ b/vcs-test/vcweb/load.go
@@ -0,0 +1,137 @@
+// Copyright 2017 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.
+
+package main
+
+import (
+ "archive/zip"
+ "bytes"
+ "context"
+ "crypto/md5"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+ "time"
+
+ "cloud.google.com/go/storage"
+)
+
+var (
+ ctx = context.Background()
+ client *storage.Client
+ bucket *storage.BucketHandle
+)
+
+var cache struct {
+ sync.Mutex
+ entry map[string]*cacheEntry
+}
+
+type cacheEntry struct {
+ sync.Mutex
+ expire time.Time
+ md5 []byte
+}
+
+func init() {
+ var err error
+ client, err = storage.NewClient(ctx)
+ if err != nil {
+ log.Fatal(err)
+ }
+ bucket = client.Bucket("vcs-test")
+ cache.entry = map[string]*cacheEntry{}
+}
+
+func loadFS(dir1, dir2 string, force bool) {
+ name := dir1 + "/" + dir2
+ defer func() {
+ if err := recover(); err != nil {
+ log.Printf("%s: %v", name, err)
+ }
+ }()
+ check := func(err error) {
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ check(os.MkdirAll(filepath.Join(*dir, dir1), 0777))
+
+ cache.Lock()
+ entry := cache.entry[name]
+ if entry == nil {
+ entry = new(cacheEntry)
+ cache.entry[name] = entry
+ }
+ cache.Unlock()
+
+ entry.Lock()
+ defer entry.Unlock()
+
+ if time.Now().Before(entry.expire) && !force {
+ return
+ }
+
+ entry.expire = time.Now().Add(5 * time.Minute)
+
+ obj := bucket.Object(name + ".zip")
+ attrs, err := obj.Attrs(ctx)
+ check(err)
+ if bytes.Equal(attrs.MD5, entry.md5) {
+ return
+ }
+
+ r, err := obj.NewReader(ctx)
+ check(err)
+ defer r.Close()
+
+ zipFile := filepath.Join(*dir, name+".zip")
+ zf, err := os.Create(zipFile)
+ check(err)
+
+ h := md5.New()
+ _, err = io.Copy(io.MultiWriter(zf, h), r)
+ check(zf.Close())
+ check(err)
+ sum := h.Sum(nil)
+
+ if !bytes.Equal(attrs.MD5, sum) {
+ panic(fmt.Sprintf("load: unexpected md5 %x != %x", sum, attrs.MD5))
+ }
+
+ zf, err = os.Open(zipFile)
+ check(err)
+ info, err := zf.Stat()
+ check(err)
+ zr, err := zip.NewReader(zf, info.Size())
+ check(err)
+
+ tmp := filepath.Join(*dir, dir1+"/_"+dir2)
+ check(os.RemoveAll(tmp))
+ check(os.MkdirAll(tmp, 0777))
+
+ for _, f := range zr.File {
+ if strings.HasSuffix(f.Name, "/") {
+ check(os.MkdirAll(filepath.Join(tmp, f.Name), 0777))
+ continue
+ }
+ w, err := os.Create(filepath.Join(tmp, f.Name))
+ check(err)
+ r, err := f.Open()
+ check(err)
+ _, err = io.Copy(w, r)
+ check(err)
+ check(w.Close())
+ }
+
+ real := filepath.Join(*dir, dir1+"/"+dir2)
+ check(os.RemoveAll(real))
+ check(os.Rename(tmp, real))
+ entry.md5 = sum
+}
diff --git a/vcs-test/vcweb/main.go b/vcs-test/vcweb/main.go
new file mode 100644
index 0000000..334d571
--- /dev/null
+++ b/vcs-test/vcweb/main.go
@@ -0,0 +1,252 @@
+// Copyright 2017 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.
+
+package main
+
+import (
+ "crypto/tls"
+ "flag"
+ "fmt"
+ "html"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "path/filepath"
+ "regexp"
+ "sort"
+ "strings"
+ "text/tabwriter"
+ "time"
+
+ "github.com/coreos/go-systemd/activation"
+ "github.com/coreos/go-systemd/daemon"
+ "golang.org/x/build/autocertcache"
+ "golang.org/x/crypto/acme"
+ "golang.org/x/crypto/acme/autocert"
+)
+
+var (
+ dir = flag.String("d", "/tmp/vcweb", "directory holding vcweb data")
+ staging = flag.Bool("staging", false, "use staging letsencrypt server")
+)
+
+var buildInfo string
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: vcsweb [-d dir] [-staging]\n")
+ os.Exit(2)
+}
+
+var isLoadDir = map[string]bool{
+ "go": true,
+ "git": true,
+ "hg": true,
+ "svn": true,
+ "fossil": true,
+ "bzr": true,
+}
+
+func main() {
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() != 0 {
+ usage()
+ }
+
+ if err := os.MkdirAll(*dir, 0777); err != nil {
+ log.Fatal(err)
+ }
+
+ http.Handle("/go/", http.StripPrefix("/go/", http.FileServer(http.Dir(filepath.Join(*dir, "go")))))
+ http.Handle("/git/", gitHandler())
+ http.Handle("/hg/", hgHandler())
+ http.Handle("/svn/", svnHandler())
+ http.Handle("/fossil/", fossilHandler())
+ http.Handle("/bzr/", bzrHandler())
+
+ handler := logger(http.HandlerFunc(loadAndHandle))
+
+ // If running under systemd, listen on 80 and 443 and serve TLS.
+ if listeners, _ := activation.Listeners(true); len(listeners) == 2 {
+ // Want listeners[0] is port 80, listeners[1] is port 443.
+ // There's no guaranteed order of the listeners!
+ // Sometimes we get 80, 443; other times we get 443, 80.
+ names := strings.Split(os.Getenv("LISTEN_FDNAMES"), ":")
+ if strings.Contains(names[0], "https") {
+ listeners[0], listeners[1] = listeners[1], listeners[0]
+ }
+
+ go func() {
+ log.Fatal(http.Serve(listeners[0], handler))
+ }()
+ dir := acme.LetsEncryptURL
+ if *staging {
+ dir = "https://acme-staging.api.letsencrypt.org/directory"
+ }
+ m := autocert.Manager{
+ Client: &acme.Client{DirectoryURL: dir},
+ Cache: autocertcache.NewGoogleCloudStorageCache(client, "vcs-test-autocert"),
+ Prompt: autocert.AcceptTOS,
+ HostPolicy: autocert.HostWhitelist("vcs-test.golang.org"),
+ }
+ mRSA := autocert.Manager{
+ Client: &acme.Client{DirectoryURL: dir},
+ Cache: autocertcache.NewGoogleCloudStorageCache(client, "vcs-test-autocert-rsa"),
+ Prompt: autocert.AcceptTOS,
+ HostPolicy: autocert.HostWhitelist("vcs-test.golang.org"),
+ ForceRSA: true,
+ }
+ s := &http.Server{
+ Addr: ":https",
+ Handler: handler,
+ TLSConfig: &tls.Config{
+ MinVersion: tls.VersionSSL30,
+ GetCertificate: fallbackSNI(mRSA.GetCertificate, m.GetCertificate, "vcs-test.golang.org"),
+ },
+ }
+
+ dt, err := daemon.SdWatchdogEnabled(true)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ daemon.SdNotify(false, "READY=1")
+ go func() {
+ for range time.NewTicker(dt / 2).C {
+ daemon.SdNotify(false, "WATCHDOG=1")
+ }
+ }()
+ log.Fatal(s.ServeTLS(listeners[1], "", ""))
+ }
+
+ // Local development on :8088.
+ l, err := net.Listen("tcp", "127.0.0.1:8088")
+ if err != nil {
+ log.Fatal(err)
+ }
+ log.Fatal(http.Serve(l, handler))
+}
+
+var nameRE = regexp.MustCompile(`^[a-zA-Z0-9_\-]+$`)
+
+func loadAndHandle(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/tls" {
+ handleTLS(w, r)
+ return
+ }
+ addTLSLog(w, r)
+ if r.URL.Path == "/" {
+ overview(w, r)
+ return
+ }
+ elem := strings.Split(r.URL.Path, "/")
+ if len(elem) >= 3 && elem[0] == "" && isLoadDir[elem[1]] && nameRE.MatchString(elem[2]) {
+ loadFS(elem[1], elem[2], r.URL.Query().Get("vcweb-force-reload") == "1" || r.URL.Query().Get("go-get") == "1")
+ }
+ http.DefaultServeMux.ServeHTTP(w, r)
+}
+
+func overview(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "<html>\n")
+ fmt.Fprintf(w, "<title>vcs-test.golang.org</title>\n<pre>\n")
+ fmt.Fprintf(w, "<b>vcs-test.golang.org</b>\n\n")
+ fmt.Fprintf(w, "This server serves various version control repos for testing the go command.\n\n")
+
+ fmt.Fprintf(w, "Date: %s\n", time.Now().Format(time.UnixDate))
+ fmt.Fprintf(w, "Build: %s\n\n", html.EscapeString(buildInfo))
+
+ fmt.Fprintf(w, "<b>cache</b>\n")
+
+ var all []string
+ cache.Lock()
+ for name, entry := range cache.entry {
+ all = append(all, fmt.Sprintf("%s\t%x\t%s\n", name, entry.md5, entry.expire.Format(time.UnixDate)))
+ }
+ cache.Unlock()
+ sort.Strings(all)
+ tw := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0)
+ for _, line := range all {
+ tw.Write([]byte(line))
+ }
+ tw.Flush()
+}
+
+func fallbackSNI(getCertRSA, getCert func(*tls.ClientHelloInfo) (*tls.Certificate, error), host string) func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
+ return func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ saveHello(hello)
+ if hello.ServerName == "" {
+ h := *hello
+ hello = &h
+ hello.ServerName = host
+ }
+ var cert *tls.Certificate
+ var err error
+ if len(hello.SupportedVersions) > 0 && hello.SupportedVersions[0] >= tls.VersionTLS12 {
+ cert, err = getCert(hello)
+ if strings.HasSuffix(hello.ServerName, ".acme.invalid") && err != nil {
+ cert, err = getCertRSA(hello)
+ }
+ } else {
+ cert, err = getCertRSA(hello)
+ }
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "getCert: %v\n", err)
+ }
+ return cert, err
+ }
+}
+
+type loggingResponseWriter struct {
+ code int
+ size int64
+ http.ResponseWriter
+}
+
+func (l *loggingResponseWriter) WriteHeader(code int) {
+ l.code = code
+ l.ResponseWriter.WriteHeader(code)
+}
+
+func (l *loggingResponseWriter) Write(data []byte) (int, error) {
+ n, err := l.ResponseWriter.Write(data)
+ l.size += int64(n)
+ return n, err
+}
+
+func dashOr(s string) string {
+ if s == "" {
+ return "-"
+ }
+ return s
+}
+
+func logger(h http.Handler) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ l := &loggingResponseWriter{
+ code: 200,
+ ResponseWriter: w,
+ }
+ startTime := time.Now().Format("02/Jan/2006:15:04:05 -0700")
+ defer func() {
+ err := recover()
+ if err != nil {
+ l.code = 999
+ }
+ fmt.Fprintf(os.Stderr, "%s - - [%s] %q %03d %d %q %q %q\n",
+ dashOr(r.RemoteAddr),
+ startTime,
+ r.Method+" "+r.URL.String()+" "+r.Proto,
+ l.code,
+ l.size,
+ r.Header.Get("Referer"),
+ r.Header.Get("User-Agent"),
+ r.Host)
+ if err != nil {
+ panic(err)
+ }
+ }()
+ h.ServeHTTP(l, r)
+ }
+}
diff --git a/vcs-test/vcweb/svn.go b/vcs-test/vcweb/svn.go
new file mode 100644
index 0000000..b959a8e
--- /dev/null
+++ b/vcs-test/vcweb/svn.go
@@ -0,0 +1,22 @@
+// Copyright 2017 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.
+
+package main
+
+import (
+ "log"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+)
+
+// Subversion is so complicated it can only run from inside Apache.
+// Assume an appropriately configured Apache is on 8888.
+func svnHandler() http.Handler {
+ u, err := url.Parse("http://127.0.0.1:8888/")
+ if err != nil {
+ log.Fatal(err)
+ }
+ return httputil.NewSingleHostReverseProxy(u)
+}
diff --git a/vcs-test/vcweb/tls.go b/vcs-test/vcweb/tls.go
new file mode 100644
index 0000000..42ab28b
--- /dev/null
+++ b/vcs-test/vcweb/tls.go
@@ -0,0 +1,648 @@
+// Copyright 2017 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.
+
+package main
+
+import (
+ "crypto/tls"
+ "fmt"
+ "html"
+ "io"
+ "net"
+ "net/http"
+ "os"
+ "reflect"
+ "runtime/debug"
+ "strconv"
+ "sync"
+ "time"
+)
+
+func handleTLS(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "<html>\n")
+ fmt.Fprintf(w, "<title>vcs-test.golang.org/tls</title>\n<pre>\n")
+ fmt.Fprintf(w, "<b>vcs-test.golang.org/tls</b>\n\n")
+ fmt.Fprintf(w, "This page shows debug information about TLS client connections.\n\n")
+ fmt.Fprintf(w, "\n<b><u>Your Request</u></b>\n\n")
+ showTLSRequest(w, time.Now(), r, r.TLS, findHello(w, true))
+ showTLSLog(w)
+ showHelloLog(w)
+}
+
+const maxTLSLog = 100
+
+var tlsLog struct {
+ mu sync.Mutex
+ w int64
+ buf [maxTLSLog]tlsLogEntry
+}
+
+type tlsLogEntry struct {
+ time time.Time
+ r *http.Request
+ cs *tls.ConnectionState
+ hello *tls.ClientHelloInfo
+}
+
+const maxHelloLog = 100
+
+var recentHello struct {
+ mu sync.Mutex
+ w int64
+ buf [maxHelloLog]helloLogEntry
+}
+
+type helloLogEntry struct {
+ time time.Time
+ hello *tls.ClientHelloInfo
+ conn net.Conn
+ used bool
+}
+
+func saveHello(hello *tls.ClientHelloInfo) {
+ recentHello.mu.Lock()
+ recentHello.buf[recentHello.w%maxHelloLog] = helloLogEntry{time.Now(), hello, hello.Conn, false}
+ recentHello.w++
+ recentHello.mu.Unlock()
+}
+
+func findHello(w http.ResponseWriter, dbg bool) *tls.ClientHelloInfo {
+ defer func() {
+ if err := recover(); err != nil && dbg {
+ fmt.Fprintf(w, "PANIC: %s\n%s\n", err, debug.Stack())
+ }
+ }()
+ rw := reflect.ValueOf(w) // *loggingResponseWriter
+ rw = rw.Elem().FieldByName("ResponseWriter").Elem()
+ switch rw.Type().String() {
+ case "*http.http2responseWriter":
+ rw = rw.Elem().FieldByName("rws").Elem().FieldByName("conn").Elem().FieldByName("conn")
+ case "*http.response":
+ rw = rw.Elem().FieldByName("conn").Elem().FieldByName("rwc")
+ }
+ // now a net.Conn implemented by *tls.Conn
+ rw = rw.Elem().Elem().FieldByName("conn").Elem()
+ // now a net.Conn implementation of some kind
+ if rw.Kind() != reflect.Ptr || rw.IsNil() {
+ if dbg {
+ fmt.Fprintf(w, "FINDHELLO STOP: %s\n", rw.Type().String())
+ }
+ return nil
+ }
+ ptr := rw.Pointer()
+ recentHello.mu.Lock()
+ defer recentHello.mu.Unlock()
+ for i, e := range recentHello.buf {
+ if e.conn == nil || e.hello == nil {
+ continue
+ }
+ rc := reflect.ValueOf(e.conn)
+ if rc.Kind() != reflect.Ptr || rc.IsNil() || rc.Type() != rw.Type() {
+ if dbg {
+ fmt.Fprintf(w, "- findhello wrong %s vs %s\n", rc.Type().String(), rw.Type())
+ }
+ continue
+ }
+ if rc.Pointer() == ptr {
+ recentHello.buf[i].used = true
+ return e.hello
+ }
+ }
+ if dbg {
+ fmt.Fprintf(w, "FINDHELLO: %s\n", rw.Type().String())
+ }
+ fmt.Fprintf(os.Stderr, "FINDHELLO: %s\n", rw.Type().String())
+ return nil
+}
+
+func addTLSLog(w http.ResponseWriter, r *http.Request) {
+ if r.TLS == nil {
+ return
+ }
+ hello := findHello(w, false)
+ tlsLog.mu.Lock()
+ tlsLog.buf[tlsLog.w%maxTLSLog] = tlsLogEntry{time.Now(), r, r.TLS, hello}
+ tlsLog.w++
+ tlsLog.mu.Unlock()
+}
+
+func showTLSLog(w http.ResponseWriter) {
+ all := make([]tlsLogEntry, 0, maxTLSLog)
+ tlsLog.mu.Lock()
+ i := tlsLog.w - maxTLSLog
+ if i < 0 {
+ i = 0
+ }
+ for ; i < tlsLog.w; i++ {
+ all = append(all, tlsLog.buf[i%maxTLSLog])
+ }
+ tlsLog.mu.Unlock()
+
+ fmt.Fprintf(w, "<b><u>Recent Requests</u></b>\n\n")
+ for _, e := range all {
+ showTLSRequest(w, e.time, e.r, e.cs, e.hello)
+ }
+}
+
+func showHelloLog(w http.ResponseWriter) {
+ all := make([]helloLogEntry, 0, maxHelloLog)
+ recentHello.mu.Lock()
+ i := recentHello.w - maxHelloLog
+ if i < 0 {
+ i = 0
+ }
+ for ; i < recentHello.w; i++ {
+ all = append(all, recentHello.buf[i%maxTLSLog])
+ }
+ recentHello.mu.Unlock()
+
+ fmt.Fprintf(w, "<b><u>Recent unmatched ClientHelloInfo</u></b>\n\n")
+ for _, e := range all {
+ if e.conn == nil || e.hello == nil || e.used {
+ continue
+ }
+ fmt.Fprintf(w, "<b>%s # %s</b>\n", e.conn.RemoteAddr(), e.time.Format(time.RFC3339))
+ printTLSHello(w, e.hello)
+ fmt.Fprintf(w, "\n")
+ }
+}
+
+func showTLSRequest(w http.ResponseWriter, t time.Time, r *http.Request, cs *tls.ConnectionState, hello *tls.ClientHelloInfo) {
+ var e = html.EscapeString
+ var q = strconv.Quote
+
+ defer fmt.Fprintf(w, "\n")
+ fmt.Fprintf(w, "<b>%s %s %s %s # %s</b>\n", e(r.RemoteAddr), e(r.Method), e(r.RequestURI), e(r.Proto), t.Format(time.RFC3339))
+ fmt.Fprintf(w, "\tUser-Agent: %s\n", e(r.Header.Get("User-Agent")))
+ if cs == nil {
+ fmt.Fprintf(w, "\tNo TLS\n")
+ return
+ }
+ fmt.Fprintf(w, "\tTLS ConnectionState:\n")
+ fmt.Fprintf(w, "\t\tVersion: %s\n", nameOrValue(int(cs.Version), versionNames, "%#x"))
+ fmt.Fprintf(w, "\t\tCipherSuite: %s\n", nameOrValue(int(cs.CipherSuite), suiteNames, "%#x"))
+ fmt.Fprintf(w, "\t\tNegotiatedProtocol: %s\n", e(q(cs.NegotiatedProtocol)))
+ fmt.Fprintf(w, "\t\tServerName: %s\n", e(q(cs.ServerName)))
+ fmt.Fprintf(w, "\tTLS ClientHello:\n")
+ if hello == nil {
+ fmt.Fprintf(w, "\t\tunavailable\n")
+ return
+ }
+ findHello(w, false) // so it doesn't show up as unmatched
+ printTLSHello(w, hello)
+}
+
+func printTLSHello(w io.Writer, hello *tls.ClientHelloInfo) {
+ var e = html.EscapeString
+ var q = strconv.Quote
+
+ fmt.Fprintf(w, "\t\tServerName: %s\n", e(q(hello.ServerName)))
+ fmt.Fprintf(w, "\t\tCipherSuites:")
+ for _, suite := range hello.CipherSuites {
+ fmt.Fprintf(w, "\n\t\t\t%s", nameOrValue(int(suite), suiteNames, "%#x"))
+ }
+ fmt.Fprintf(w, "\n")
+ fmt.Fprintf(w, "\t\tSupportedCurves:")
+ for _, curve := range hello.SupportedCurves {
+ fmt.Fprintf(w, " %s", nameOrValue(int(curve), curveNames, "%d"))
+ }
+ fmt.Fprintf(w, "\n")
+ fmt.Fprintf(w, "\t\tSupportedPoints:")
+ for _, pt := range hello.SupportedPoints {
+ fmt.Fprintf(w, " %s", nameOrValue(int(pt), pointNames, "%d"))
+ }
+ fmt.Fprintf(w, "\n")
+ fmt.Fprintf(w, "\t\tSignatureSchemes:")
+ for _, sig := range hello.SignatureSchemes {
+ fmt.Fprintf(w, " %s", nameOrValue(int(sig), sigNames, "%#x"))
+ }
+ fmt.Fprintf(w, "\n")
+ fmt.Fprintf(w, "\t\tSupportedVersions:")
+ for _, v := range hello.SupportedVersions {
+ fmt.Fprintf(w, " %s", nameOrValue(int(v), versionNames, "%#x"))
+ }
+ fmt.Fprintf(w, "\n")
+ fmt.Fprintf(w, "\t\tSupportedProtos:")
+ for _, s := range hello.SupportedProtos {
+ fmt.Fprintf(w, " %s", e(q(s)))
+ }
+ fmt.Fprintf(w, "\n")
+}
+
+func nameOrValue(x int, names map[int]string, format string) string {
+ name := names[x]
+ if name != "" {
+ return name
+ }
+ return fmt.Sprintf(format, x)
+}
+
+var suiteNames = map[int]string{
+ // https://www.iana.org/assignments/tls-parameters/tls-parameters-4.csv
+ // csv2tsv tls-parameters-4.csv | awk '{print "\t"$1": \"" $2 "\","}'| sed 's/,0x//' | egrep -v 'Unassigned|Reserved'
+ 0x0000: "TLS_NULL_WITH_NULL_NULL",
+ 0x0001: "TLS_RSA_WITH_NULL_MD5",
+ 0x0002: "TLS_RSA_WITH_NULL_SHA",
+ 0x0003: "TLS_RSA_EXPORT_WITH_RC4_40_MD5",
+ 0x0004: "TLS_RSA_WITH_RC4_128_MD5",
+ 0x0005: "TLS_RSA_WITH_RC4_128_SHA",
+ 0x0006: "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5",
+ 0x0007: "TLS_RSA_WITH_IDEA_CBC_SHA",
+ 0x0008: "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA",
+ 0x0009: "TLS_RSA_WITH_DES_CBC_SHA",
+ 0x000A: "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
+ 0x000B: "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA",
+ 0x000C: "TLS_DH_DSS_WITH_DES_CBC_SHA",
+ 0x000D: "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA",
+ 0x000E: "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA",
+ 0x000F: "TLS_DH_RSA_WITH_DES_CBC_SHA",
+ 0x0010: "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA",
+ 0x0011: "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
+ 0x0012: "TLS_DHE_DSS_WITH_DES_CBC_SHA",
+ 0x0013: "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
+ 0x0014: "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
+ 0x0015: "TLS_DHE_RSA_WITH_DES_CBC_SHA",
+ 0x0016: "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
+ 0x0017: "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5",
+ 0x0018: "TLS_DH_anon_WITH_RC4_128_MD5",
+ 0x0019: "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA",
+ 0x001A: "TLS_DH_anon_WITH_DES_CBC_SHA",
+ 0x001B: "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA",
+ 0x001E: "TLS_KRB5_WITH_DES_CBC_SHA",
+ 0x001F: "TLS_KRB5_WITH_3DES_EDE_CBC_SHA",
+ 0x0020: "TLS_KRB5_WITH_RC4_128_SHA",
+ 0x0021: "TLS_KRB5_WITH_IDEA_CBC_SHA",
+ 0x0022: "TLS_KRB5_WITH_DES_CBC_MD5",
+ 0x0023: "TLS_KRB5_WITH_3DES_EDE_CBC_MD5",
+ 0x0024: "TLS_KRB5_WITH_RC4_128_MD5",
+ 0x0025: "TLS_KRB5_WITH_IDEA_CBC_MD5",
+ 0x0026: "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA",
+ 0x0027: "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA",
+ 0x0028: "TLS_KRB5_EXPORT_WITH_RC4_40_SHA",
+ 0x0029: "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5",
+ 0x002A: "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5",
+ 0x002B: "TLS_KRB5_EXPORT_WITH_RC4_40_MD5",
+ 0x002C: "TLS_PSK_WITH_NULL_SHA",
+ 0x002D: "TLS_DHE_PSK_WITH_NULL_SHA",
+ 0x002E: "TLS_RSA_PSK_WITH_NULL_SHA",
+ 0x002F: "TLS_RSA_WITH_AES_128_CBC_SHA",
+ 0x0030: "TLS_DH_DSS_WITH_AES_128_CBC_SHA",
+ 0x0031: "TLS_DH_RSA_WITH_AES_128_CBC_SHA",
+ 0x0032: "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
+ 0x0033: "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
+ 0x0034: "TLS_DH_anon_WITH_AES_128_CBC_SHA",
+ 0x0035: "TLS_RSA_WITH_AES_256_CBC_SHA",
+ 0x0036: "TLS_DH_DSS_WITH_AES_256_CBC_SHA",
+ 0x0037: "TLS_DH_RSA_WITH_AES_256_CBC_SHA",
+ 0x0038: "TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
+ 0x0039: "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
+ 0x003A: "TLS_DH_anon_WITH_AES_256_CBC_SHA",
+ 0x003B: "TLS_RSA_WITH_NULL_SHA256",
+ 0x003C: "TLS_RSA_WITH_AES_128_CBC_SHA256",
+ 0x003D: "TLS_RSA_WITH_AES_256_CBC_SHA256",
+ 0x003E: "TLS_DH_DSS_WITH_AES_128_CBC_SHA256",
+ 0x003F: "TLS_DH_RSA_WITH_AES_128_CBC_SHA256",
+ 0x0040: "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",
+ 0x0041: "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA",
+ 0x0042: "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA",
+ 0x0043: "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA",
+ 0x0044: "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA",
+ 0x0045: "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA",
+ 0x0046: "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA",
+ 0x0067: "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
+ 0x0068: "TLS_DH_DSS_WITH_AES_256_CBC_SHA256",
+ 0x0069: "TLS_DH_RSA_WITH_AES_256_CBC_SHA256",
+ 0x006A: "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
+ 0x006B: "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
+ 0x006C: "TLS_DH_anon_WITH_AES_128_CBC_SHA256",
+ 0x006D: "TLS_DH_anon_WITH_AES_256_CBC_SHA256",
+ 0x0084: "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA",
+ 0x0085: "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA",
+ 0x0086: "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA",
+ 0x0087: "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA",
+ 0x0088: "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA",
+ 0x0089: "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA",
+ 0x008A: "TLS_PSK_WITH_RC4_128_SHA",
+ 0x008B: "TLS_PSK_WITH_3DES_EDE_CBC_SHA",
+ 0x008C: "TLS_PSK_WITH_AES_128_CBC_SHA",
+ 0x008D: "TLS_PSK_WITH_AES_256_CBC_SHA",
+ 0x008E: "TLS_DHE_PSK_WITH_RC4_128_SHA",
+ 0x008F: "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA",
+ 0x0090: "TLS_DHE_PSK_WITH_AES_128_CBC_SHA",
+ 0x0091: "TLS_DHE_PSK_WITH_AES_256_CBC_SHA",
+ 0x0092: "TLS_RSA_PSK_WITH_RC4_128_SHA",
+ 0x0093: "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA",
+ 0x0094: "TLS_RSA_PSK_WITH_AES_128_CBC_SHA",
+ 0x0095: "TLS_RSA_PSK_WITH_AES_256_CBC_SHA",
+ 0x0096: "TLS_RSA_WITH_SEED_CBC_SHA",
+ 0x0097: "TLS_DH_DSS_WITH_SEED_CBC_SHA",
+ 0x0098: "TLS_DH_RSA_WITH_SEED_CBC_SHA",
+ 0x0099: "TLS_DHE_DSS_WITH_SEED_CBC_SHA",
+ 0x009A: "TLS_DHE_RSA_WITH_SEED_CBC_SHA",
+ 0x009B: "TLS_DH_anon_WITH_SEED_CBC_SHA",
+ 0x009C: "TLS_RSA_WITH_AES_128_GCM_SHA256",
+ 0x009D: "TLS_RSA_WITH_AES_256_GCM_SHA384",
+ 0x009E: "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
+ 0x009F: "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
+ 0x00A0: "TLS_DH_RSA_WITH_AES_128_GCM_SHA256",
+ 0x00A1: "TLS_DH_RSA_WITH_AES_256_GCM_SHA384",
+ 0x00A2: "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256",
+ 0x00A3: "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",
+ 0x00A4: "TLS_DH_DSS_WITH_AES_128_GCM_SHA256",
+ 0x00A5: "TLS_DH_DSS_WITH_AES_256_GCM_SHA384",
+ 0x00A6: "TLS_DH_anon_WITH_AES_128_GCM_SHA256",
+ 0x00A7: "TLS_DH_anon_WITH_AES_256_GCM_SHA384",
+ 0x00A8: "TLS_PSK_WITH_AES_128_GCM_SHA256",
+ 0x00A9: "TLS_PSK_WITH_AES_256_GCM_SHA384",
+ 0x00AA: "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256",
+ 0x00AB: "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384",
+ 0x00AC: "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256",
+ 0x00AD: "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384",
+ 0x00AE: "TLS_PSK_WITH_AES_128_CBC_SHA256",
+ 0x00AF: "TLS_PSK_WITH_AES_256_CBC_SHA384",
+ 0x00B0: "TLS_PSK_WITH_NULL_SHA256",
+ 0x00B1: "TLS_PSK_WITH_NULL_SHA384",
+ 0x00B2: "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256",
+ 0x00B3: "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384",
+ 0x00B4: "TLS_DHE_PSK_WITH_NULL_SHA256",
+ 0x00B5: "TLS_DHE_PSK_WITH_NULL_SHA384",
+ 0x00B6: "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256",
+ 0x00B7: "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384",
+ 0x00B8: "TLS_RSA_PSK_WITH_NULL_SHA256",
+ 0x00B9: "TLS_RSA_PSK_WITH_NULL_SHA384",
+ 0x00BA: "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256",
+ 0x00BB: "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256",
+ 0x00BC: "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256",
+ 0x00BD: "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256",
+ 0x00BE: "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
+ 0x00BF: "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256",
+ 0x00C0: "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256",
+ 0x00C1: "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256",
+ 0x00C2: "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256",
+ 0x00C3: "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256",
+ 0x00C4: "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256",
+ 0x00C5: "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256",
+ 0x00FF: "TLS_EMPTY_RENEGOTIATION_INFO_SCSV",
+ 0x5600: "TLS_FALLBACK_SCSV",
+ 0xC001: "TLS_ECDH_ECDSA_WITH_NULL_SHA",
+ 0xC002: "TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
+ 0xC003: "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
+ 0xC004: "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
+ 0xC005: "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
+ 0xC006: "TLS_ECDHE_ECDSA_WITH_NULL_SHA",
+ 0xC007: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
+ 0xC008: "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
+ 0xC009: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
+ 0xC00A: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
+ 0xC00B: "TLS_ECDH_RSA_WITH_NULL_SHA",
+ 0xC00C: "TLS_ECDH_RSA_WITH_RC4_128_SHA",
+ 0xC00D: "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",
+ 0xC00E: "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
+ 0xC00F: "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
+ 0xC010: "TLS_ECDHE_RSA_WITH_NULL_SHA",
+ 0xC011: "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
+ 0xC012: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
+ 0xC013: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
+ 0xC014: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
+ 0xC015: "TLS_ECDH_anon_WITH_NULL_SHA",
+ 0xC016: "TLS_ECDH_anon_WITH_RC4_128_SHA",
+ 0xC017: "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA",
+ 0xC018: "TLS_ECDH_anon_WITH_AES_128_CBC_SHA",
+ 0xC019: "TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
+ 0xC01A: "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA",
+ 0xC01B: "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA",
+ 0xC01C: "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA",
+ 0xC01D: "TLS_SRP_SHA_WITH_AES_128_CBC_SHA",
+ 0xC01E: "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA",
+ 0xC01F: "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA",
+ 0xC020: "TLS_SRP_SHA_WITH_AES_256_CBC_SHA",
+ 0xC021: "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA",
+ 0xC022: "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA",
+ 0xC023: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
+ 0xC024: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
+ 0xC025: "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
+ 0xC026: "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
+ 0xC027: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
+ 0xC028: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
+ 0xC029: "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",
+ 0xC02A: "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",
+ 0xC02B: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
+ 0xC02C: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
+ 0xC02D: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
+ 0xC02E: "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384",
+ 0xC02F: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ 0xC030: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
+ 0xC031: "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256",
+ 0xC032: "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384",
+ 0xC033: "TLS_ECDHE_PSK_WITH_RC4_128_SHA",
+ 0xC034: "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA",
+ 0xC035: "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA",
+ 0xC036: "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA",
+ 0xC037: "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256",
+ 0xC038: "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384",
+ 0xC039: "TLS_ECDHE_PSK_WITH_NULL_SHA",
+ 0xC03A: "TLS_ECDHE_PSK_WITH_NULL_SHA256",
+ 0xC03B: "TLS_ECDHE_PSK_WITH_NULL_SHA384",
+ 0xC03C: "TLS_RSA_WITH_ARIA_128_CBC_SHA256",
+ 0xC03D: "TLS_RSA_WITH_ARIA_256_CBC_SHA384",
+ 0xC03E: "TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256",
+ 0xC03F: "TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384",
+ 0xC040: "TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256",
+ 0xC041: "TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384",
+ 0xC042: "TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256",
+ 0xC043: "TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384",
+ 0xC044: "TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256",
+ 0xC045: "TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384",
+ 0xC046: "TLS_DH_anon_WITH_ARIA_128_CBC_SHA256",
+ 0xC047: "TLS_DH_anon_WITH_ARIA_256_CBC_SHA384",
+ 0xC048: "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256",
+ 0xC049: "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384",
+ 0xC04A: "TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256",
+ 0xC04B: "TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384",
+ 0xC04C: "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256",
+ 0xC04D: "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384",
+ 0xC04E: "TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256",
+ 0xC04F: "TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384",
+ 0xC050: "TLS_RSA_WITH_ARIA_128_GCM_SHA256",
+ 0xC051: "TLS_RSA_WITH_ARIA_256_GCM_SHA384",
+ 0xC052: "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256",
+ 0xC053: "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384",
+ 0xC054: "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256",
+ 0xC055: "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384",
+ 0xC056: "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256",
+ 0xC057: "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384",
+ 0xC058: "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256",
+ 0xC059: "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384",
+ 0xC05A: "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256",
+ 0xC05B: "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384",
+ 0xC05C: "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256",
+ 0xC05D: "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384",
+ 0xC05E: "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256",
+ 0xC05F: "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384",
+ 0xC060: "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256",
+ 0xC061: "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384",
+ 0xC062: "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256",
+ 0xC063: "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384",
+ 0xC064: "TLS_PSK_WITH_ARIA_128_CBC_SHA256",
+ 0xC065: "TLS_PSK_WITH_ARIA_256_CBC_SHA384",
+ 0xC066: "TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256",
+ 0xC067: "TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384",
+ 0xC068: "TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256",
+ 0xC069: "TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384",
+ 0xC06A: "TLS_PSK_WITH_ARIA_128_GCM_SHA256",
+ 0xC06B: "TLS_PSK_WITH_ARIA_256_GCM_SHA384",
+ 0xC06C: "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256",
+ 0xC06D: "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384",
+ 0xC06E: "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256",
+ 0xC06F: "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384",
+ 0xC070: "TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256",
+ 0xC071: "TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384",
+ 0xC072: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256",
+ 0xC073: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384",
+ 0xC074: "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256",
+ 0xC075: "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384",
+ 0xC076: "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
+ 0xC077: "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384",
+ 0xC078: "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256",
+ 0xC079: "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384",
+ 0xC07A: "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256",
+ 0xC07B: "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384",
+ 0xC07C: "TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256",
+ 0xC07D: "TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384",
+ 0xC07E: "TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256",
+ 0xC07F: "TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384",
+ 0xC080: "TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256",
+ 0xC081: "TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384",
+ 0xC082: "TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256",
+ 0xC083: "TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384",
+ 0xC084: "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256",
+ 0xC085: "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384",
+ 0xC086: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256",
+ 0xC087: "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384",
+ 0xC088: "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256",
+ 0xC089: "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384",
+ 0xC08A: "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256",
+ 0xC08B: "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384",
+ 0xC08C: "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256",
+ 0xC08D: "TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384",
+ 0xC08E: "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256",
+ 0xC08F: "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384",
+ 0xC090: "TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256",
+ 0xC091: "TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384",
+ 0xC092: "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256",
+ 0xC093: "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384",
+ 0xC094: "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256",
+ 0xC095: "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384",
+ 0xC096: "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256",
+ 0xC097: "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384",
+ 0xC098: "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256",
+ 0xC099: "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384",
+ 0xC09A: "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256",
+ 0xC09B: "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384",
+ 0xC09C: "TLS_RSA_WITH_AES_128_CCM",
+ 0xC09D: "TLS_RSA_WITH_AES_256_CCM",
+ 0xC09E: "TLS_DHE_RSA_WITH_AES_128_CCM",
+ 0xC09F: "TLS_DHE_RSA_WITH_AES_256_CCM",
+ 0xC0A0: "TLS_RSA_WITH_AES_128_CCM_8",
+ 0xC0A1: "TLS_RSA_WITH_AES_256_CCM_8",
+ 0xC0A2: "TLS_DHE_RSA_WITH_AES_128_CCM_8",
+ 0xC0A3: "TLS_DHE_RSA_WITH_AES_256_CCM_8",
+ 0xC0A4: "TLS_PSK_WITH_AES_128_CCM",
+ 0xC0A5: "TLS_PSK_WITH_AES_256_CCM",
+ 0xC0A6: "TLS_DHE_PSK_WITH_AES_128_CCM",
+ 0xC0A7: "TLS_DHE_PSK_WITH_AES_256_CCM",
+ 0xC0A8: "TLS_PSK_WITH_AES_128_CCM_8",
+ 0xC0A9: "TLS_PSK_WITH_AES_256_CCM_8",
+ 0xC0AA: "TLS_PSK_DHE_WITH_AES_128_CCM_8",
+ 0xC0AB: "TLS_PSK_DHE_WITH_AES_256_CCM_8",
+ 0xC0AC: "TLS_ECDHE_ECDSA_WITH_AES_128_CCM",
+ 0xC0AD: "TLS_ECDHE_ECDSA_WITH_AES_256_CCM",
+ 0xC0AE: "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8",
+ 0xC0AF: "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8",
+ 0xCCA8: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
+ 0xCCA9: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
+ 0xCCAA: "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
+ 0xCCAB: "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256",
+ 0xCCAC: "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256",
+ 0xCCAD: "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256",
+ 0xCCAE: "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256",
+ 0xD001: "TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256",
+ 0xD002: "TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384",
+ 0xD003: "TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256",
+ 0xD005: "TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256",
+}
+
+var versionNames = map[int]string{
+ tls.VersionSSL30: "SSL3.0",
+ tls.VersionTLS10: "TLS1.0",
+ tls.VersionTLS11: "TLS1.1",
+ tls.VersionTLS12: "TLS1.2",
+}
+
+var pointNames = map[int]string{
+ // csv2tsv tls-parameters-9.csv | awk '{print "\t" $1 ": \"" $2 "\","}'| egrep -v 'Unassigned|Reserved'
+ 0: "uncompressed",
+ 1: "ansiX962_compressed_prime",
+ 2: "ansiX962_compressed_char2",
+}
+
+var curveNames = map[int]string{
+ // csv2tsv tls-parameters-8.csv | awk '{print "\t" $1 ": \"" $2 "\","}'| egrep -v 'Unassigned|Reserved'
+ 1: "sect163k1",
+ 2: "sect163r1",
+ 3: "sect163r2",
+ 4: "sect193r1",
+ 5: "sect193r2",
+ 6: "sect233k1",
+ 7: "sect233r1",
+ 8: "sect239k1",
+ 9: "sect283k1",
+ 10: "sect283r1",
+ 11: "sect409k1",
+ 12: "sect409r1",
+ 13: "sect571k1",
+ 14: "sect571r1",
+ 15: "secp160k1",
+ 16: "secp160r1",
+ 17: "secp160r2",
+ 18: "secp192k1",
+ 19: "secp192r1",
+ 20: "secp224k1",
+ 21: "secp224r1",
+ 22: "secp256k1",
+ 23: "secp256r1",
+ 24: "secp384r1",
+ 25: "secp521r1",
+ 26: "brainpoolP256r1",
+ 27: "brainpoolP384r1",
+ 28: "brainpoolP512r1",
+ 29: "x25519",
+ 30: "x448",
+ 256: "ffdhe2048",
+ 257: "ffdhe3072",
+ 258: "ffdhe4096",
+ 259: "ffdhe6144",
+ 260: "ffdhe8192",
+ 65281: "arbitrary_explicit_prime_curves",
+ 65282: "arbitrary_explicit_char2_curves",
+}
+
+var sigNames = map[int]string{
+ // https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.2.3
+ 0x0201: "rsa_pkcs1_sha1",
+ 0x0401: "rsa_pkcs1_sha256",
+ 0x0501: "rsa_pkcs1_sha384",
+ 0x0601: "rsa_pkcs1_sha512",
+
+ 0x0403: "ecdsa_secp256r1_sha256",
+ 0x0503: "ecdsa_secp384r1_sha384",
+ 0x0603: "ecdsa_secp521r1_sha512",
+
+ 0x0804: "rsa_pss_sha256",
+ 0x0805: "rsa_pss_sha384",
+ 0x0806: "rsa_pss_sha512",
+
+ 0x0807: "ed25519",
+ 0x0808: "ed448",
+}