diff --git a/client/client.go b/client/client.go index 49b62e65..66de1351 100644 --- a/client/client.go +++ b/client/client.go @@ -2,10 +2,15 @@ package chclient import ( "context" + "crypto/ecdh" + "crypto/ecdsa" + "crypto/ed25519" "crypto/md5" + "crypto/rsa" "crypto/tls" "crypto/x509" "encoding/base64" + "encoding/pem" "errors" "fmt" "net" @@ -23,6 +28,7 @@ import ( "github.com/jpillora/chisel/share/cnet" "github.com/jpillora/chisel/share/settings" "github.com/jpillora/chisel/share/tunnel" + "github.com/youmark/pkcs8" "golang.org/x/crypto/ssh" "golang.org/x/net/proxy" @@ -51,6 +57,7 @@ type TLSConfig struct { CA string Cert string Key string + KeyPass string ServerName string } @@ -129,7 +136,43 @@ func NewClient(c *Config) (*Client, error) { } //provide client cert and key pair for mtls if c.TLS.Cert != "" && c.TLS.Key != "" { - c, err := tls.LoadX509KeyPair(c.TLS.Cert, c.TLS.Key) + k, err := os.ReadFile(c.TLS.Key) + if err != nil { + return nil, fmt.Errorf("Failed to load file: %s", c.TLS.Key) + } + pemBlock, _ := pem.Decode(k) + var privateKey any + if strings.HasPrefix(pemBlock.Type, "ENCRYPTED") { + privateKey, err = pkcs8.ParsePKCS8PrivateKey(pemBlock.Bytes, []byte(c.TLS.KeyPass)) + } else { + privateKey, err = pkcs8.ParsePKCS8PrivateKey(pemBlock.Bytes, nil) + } + if err != nil { + return nil, fmt.Errorf("%s", err) + } + switch pk := privateKey.(type) { + case *rsa.PrivateKey: + privateKey = pk + case ed25519.PrivateKey: + privateKey = pk + case *ecdh.PrivateKey: + privateKey = pk + case *ecdsa.PrivateKey: + privateKey = pk + default: + return nil, fmt.Errorf("Unknown private key type") + } + privateKeyDER, _ := x509.MarshalPKCS8PrivateKey(privateKey) + pemBlock = &pem.Block{ + Type: "PRIVATE KEY", + Bytes: privateKeyDER, + } + privateKeyPEMBlock := pem.EncodeToMemory(pemBlock) + certPEMBlock, err := os.ReadFile(c.TLS.Cert) + if err != nil { + return nil, fmt.Errorf("Failed to load file: %s", c.TLS.Cert) + } + c, err := tls.X509KeyPair(certPEMBlock, privateKeyPEMBlock) if err != nil { return nil, fmt.Errorf("Error loading client cert and key pair: %v", err) } diff --git a/go.mod b/go.mod index 34f0abe2..a8290bbf 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,9 @@ require ( github.com/jpillora/backoff v1.0.0 github.com/jpillora/requestlog v1.0.0 github.com/jpillora/sizestr v1.0.0 - golang.org/x/crypto v0.16.0 - golang.org/x/net v0.14.0 + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 + golang.org/x/crypto v0.22.0 + golang.org/x/net v0.21.0 golang.org/x/sync v0.5.0 ) @@ -18,6 +19,6 @@ require ( github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect github.com/jpillora/ansi v1.0.3 // indirect github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index e2f4898d..676602e3 100644 --- a/go.sum +++ b/go.sum @@ -16,16 +16,18 @@ github.com/jpillora/sizestr v1.0.0 h1:4tr0FLxs1Mtq3TnsLDV+GYUWG7Q26a6s+tV5Zfw2yg github.com/jpillora/sizestr v1.0.0/go.mod h1:bUhLv4ctkknatr6gR42qPxirmd5+ds1u7mzD+MZ33f0= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= diff --git a/main.go b/main.go index 01f9ca3b..fd0cffd5 100644 --- a/main.go +++ b/main.go @@ -154,12 +154,15 @@ var serverHelp = ` --reverse, Allow clients to specify reverse port forwarding remotes in addition to normal remotes. - --tls-key, Enables TLS and provides optional path to a PEM-encoded + --tls-key, Enables TLS and provides optional path to a PEM-encoded TLS private key. When this flag is set, you must also set --tls-cert, and you cannot set --tls-domain. + --tls-keypass, Specifies a password to decrypt a PEM-encoded encrypted + private key in PKCS#8 format. + --tls-cert, Enables TLS and provides optional path to a PEM-encoded - TLS certificate. When this flag is set, you must also set --tls-key, + TLS certificate. When this flag is set, you must also set --tls-key, and you cannot set --tls-domain. --tls-domain, Enables TLS and automatically acquires a TLS key and @@ -191,6 +194,7 @@ func server(args []string) { flags.BoolVar(&config.Socks5, "socks5", false, "") flags.BoolVar(&config.Reverse, "reverse", false, "") flags.StringVar(&config.TLS.Key, "tls-key", "", "") + flags.StringVar(&config.TLS.KeyPass, "tls-keypass", "", "") flags.StringVar(&config.TLS.Cert, "tls-cert", "", "") flags.Var(multiFlag{&config.TLS.Domains}, "tls-domain", "") flags.StringVar(&config.TLS.CA, "tls-ca", "", "") @@ -412,9 +416,12 @@ var clientHelp = ` may be still verified (see --fingerprint) after inner connection is established. - --tls-key, a path to a PEM encoded private key used for client + --tls-key, a path to a PEM-encoded private key used for client authentication (mutual-TLS). + --tls-keypass, Specifies a password to decrypt a PEM-encoded encrypted + private key in PKCS#8 format (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). @@ -433,6 +440,7 @@ func client(args []string) { flags.BoolVar(&config.TLS.SkipVerify, "tls-skip-verify", false, "") flags.StringVar(&config.TLS.Cert, "tls-cert", "", "") flags.StringVar(&config.TLS.Key, "tls-key", "", "") + flags.StringVar(&config.TLS.KeyPass, "tls-keypass", "", "") flags.Var(&headerFlags{config.Headers}, "header", "") hostname := flags.String("hostname", "", "") sni := flags.String("sni", "", "") diff --git a/server/server.go b/server/server.go index 8a702fce..addc38a3 100644 --- a/server/server.go +++ b/server/server.go @@ -169,6 +169,7 @@ func (s *Server) StartContext(ctx context.Context, host, port string) error { } l, err := s.listener(host, port) if err != nil { + s.Infof("%s", err) return err } h := http.Handler(http.HandlerFunc(s.handleClientHandler)) diff --git a/server/server_listen.go b/server/server_listen.go index f6eb1ffa..aaca8c5c 100644 --- a/server/server_listen.go +++ b/server/server_listen.go @@ -1,21 +1,29 @@ package chserver import ( + "crypto/ecdh" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" "crypto/tls" "crypto/x509" + "encoding/pem" "errors" "net" "os" "os/user" "path/filepath" + "strings" "github.com/jpillora/chisel/share/settings" + "github.com/youmark/pkcs8" "golang.org/x/crypto/acme/autocert" ) -//TLSConfig enables configures TLS +// TLSConfig enables configures TLS type TLSConfig struct { Key string + KeyPass string Cert string Domains []string CA string @@ -33,7 +41,7 @@ func (s *Server) listener(host, port string) (net.Listener, error) { } extra := "" if hasKeyCert { - c, err := s.tlsKeyCert(s.config.TLS.Key, s.config.TLS.Cert, s.config.TLS.CA) + c, err := s.tlsKeyCert(s.config.TLS.Key, s.config.TLS.KeyPass, s.config.TLS.Cert, s.config.TLS.CA) if err != nil { return nil, err } @@ -88,8 +96,44 @@ func (s *Server) tlsLetsEncrypt(domains []string) *tls.Config { return m.TLSConfig() } -func (s *Server) tlsKeyCert(key, cert string, ca string) (*tls.Config, error) { - keypair, err := tls.LoadX509KeyPair(cert, key) +func (s *Server) tlsKeyCert(key, keypass string, cert string, ca string) (*tls.Config, error) { + k, err := os.ReadFile(key) + if err != nil { + return nil, err + } + pemBlock, _ := pem.Decode(k) + var privateKey any + if strings.HasPrefix(pemBlock.Type, "ENCRYPTED") { + privateKey, err = pkcs8.ParsePKCS8PrivateKey(pemBlock.Bytes, []byte(keypass)) + } else { + privateKey, err = pkcs8.ParsePKCS8PrivateKey(pemBlock.Bytes, nil) + } + if err != nil { + return nil, err + } + switch pk := privateKey.(type) { + case *rsa.PrivateKey: + privateKey = pk + case ed25519.PrivateKey: + privateKey = pk + case *ecdh.PrivateKey: + privateKey = pk + case *ecdsa.PrivateKey: + privateKey = pk + default: + return nil, errors.New("Unknown private key type") + } + privateKeyDER, _ := x509.MarshalPKCS8PrivateKey(privateKey) + pemBlock = &pem.Block{ + Type: "PRIVATE KEY", + Bytes: privateKeyDER, + } + privateKeyPEMBlock := pem.EncodeToMemory(pemBlock) + certPEMBlock, err := os.ReadFile(cert) + if err != nil { + return nil, err + } + keypair, err := tls.X509KeyPair(certPEMBlock, privateKeyPEMBlock) if err != nil { return nil, err }