Merge remote-tracking branch 'wxia/mTLS_authentication'

This commit is contained in:
Jaime Pillora
2020-09-01 07:08:18 +10:00
11 changed files with 312 additions and 11 deletions
+12
View File
@@ -46,6 +46,8 @@ type Config struct {
type TLSConfig struct {
SkipVerify bool
CA string
Cert string
Key string
}
//Client represents a client instance
@@ -117,6 +119,16 @@ func NewClient(c *Config) (*Client, error) {
tc.RootCAs = rootCAs
}
}
//Specify client cert and key pair
if c.TLS.Cert != "" && c.TLS.Key != "" {
c, err := tls.LoadX509KeyPair(c.TLS.Cert, c.TLS.Key)
if err != nil {
return nil, fmt.Errorf("Error loading client cert and key pair: %v", err)
}
tc.Certificates = []tls.Certificate{c}
} else if c.TLS.Cert != "" || c.TLS.Key != "" {
return nil, fmt.Errorf("Please specify client cert and key pair BOTH")
}
client.tlsConfig = tc
}
//validate remotes
+15
View File
@@ -157,6 +157,11 @@ var serverHelp = `
You can modify this path by setting the CHISEL_LE_CACHE variable,
or disable caching by setting this variable to "-". You can optionally
provide a certificate notification email by setting CHISEL_LE_EMAIL.
--tls-ca, a path to a PEM encoded CA certificate bundle or a directory
holding multiple PEM encode CA certificate bundle files, which is used to
validate client connections. The provided CA certificates will be used
instead of the system roots. This is commonly used to implement mutual-TLS.
` + commonHelp
func server(args []string) {
@@ -175,6 +180,7 @@ func server(args []string) {
flags.StringVar(&config.TLS.Key, "tls-key", "", "")
flags.StringVar(&config.TLS.Cert, "tls-cert", "", "")
flags.Var(multiFlag{&config.TLS.Domains}, "tls-domain", "")
flags.StringVar(&config.TLS.CA, "tls-ca", "", "")
host := flags.String("host", "", "")
p := flags.String("p", "", "")
@@ -367,6 +373,13 @@ var clientHelp = `
transport https (wss) connection. Chisel server's public key
may be still verified (see --fingerprint) after inner connection
is established.
--tls-key, a path to a PEM encoded private key used for client
authentication (mutual-TLS).
--tls-cert, a path to a PEM encoded certificate matching the provided
private key. The certificate must have client authentication
enabled (mutual-TLS).
` + commonHelp
func client(args []string) {
@@ -380,6 +393,8 @@ func client(args []string) {
flags.StringVar(&config.Proxy, "proxy", "", "")
flags.StringVar(&config.TLS.CA, "tls-ca", "", "")
flags.BoolVar(&config.TLS.SkipVerify, "tls-skip-verify", false, "")
flags.StringVar(&config.TLS.Cert, "tls-cert", "", "")
flags.StringVar(&config.TLS.Key, "tls-key", "", "")
flags.Var(&headerFlags{config.Headers}, "header", "")
hostname := flags.String("hostname", "", "")
pid := flags.Bool("pid", false, "")
+51 -7
View File
@@ -2,13 +2,14 @@ package chserver
import (
"crypto/tls"
"crypto/x509"
"errors"
"golang.org/x/crypto/acme/autocert"
"io/ioutil"
"net"
"os"
"os/user"
"path/filepath"
"golang.org/x/crypto/acme/autocert"
)
//TLSConfig enables configures TLS
@@ -16,6 +17,7 @@ type TLSConfig struct {
Key string
Cert string
Domains []string
CA string
}
func (s *Server) listener(host, port string) (net.Listener, error) {
@@ -30,7 +32,7 @@ func (s *Server) listener(host, port string) (net.Listener, error) {
}
extra := ""
if hasKeyCert {
c, err := tlsKeyCert(s.config.TLS.Key, s.config.TLS.Cert)
c, err := s.tlsKeyCert(s.config.TLS.Key, s.config.TLS.Cert, s.config.TLS.CA)
if err != nil {
return nil, err
}
@@ -82,13 +84,55 @@ func (s *Server) tlsLetsEncrypt(domains []string) *tls.Config {
return m.TLSConfig()
}
func tlsKeyCert(key, cert string) (*tls.Config, error) {
func (s *Server) tlsKeyCert(key, cert string, clientCaPath string) (*tls.Config, error) {
c, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
return nil, err
}
//return file based tls config using tls defaults
return &tls.Config{
//file based tls config using tls defaults
tlsCfg := &tls.Config{
Certificates: []tls.Certificate{c},
}, nil
}
//mTLS requires Client CAs
if clientCaPath != "" {
fileInfo, err := os.Stat(clientCaPath)
if err != nil {
return nil, err
}
clientCAPool := x509.NewCertPool()
if fileInfo.IsDir() {
//this is a directory holding CA bundle files
files, err := ioutil.ReadDir(clientCaPath)
if err != nil {
return nil, err
}
s.Infof("Looking for client CA files from %s", clientCaPath)
//add all cert files from path
for _, file := range files {
f := file.Name()
if err := addCABundle(filepath.Join(clientCaPath, f), clientCAPool); err != nil {
return nil, err
}
}
} else {
//this is a CA bundle file
if err := addCABundle(clientCaPath, clientCAPool); err != nil {
return nil, err
}
}
//set client CAs and enable cert verification
tlsCfg.ClientCAs = clientCAPool
tlsCfg.ClientAuth = tls.RequireAndVerifyClientCert
}
return tlsCfg, nil
}
func addCABundle(clientCaPath string, clientCAPool *x509.CertPool) error {
if content, err := ioutil.ReadFile(clientCaPath); err != nil {
return nil
} else if !clientCAPool.AppendCertsFromPEM(content) {
return errors.New("Fail to load certificates from : " + clientCaPath)
} else {
return err
}
}
+6 -1
View File
@@ -74,7 +74,12 @@ func (tl *testLayout) setup(t *testing.T) (server *chserver.Server, client *chcl
}()
//client (with defaults)
tl.client.Fingerprint = server.GetFingerprint()
tl.client.Server = "http://127.0.0.1:" + port
if tl.server.TLS.Key != "" {
//the domain name has to be localhost to match the ssl cert
tl.client.Server = "https://localhost:" + port
} else {
tl.client.Server = "http://127.0.0.1:" + port
}
for i, r := range tl.client.Remotes {
//convert $FILEPORT into the allocated port for this test case
if tl.fileServer {
+16
View File
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICezCCAeQCCQDwdWskfbwmzzANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UEBhMC
dXMxCzAJBgNVBAgMAm1hMQ8wDQYDVQQHDAZCb3N0b24xDzANBgNVBAoMBkNoaXNl
bDENMAsGA1UECwwEdGVzdDESMBAGA1UEAwwJbG9jYWxob3N0MSAwHgYJKoZIhvcN
AQkBFhF3aWxseGlhQGdtYWlsLmNvbTAeFw0yMDA4MjQxOTQ4MTdaFw0zMDA4MjIx
OTQ4MTdaMIGBMQswCQYDVQQGEwJ1czELMAkGA1UECAwCbWExDzANBgNVBAcMBkJv
c3RvbjEPMA0GA1UECgwGQ2hpc2VsMQ0wCwYDVQQLDAR0ZXN0MRIwEAYDVQQDDAls
b2NhbGhvc3QxIDAeBgkqhkiG9w0BCQEWEXdpbGx4aWFAZ21haWwuY29tMIGfMA0G
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC128s6L6YN0eNSNbI40URFHd9xzfnPlUcH
n9n7D6YkJL7LsTAtUfjubNAX0Q1gclDnDZCfYi9UZVzzID4s1gZJZAEZGnce8loO
a+WcPUgIOJngk2bwUHfrWPl+R5mvE9p60rfYNdo86wLMaLAJu+VagNmaoilSU7OS
uZ/AgTUMFQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAEzq2qsH5VfmjUcvlhS4a7X5
zOAtEIVB1+oef/1NcyT3PaMX0ry0Ddbo3NJs3G9KTF0k+TCGtT7nAG2jRQvs6omZ
3+9C3x+6TQq+95KMBWXuZLZEPNa4iCGFbGrHq4wcWDehBAPSjdctqnmowd8yIgov
gNSN2xEMPNKYIhHt0lyc
-----END CERTIFICATE-----
+16
View File
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICfTCCAeYCCQDIXTlEp6na1zANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMC
dXMxCzAJBgNVBAgMAm1hMQ8wDQYDVQQHDAZCb3N0b24xDzANBgNVBAoMBkNoaXNl
bDENMAsGA1UECwwEdGVzdDETMBEGA1UEAwwKbVRMU0NsaWVudDEgMB4GCSqGSIb3
DQEJARYRd2lsbHhpYUBnbWFpbC5jb20wHhcNMjAwODI0MTk0ODQxWhcNMzAwODIy
MTk0ODQxWjCBgjELMAkGA1UEBhMCdXMxCzAJBgNVBAgMAm1hMQ8wDQYDVQQHDAZC
b3N0b24xDzANBgNVBAoMBkNoaXNlbDENMAsGA1UECwwEdGVzdDETMBEGA1UEAwwK
bVRMU0NsaWVudDEgMB4GCSqGSIb3DQEJARYRd2lsbHhpYUBnbWFpbC5jb20wgZ8w
DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4jt9TxHsCNegij34we4yzOykAuMVuz
DzW++Jh4/xWeOoU3xb7I2ETIzmusIM70o2lm+e+gy9VfAAXaNgZg63QV54jRn2nk
BWoXJYvYOJwt5YzOsLkbh6epSlrqYI0H34Sy5rEkacXCkcpcEvom/tvJ+SpHyIL1
PYNN1CCx/eg5AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAJysuLKCgVqMW628SFcpu
ojtBSNy2KETDwmMTaLg/XTaAPOvxAO3W9F7KJ1JxVFf2oIW7ROL9sP862lSMQLZ5
R45pBlPZycb1CQplD50wMqknaaMJ1qnld9Jkv802cJa2riqzdHb5rnjrewmuLOOB
V0cZ9PJA3KdXbJW1o+WjQz4=
-----END CERTIFICATE-----
+15
View File
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDOI7fU8R7AjXoIo9+MHuMszspALjFbsw81vviYeP8VnjqFN8W+
yNhEyM5rrCDO9KNpZvnvoMvVXwAF2jYGYOt0FeeI0Z9p5AVqFyWL2DicLeWMzrC5
G4enqUpa6mCNB9+EsuaxJGnFwpHKXBL6Jv7byfkqR8iC9T2DTdQgsf3oOQIDAQAB
AoGAArLhAz6s4mR3xokusgzteHa0myZ/qu2rM07uvkBHRqctqPTT9+11N2FRooM8
Yrk9MnIQr5xxTrfRrkHvFyJJstNX809ve9Klu1vbT+S19se/m+jLKTOtOoYoPRaK
w7ekvjhLct00zQevphEX30xqA2S3HSjWD3HmjVwdadAUgQECQQDz+LShNRkJ97+n
hiRgShHupW7CmAb67hrenbbzkCaY8Kf9cAFiscEmjH+lZsbufCgzVvHKDNKi9/JN
dPTSQvURAkEA2E2D0BqTDOiqjwyueSr2V5m63mzWR0Jd1TAl0dxB6SBumYQQ1FFP
DmQ/J3lcT2RTS+PmKAkuPpSOalw1kqggqQJAbotPVQgZG1IdjguS6epF68sbv6Jg
70v58sqlfgDf7EaG56fbiNuf+BaLM+e41ZB+Kp0Hm5Rp0JvmN0B6OddK8QJAcZbD
UdWiw3SrnNOcDCVzmC0y5Ptiy6kefYX7VmnEcxiE/DlOXTEVwwkB4UjqIQcedwwH
IZ8wmcyJvXEO8SU5gQJAfHxBcFdX2vrDNNjm5GG11zrT86Ii+ieXa0Ty5vapRSsz
FQH3KnM2t7nNDMlFOaHuvVXHmPasudtxBDc5xDoHNA==
-----END RSA PRIVATE KEY-----
+16
View File
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICfTCCAeYCCQDIXTlEp6na1zANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMC
dXMxCzAJBgNVBAgMAm1hMQ8wDQYDVQQHDAZCb3N0b24xDzANBgNVBAoMBkNoaXNl
bDENMAsGA1UECwwEdGVzdDETMBEGA1UEAwwKbVRMU0NsaWVudDEgMB4GCSqGSIb3
DQEJARYRd2lsbHhpYUBnbWFpbC5jb20wHhcNMjAwODI0MTk0ODQxWhcNMzAwODIy
MTk0ODQxWjCBgjELMAkGA1UEBhMCdXMxCzAJBgNVBAgMAm1hMQ8wDQYDVQQHDAZC
b3N0b24xDzANBgNVBAoMBkNoaXNlbDENMAsGA1UECwwEdGVzdDETMBEGA1UEAwwK
bVRMU0NsaWVudDEgMB4GCSqGSIb3DQEJARYRd2lsbHhpYUBnbWFpbC5jb20wgZ8w
DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4jt9TxHsCNegij34we4yzOykAuMVuz
DzW++Jh4/xWeOoU3xb7I2ETIzmusIM70o2lm+e+gy9VfAAXaNgZg63QV54jRn2nk
BWoXJYvYOJwt5YzOsLkbh6epSlrqYI0H34Sy5rEkacXCkcpcEvom/tvJ+SpHyIL1
PYNN1CCx/eg5AgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAJysuLKCgVqMW628SFcpu
ojtBSNy2KETDwmMTaLg/XTaAPOvxAO3W9F7KJ1JxVFf2oIW7ROL9sP862lSMQLZ5
R45pBlPZycb1CQplD50wMqknaaMJ1qnld9Jkv802cJa2riqzdHb5rnjrewmuLOOB
V0cZ9PJA3KdXbJW1o+WjQz4=
-----END CERTIFICATE-----
+16
View File
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICezCCAeQCCQDwdWskfbwmzzANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UEBhMC
dXMxCzAJBgNVBAgMAm1hMQ8wDQYDVQQHDAZCb3N0b24xDzANBgNVBAoMBkNoaXNl
bDENMAsGA1UECwwEdGVzdDESMBAGA1UEAwwJbG9jYWxob3N0MSAwHgYJKoZIhvcN
AQkBFhF3aWxseGlhQGdtYWlsLmNvbTAeFw0yMDA4MjQxOTQ4MTdaFw0zMDA4MjIx
OTQ4MTdaMIGBMQswCQYDVQQGEwJ1czELMAkGA1UECAwCbWExDzANBgNVBAcMBkJv
c3RvbjEPMA0GA1UECgwGQ2hpc2VsMQ0wCwYDVQQLDAR0ZXN0MRIwEAYDVQQDDAls
b2NhbGhvc3QxIDAeBgkqhkiG9w0BCQEWEXdpbGx4aWFAZ21haWwuY29tMIGfMA0G
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC128s6L6YN0eNSNbI40URFHd9xzfnPlUcH
n9n7D6YkJL7LsTAtUfjubNAX0Q1gclDnDZCfYi9UZVzzID4s1gZJZAEZGnce8loO
a+WcPUgIOJngk2bwUHfrWPl+R5mvE9p60rfYNdo86wLMaLAJu+VagNmaoilSU7OS
uZ/AgTUMFQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAEzq2qsH5VfmjUcvlhS4a7X5
zOAtEIVB1+oef/1NcyT3PaMX0ry0Ddbo3NJs3G9KTF0k+TCGtT7nAG2jRQvs6omZ
3+9C3x+6TQq+95KMBWXuZLZEPNa4iCGFbGrHq4wcWDehBAPSjdctqnmowd8yIgov
gNSN2xEMPNKYIhHt0lyc
-----END CERTIFICATE-----
+15
View File
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC128s6L6YN0eNSNbI40URFHd9xzfnPlUcHn9n7D6YkJL7LsTAt
UfjubNAX0Q1gclDnDZCfYi9UZVzzID4s1gZJZAEZGnce8loOa+WcPUgIOJngk2bw
UHfrWPl+R5mvE9p60rfYNdo86wLMaLAJu+VagNmaoilSU7OSuZ/AgTUMFQIDAQAB
AoGBAJva4JLfXyqc5HsCNdlnz2CEt4irBBsZTiSEpKX7xWFYdIPROP6+L972NmkS
6qnrjtZV08oktXdY344l5eM7EWrFnqKH1pyTRUPnyKGY53jY4yZMad1GYMXLo8Mj
gkEOsfIhuieEBKGXAX54moDLTFzn14q+V+7g3OrLmMYXFN7JAkEA54S00eeuy0Eg
6+qx9dO4iDBp3qut5PShjda4M11MWobRQH71gO0g25qSrnw0x7BXJQl1hhgYtSUy
zbaF+5ZORwJBAMkWxt55YDpXdP4vudugmzP7F8aSB6rUysowlv1uStFhARNYwgs+
Tl9EGhFPF0ganNv4di1iYLarIKoWas8nNMMCQEex4ekKzSdmUNKeCGQvH3sVOwPY
uG4pj4oED2DgqI90JoLJji9Rv5YiBQCBuDqKkkIG7t0Kw0P9dAEeX9lsT2sCQD5x
iznEmSQkyliwe1d/LRLcMwrfh+/9eieFJS33lNYl+E6IrmENbQraO/oKBGHImdMY
+aGoPf4bb95BbdN8Cj8CQECXWVHkFFVQ2B78r7ENWnmbVG5XJW/iwfaZtmbsPPrI
KqGlB8leQcLLlCC48SCLlc64VtWOaJVxBDyvO4NuD/U=
-----END RSA PRIVATE KEY-----
+134 -3
View File
@@ -1,5 +1,136 @@
package e2e_test
//TODO tests for:
// PRE-REQ generate local certs
// - client TLS (using local CA) -> server TLS (using local files)
import (
"testing"
chclient "github.com/jpillora/chisel/client"
chserver "github.com/jpillora/chisel/server"
)
func TestTls(t *testing.T) {
tmpPort := availablePort()
//setup server, client, fileserver
teardown := simpleSetup(t,
&chserver.Config{
TLS: chserver.TLSConfig{
Cert: "tls/server-crt/server.crt",
Key: "tls/server-crt/server.key",
CA: "tls/server-ca/client.crt",
},
},
&chclient.Config{
Remotes: []string{tmpPort + ":$FILEPORT"},
TLS: chclient.TLSConfig{
SkipVerify: false,
//for self signed cert, it needs the server cert, for real cert, this need to be the trusted CA cert
CA: "tls/client-ca/server.crt",
Cert: "tls/client-crt/client.crt",
Key: "tls/client-crt/client.key",
},
Server: "https://localhost:" + tmpPort,
})
defer teardown()
//test remote
result, err := post("http://localhost:"+tmpPort, "foo")
if err != nil {
t.Fatal(err)
}
if result != "foo!" {
t.Fatalf("expected exclamation mark added")
}
}
func TestTlsMitiCAs(t *testing.T) {
tmpPort := availablePort()
//setup server, client, fileserver
teardown := simpleSetup(t,
&chserver.Config{
TLS: chserver.TLSConfig{
Cert: "tls/server-crt/server.crt",
Key: "tls/server-crt/server.key",
CA: "tls/server-ca",
},
},
&chclient.Config{
Remotes: []string{tmpPort + ":$FILEPORT"},
TLS: chclient.TLSConfig{
SkipVerify: false,
//for self signed cert, it needs the server cert, for real cert, this need to be the trusted CA cert
CA: "tls/client-ca/server.crt",
Cert: "tls/client-crt/client.crt",
Key: "tls/client-crt/client.key",
},
Server: "https://localhost:" + tmpPort,
})
defer teardown()
//test remote
result, err := post("http://localhost:"+tmpPort, "foo")
if err != nil {
t.Fatal(err)
}
if result != "foo!" {
t.Fatalf("expected exclamation mark added")
}
}
func TestTlsMissingClientCert(t *testing.T) {
tmpPort := availablePort()
//setup server, client, fileserver
teardown := simpleSetup(t,
&chserver.Config{
TLS: chserver.TLSConfig{
Cert: "tls/server-crt/server.crt",
Key: "tls/server-crt/server.key",
CA: "tls/server-ca/client.crt",
},
},
&chclient.Config{
Remotes: []string{tmpPort + ":$FILEPORT"},
TLS: chclient.TLSConfig{
SkipVerify: false,
CA: "tls/client-ca/server.crt",
//provide no client cert, server should reject the client request
//Cert: "tls/client-crt/client.crt",
//Key: "tls/client-crt/client.key",
},
Server: "https://localhost:" + tmpPort,
})
defer teardown()
//test remote
_, err := post("http://localhost:"+tmpPort, "foo")
if err == nil {
t.Fatal(err)
}
}
func TestTlsMissingClientCA(t *testing.T) {
tmpPort := availablePort()
//setup server, client, fileserver
teardown := simpleSetup(t,
&chserver.Config{
TLS: chserver.TLSConfig{
Cert: "tls/server-crt/server.crt",
Key: "tls/server-crt/server.key",
//specify a CA which does not match the client cert
//server should reject the client request
CA: "tls/server-crt/server.crt",
},
},
&chclient.Config{
Remotes: []string{tmpPort + ":$FILEPORT"},
TLS: chclient.TLSConfig{
SkipVerify: false,
//for self signed cert, it needs the server cert, for real cert, this need to be the trusted CA cert
CA: "tls/client-ca/server.crt",
Cert: "tls/client-crt/client.crt",
Key: "tls/client-crt/client.key",
},
Server: "https://localhost:" + tmpPort,
})
defer teardown()
//test remote
_, err := post("http://localhost:"+tmpPort, "foo")
if err == nil {
t.Fatal(err)
}
}