Skip to content

Commit

Permalink
feat: tls fingerprint verification
Browse files Browse the repository at this point in the history
  • Loading branch information
Equim-chan committed Aug 28, 2018
1 parent ed8a40f commit 3343ebb
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 13 deletions.
13 changes: 13 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,21 @@ An example config file:
// Most users could just leave it empty.
"serverName": "secure.proxy.example.com",
// If you prefer to set a fingerprint instead of providing certs, you can
// set this to true.
// Do not set to true unless you know what you are doing.
"insecureSkipVerify": false,
// Server's SHA256 fingerprint, used to verify as an alternative to providing
// the whole server certs, should be used with insecureSkipVerify to true.
// If both rootCA and sha256Fingerprint are provided, they will both be
// verified.
//
// An example to get fingerprint of a given cert:
// openssl x509 -fingerprint -sha256 -noout -in cert.cer | cut -f2 -d'=' | sed s/://g
// or of a server with TLS enabled:
// openssl s_client -showcerts -connect example.com:443 < /dev/null | \
// openssl x509 -fingerprint -sha256 -noout | cut -f2 -d'=' | sed s/://g
"sha256fingerprint": "22B975A1409850EF7F4522183E9C5A8955758FC899D70FE257112DA2FC430CCC",
// rootCA is useful for self-signed certs. Be careful with it.
// If the server has a trusted cert, you don't have to set it.
Expand Down
37 changes: 29 additions & 8 deletions h2s.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -35,8 +36,14 @@ type TLSConfig struct {
// since Cloudflare would simply reject CONNECT method.
ServerName string `json:"serverName"`

// If you prefer to set a fingerprint instead of providing certs, you can
// set this to true.
//
// Do not set to true unless you know what you are doing.
InsecureSkipVerify bool `json:"insecureSkipVerify"`
// Server's SHA256 fingerprint, used to verify as an alternative to providing
// the whole server certs.
SHA256Fingerprint string `json:"sha256Fingerprint"`

// For self-signed certs. Be careful.
RootCA string `json:"rootCA"`
Expand Down Expand Up @@ -68,9 +75,10 @@ type Config struct {
}

type internalUpstream struct {
address string
header http.Header
tlsConfig *tls.Config
address string
header http.Header
tlsConfig *tls.Config
tlsFingerprint []byte
}

type internalAccount struct {
Expand Down Expand Up @@ -153,6 +161,11 @@ func NewServer(c *Config) (*Server, error) {
}
addr = net.JoinHostPort(host, port)

upstream := &internalUpstream{
address: addr,
header: basicauth(v.Username, v.Password),
}

tlsConfig := (*tls.Config)(nil)
if t := v.TLSConfig; t != nil {
tlsConfig = new(tls.Config)
Expand All @@ -168,6 +181,17 @@ func NewServer(c *Config) (*Server, error) {
}

tlsConfig.InsecureSkipVerify = t.InsecureSkipVerify
if t.SHA256Fingerprint != "" {
fin, err := hex.DecodeString(t.SHA256Fingerprint)
if err != nil {
return nil, errors.New("h2s: create server: tls: failed to parse fingerprint")
}
if len(fin) != 32 {
return nil, errors.New("h2s: create server: tls: fingerprint: wrong length, not like a sha256 digest")
}

upstream.tlsFingerprint = fin
}

if t.RootCA != "" {
certPool := x509.NewCertPool()
Expand All @@ -189,12 +213,9 @@ func NewServer(c *Config) (*Server, error) {
tlsConfig.Certificates = []tls.Certificate{cert}
}
}
upstream.tlsConfig = tlsConfig

upstreams[i] = &internalUpstream{
address: addr,
header: basicauth(v.Username, v.Password),
tlsConfig: tlsConfig,
}
upstreams[i] = upstream
}

s.next = make(chan *internalUpstream)
Expand Down
32 changes: 27 additions & 5 deletions upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package h2s

import (
"bufio"
"crypto/hmac"
"crypto/sha256"
"crypto/tls"
"errors"
"net"
Expand All @@ -18,14 +20,34 @@ func (s *Server) dialUpstream() (conn net.Conn, u *internalUpstream, err error)
return
}

if u.tlsConfig != nil {
conn, err = tls.DialWithDialer(s.dialer, "tcp", u.address, u.tlsConfig)
} else {
if u.tlsConfig == nil {
conn, err = s.dialer.Dial("tcp", u.address)
if err == nil {
return
}
}
if err == nil {
return

tlsConn, terr := tls.DialWithDialer(s.dialer, "tcp", u.address, u.tlsConfig)
if terr != nil {
err = terr
continue
}
if u.tlsFingerprint != nil {
certs := tlsConn.ConnectionState().PeerCertificates
if len(certs) < 1 {
err = errors.New("the server gives no cert")
continue
}

fin := sha256.Sum256(certs[0].Raw)
if !hmac.Equal(fin[:], u.tlsFingerprint) {
err = errors.New("fingerprint not matched")
continue
}
}

conn = tlsConn
return
}
err = errors.New("max retry exceeded: " + err.Error())

Expand Down

0 comments on commit 3343ebb

Please sign in to comment.