Skip to content

Commit

Permalink
Implement mTLS authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
Doridian committed Jun 19, 2022
1 parent 19a5ab9 commit 842100f
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 20 deletions.
44 changes: 33 additions & 11 deletions client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ var ifaceName = flag.String("interface-name", "", "Interface name of the interfa

var caCertFile = flag.String("ca-certificates", "", "If specified, use all PEM certs in this file as valid root certs only")
var insecure = flag.Bool("insecure", false, "Disable all TLS verification")
var tlsClientCert = flag.String("tls-client-cert", "", "TLS certificate file for client authentication")
var tlsClientKey = flag.String("tls-client-key", "", "TLS key file for client authentication")

func runEventScript(script *string, op string, cRemoteNet *remoteNet, iface *water.Interface) error {
if script == nil {
Expand Down Expand Up @@ -88,17 +90,6 @@ func main() {
log.Printf("[C] WARNING: You have put your password on the command line! This can cause security issues!")
}

header := http.Header{}
if userInfo != nil {
log.Printf("[C] Connecting to %s as user %s", dest.Redacted(), userInfo.Username())
if _, pws := userInfo.Password(); !pws {
log.Printf("[C] WARNING: You have specified to connect with a username but without a password!")
}
header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(userInfo.String())))
} else {
log.Printf("[C] WARNING: Connecting to %s without authentication!", dest.String())
}

dialer := websocket.Dialer{}

proxyUrlString := *proxyAddr
Expand Down Expand Up @@ -137,6 +128,37 @@ func main() {
tlsConfig.RootCAs = certPool
}

tlsClientCertStr := *tlsClientCert
tlsClientKeyStr := *tlsClientKey
if tlsClientCertStr != "" || tlsClientKeyStr != "" {
if tlsClientCertStr == "" || tlsClientKeyStr == "" {
panic(errors.New("provide either both tls-client-key and tls-client-cert or neither"))
}

tlsClientCertX509, err := tls.LoadX509KeyPair(tlsClientCertStr, tlsClientKeyStr)
if err != nil {
panic(err)
}
tlsConfig.Certificates = []tls.Certificate{tlsClientCertX509}
}

header := http.Header{}
if userInfo != nil {
if tlsClientCertStr == "" {
log.Printf("[C] Connecting to %s as user %s", dest.Redacted(), userInfo.Username())
} else {
log.Printf("[C] Connecting to %s as user %s with mutual TLS authentication", dest.Redacted(), userInfo.Username())
}
if _, pws := userInfo.Password(); !pws {
log.Printf("[C] WARNING: You have specified to connect with a username but without a password!")
}
header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(userInfo.String())))
} else if tlsClientCertStr == "" {
log.Printf("[C] WARNING: Connecting to %s without authentication!", dest.Redacted())
} else {
log.Printf("[C] Connecting to %s with mutual TLS authentication", dest.Redacted())
}

conn, _, err := dialer.Dial(dest.String(), header)
if err != nil {
panic(err)
Expand Down
4 changes: 2 additions & 2 deletions server/authenticators/allow_all.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ func (a *AllowAllAuthenticator) Load(configFile string) error {
return nil
}

func (a *AllowAllAuthenticator) Authenticate(r *http.Request, w http.ResponseWriter) AuthResult {
return AUTH_OK
func (a *AllowAllAuthenticator) Authenticate(r *http.Request, w http.ResponseWriter) (AuthResult, string) {
return AUTH_OK, ""
}
2 changes: 1 addition & 1 deletion server/authenticators/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ const (

type Authenticator interface {
Load(configFile string) error
Authenticate(r *http.Request, w http.ResponseWriter) AuthResult
Authenticate(r *http.Request, w http.ResponseWriter) (AuthResult, string)
}
8 changes: 4 additions & 4 deletions server/authenticators/htpasswd.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,18 @@ func respondWWWAuthenticateBasic(w http.ResponseWriter) {
w.Header().Add("WWW-Authenticate", "Basic")
}

func (a *HtpasswdAuthenticator) Authenticate(r *http.Request, w http.ResponseWriter) AuthResult {
func (a *HtpasswdAuthenticator) Authenticate(r *http.Request, w http.ResponseWriter) (AuthResult, string) {
username, password, ok := r.BasicAuth()
if !ok {
respondWWWAuthenticateBasic(w)
return AUTH_FAILED_DEFAULT
return AUTH_FAILED_DEFAULT, ""
}

authOk := a.authFile.Match(username, password)
if !authOk {
respondWWWAuthenticateBasic(w)
return AUTH_FAILED_DEFAULT
return AUTH_FAILED_DEFAULT, ""
}

return AUTH_OK
return AUTH_OK, username
}
47 changes: 45 additions & 2 deletions server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package main

import (
"crypto/tls"
"crypto/x509"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
Expand Down Expand Up @@ -43,6 +45,7 @@ var useClientToClient = flag.Bool("allow-client-to-client", false, "Allow client

var tlsCert = flag.String("tls-cert", "", "TLS certificate file for listener")
var tlsKey = flag.String("tls-key", "", "TLS key file for listener")
var tlsClientCA = flag.String("tls-client-ca", "", "If set, performs TLS client certificate authentication based on given CA certificate")

var subnet *net.IPNet
var ipServer net.IP
Expand Down Expand Up @@ -142,12 +145,34 @@ func main() {

tlsCertStr := *tlsCert
tlsKeyStr := *tlsKey
if tlsCertStr != "" || tlsKeyStr != "" {
tlsClientCAStr := *tlsClientCA
if tlsCertStr != "" || tlsKeyStr != "" || tlsClientCAStr != "" {
if tlsCertStr == "" && tlsKeyStr == "" {
panic(errors.New("tls-client-ca requires tls-key and tls-cert"))
}

if tlsCertStr == "" || tlsKeyStr == "" {
panic(errors.New("provide either both tls-key and tls-cert or neither"))
}

tlsConfig := &tls.Config{}

if tlsClientCAStr != "" {
tlsClientCAPEM, err := ioutil.ReadFile(tlsClientCAStr)
if err != nil {
panic(err)
}

tlsClientCAPool := x509.NewCertPool()
ok := tlsClientCAPool.AppendCertsFromPEM(tlsClientCAPEM)
if !ok {
panic(errors.New("error reading tls-client-ca PEM"))
}

tlsConfig.ClientCAs = tlsClientCAPool
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}

shared.TlsUseFlags(tlsConfig)

server := http.Server{
Expand Down Expand Up @@ -195,14 +220,23 @@ func serveTap() {
}

func serveWs(w http.ResponseWriter, r *http.Request) {
authResult := authenticator.Authenticate(r, w)
tlsUsername := ""
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
tlsUsername = r.TLS.PeerCertificates[0].Subject.CommonName
}
authResult, authUsername := authenticator.Authenticate(r, w)
if authResult != authenticators.AUTH_OK {
if authResult == authenticators.AUTH_FAILED_DEFAULT {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
}
return
}

if authUsername != "" && tlsUsername != "" && authUsername != tlsUsername {
http.Error(w, "Mutual TLS CN is not equal authenticator username", http.StatusUnauthorized)
return
}

var err error

conn, err := upgrader.Upgrade(w, r, nil)
Expand All @@ -221,6 +255,7 @@ func serveWs(w http.ResponseWriter, r *http.Request) {
}
err = extendTUNConfig(&tunConfig)
if err != nil {
ifaceCreationMutex.Unlock()
log.Printf("[S] Error extending TUN config: %v", err)
conn.Close()
return
Expand Down Expand Up @@ -256,6 +291,14 @@ func serveWs(w http.ResponseWriter, r *http.Request) {

log.Printf("[%s] Client ENTER (interface %s)", connId, iface.Name())

if authUsername != "" && tlsUsername != "" {
log.Printf("[%s] Client authenticated as %s via TLS and authenticator", connId, authUsername)
} else if authUsername != "" {
log.Printf("[%s] Client authenticated as %s via authenticator", connId, authUsername)
} else if tlsUsername != "" {
log.Printf("[%s] Client authenticated as %s via TLS", connId, tlsUsername)
}

adapter := adapters.NewWebSocketAdapter(conn)
socket := sockets.MakeSocket(connId, adapter, iface, tapMode)

Expand Down

0 comments on commit 842100f

Please sign in to comment.