diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9a5e63..f0f9a61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,8 +100,35 @@ jobs: - name: Encrypt with AES-CTR run: | set -eux - ./piping-tunnel -s http://localhost:8080 server -p 2022 --symmetric --cipher-type=aes-ctr --passphrase=mypass aaa bbb & - ./piping-tunnel -s http://localhost:8080 client -p 3322 --symmetric --cipher-type=aes-ctr --passphrase=mypass aaa bbb & + ./piping-tunnel -s http://localhost:8080 server -p 2022 --symmetric --cipher-type=aes-ctr --pass=mypass aesctraaa aesctrbbb & + ./piping-tunnel -s http://localhost:8080 client -p 3322 --symmetric --cipher-type=aes-ctr --pass=mypass aesctraaa aesctrbbb & + sleep 1 + # (base: -o option: https://www.cyberithub.com/ssh-host-key-verification-failed-error-in-linux/) + ssh -p 3322 -o 'StrictHostKeyChecking no' guest@localhost hostname + + - name: Encrypt with OpenSSL-compabile AES-CTR + run: | + set -eux + ./piping-tunnel -s http://localhost:8080 server -p 2022 --symmetric --cipher-type=openssl-aes-256-ctr --pbkdf2='{"iter":100000,"hash":"sha256"}' --pass=mypass openssl1aaa openssl1bbb & echo $! > pid1 + ./piping-tunnel -s http://localhost:8080 client -p 3322 --symmetric --cipher-type=openssl-aes-256-ctr --pbkdf2='{"iter":100000,"hash":"sha256"}' --pass=mypass openssl1aaa openssl1bbb & echo $! > pid2 + sleep 1 + # (base: -o option: https://www.cyberithub.com/ssh-host-key-verification-failed-error-in-linux/) + ssh -p 3322 -o 'StrictHostKeyChecking no' guest@localhost hostname + + - name: Encrypt with OpenSSL-compabile AES-CTR using real openssl in server host + run: | + set -eux + curl -sSN http://localhost:8080/openssl2aaa | stdbuf -i0 -o0 openssl aes-256-ctr -d -pass "pass:mypass" -bufsize 1 -pbkdf2 -iter 100000 -md sha256 | nc localhost 2022 | stdbuf -i0 -o0 openssl aes-256-ctr -pass "pass:mypass" -bufsize 1 -pbkdf2 -iter 100000 -md sha256 | curl -sSNT - http://localhost:8080/openssl2bbb & + ./piping-tunnel -s http://localhost:8080 client -p 3322 --symmetric --cipher-type=openssl-aes-256-ctr --pbkdf2='{"iter":100000,"hash":"sha256"}' --pass=mypass openssl2aaa openssl2bbb & + sleep 1 + # (base: -o option: https://www.cyberithub.com/ssh-host-key-verification-failed-error-in-linux/) + ssh -p 3322 -o 'StrictHostKeyChecking no' guest@localhost hostname + + - name: Encrypt with OpenSSL-compabile AES-CTR using real openssl in client host + run: | + set -eux + ./piping-tunnel -s http://localhost:8080 server -p 2022 --symmetric --cipher-type=openssl-aes-256-ctr --pbkdf2='{"iter":100000,"hash":"sha256"}' --pass=mypass openssl3aaa openssl3bbb & + curl -NsS http://localhost:8080/openssl3bbb | stdbuf -i0 -o0 openssl aes-256-ctr -d -pass "pass:mypass" -bufsize 1 -pbkdf2 -iter 100000 -md sha256 | nc -l -p 3322 | stdbuf -i0 -o0 openssl aes-256-ctr -pass "pass:mypass" -bufsize 1 -pbkdf2 -iter 100000 -md sha256 | curl -NsST - http://localhost:8080/openssl3aaa & sleep 1 # (base: -o option: https://www.cyberithub.com/ssh-host-key-verification-failed-error-in-linux/) ssh -p 3322 -o 'StrictHostKeyChecking no' guest@localhost hostname @@ -124,9 +151,9 @@ jobs: run: | set -eux # Run server-host with yamux (encrypt with AES-CTR) - ./piping-tunnel -s http://localhost:8080 server -p 2022 --yamux --symmetric --cipher-type=aes-ctr --passphrase=mypass aaa-yamux bbb-yamux & echo $! > pid1 + ./piping-tunnel -s http://localhost:8080 server -p 2022 --yamux --symmetric --cipher-type=aes-ctr --pass=mypass aaa-yamux bbb-yamux & echo $! > pid1 # Run client-host with yamux (encrypt with AES-CTR) - ./piping-tunnel -s http://localhost:8080 client -p 4422 --yamux --symmetric --cipher-type=aes-ctr --passphrase=mypass aaa-yamux bbb-yamux & echo $! > pid2 + ./piping-tunnel -s http://localhost:8080 client -p 4422 --yamux --symmetric --cipher-type=aes-ctr --pass=mypass aaa-yamux bbb-yamux & echo $! > pid2 sleep 1 # Check whether ssh multiple times # (base: -o option: https://www.cyberithub.com/ssh-host-key-verification-failed-error-in-linux/) @@ -150,9 +177,9 @@ jobs: run: | set -eux # Run socks with yamux (encrypt with AES-CTR) - ./piping-tunnel -s http://localhost:8080 socks --yamux --symmetric --cipher-type=aes-ctr --passphrase=mypass aaa-socks bbb-socks & echo $! > pid1 + ./piping-tunnel -s http://localhost:8080 socks --yamux --symmetric --cipher-type=aes-ctr --pass=mypass aaa-socks bbb-socks & echo $! > pid1 # Run client-host with yamux (encrypt with AES-CTR) - ./piping-tunnel -s http://localhost:8080 client -p 1081 --yamux --symmetric --cipher-type=aes-ctr --passphrase=mypass aaa-socks bbb-socks & echo $! > pid2 + ./piping-tunnel -s http://localhost:8080 client -p 1081 --yamux --symmetric --cipher-type=aes-ctr --pass=mypass aaa-socks bbb-socks & echo $! > pid2 sleep 1 # NOTE: Depends on external resource: example.com curl -x socks5h://localhost:1081 https://example.com diff --git a/CHANGELOG.md b/CHANGELOG.md index 02c51fc..60acbec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) ## [Unreleased] +## [0.10.0] - 2021-08-09 +### Added +* Add OpenSSL-compatible AES-CTR encryption + +### Changed +* (breaking change) Rename --passphrase flag to --pass flag + ## [0.9.0] - 2021-04-23 ### Added * Create pmux, which is a multiplexer specialized in Piping Server @@ -90,7 +97,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) ### Added * Initial release -[Unreleased]: https://github.com/nwtgck/go-piping-tunnel/compare/v0.9.0...HEAD +[Unreleased]: https://github.com/nwtgck/go-piping-tunnel/compare/v0.10.0...HEAD +[0.10.0]: https://github.com/nwtgck/go-piping-tunnel/compare/v0.9.0...v0.10.0 [0.9.0]: https://github.com/nwtgck/go-piping-tunnel/compare/v0.8.0...v0.9.0 [0.8.0]: https://github.com/nwtgck/go-piping-tunnel/compare/v0.7.0...v0.8.0 [0.7.0]: https://github.com/nwtgck/go-piping-tunnel/compare/v0.6.0...v0.7.0 diff --git a/README.md b/README.md index 6d48940..1956749 100644 --- a/README.md +++ b/README.md @@ -82,11 +82,12 @@ Usage: piping-tunnel server [flags] Flags: - --cipher-type string Cipher type: aes-ctr, openpgp (default "aes-ctr") + --cipher-type string Cipher type: aes-ctr, openssl-aes-128-ctr, openssl-aes-256-ctr, openpgp (default "aes-ctr") --cs-buf-size uint Buffer size of client-to-server in bytes (default 16) -h, --help help for server --host string Target host (default "localhost") - --passphrase string Passphrase for encryption + --pass string Passphrase for encryption + --pbkdf2 string e.g. {"iter":100000,"hash":"sha256"} --pmux Multiplex connection by pmux (experimental) --pmux-config string pmux config in JSON (experimental) (default "{\"hb\": true}") -p, --port int TCP port of server host @@ -113,9 +114,10 @@ Usage: piping-tunnel client [flags] Flags: - --cipher-type string Cipher type: aes-ctr, openpgp (default "aes-ctr") + --cipher-type string Cipher type: aes-ctr, openssl-aes-128-ctr, openssl-aes-256-ctr, openpgp (default "aes-ctr") -h, --help help for client - --passphrase string Passphrase for encryption + --pass string Passphrase for encryption + --pbkdf2 string e.g. {"iter":100000,"hash":"sha256"} --pmux Multiplex connection by pmux (experimental) --pmux-config string pmux config in JSON (experimental) (default "{\"hb\": true}") -p, --port int TCP port of client host @@ -144,9 +146,10 @@ Usage: piping-tunnel socks [flags] Flags: - --cipher-type string Cipher type: aes-ctr, openpgp (default "aes-ctr") + --cipher-type string Cipher type: aes-ctr, openssl-aes-128-ctr, openssl-aes-256-ctr, openpgp (default "aes-ctr") -h, --help help for socks - --passphrase string Passphrase for encryption + --pass string Passphrase for encryption + --pbkdf2 string e.g. {"iter":100000,"hash":"sha256"} --pmux Multiplex connection by pmux (experimental) --pmux-config string pmux config in JSON (experimental) (default "{\"hb\": true}") -c, --symmetric Encrypt symmetrically diff --git a/crypto_duplex/aes_ctr.go b/aes_ctr_duplex/aes_ctr_duplex.go similarity index 93% rename from crypto_duplex/aes_ctr.go rename to aes_ctr_duplex/aes_ctr_duplex.go index fea4ab4..0d4eee1 100644 --- a/crypto_duplex/aes_ctr.go +++ b/aes_ctr_duplex/aes_ctr_duplex.go @@ -1,4 +1,4 @@ -package crypto_duplex +package aes_ctr_duplex import ( "crypto" @@ -19,7 +19,7 @@ type aesCtrDuplex struct { closeBaseReader func() error } -func EncryptDuplexWithAesCtr(baseWriter io.WriteCloser, baseReader io.ReadCloser, passphrase []byte) (*aesCtrDuplex, error) { +func Duplex(baseWriter io.WriteCloser, baseReader io.ReadCloser, passphrase []byte) (*aesCtrDuplex, error) { // Generate salt salt1, err := util.GenerateRandomBytes(saltLen) if err != nil { diff --git a/cmd/client.go b/cmd/client.go deleted file mode 100644 index c704bac..0000000 --- a/cmd/client.go +++ /dev/null @@ -1,327 +0,0 @@ -package cmd - -import ( - "encoding/json" - "fmt" - "github.com/hashicorp/yamux" - "github.com/nwtgck/go-piping-tunnel/piping_util" - "github.com/nwtgck/go-piping-tunnel/pmux" - "github.com/nwtgck/go-piping-tunnel/util" - "github.com/nwtgck/go-piping-tunnel/version" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "io" - "net" - "net/http" - "strconv" - "strings" -) - -var clientHostPort int -var clientHostUnixSocket string -var clientServerToClientBufSize uint -var clientYamux bool -var clientPmux bool -var clientPmuxConfig string -var clientSymmetricallyEncrypts bool -var clientSymmetricallyEncryptPassphrase string -var clientCipherType string - -func init() { - RootCmd.AddCommand(clientCmd) - clientCmd.Flags().IntVarP(&clientHostPort, "port", "p", 0, "TCP port of client host") - clientCmd.Flags().StringVarP(&clientHostUnixSocket, "unix-socket", "", "", "Unix socket of client host") - clientCmd.Flags().UintVarP(&clientServerToClientBufSize, "sc-buf-size", "", 16, "Buffer size of server-to-client in bytes") - clientCmd.Flags().BoolVarP(&clientYamux, yamuxFlagLongName, "", false, "Multiplex connection by hashicorp/yamux") - clientCmd.Flags().BoolVarP(&clientPmux, pmuxFlagLongName, "", false, "Multiplex connection by pmux (experimental)") - clientCmd.Flags().StringVarP(&clientPmuxConfig, pmuxConfigFlagLongName, "", `{"hb": true}`, "pmux config in JSON (experimental)") - clientCmd.Flags().BoolVarP(&clientSymmetricallyEncrypts, symmetricallyEncryptsFlagLongName, symmetricallyEncryptsFlagShortName, false, "Encrypt symmetrically") - clientCmd.Flags().StringVarP(&clientSymmetricallyEncryptPassphrase, symmetricallyEncryptPassphraseFlagLongName, "", "", "Passphrase for encryption") - clientCmd.Flags().StringVarP(&clientCipherType, cipherTypeFlagLongName, "", defaultCipherType, fmt.Sprintf("Cipher type: %s, %s", piping_util.CipherTypeAesCtr, piping_util.CipherTypeOpenpgp)) -} - -var clientCmd = &cobra.Command{ - Use: "client", - Short: "Run client-host", - RunE: func(cmd *cobra.Command, args []string) error { - // Validate cipher-type - if clientSymmetricallyEncrypts { - if err := validateClientCipher(clientCipherType); err != nil { - return nil - } - } - clientToServerPath, serverToClientPath, err := generatePaths(args) - if err != nil { - return err - } - headers, err := piping_util.ParseKeyValueStrings(headerKeyValueStrs) - if err != nil { - return err - } - httpClient := util.CreateHttpClient(insecure, httpWriteBufSize, httpReadBufSize) - if dnsServer != "" { - // Set DNS resolver - httpClient.Transport.(*http.Transport).DialContext = util.CreateDialContext(dnsServer) - } - clientToServerUrl, err := util.UrlJoin(serverUrl, clientToServerPath) - if err != nil { - return err - } - serverToClientUrl, err := util.UrlJoin(serverUrl, serverToClientPath) - if err != nil { - return err - } - var ln net.Listener - if clientHostUnixSocket == "" { - ln, err = net.Listen("tcp", fmt.Sprintf(":%d", clientHostPort)) - } else { - ln, err = net.Listen("unix", clientHostUnixSocket) - } - if err != nil { - return err - } - // Print hint - printHintForServerHost(ln, clientToServerUrl, serverToClientUrl, clientToServerPath, serverToClientPath) - // Make user input passphrase if it is empty - if clientSymmetricallyEncrypts { - err = makeUserInputPassphraseIfEmpty(&clientSymmetricallyEncryptPassphrase) - if err != nil { - return err - } - } - // Use multiplexer with yamux - if clientYamux { - fmt.Println("[INFO] Multiplexing with hashicorp/yamux") - return clientHandleWithYamux(ln, httpClient, headers, clientToServerUrl, serverToClientUrl) - } - // If pmux is enabled - if clientPmux { - fmt.Println("[INFO] Multiplexing with pmux") - return clientHandleWithPmux(ln, httpClient, headers, clientToServerUrl, serverToClientUrl) - } - conn, err := ln.Accept() - if err != nil { - return err - } - fmt.Println("[INFO] accepted") - // Refuse another new connection - ln.Close() - // If encryption is enabled - if clientSymmetricallyEncrypts { - var duplex io.ReadWriteCloser - duplex, err := piping_util.DuplexConnect(httpClient, headers, clientToServerUrl, serverToClientUrl) - if err != nil { - return err - } - duplex, err = makeDuplexWithEncryptionAndProgressIfNeed(duplex, clientSymmetricallyEncrypts, clientSymmetricallyEncryptPassphrase, clientCipherType) - if err != nil { - return err - } - fin := make(chan error) - go func() { - // TODO: hard code - var buf = make([]byte, 16) - _, err := io.CopyBuffer(duplex, conn, buf) - fin <- err - }() - go func() { - // TODO: hard code - var buf = make([]byte, 16) - _, err := io.CopyBuffer(conn, duplex, buf) - fin <- err - }() - return util.CombineErrors(<-fin, <-fin) - } - err = piping_util.HandleDuplex(httpClient, conn, headers, clientToServerUrl, serverToClientUrl, clientServerToClientBufSize, nil, showProgress, makeProgressMessage) - fmt.Println() - if err != nil { - return err - } - fmt.Println("[INFO] Finished") - - return nil - }, -} - -func printHintForServerHost(ln net.Listener, clientToServerUrl string, serverToClientUrl string, clientToServerPath string, serverToClientPath string) { - var listeningOn string - if addr, ok := ln.Addr().(*net.TCPAddr); ok { - // (base: https://stackoverflow.com/a/43425461) - clientHostPort = addr.Port - listeningOn = strconv.Itoa(addr.Port) - } else { - listeningOn = clientHostUnixSocket - } - fmt.Printf("[INFO] Client host listening on %s ...\n", listeningOn) - if !clientYamux && !clientPmux { - fmt.Println("[INFO] Hint: Server host (socat + curl)") - fmt.Printf( - " socat 'EXEC:curl -NsS %s!!EXEC:curl -NsST - %s' TCP:127.0.0.1:\n", - strings.Replace(clientToServerUrl, ":", "\\:", -1), - strings.Replace(serverToClientUrl, ":", "\\:", -1), - ) - } - fmt.Println("[INFO] Hint: Server host (piping-tunnel)") - flags := "" - if clientSymmetricallyEncrypts { - flags += fmt.Sprintf("-%s ", symmetricallyEncryptsFlagShortName) - if clientCipherType != defaultCipherType { - flags += fmt.Sprintf("--%s=%s ", cipherTypeFlagLongName, clientCipherType) - } - } - if clientYamux { - flags += fmt.Sprintf("--%s ", yamuxFlagLongName) - } - if clientPmux { - flags += fmt.Sprintf("--%s ", pmuxFlagLongName) - } - fmt.Printf( - " piping-tunnel -s %s server -p %s%s %s\n", - serverUrl, - flags, - clientToServerPath, - serverToClientPath, - ) - fmt.Println(" OR") - fmt.Printf( - " piping-tunnel -s %s socks %s%s %s\n", - serverUrl, - flags, - clientToServerPath, - serverToClientPath, - ) -} - -func clientHandleWithYamux(ln net.Listener, httpClient *http.Client, headers []piping_util.KeyValue, clientToServerUrl string, serverToClientUrl string) error { - var duplex io.ReadWriteCloser - duplex, err := piping_util.DuplexConnectWithHandlers( - func(body io.Reader) (*http.Response, error) { - return piping_util.PipingSend(httpClient, headersWithYamux(headers), clientToServerUrl, body) - }, - func() (*http.Response, error) { - res, err := piping_util.PipingGet(httpClient, headers, serverToClientUrl) - if err != nil { - return nil, err - } - contentType := res.Header.Get("Content-Type") - // NOTE: application/octet-stream is for compatibility - if contentType != yamuxMimeType && contentType != "application/octet-stream" { - return nil, errors.Errorf("invalid content-type: %s", contentType) - } - return res, nil - }, - ) - if err != nil { - return err - } - duplex, err = makeDuplexWithEncryptionAndProgressIfNeed(duplex, clientSymmetricallyEncrypts, clientSymmetricallyEncryptPassphrase, clientCipherType) - if err != nil { - return err - } - yamuxSession, err := yamux.Client(duplex, nil) - if err != nil { - return err - } - - for { - conn, err := ln.Accept() - if err != nil { - return err - } - yamuxStream, err := yamuxSession.Open() - if err != nil { - return err - } - fin := make(chan struct{}) - go func() { - // TODO: hard code - var buf = make([]byte, 16) - io.CopyBuffer(yamuxStream, conn, buf) - fin <- struct{}{} - }() - go func() { - // TODO: hard code - var buf = make([]byte, 16) - io.CopyBuffer(conn, yamuxStream, buf) - fin <- struct{}{} - }() - go func() { - <-fin - <-fin - close(fin) - conn.Close() - yamuxStream.Close() - }() - } -} - -func clientHandleWithPmux(ln net.Listener, httpClient *http.Client, headers []piping_util.KeyValue, clientToServerUrl string, serverToClientUrl string) error { - var config clientPmuxConfigJson - if json.Unmarshal([]byte(clientPmuxConfig), &config) != nil { - return errors.Errorf("invalid pmux config format") - } - pmuxClient, err := pmux.Client(httpClient, headers, clientToServerUrl, serverToClientUrl, config.Hb, clientSymmetricallyEncrypts, clientSymmetricallyEncryptPassphrase, clientCipherType) - if err != nil { - if err == pmux.NonPmuxMimeTypeError { - return errors.Errorf("--%s may be missing in server", pmuxFlagLongName) - } - if err == pmux.IncompatiblePmuxVersion { - return errors.Errorf("%s, hint: use the same piping-tunnel version (current: %s)", err.Error(), version.Version) - } - if err == pmux.IncompatibleServerConfigError { - return errors.Errorf("%s, hint: use the same piping-tunnel version (current: %s)", err.Error(), version.Version) - } - return err - } - for { - conn, err := ln.Accept() - if err != nil { - break - } - stream, err := pmuxClient.Open() - if err != nil { - vlog.Log( - fmt.Sprintf("error(pmux open): %v", errors.WithStack(err)), - fmt.Sprintf("error(pmux open): %+v", errors.WithStack(err)), - ) - continue - } - fin := make(chan struct{}) - go func() { - // TODO: hard code - var buf = make([]byte, 16) - _, err := io.CopyBuffer(conn, stream, buf) - fin <- struct{}{} - if err != nil { - vlog.Log( - fmt.Sprintf("error(pmux stream → conn): %v", errors.WithStack(err)), - fmt.Sprintf("error(pmux stream → conn): %+v", errors.WithStack(err)), - ) - return - } - }() - - go func() { - // TODO: hard code - var buf = make([]byte, 16) - _, err := io.CopyBuffer(stream, conn, buf) - fin <- struct{}{} - if err != nil { - vlog.Log( - fmt.Sprintf("error(conn → pmux stream): %v", errors.WithStack(err)), - fmt.Sprintf("error(conn → pmux stream): %+v", errors.WithStack(err)), - ) - return - } - }() - - go func() { - <-fin - <-fin - conn.Close() - stream.Close() - close(fin) - }() - } - return nil -} diff --git a/cmd/client/client.go b/cmd/client/client.go new file mode 100644 index 0000000..abdd604 --- /dev/null +++ b/cmd/client/client.go @@ -0,0 +1,356 @@ +package client + +import ( + "encoding/json" + "fmt" + "github.com/hashicorp/yamux" + "github.com/nwtgck/go-piping-tunnel/cmd" + "github.com/nwtgck/go-piping-tunnel/piping_util" + "github.com/nwtgck/go-piping-tunnel/pmux" + "github.com/nwtgck/go-piping-tunnel/util" + "github.com/nwtgck/go-piping-tunnel/version" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "io" + "net" + "net/http" + "strconv" +) + +var flag struct { + clientHostPort int + clientHostUnixSocket string + serverToClientBufSize uint + yamux bool + pmux bool + pmuxConfig string + symmetricallyEncrypts bool + symmetricallyEncryptPassphrase string + cipherType string + pbkdf2JsonString string +} + +func init() { + cmd.RootCmd.AddCommand(clientCmd) + clientCmd.Flags().IntVarP(&flag.clientHostPort, "port", "p", 0, "TCP port of client host") + clientCmd.Flags().StringVarP(&flag.clientHostUnixSocket, "unix-socket", "", "", "Unix socket of client host") + clientCmd.Flags().UintVarP(&flag.serverToClientBufSize, "sc-buf-size", "", 16, "Buffer size of server-to-client in bytes") + clientCmd.Flags().BoolVarP(&flag.yamux, cmd.YamuxFlagLongName, "", false, "Multiplex connection by hashicorp/yamux") + clientCmd.Flags().BoolVarP(&flag.pmux, cmd.PmuxFlagLongName, "", false, "Multiplex connection by pmux (experimental)") + clientCmd.Flags().StringVarP(&flag.pmuxConfig, cmd.PmuxConfigFlagLongName, "", `{"hb": true}`, "pmux config in JSON (experimental)") + clientCmd.Flags().BoolVarP(&flag.symmetricallyEncrypts, cmd.SymmetricallyEncryptsFlagLongName, cmd.SymmetricallyEncryptsFlagShortName, false, "Encrypt symmetrically") + clientCmd.Flags().StringVarP(&flag.symmetricallyEncryptPassphrase, cmd.SymmetricallyEncryptPassphraseFlagLongName, "", "", "Passphrase for encryption") + clientCmd.Flags().StringVarP(&flag.cipherType, cmd.CipherTypeFlagLongName, "", cmd.DefaultCipherType, fmt.Sprintf("Cipher type: %s, %s, %s, %s ", piping_util.CipherTypeAesCtr, piping_util.CipherTypeOpensslAes128Ctr, piping_util.CipherTypeOpensslAes256Ctr, piping_util.CipherTypeOpenpgp)) + // NOTE: default value of --pbkdf2 should be empty to detect key derive derivation from multiple algorithms in the future. + clientCmd.Flags().StringVarP(&flag.pbkdf2JsonString, cmd.Pbkdf2FlagLongName, "", "", fmt.Sprintf("e.g. %s", cmd.ExamplePbkdf2JsonStr())) +} + +var clientCmd = &cobra.Command{ + Use: "client", + Short: "Run client-host", + RunE: func(_ *cobra.Command, args []string) error { + // Validate cipher-type + if flag.symmetricallyEncrypts { + if err := cmd.ValidateClientCipher(flag.cipherType); err != nil { + return err + } + } + clientToServerPath, serverToClientPath, err := cmd.GeneratePaths(args) + if err != nil { + return err + } + headers, err := piping_util.ParseKeyValueStrings(cmd.HeaderKeyValueStrs) + if err != nil { + return err + } + httpClient := util.CreateHttpClient(cmd.Insecure, cmd.HttpWriteBufSize, cmd.HttpReadBufSize) + if cmd.DnsServer != "" { + // Set DNS resolver + httpClient.Transport.(*http.Transport).DialContext = util.CreateDialContext(cmd.DnsServer) + } + clientToServerUrl, err := util.UrlJoin(cmd.ServerUrl, clientToServerPath) + if err != nil { + return err + } + serverToClientUrl, err := util.UrlJoin(cmd.ServerUrl, serverToClientPath) + if err != nil { + return err + } + var ln net.Listener + if flag.clientHostUnixSocket == "" { + ln, err = net.Listen("tcp", fmt.Sprintf(":%d", flag.clientHostPort)) + } else { + ln, err = net.Listen("unix", flag.clientHostUnixSocket) + } + if err != nil { + return err + } + var opensslAesCtrParams *cmd.OpensslAesCtrParams = nil + if flag.symmetricallyEncrypts { + opensslAesCtrParams, err = cmd.ParseOpensslAesCtrParams(flag.cipherType, flag.pbkdf2JsonString) + if err != nil { + return err + } + } + // Print hint + printHintForServerHost(ln, clientToServerUrl, serverToClientUrl, clientToServerPath, serverToClientPath, opensslAesCtrParams) + // Make user input passphrase if it is empty + if flag.symmetricallyEncrypts { + err = cmd.MakeUserInputPassphraseIfEmpty(&flag.symmetricallyEncryptPassphrase) + if err != nil { + return err + } + } + // Use multiplexer with yamux + if flag.yamux { + fmt.Println("[INFO] Multiplexing with hashicorp/yamux") + return clientHandleWithYamux(ln, httpClient, headers, clientToServerUrl, serverToClientUrl) + } + // If pmux is enabled + if flag.pmux { + fmt.Println("[INFO] Multiplexing with pmux") + return clientHandleWithPmux(ln, httpClient, headers, clientToServerUrl, serverToClientUrl) + } + conn, err := ln.Accept() + if err != nil { + return err + } + fmt.Println("[INFO] accepted") + // Refuse another new connection + ln.Close() + // If encryption is enabled + if flag.symmetricallyEncrypts { + var duplex io.ReadWriteCloser + duplex, err := piping_util.DuplexConnect(httpClient, headers, clientToServerUrl, serverToClientUrl) + if err != nil { + return err + } + duplex, err = cmd.MakeDuplexWithEncryptionAndProgressIfNeed(duplex, flag.symmetricallyEncrypts, flag.symmetricallyEncryptPassphrase, flag.cipherType, flag.pbkdf2JsonString) + if err != nil { + return err + } + fin := make(chan error) + go func() { + // TODO: hard code + var buf = make([]byte, 16) + _, err := io.CopyBuffer(duplex, conn, buf) + fin <- err + }() + go func() { + // TODO: hard code + var buf = make([]byte, 16) + _, err := io.CopyBuffer(conn, duplex, buf) + fin <- err + }() + return util.CombineErrors(<-fin, <-fin) + } + err = piping_util.HandleDuplex(httpClient, conn, headers, clientToServerUrl, serverToClientUrl, flag.serverToClientBufSize, nil, cmd.ShowProgress, cmd.MakeProgressMessage) + fmt.Println() + if err != nil { + return err + } + fmt.Println("[INFO] Finished") + + return nil + }, +} + +func printHintForServerHost(ln net.Listener, clientToServerUrl string, serverToClientUrl string, clientToServerPath string, serverToClientPath string, opensslAesCtrParams *cmd.OpensslAesCtrParams) { + var listeningOn string + if addr, ok := ln.Addr().(*net.TCPAddr); ok { + // (base: https://stackoverflow.com/a/43425461) + flag.clientHostPort = addr.Port + listeningOn = strconv.Itoa(addr.Port) + } else { + listeningOn = flag.clientHostUnixSocket + } + fmt.Printf("[INFO] Client host listening on %s ...\n", listeningOn) + if !flag.yamux && !flag.pmux { + if flag.symmetricallyEncrypts { + if opensslAesCtrParams != nil { + fmt.Println("[INFO] Hint: Server host. should be replaced (nc + curl + openssl)") + fmt.Printf( + " read -p \"passphrase: \" -s pass && curl -sSN %s | stdbuf -i0 -o0 openssl aes-%d-ctr -d -pass \"pass:$pass\" -bufsize 1 -pbkdf2 -iter %d -md %s | nc 127.0.0.1 | stdbuf -i0 -o0 openssl aes-%d-ctr -pass \"pass:$pass\" -bufsize 1 -pbkdf2 -iter %d -md %s | curl -sSNT - %s; unset pass\n", + clientToServerUrl, + opensslAesCtrParams.KeyBits, + opensslAesCtrParams.Pbkdf2.Iter, + opensslAesCtrParams.Pbkdf2.HashNameForCommandHint, + opensslAesCtrParams.KeyBits, + opensslAesCtrParams.Pbkdf2.Iter, + opensslAesCtrParams.Pbkdf2.HashNameForCommandHint, + serverToClientUrl, + ) + } + } else { + fmt.Println("[INFO] Hint: Server host (nc + curl)") + fmt.Printf(" curl -sSN %s | nc 127.0.0.1 | curl -sSNT - %s\n", clientToServerUrl, serverToClientUrl) + } + } + fmt.Println("[INFO] Hint: Server host (piping-tunnel)") + flags := "" + if flag.symmetricallyEncrypts { + flags += fmt.Sprintf("-%s ", cmd.SymmetricallyEncryptsFlagShortName) + flags += fmt.Sprintf("--%s=%s ", cmd.CipherTypeFlagLongName, flag.cipherType) + switch flag.cipherType { + case piping_util.CipherTypeOpensslAes128Ctr: + fallthrough + case piping_util.CipherTypeOpensslAes256Ctr: + flags += fmt.Sprintf("--%s='%s' ", cmd.Pbkdf2FlagLongName, flag.pbkdf2JsonString) + } + } + if flag.yamux { + flags += fmt.Sprintf("--%s ", cmd.YamuxFlagLongName) + } + if flag.pmux { + flags += fmt.Sprintf("--%s ", cmd.PmuxFlagLongName) + } + fmt.Printf( + " piping-tunnel -s %s server -p %s%s %s\n", + cmd.ServerUrl, + flags, + clientToServerPath, + serverToClientPath, + ) + fmt.Println(" OR") + fmt.Printf( + " piping-tunnel -s %s socks %s%s %s\n", + cmd.ServerUrl, + flags, + clientToServerPath, + serverToClientPath, + ) +} + +func clientHandleWithYamux(ln net.Listener, httpClient *http.Client, headers []piping_util.KeyValue, clientToServerUrl string, serverToClientUrl string) error { + var duplex io.ReadWriteCloser + duplex, err := piping_util.DuplexConnectWithHandlers( + func(body io.Reader) (*http.Response, error) { + return piping_util.PipingSend(httpClient, cmd.HeadersWithYamux(headers), clientToServerUrl, body) + }, + func() (*http.Response, error) { + res, err := piping_util.PipingGet(httpClient, headers, serverToClientUrl) + if err != nil { + return nil, err + } + contentType := res.Header.Get("Content-Type") + // NOTE: application/octet-stream is for compatibility + if contentType != cmd.YamuxMimeType && contentType != "application/octet-stream" { + return nil, errors.Errorf("invalid content-type: %s", contentType) + } + return res, nil + }, + ) + if err != nil { + return err + } + duplex, err = cmd.MakeDuplexWithEncryptionAndProgressIfNeed(duplex, flag.symmetricallyEncrypts, flag.symmetricallyEncryptPassphrase, flag.cipherType, flag.pbkdf2JsonString) + if err != nil { + return err + } + yamuxSession, err := yamux.Client(duplex, nil) + if err != nil { + return err + } + + for { + conn, err := ln.Accept() + if err != nil { + return err + } + yamuxStream, err := yamuxSession.Open() + if err != nil { + return err + } + fin := make(chan struct{}) + go func() { + // TODO: hard code + var buf = make([]byte, 16) + io.CopyBuffer(yamuxStream, conn, buf) + fin <- struct{}{} + }() + go func() { + // TODO: hard code + var buf = make([]byte, 16) + io.CopyBuffer(conn, yamuxStream, buf) + fin <- struct{}{} + }() + go func() { + <-fin + <-fin + close(fin) + conn.Close() + yamuxStream.Close() + }() + } +} + +func clientHandleWithPmux(ln net.Listener, httpClient *http.Client, headers []piping_util.KeyValue, clientToServerUrl string, serverToClientUrl string) error { + var config cmd.ClientPmuxConfigJson + if json.Unmarshal([]byte(flag.pmuxConfig), &config) != nil { + return errors.Errorf("invalid pmux config format") + } + pmuxClient, err := pmux.Client(httpClient, headers, clientToServerUrl, serverToClientUrl, config.Hb, flag.symmetricallyEncrypts, flag.symmetricallyEncryptPassphrase, flag.cipherType) + if err != nil { + if err == pmux.NonPmuxMimeTypeError { + return errors.Errorf("--%s may be missing in server", cmd.PmuxFlagLongName) + } + if err == pmux.IncompatiblePmuxVersion { + return errors.Errorf("%s, hint: use the same piping-tunnel version (current: %s)", err.Error(), version.Version) + } + if err == pmux.IncompatibleServerConfigError { + return errors.Errorf("%s, hint: use the same piping-tunnel version (current: %s)", err.Error(), version.Version) + } + return err + } + for { + conn, err := ln.Accept() + if err != nil { + break + } + stream, err := pmuxClient.Open() + if err != nil { + cmd.Vlog.Log( + fmt.Sprintf("error(pmux open): %v", errors.WithStack(err)), + fmt.Sprintf("error(pmux open): %+v", errors.WithStack(err)), + ) + continue + } + fin := make(chan struct{}) + go func() { + // TODO: hard code + var buf = make([]byte, 16) + _, err := io.CopyBuffer(conn, stream, buf) + fin <- struct{}{} + if err != nil { + cmd.Vlog.Log( + fmt.Sprintf("error(pmux stream → conn): %v", errors.WithStack(err)), + fmt.Sprintf("error(pmux stream → conn): %+v", errors.WithStack(err)), + ) + return + } + }() + + go func() { + // TODO: hard code + var buf = make([]byte, 16) + _, err := io.CopyBuffer(stream, conn, buf) + fin <- struct{}{} + if err != nil { + cmd.Vlog.Log( + fmt.Sprintf("error(conn → pmux stream): %v", errors.WithStack(err)), + fmt.Sprintf("error(conn → pmux stream): %+v", errors.WithStack(err)), + ) + return + } + }() + + go func() { + <-fin + <-fin + conn.Close() + stream.Close() + close(fin) + }() + } + return nil +} diff --git a/cmd/root.go b/cmd/root.go index 8677c14..cd75421 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -11,14 +11,14 @@ const ( ServerUrlEnvName = "PIPING_SERVER" ) -var serverUrl string -var insecure bool -var dnsServer string +var ServerUrl string +var Insecure bool +var DnsServer string var showsVersion bool -var showProgress bool -var headerKeyValueStrs []string -var httpWriteBufSize int -var httpReadBufSize int +var ShowProgress bool +var HeaderKeyValueStrs []string +var HttpWriteBufSize int +var HttpReadBufSize int var verboseLoggerLevel int func init() { @@ -27,14 +27,14 @@ func init() { if !ok { defaultServer = "https://ppng.io" } - RootCmd.PersistentFlags().StringVarP(&serverUrl, "server", "s", defaultServer, "Piping Server URL") - RootCmd.PersistentFlags().StringVar(&dnsServer, "dns-server", "", "DNS server (e.g. 1.1.1.1:53)") + RootCmd.PersistentFlags().StringVarP(&ServerUrl, "server", "s", defaultServer, "Piping Server URL") + RootCmd.PersistentFlags().StringVar(&DnsServer, "dns-server", "", "DNS server (e.g. 1.1.1.1:53)") // NOTE: --insecure, -k is inspired by curl - RootCmd.PersistentFlags().BoolVarP(&insecure, "insecure", "k", false, "Allow insecure server connections when using SSL") - RootCmd.PersistentFlags().StringArrayVarP(&headerKeyValueStrs, "header", "H", []string{}, "HTTP header") - RootCmd.PersistentFlags().IntVarP(&httpWriteBufSize, "http-write-buf-size", "", 16, "HTTP write-buffer size in bytes") - RootCmd.PersistentFlags().IntVarP(&httpReadBufSize, "http-read-buf-size", "", 16, "HTTP read-buffer size in bytes") - RootCmd.PersistentFlags().BoolVarP(&showProgress, "progress", "", true, "Show progress") + RootCmd.PersistentFlags().BoolVarP(&Insecure, "insecure", "k", false, "Allow insecure server connections when using SSL") + RootCmd.PersistentFlags().StringArrayVarP(&HeaderKeyValueStrs, "header", "H", []string{}, "HTTP header") + RootCmd.PersistentFlags().IntVarP(&HttpWriteBufSize, "http-write-buf-size", "", 16, "HTTP write-buffer size in bytes") + RootCmd.PersistentFlags().IntVarP(&HttpReadBufSize, "http-read-buf-size", "", 16, "HTTP read-buffer size in bytes") + RootCmd.PersistentFlags().BoolVarP(&ShowProgress, "progress", "", true, "Show progress") RootCmd.Flags().BoolVarP(&showsVersion, "version", "v", false, "show version") RootCmd.PersistentFlags().IntVarP(&verboseLoggerLevel, "verbose", "", 0, "Verbose logging level") } @@ -72,6 +72,6 @@ Environment variable: return cmd.Help() }, PersistentPreRun: func(cmd *cobra.Command, args []string) { - vlog.Level = verboseLoggerLevel + Vlog.Level = verboseLoggerLevel }, } diff --git a/cmd/server.go b/cmd/server.go deleted file mode 100644 index 27c717e..0000000 --- a/cmd/server.go +++ /dev/null @@ -1,294 +0,0 @@ -package cmd - -import ( - "encoding/json" - "fmt" - "github.com/hashicorp/yamux" - "github.com/nwtgck/go-piping-tunnel/backoff" - "github.com/nwtgck/go-piping-tunnel/piping_util" - "github.com/nwtgck/go-piping-tunnel/pmux" - "github.com/nwtgck/go-piping-tunnel/util" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "io" - "net" - "net/http" - "strings" - "time" -) - -var serverTargetHost string -var serverHostPort int -var serverHostUnixSocket string -var serverClientToServerBufSize uint -var serverYamux bool -var serverPmux bool -var serverPmuxConfig string -var serverSymmetricallyEncrypts bool -var serverSymmetricallyEncryptPassphrase string -var serverCipherType string - -func init() { - RootCmd.AddCommand(serverCmd) - serverCmd.Flags().StringVarP(&serverTargetHost, "host", "", "localhost", "Target host") - serverCmd.Flags().IntVarP(&serverHostPort, "port", "p", 0, "TCP port of server host") - serverCmd.Flags().StringVarP(&serverHostUnixSocket, "unix-socket", "", "", "Unix socket of server host") - serverCmd.Flags().UintVarP(&serverClientToServerBufSize, "cs-buf-size", "", 16, "Buffer size of client-to-server in bytes") - serverCmd.Flags().BoolVarP(&serverYamux, yamuxFlagLongName, "", false, "Multiplex connection by hashicorp/yamux") - serverCmd.Flags().BoolVarP(&serverPmux, pmuxFlagLongName, "", false, "Multiplex connection by pmux (experimental)") - serverCmd.Flags().StringVarP(&serverPmuxConfig, pmuxConfigFlagLongName, "", `{"hb": true}`, "pmux config in JSON (experimental)") - serverCmd.Flags().BoolVarP(&serverSymmetricallyEncrypts, symmetricallyEncryptsFlagLongName, symmetricallyEncryptsFlagShortName, false, "Encrypt symmetrically") - serverCmd.Flags().StringVarP(&serverSymmetricallyEncryptPassphrase, symmetricallyEncryptPassphraseFlagLongName, "", "", "Passphrase for encryption") - serverCmd.Flags().StringVarP(&serverCipherType, cipherTypeFlagLongName, "", defaultCipherType, fmt.Sprintf("Cipher type: %s, %s", piping_util.CipherTypeAesCtr, piping_util.CipherTypeOpenpgp)) -} - -var serverCmd = &cobra.Command{ - Use: "server", - Short: "Run server-host", - RunE: func(cmd *cobra.Command, args []string) error { - // Validate cipher-type - if serverSymmetricallyEncrypts { - if err := validateClientCipher(serverCipherType); err != nil { - return nil - } - } - clientToServerPath, serverToClientPath, err := generatePaths(args) - if err != nil { - return err - } - headers, err := piping_util.ParseKeyValueStrings(headerKeyValueStrs) - if err != nil { - return err - } - httpClient := util.CreateHttpClient(insecure, httpWriteBufSize, httpReadBufSize) - if dnsServer != "" { - // Set DNS resolver - httpClient.Transport.(*http.Transport).DialContext = util.CreateDialContext(dnsServer) - } - serverToClientUrl, err := util.UrlJoin(serverUrl, serverToClientPath) - if err != nil { - return err - } - clientToServerUrl, err := util.UrlJoin(serverUrl, clientToServerPath) - if err != nil { - return err - } - // Print hint - printHintForClientHost(clientToServerUrl, serverToClientUrl, clientToServerPath, serverToClientPath) - // Make user input passphrase if it is empty - if serverSymmetricallyEncrypts { - err = makeUserInputPassphraseIfEmpty(&serverSymmetricallyEncryptPassphrase) - if err != nil { - return err - } - } - // Use multiplexer with yamux - if serverYamux { - fmt.Println("[INFO] Multiplexing with hashicorp/yamux") - return serverHandleWithYamux(httpClient, headers, clientToServerUrl, serverToClientUrl) - } - - // If pmux is enabled - if serverPmux { - fmt.Println("[INFO] Multiplexing with pmux") - return serverHandleWithPmux(httpClient, headers, clientToServerUrl, serverToClientUrl) - } - - conn, err := serverHostDial() - if err != nil { - return err - } - defer conn.Close() - // If encryption is enabled - if serverSymmetricallyEncrypts { - var duplex io.ReadWriteCloser - duplex, err := piping_util.DuplexConnect(httpClient, headers, serverToClientUrl, clientToServerUrl) - if err != nil { - return err - } - duplex, err = makeDuplexWithEncryptionAndProgressIfNeed(duplex, serverSymmetricallyEncrypts, serverSymmetricallyEncryptPassphrase, serverCipherType) - if err != nil { - return err - } - fin := make(chan error) - go func() { - // TODO: hard code - var buf = make([]byte, 16) - _, err := io.CopyBuffer(duplex, conn, buf) - fin <- err - }() - go func() { - // TODO: hard code - var buf = make([]byte, 16) - _, err := io.CopyBuffer(conn, duplex, buf) - fin <- err - }() - return util.CombineErrors(<-fin, <-fin) - } - err = piping_util.HandleDuplex(httpClient, conn, headers, serverToClientUrl, clientToServerUrl, serverClientToServerBufSize, nil, showProgress, makeProgressMessage) - fmt.Println() - if err != nil { - return err - } - fmt.Println("[INFO] Finished") - - return nil - }, -} - -func serverHostDial() (net.Conn, error) { - if serverHostUnixSocket == "" { - return net.Dial("tcp", fmt.Sprintf("%s:%d", serverTargetHost, serverHostPort)) - } else { - return net.Dial("unix", serverHostUnixSocket) - } -} - -func printHintForClientHost(clientToServerUrl string, serverToClientUrl string, clientToServerPath string, serverToClientPath string) { - if !serverYamux && !serverPmux { - fmt.Println("[INFO] Hint: Client host (socat + curl)") - fmt.Printf( - " socat TCP-LISTEN:31376 'EXEC:curl -NsS %s!!EXEC:curl -NsST - %s'\n", - strings.Replace(serverToClientUrl, ":", "\\:", -1), - strings.Replace(clientToServerUrl, ":", "\\:", -1), - ) - } - flags := "" - if serverSymmetricallyEncrypts { - flags += fmt.Sprintf("-%s ", symmetricallyEncryptsFlagShortName) - if serverCipherType != defaultCipherType { - flags += fmt.Sprintf("--%s=%s ", cipherTypeFlagLongName, serverCipherType) - } - } - if serverYamux { - flags += fmt.Sprintf("--%s ", yamuxFlagLongName) - } - if serverPmux { - flags += fmt.Sprintf("--%s ", pmuxFlagLongName) - } - fmt.Println("[INFO] Hint: Client host (piping-tunnel)") - fmt.Printf( - " piping-tunnel -s %s client -p 31376 %s%s %s\n", - serverUrl, - flags, - clientToServerPath, - serverToClientPath, - ) -} - -func serverHandleWithYamux(httpClient *http.Client, headers []piping_util.KeyValue, clientToServerUrl string, serverToClientUrl string) error { - var duplex io.ReadWriteCloser - duplex, err := piping_util.DuplexConnectWithHandlers( - func(body io.Reader) (*http.Response, error) { - return piping_util.PipingSend(httpClient, headersWithYamux(headers), serverToClientUrl, body) - }, - func() (*http.Response, error) { - res, err := piping_util.PipingGet(httpClient, headers, clientToServerUrl) - if err != nil { - return nil, err - } - contentType := res.Header.Get("Content-Type") - // NOTE: application/octet-stream is for compatibility - if contentType != yamuxMimeType && contentType != "application/octet-stream" { - return nil, errors.Errorf("invalid content-type: %s", contentType) - } - return res, nil - }, - ) - if err != nil { - return err - } - duplex, err = makeDuplexWithEncryptionAndProgressIfNeed(duplex, serverSymmetricallyEncrypts, serverSymmetricallyEncryptPassphrase, serverCipherType) - if err != nil { - return err - } - yamuxSession, err := yamux.Server(duplex, nil) - if err != nil { - return err - } - for { - yamuxStream, err := yamuxSession.Accept() - if err != nil { - return err - } - conn, err := serverHostDial() - if err != nil { - return err - } - fin := make(chan struct{}) - go func() { - // TODO: hard code - var buf = make([]byte, 16) - io.CopyBuffer(yamuxStream, conn, buf) - fin <- struct{}{} - }() - go func() { - // TODO: hard code - var buf = make([]byte, 16) - io.CopyBuffer(conn, yamuxStream, buf) - fin <- struct{}{} - }() - go func() { - <-fin - <-fin - close(fin) - conn.Close() - yamuxStream.Close() - }() - } -} - -func dialLoop() net.Conn { - b := backoff.NewExponentialBackoff() - for { - conn, err := serverHostDial() - if err != nil { - // backoff - time.Sleep(b.NextDuration()) - continue - } - return conn - } -} - -func serverHandleWithPmux(httpClient *http.Client, headers []piping_util.KeyValue, clientToServerUrl string, serverToClientUrl string) error { - var config serverPmuxConfigJson - if json.Unmarshal([]byte(serverPmuxConfig), &config) != nil { - return errors.Errorf("invalid pmux config format") - } - pmuxServer := pmux.Server(httpClient, headers, serverToClientUrl, clientToServerUrl, config.Hb, serverSymmetricallyEncrypts, serverSymmetricallyEncryptPassphrase, serverCipherType) - for { - stream, err := pmuxServer.Accept() - if err != nil { - return err - } - conn := dialLoop() - go func() { - // TODO: hard code - var buf = make([]byte, 16) - _, err := io.CopyBuffer(conn, stream, buf) - if err != nil { - vlog.Log( - fmt.Sprintf("error(pmux stream → conn): %v", errors.WithStack(err)), - fmt.Sprintf("error(pmux stream → conn): %+v", errors.WithStack(err)), - ) - conn.Close() - return - } - }() - - go func() { - // TODO: hard code - var buf = make([]byte, 16) - _, err := io.CopyBuffer(stream, conn, buf) - if err != nil { - vlog.Log( - fmt.Sprintf("error(conn → pmux stream): %v", errors.WithStack(err)), - fmt.Sprintf("error(conn → pmux stream): %+v", errors.WithStack(err)), - ) - conn.Close() - return - } - }() - } -} diff --git a/cmd/server/server.go b/cmd/server/server.go new file mode 100644 index 0000000..5e87f10 --- /dev/null +++ b/cmd/server/server.go @@ -0,0 +1,324 @@ +package server + +import ( + "encoding/json" + "fmt" + "github.com/hashicorp/yamux" + "github.com/nwtgck/go-piping-tunnel/backoff" + "github.com/nwtgck/go-piping-tunnel/cmd" + "github.com/nwtgck/go-piping-tunnel/piping_util" + "github.com/nwtgck/go-piping-tunnel/pmux" + "github.com/nwtgck/go-piping-tunnel/util" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "io" + "net" + "net/http" + "time" +) + +var flag struct { + targetHost string + serverHostPort int + serverHostUnixSocket string + clientToServerBufSize uint + yamux bool + pmux bool + pmuxConfig string + symmetricallyEncrypts bool + symmetricallyEncryptPassphrase string + cipherType string + pbkdf2JsonString string +} + +func init() { + cmd.RootCmd.AddCommand(serverCmd) + serverCmd.Flags().StringVarP(&flag.targetHost, "host", "", "localhost", "Target host") + serverCmd.Flags().IntVarP(&flag.serverHostPort, "port", "p", 0, "TCP port of server host") + serverCmd.Flags().StringVarP(&flag.serverHostUnixSocket, "unix-socket", "", "", "Unix socket of server host") + serverCmd.Flags().UintVarP(&flag.clientToServerBufSize, "cs-buf-size", "", 16, "Buffer size of client-to-server in bytes") + serverCmd.Flags().BoolVarP(&flag.yamux, cmd.YamuxFlagLongName, "", false, "Multiplex connection by hashicorp/yamux") + serverCmd.Flags().BoolVarP(&flag.pmux, cmd.PmuxFlagLongName, "", false, "Multiplex connection by pmux (experimental)") + serverCmd.Flags().StringVarP(&flag.pmuxConfig, cmd.PmuxConfigFlagLongName, "", `{"hb": true}`, "pmux config in JSON (experimental)") + serverCmd.Flags().BoolVarP(&flag.symmetricallyEncrypts, cmd.SymmetricallyEncryptsFlagLongName, cmd.SymmetricallyEncryptsFlagShortName, false, "Encrypt symmetrically") + serverCmd.Flags().StringVarP(&flag.symmetricallyEncryptPassphrase, cmd.SymmetricallyEncryptPassphraseFlagLongName, "", "", "Passphrase for encryption") + serverCmd.Flags().StringVarP(&flag.cipherType, cmd.CipherTypeFlagLongName, "", cmd.DefaultCipherType, fmt.Sprintf("Cipher type: %s, %s, %s, %s ", piping_util.CipherTypeAesCtr, piping_util.CipherTypeOpensslAes128Ctr, piping_util.CipherTypeOpensslAes256Ctr, piping_util.CipherTypeOpenpgp)) + // NOTE: default value of --pbkdf2 should be empty to detect key derive derivation from multiple algorithms in the future. + serverCmd.Flags().StringVarP(&flag.pbkdf2JsonString, cmd.Pbkdf2FlagLongName, "", "", fmt.Sprintf("e.g. %s", cmd.ExamplePbkdf2JsonStr())) +} + +var serverCmd = &cobra.Command{ + Use: "server", + Short: "Run server-host", + RunE: func(_ *cobra.Command, args []string) error { + // Validate cipher-type + if flag.symmetricallyEncrypts { + if err := cmd.ValidateClientCipher(flag.cipherType); err != nil { + return err + } + } + clientToServerPath, serverToClientPath, err := cmd.GeneratePaths(args) + if err != nil { + return err + } + headers, err := piping_util.ParseKeyValueStrings(cmd.HeaderKeyValueStrs) + if err != nil { + return err + } + httpClient := util.CreateHttpClient(cmd.Insecure, cmd.HttpWriteBufSize, cmd.HttpReadBufSize) + if cmd.DnsServer != "" { + // Set DNS resolver + httpClient.Transport.(*http.Transport).DialContext = util.CreateDialContext(cmd.DnsServer) + } + serverToClientUrl, err := util.UrlJoin(cmd.ServerUrl, serverToClientPath) + if err != nil { + return err + } + clientToServerUrl, err := util.UrlJoin(cmd.ServerUrl, clientToServerPath) + if err != nil { + return err + } + var opensslAesCtrParams *cmd.OpensslAesCtrParams = nil + if flag.symmetricallyEncrypts { + opensslAesCtrParams, err = cmd.ParseOpensslAesCtrParams(flag.cipherType, flag.pbkdf2JsonString) + if err != nil { + return err + } + } + // Print hint + printHintForClientHost(clientToServerUrl, serverToClientUrl, clientToServerPath, serverToClientPath, opensslAesCtrParams) + // Make user input passphrase if it is empty + if flag.symmetricallyEncrypts { + err = cmd.MakeUserInputPassphraseIfEmpty(&flag.symmetricallyEncryptPassphrase) + if err != nil { + return err + } + } + // Use multiplexer with yamux + if flag.yamux { + fmt.Println("[INFO] Multiplexing with hashicorp/yamux") + return serverHandleWithYamux(httpClient, headers, clientToServerUrl, serverToClientUrl) + } + + // If pmux is enabled + if flag.pmux { + fmt.Println("[INFO] Multiplexing with pmux") + return serverHandleWithPmux(httpClient, headers, clientToServerUrl, serverToClientUrl) + } + + conn, err := serverHostDial() + if err != nil { + return err + } + defer conn.Close() + // If encryption is enabled + if flag.symmetricallyEncrypts { + var duplex io.ReadWriteCloser + duplex, err := piping_util.DuplexConnect(httpClient, headers, serverToClientUrl, clientToServerUrl) + if err != nil { + return err + } + duplex, err = cmd.MakeDuplexWithEncryptionAndProgressIfNeed(duplex, flag.symmetricallyEncrypts, flag.symmetricallyEncryptPassphrase, flag.cipherType, flag.pbkdf2JsonString) + if err != nil { + return err + } + fin := make(chan error) + go func() { + // TODO: hard code + var buf = make([]byte, 16) + _, err := io.CopyBuffer(duplex, conn, buf) + fin <- err + }() + go func() { + // TODO: hard code + var buf = make([]byte, 16) + _, err := io.CopyBuffer(conn, duplex, buf) + fin <- err + }() + return util.CombineErrors(<-fin, <-fin) + } + err = piping_util.HandleDuplex(httpClient, conn, headers, serverToClientUrl, clientToServerUrl, flag.clientToServerBufSize, nil, cmd.ShowProgress, cmd.MakeProgressMessage) + fmt.Println() + if err != nil { + return err + } + fmt.Println("[INFO] Finished") + + return nil + }, +} + +func serverHostDial() (net.Conn, error) { + if flag.serverHostUnixSocket == "" { + return net.Dial("tcp", fmt.Sprintf("%s:%d", flag.targetHost, flag.serverHostPort)) + } else { + return net.Dial("unix", flag.serverHostUnixSocket) + } +} + +func printHintForClientHost(clientToServerUrl string, serverToClientUrl string, clientToServerPath string, serverToClientPath string, opensslAesCtrParams *cmd.OpensslAesCtrParams) { + if !flag.yamux && !flag.pmux { + if flag.symmetricallyEncrypts { + if opensslAesCtrParams != nil { + fmt.Println("[INFO] Hint: Client host. Port 31376 may be replaced (socat + curl + openssl)") + fmt.Printf( + " read -p \"passphrase: \" -s pass && curl -NsS %s | stdbuf -i0 -o0 openssl aes-%d-ctr -d -pass \"pass:$pass\" -bufsize 1 -pbkdf2 -iter %d -md %s | socat TCP-LISTEN:31376 - | stdbuf -i0 -o0 openssl aes-%d-ctr -pass \"pass:$pass\" -bufsize 1 -pbkdf2 -iter %d -md %s | curl -NsST - %s; unset pass\n", + serverToClientUrl, + opensslAesCtrParams.KeyBits, + opensslAesCtrParams.Pbkdf2.Iter, + opensslAesCtrParams.Pbkdf2.HashNameForCommandHint, + opensslAesCtrParams.KeyBits, + opensslAesCtrParams.Pbkdf2.Iter, + opensslAesCtrParams.Pbkdf2.HashNameForCommandHint, + clientToServerUrl, + ) + } + } else { + fmt.Println("[INFO] Hint: Client host (socat + curl)") + // NOTE: nc can be used instead of socat but nc has variant: `nc -l 31376` in BSD version, `nc -l -p 31376` in GNU version. + fmt.Printf(" curl -NsS %s | socat TCP-LISTEN:31376 - | curl -NsST - %s\n", serverToClientUrl, clientToServerUrl) + } + } + flags := "" + if flag.symmetricallyEncrypts { + flags += fmt.Sprintf("-%s ", cmd.SymmetricallyEncryptsFlagShortName) + flags += fmt.Sprintf("--%s=%s ", cmd.CipherTypeFlagLongName, flag.cipherType) + switch flag.cipherType { + case piping_util.CipherTypeOpensslAes128Ctr: + fallthrough + case piping_util.CipherTypeOpensslAes256Ctr: + flags += fmt.Sprintf("--%s='%s' ", cmd.Pbkdf2FlagLongName, flag.pbkdf2JsonString) + } + } + if flag.yamux { + flags += fmt.Sprintf("--%s ", cmd.YamuxFlagLongName) + } + if flag.pmux { + flags += fmt.Sprintf("--%s ", cmd.PmuxFlagLongName) + } + fmt.Println("[INFO] Hint: Client host (piping-tunnel)") + fmt.Printf( + " piping-tunnel -s %s client -p 31376 %s%s %s\n", + cmd.ServerUrl, + flags, + clientToServerPath, + serverToClientPath, + ) +} + +func serverHandleWithYamux(httpClient *http.Client, headers []piping_util.KeyValue, clientToServerUrl string, serverToClientUrl string) error { + var duplex io.ReadWriteCloser + duplex, err := piping_util.DuplexConnectWithHandlers( + func(body io.Reader) (*http.Response, error) { + return piping_util.PipingSend(httpClient, cmd.HeadersWithYamux(headers), serverToClientUrl, body) + }, + func() (*http.Response, error) { + res, err := piping_util.PipingGet(httpClient, headers, clientToServerUrl) + if err != nil { + return nil, err + } + contentType := res.Header.Get("Content-Type") + // NOTE: application/octet-stream is for compatibility + if contentType != cmd.YamuxMimeType && contentType != "application/octet-stream" { + return nil, errors.Errorf("invalid content-type: %s", contentType) + } + return res, nil + }, + ) + if err != nil { + return err + } + duplex, err = cmd.MakeDuplexWithEncryptionAndProgressIfNeed(duplex, flag.symmetricallyEncrypts, flag.symmetricallyEncryptPassphrase, flag.cipherType, flag.pbkdf2JsonString) + if err != nil { + return err + } + yamuxSession, err := yamux.Server(duplex, nil) + if err != nil { + return err + } + for { + yamuxStream, err := yamuxSession.Accept() + if err != nil { + return err + } + conn, err := serverHostDial() + if err != nil { + return err + } + fin := make(chan struct{}) + go func() { + // TODO: hard code + var buf = make([]byte, 16) + io.CopyBuffer(yamuxStream, conn, buf) + fin <- struct{}{} + }() + go func() { + // TODO: hard code + var buf = make([]byte, 16) + io.CopyBuffer(conn, yamuxStream, buf) + fin <- struct{}{} + }() + go func() { + <-fin + <-fin + close(fin) + conn.Close() + yamuxStream.Close() + }() + } +} + +func dialLoop() net.Conn { + b := backoff.NewExponentialBackoff() + for { + conn, err := serverHostDial() + if err != nil { + // backoff + time.Sleep(b.NextDuration()) + continue + } + return conn + } +} + +func serverHandleWithPmux(httpClient *http.Client, headers []piping_util.KeyValue, clientToServerUrl string, serverToClientUrl string) error { + var config cmd.ServerPmuxConfigJson + if json.Unmarshal([]byte(flag.pmuxConfig), &config) != nil { + return errors.Errorf("invalid pmux config format") + } + pmuxServer := pmux.Server(httpClient, headers, serverToClientUrl, clientToServerUrl, config.Hb, flag.symmetricallyEncrypts, flag.symmetricallyEncryptPassphrase, flag.cipherType) + for { + stream, err := pmuxServer.Accept() + if err != nil { + return err + } + conn := dialLoop() + go func() { + // TODO: hard code + var buf = make([]byte, 16) + _, err := io.CopyBuffer(conn, stream, buf) + if err != nil { + cmd.Vlog.Log( + fmt.Sprintf("error(pmux stream → conn): %v", errors.WithStack(err)), + fmt.Sprintf("error(pmux stream → conn): %+v", errors.WithStack(err)), + ) + conn.Close() + return + } + }() + + go func() { + // TODO: hard code + var buf = make([]byte, 16) + _, err := io.CopyBuffer(stream, conn, buf) + if err != nil { + cmd.Vlog.Log( + fmt.Sprintf("error(conn → pmux stream): %v", errors.WithStack(err)), + fmt.Sprintf("error(conn → pmux stream): %+v", errors.WithStack(err)), + ) + conn.Close() + return + } + }() + } +} diff --git a/cmd/shared.go b/cmd/shared.go index b90f11d..e917251 100644 --- a/cmd/shared.go +++ b/cmd/shared.go @@ -1,51 +1,79 @@ package cmd import ( + "crypto" + _ "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" + "encoding/json" "fmt" - "github.com/nwtgck/go-piping-tunnel/crypto_duplex" + "github.com/nwtgck/go-piping-tunnel/aes_ctr_duplex" "github.com/nwtgck/go-piping-tunnel/io_progress" "github.com/nwtgck/go-piping-tunnel/openpgp_duplex" + "github.com/nwtgck/go-piping-tunnel/openssl_aes_ctr_duplex" "github.com/nwtgck/go-piping-tunnel/piping_util" "github.com/nwtgck/go-piping-tunnel/util" "github.com/nwtgck/go-piping-tunnel/verbose_logger" "github.com/pkg/errors" + "hash" "io" "os" "time" ) -const defaultCipherType = piping_util.CipherTypeAesCtr +const DefaultCipherType = piping_util.CipherTypeAesCtr const ( - yamuxFlagLongName = "yamux" - pmuxFlagLongName = "pmux" - pmuxConfigFlagLongName = "pmux-config" - symmetricallyEncryptsFlagLongName = "symmetric" - symmetricallyEncryptsFlagShortName = "c" - symmetricallyEncryptPassphraseFlagLongName = "passphrase" - cipherTypeFlagLongName = "cipher-type" + YamuxFlagLongName = "yamux" + PmuxFlagLongName = "pmux" + PmuxConfigFlagLongName = "pmux-config" + SymmetricallyEncryptsFlagLongName = "symmetric" + SymmetricallyEncryptsFlagShortName = "c" + SymmetricallyEncryptPassphraseFlagLongName = "pass" + CipherTypeFlagLongName = "cipher-type" + Pbkdf2FlagLongName = "pbkdf2" ) -const yamuxMimeType = "application/yamux" +const YamuxMimeType = "application/yamux" -type serverPmuxConfigJson struct { +type ServerPmuxConfigJson struct { Hb bool `json:"hb"` } -type clientPmuxConfigJson struct { +type ClientPmuxConfigJson struct { Hb bool `json:"hb"` } -var vlog *verbose_logger.Logger +type pbkdf2ConfigJson struct { + Iter int `json:"iter"` + Hash string `json:"hash"` +} + +type Pbkdf2Config struct { + Iter int + Hash func() hash.Hash + HashNameForCommandHint string // for command hint +} + +type OpensslAesCtrParams struct { + KeyBits uint16 + Pbkdf2 *Pbkdf2Config +} + +var Vlog *verbose_logger.Logger func init() { - vlog = &verbose_logger.Logger{} + Vlog = &verbose_logger.Logger{} } -func validateClientCipher(str string) error { +func ValidateClientCipher(str string) error { switch str { case piping_util.CipherTypeAesCtr: return nil + case piping_util.CipherTypeOpensslAes128Ctr: + return nil + case piping_util.CipherTypeOpensslAes256Ctr: + return nil case piping_util.CipherTypeOpenpgp: return nil default: @@ -53,7 +81,61 @@ func validateClientCipher(str string) error { } } -func generatePaths(args []string) (string, string, error) { +func validateHashFunctionName(str string) (func() hash.Hash, error) { + switch str { + case "sha1": + return crypto.SHA1.New, nil + case "sha256": + return crypto.SHA256.New, nil + case "sha512": + return crypto.SHA512.New, nil + default: + return nil, errors.Errorf("unsupported hash: %s", str) + } +} + +func ParsePbkdf2(str string) (*Pbkdf2Config, error) { + var configJson pbkdf2ConfigJson + if json.Unmarshal([]byte(str), &configJson) != nil { + return nil, errors.Errorf("invalid pbkdf2 JSON format: e.g. --%s='%s'", Pbkdf2FlagLongName, ExamplePbkdf2JsonStr()) + } + h, err := validateHashFunctionName(configJson.Hash) + if err != nil { + return nil, err + } + return &Pbkdf2Config{Iter: configJson.Iter, Hash: h, HashNameForCommandHint: configJson.Hash}, nil +} + +func ParseOpensslAesCtrParams(cipherType string, pbkdf2ConfigJsonStr string) (*OpensslAesCtrParams, error) { + var keyBits uint16 + switch cipherType { + case piping_util.CipherTypeOpensslAes128Ctr: + keyBits = 128 + case piping_util.CipherTypeOpensslAes256Ctr: + keyBits = 256 + } + switch cipherType { + case piping_util.CipherTypeOpensslAes128Ctr: + fallthrough + case piping_util.CipherTypeOpensslAes256Ctr: + pbkdf2Config, err := ParsePbkdf2(pbkdf2ConfigJsonStr) + if err != nil { + return nil, err + } + return &OpensslAesCtrParams{KeyBits: keyBits, Pbkdf2: pbkdf2Config}, nil + } + return nil, nil +} + +func ExamplePbkdf2JsonStr() string { + b, err := json.Marshal(&pbkdf2ConfigJson{Iter: 100000, Hash: "sha256"}) + if err != nil { + panic(err) + } + return string(b) +} + +func GeneratePaths(args []string) (string, string, error) { var clientToServerPath string var serverToClientPath string @@ -72,7 +154,7 @@ func generatePaths(args []string) (string, string, error) { return clientToServerPath, serverToClientPath, nil } -func makeProgressMessage(progress *io_progress.IOProgress) string { +func MakeProgressMessage(progress *io_progress.IOProgress) string { return fmt.Sprintf( "↑ %s (%s/s) | ↓ %s (%s/s)", util.HumanizeBytes(float64(progress.CurrReadBytes)), @@ -82,7 +164,7 @@ func makeProgressMessage(progress *io_progress.IOProgress) string { ) } -func makeUserInputPassphraseIfEmpty(passphrase *string) (err error) { +func MakeUserInputPassphraseIfEmpty(passphrase *string) (err error) { // If the passphrase is empty if *passphrase == "" { // Get user-input passphrase @@ -92,7 +174,7 @@ func makeUserInputPassphraseIfEmpty(passphrase *string) (err error) { return nil } -func makeDuplexWithEncryptionAndProgressIfNeed(duplex io.ReadWriteCloser, encrypts bool, passphrase string, cipherType string) (io.ReadWriteCloser, error) { +func MakeDuplexWithEncryptionAndProgressIfNeed(duplex io.ReadWriteCloser, encrypts bool, passphrase string, cipherType string, pbkdf2JsonStr string) (io.ReadWriteCloser, error) { var err error // If encryption is enabled if encrypts { @@ -100,8 +182,22 @@ func makeDuplexWithEncryptionAndProgressIfNeed(duplex io.ReadWriteCloser, encryp switch cipherType { case piping_util.CipherTypeAesCtr: // Encrypt with AES-CTR - duplex, err = crypto_duplex.EncryptDuplexWithAesCtr(duplex, duplex, []byte(passphrase)) + duplex, err = aes_ctr_duplex.Duplex(duplex, duplex, []byte(passphrase)) cipherName = "AES-CTR" + case piping_util.CipherTypeOpensslAes128Ctr: + pbkdf2, err := ParsePbkdf2(pbkdf2JsonStr) + if err != nil { + return nil, err + } + duplex, err = openssl_aes_ctr_duplex.Duplex(duplex, duplex, []byte(passphrase), pbkdf2.Iter, 128/8, pbkdf2.Hash) + cipherName = "OpenSSL-AES-128-CTR-compatible" + case piping_util.CipherTypeOpensslAes256Ctr: + pbkdf2, err := ParsePbkdf2(pbkdf2JsonStr) + if err != nil { + return nil, err + } + duplex, err = openssl_aes_ctr_duplex.Duplex(duplex, duplex, []byte(passphrase), pbkdf2.Iter, 256/8, pbkdf2.Hash) + cipherName = "OpenSSL-AES-256-CTR-compatible" case piping_util.CipherTypeOpenpgp: duplex, err = openpgp_duplex.SymmetricallyEncryptDuplexWithOpenPGP(duplex, duplex, []byte(passphrase)) cipherName = "OpenPGP" @@ -113,12 +209,12 @@ func makeDuplexWithEncryptionAndProgressIfNeed(duplex io.ReadWriteCloser, encryp } fmt.Printf("[INFO] End-to-end encryption with %s\n", cipherName) } - if showProgress { - duplex = io_progress.NewIOProgress(duplex, duplex, os.Stderr, makeProgressMessage) + if ShowProgress { + duplex = io_progress.NewIOProgress(duplex, duplex, os.Stderr, MakeProgressMessage) } return duplex, nil } -func headersWithYamux(headers []piping_util.KeyValue) []piping_util.KeyValue { - return append(headers, piping_util.KeyValue{Key: "Content-Type", Value: yamuxMimeType}) +func HeadersWithYamux(headers []piping_util.KeyValue) []piping_util.KeyValue { + return append(headers, piping_util.KeyValue{Key: "Content-Type", Value: YamuxMimeType}) } diff --git a/cmd/socks.go b/cmd/socks.go deleted file mode 100644 index bdd8c11..0000000 --- a/cmd/socks.go +++ /dev/null @@ -1,185 +0,0 @@ -package cmd - -import ( - "encoding/json" - "fmt" - "github.com/hashicorp/yamux" - "github.com/nwtgck/go-piping-tunnel/piping_util" - "github.com/nwtgck/go-piping-tunnel/pmux" - "github.com/nwtgck/go-piping-tunnel/util" - "github.com/nwtgck/go-socks" - "github.com/pkg/errors" - "github.com/spf13/cobra" - "io" - "net/http" - "strings" -) - -var socksYamux bool -var socksPmux bool -var socksPmuxConfig string -var socksSymmetricallyEncrypts bool -var socksSymmetricallyEncryptPassphrase string -var socksCipherType string - -func init() { - RootCmd.AddCommand(socksCmd) - socksCmd.Flags().BoolVarP(&socksYamux, "yamux", "", false, "Multiplex connection by hashicorp/yamux") - socksCmd.Flags().BoolVarP(&socksPmux, pmuxFlagLongName, "", false, "Multiplex connection by pmux (experimental)") - socksCmd.Flags().StringVarP(&socksPmuxConfig, pmuxConfigFlagLongName, "", `{"hb": true}`, "pmux config in JSON (experimental)") - socksCmd.Flags().BoolVarP(&socksSymmetricallyEncrypts, symmetricallyEncryptsFlagLongName, symmetricallyEncryptsFlagShortName, false, "Encrypt symmetrically") - socksCmd.Flags().StringVarP(&socksSymmetricallyEncryptPassphrase, symmetricallyEncryptPassphraseFlagLongName, "", "", "Passphrase for encryption") - socksCmd.Flags().StringVarP(&socksCipherType, cipherTypeFlagLongName, "", defaultCipherType, fmt.Sprintf("Cipher type: %s, %s", piping_util.CipherTypeAesCtr, piping_util.CipherTypeOpenpgp)) -} - -var socksCmd = &cobra.Command{ - Use: "socks", - Short: "Run SOCKS server", - RunE: func(cmd *cobra.Command, args []string) error { - // Validate cipher-type - if socksSymmetricallyEncrypts { - if err := validateClientCipher(socksCipherType); err != nil { - return nil - } - } - clientToServerPath, serverToClientPath, err := generatePaths(args) - if err != nil { - return err - } - headers, err := piping_util.ParseKeyValueStrings(headerKeyValueStrs) - if err != nil { - return err - } - httpClient := util.CreateHttpClient(insecure, httpWriteBufSize, httpReadBufSize) - if dnsServer != "" { - // Set DNS resolver - httpClient.Transport.(*http.Transport).DialContext = util.CreateDialContext(dnsServer) - } - serverToClientUrl, err := util.UrlJoin(serverUrl, serverToClientPath) - if err != nil { - return err - } - clientToServerUrl, err := util.UrlJoin(serverUrl, clientToServerPath) - if err != nil { - return err - } - // Print hint - socksPrintHintForClientHost(clientToServerUrl, serverToClientUrl, clientToServerPath, serverToClientPath) - // Make user input passphrase if it is empty - if socksSymmetricallyEncrypts { - err = makeUserInputPassphraseIfEmpty(&socksSymmetricallyEncryptPassphrase) - if err != nil { - return err - } - } - - // If not using multiplexer - if !socksYamux && !socksPmux { - return errors.Errorf("--%s or --%s must be specified", yamuxFlagLongName, pmuxFlagLongName) - } - - socksConf := &socks.Config{} - socksServer, err := socks.New(socksConf) - - // If yamux is enabled - if socksYamux { - fmt.Println("[INFO] Multiplexing with hashicorp/yamux") - return socksHandleWithYamux(socksServer, httpClient, headers, clientToServerUrl, serverToClientUrl) - } - - // If pmux is enabled - fmt.Println("[INFO] Multiplexing with pmux") - return socksHandleWithPmux(socksServer, httpClient, headers, clientToServerUrl, serverToClientUrl) - }, -} - -func socksPrintHintForClientHost(clientToServerUrl string, serverToClientUrl string, clientToServerPath string, serverToClientPath string) { - if !socksYamux && !socksPmux { - fmt.Println("[INFO] Hint: Client host (socat + curl)") - fmt.Printf( - " socat TCP-LISTEN:31376 'EXEC:curl -NsS %s!!EXEC:curl -NsST - %s'\n", - strings.Replace(serverToClientUrl, ":", "\\:", -1), - strings.Replace(clientToServerUrl, ":", "\\:", -1), - ) - } - flags := "" - if socksSymmetricallyEncrypts { - flags += fmt.Sprintf("-%s ", symmetricallyEncryptsFlagShortName) - if socksCipherType != defaultCipherType { - flags += fmt.Sprintf("--%s=%s ", cipherTypeFlagLongName, socksCipherType) - } - } - if socksYamux { - flags += fmt.Sprintf("--%s ", yamuxFlagLongName) - } - if socksPmux { - flags += fmt.Sprintf("--%s ", pmuxFlagLongName) - } - fmt.Println("[INFO] Hint: Client host (piping-tunnel)") - fmt.Printf( - " piping-tunnel -s %s client -p 1080 %s%s %s\n", - serverUrl, - flags, - clientToServerPath, - serverToClientPath, - ) -} - -func socksHandleWithYamux(socksServer *socks.Server, httpClient *http.Client, headers []piping_util.KeyValue, clientToServerUrl string, serverToClientUrl string) error { - var duplex io.ReadWriteCloser - duplex, err := piping_util.DuplexConnectWithHandlers( - func(body io.Reader) (*http.Response, error) { - return piping_util.PipingSend(httpClient, headersWithYamux(headers), serverToClientUrl, body) - }, - func() (*http.Response, error) { - res, err := piping_util.PipingGet(httpClient, headers, clientToServerUrl) - if err != nil { - return nil, err - } - contentType := res.Header.Get("Content-Type") - // NOTE: application/octet-stream is for compatibility - if contentType != yamuxMimeType && contentType != "application/octet-stream" { - return nil, errors.Errorf("invalid content-type: %s", contentType) - } - return res, nil - }, - ) - duplex, err = makeDuplexWithEncryptionAndProgressIfNeed(duplex, socksSymmetricallyEncrypts, socksSymmetricallyEncryptPassphrase, socksCipherType) - if err != nil { - return err - } - yamuxSession, err := yamux.Server(duplex, nil) - if err != nil { - return err - } - for { - yamuxStream, err := yamuxSession.Accept() - if err != nil { - return err - } - go socksServer.ServeConn(yamuxStream) - } -} - -func socksHandleWithPmux(socksServer *socks.Server, httpClient *http.Client, headers []piping_util.KeyValue, clientToServerUrl string, serverToClientUrl string) error { - var config serverPmuxConfigJson - if json.Unmarshal([]byte(socksPmuxConfig), &config) != nil { - return errors.Errorf("invalid pmux config format") - } - pmuxServer := pmux.Server(httpClient, headers, serverToClientUrl, clientToServerUrl, config.Hb, socksSymmetricallyEncrypts, socksSymmetricallyEncryptPassphrase, socksCipherType) - for { - stream, err := pmuxServer.Accept() - if err != nil { - return err - } - go func() { - err := socksServer.ServeConn(util.NewDuplexConn(stream)) - if err != nil { - vlog.Log( - fmt.Sprintf("error(serve conn): %v", errors.WithStack(err)), - fmt.Sprintf("error(serve conn): %+v", errors.WithStack(err)), - ) - } - }() - } -} diff --git a/cmd/socks/socks.go b/cmd/socks/socks.go new file mode 100644 index 0000000..4850349 --- /dev/null +++ b/cmd/socks/socks.go @@ -0,0 +1,187 @@ +package socks + +import ( + "encoding/json" + "fmt" + "github.com/hashicorp/yamux" + "github.com/nwtgck/go-piping-tunnel/cmd" + "github.com/nwtgck/go-piping-tunnel/piping_util" + "github.com/nwtgck/go-piping-tunnel/pmux" + "github.com/nwtgck/go-piping-tunnel/util" + "github.com/nwtgck/go-socks" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "io" + "net/http" +) + +var flag struct { + yamux bool + pmux bool + pmuxConfig string + symmetricallyEncrypts bool + symmetricallyEncryptPassphrase string + cipherType string + pbkdf2JsonString string +} + +func init() { + cmd.RootCmd.AddCommand(socksCmd) + socksCmd.Flags().BoolVarP(&flag.yamux, "yamux", "", false, "Multiplex connection by hashicorp/yamux") + socksCmd.Flags().BoolVarP(&flag.pmux, cmd.PmuxFlagLongName, "", false, "Multiplex connection by pmux (experimental)") + socksCmd.Flags().StringVarP(&flag.pmuxConfig, cmd.PmuxConfigFlagLongName, "", `{"hb": true}`, "pmux config in JSON (experimental)") + socksCmd.Flags().BoolVarP(&flag.symmetricallyEncrypts, cmd.SymmetricallyEncryptsFlagLongName, cmd.SymmetricallyEncryptsFlagShortName, false, "Encrypt symmetrically") + socksCmd.Flags().StringVarP(&flag.symmetricallyEncryptPassphrase, cmd.SymmetricallyEncryptPassphraseFlagLongName, "", "", "Passphrase for encryption") + socksCmd.Flags().StringVarP(&flag.cipherType, cmd.CipherTypeFlagLongName, "", cmd.DefaultCipherType, fmt.Sprintf("Cipher type: %s, %s, %s, %s ", piping_util.CipherTypeAesCtr, piping_util.CipherTypeOpensslAes128Ctr, piping_util.CipherTypeOpensslAes256Ctr, piping_util.CipherTypeOpenpgp)) + // NOTE: default value of --pbkdf2 should be empty to detect key derive derivation from multiple algorithms in the future. + socksCmd.Flags().StringVarP(&flag.pbkdf2JsonString, cmd.Pbkdf2FlagLongName, "", "", fmt.Sprintf("e.g. %s", cmd.ExamplePbkdf2JsonStr())) +} + +var socksCmd = &cobra.Command{ + Use: "socks", + Short: "Run SOCKS server", + RunE: func(_ *cobra.Command, args []string) error { + // Validate cipher-type + if flag.symmetricallyEncrypts { + if err := cmd.ValidateClientCipher(flag.cipherType); err != nil { + return err + } + } + clientToServerPath, serverToClientPath, err := cmd.GeneratePaths(args) + if err != nil { + return err + } + headers, err := piping_util.ParseKeyValueStrings(cmd.HeaderKeyValueStrs) + if err != nil { + return err + } + httpClient := util.CreateHttpClient(cmd.Insecure, cmd.HttpWriteBufSize, cmd.HttpReadBufSize) + if cmd.DnsServer != "" { + // Set DNS resolver + httpClient.Transport.(*http.Transport).DialContext = util.CreateDialContext(cmd.DnsServer) + } + serverToClientUrl, err := util.UrlJoin(cmd.ServerUrl, serverToClientPath) + if err != nil { + return err + } + clientToServerUrl, err := util.UrlJoin(cmd.ServerUrl, clientToServerPath) + if err != nil { + return err + } + // Print hint + socksPrintHintForClientHost(clientToServerPath, serverToClientPath) + // Make user input passphrase if it is empty + if flag.symmetricallyEncrypts { + err = cmd.MakeUserInputPassphraseIfEmpty(&flag.symmetricallyEncryptPassphrase) + if err != nil { + return err + } + } + + // If not using multiplexer + if !flag.yamux && !flag.pmux { + return errors.Errorf("--%s or --%s must be specified", cmd.YamuxFlagLongName, cmd.PmuxFlagLongName) + } + + socksConf := &socks.Config{} + socksServer, err := socks.New(socksConf) + + // If yamux is enabled + if flag.yamux { + fmt.Println("[INFO] Multiplexing with hashicorp/yamux") + return socksHandleWithYamux(socksServer, httpClient, headers, clientToServerUrl, serverToClientUrl) + } + + // If pmux is enabled + fmt.Println("[INFO] Multiplexing with pmux") + return socksHandleWithPmux(socksServer, httpClient, headers, clientToServerUrl, serverToClientUrl) + }, +} + +// NOTE: multiplexing should be enabled, so there is no socat-curl hint +func socksPrintHintForClientHost(clientToServerPath string, serverToClientPath string) { + flags := "" + if flag.symmetricallyEncrypts { + flags += fmt.Sprintf("-%s ", cmd.SymmetricallyEncryptsFlagShortName) + flags += fmt.Sprintf("--%s=%s ", cmd.CipherTypeFlagLongName, flag.cipherType) + switch flag.cipherType { + case piping_util.CipherTypeOpensslAes128Ctr: + fallthrough + case piping_util.CipherTypeOpensslAes256Ctr: + flags += fmt.Sprintf("--%s='%s' ", cmd.Pbkdf2FlagLongName, flag.pbkdf2JsonString) + } + } + if flag.yamux { + flags += fmt.Sprintf("--%s ", cmd.YamuxFlagLongName) + } + if flag.pmux { + flags += fmt.Sprintf("--%s ", cmd.PmuxFlagLongName) + } + fmt.Println("[INFO] Hint: Client host (piping-tunnel)") + fmt.Printf( + " piping-tunnel -s %s client -p 1080 %s%s %s\n", + cmd.ServerUrl, + flags, + clientToServerPath, + serverToClientPath, + ) +} + +func socksHandleWithYamux(socksServer *socks.Server, httpClient *http.Client, headers []piping_util.KeyValue, clientToServerUrl string, serverToClientUrl string) error { + var duplex io.ReadWriteCloser + duplex, err := piping_util.DuplexConnectWithHandlers( + func(body io.Reader) (*http.Response, error) { + return piping_util.PipingSend(httpClient, cmd.HeadersWithYamux(headers), serverToClientUrl, body) + }, + func() (*http.Response, error) { + res, err := piping_util.PipingGet(httpClient, headers, clientToServerUrl) + if err != nil { + return nil, err + } + contentType := res.Header.Get("Content-Type") + // NOTE: application/octet-stream is for compatibility + if contentType != cmd.YamuxMimeType && contentType != "application/octet-stream" { + return nil, errors.Errorf("invalid content-type: %s", contentType) + } + return res, nil + }, + ) + duplex, err = cmd.MakeDuplexWithEncryptionAndProgressIfNeed(duplex, flag.symmetricallyEncrypts, flag.symmetricallyEncryptPassphrase, flag.cipherType, flag.pbkdf2JsonString) + if err != nil { + return err + } + yamuxSession, err := yamux.Server(duplex, nil) + if err != nil { + return err + } + for { + yamuxStream, err := yamuxSession.Accept() + if err != nil { + return err + } + go socksServer.ServeConn(yamuxStream) + } +} + +func socksHandleWithPmux(socksServer *socks.Server, httpClient *http.Client, headers []piping_util.KeyValue, clientToServerUrl string, serverToClientUrl string) error { + var config cmd.ServerPmuxConfigJson + if json.Unmarshal([]byte(flag.pmuxConfig), &config) != nil { + return errors.Errorf("invalid pmux config format") + } + pmuxServer := pmux.Server(httpClient, headers, serverToClientUrl, clientToServerUrl, config.Hb, flag.symmetricallyEncrypts, flag.symmetricallyEncryptPassphrase, flag.cipherType) + for { + stream, err := pmuxServer.Accept() + if err != nil { + return err + } + go func() { + err := socksServer.ServeConn(util.NewDuplexConn(stream)) + if err != nil { + cmd.Vlog.Log( + fmt.Sprintf("error(serve conn): %v", errors.WithStack(err)), + fmt.Sprintf("error(serve conn): %+v", errors.WithStack(err)), + ) + } + }() + } +} diff --git a/go.sum b/go.sum index 913fece..ca83151 100644 --- a/go.sum +++ b/go.sum @@ -133,7 +133,6 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -164,7 +163,6 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -219,7 +217,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -247,7 +244,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/main/main.go b/main/main.go index 6ae7fd5..53e2d97 100644 --- a/main/main.go +++ b/main/main.go @@ -3,6 +3,9 @@ package main import ( "fmt" "github.com/nwtgck/go-piping-tunnel/cmd" + _ "github.com/nwtgck/go-piping-tunnel/cmd/client" + _ "github.com/nwtgck/go-piping-tunnel/cmd/server" + _ "github.com/nwtgck/go-piping-tunnel/cmd/socks" "os" ) diff --git a/openssl_aes_ctr_duplex/openssl_aes_ctr.go b/openssl_aes_ctr_duplex/openssl_aes_ctr.go new file mode 100644 index 0000000..12ef0c5 --- /dev/null +++ b/openssl_aes_ctr_duplex/openssl_aes_ctr.go @@ -0,0 +1,71 @@ +package openssl_aes_ctr_duplex + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "github.com/pkg/errors" + "golang.org/x/crypto/pbkdf2" + "hash" + "io" +) + +type KeyAndIV struct { + Key []byte + Iv []byte +} + +const ivLen = 16 + +func DeriveKeyAndIvByPbkdf2(password []byte, salt []byte, iter int, keyLen int, h func() hash.Hash) KeyAndIV { + keyAndIv := pbkdf2.Key(password, salt, iter, keyLen+ivLen, h) + return KeyAndIV{ + Key: keyAndIv[:keyLen], + Iv: keyAndIv[keyLen:], + } +} + +func AesCtrEncryptWithPbkdf2(w io.Writer, password []byte, pbkdf2Iter int, keyLen int, h func() hash.Hash) (io.WriteCloser, error) { + var salt [8]byte + if _, err := io.ReadFull(rand.Reader, salt[:]); err != nil { + return nil, err + } + keyAndIV := DeriveKeyAndIvByPbkdf2(password, salt[:], pbkdf2Iter, keyLen, h) + block, err := aes.NewCipher(keyAndIV.Key) + if err != nil { + return nil, err + } + if _, err := w.Write(append([]byte("Salted__"), salt[:]...)); err != nil { + return nil, err + } + encryptingWriter := &cipher.StreamWriter{ + S: cipher.NewCTR(block, keyAndIV.Iv), + W: w, + } + return encryptingWriter, nil +} + +func AesCtrDecryptWithPbkdf2(encryptedReader io.Reader, password []byte, pbkdf2Iter int, keyLen int, h func() hash.Hash) (io.Reader, error) { + var eightBytes [8]byte + if _, err := io.ReadFull(encryptedReader, eightBytes[:]); err != nil { + return nil, err + } + if string(eightBytes[:]) != "Salted__" { + return nil, errors.New("not start with Salted__") + } + // Read salt + if _, err := io.ReadFull(encryptedReader, eightBytes[:]); err != nil { + return nil, err + } + // Derive key and IV + keyAndIV := DeriveKeyAndIvByPbkdf2(password, eightBytes[:], pbkdf2Iter, keyLen, h) + block, err := aes.NewCipher(keyAndIV.Key) + if err != nil { + return nil, err + } + decryptedReader := &cipher.StreamReader{ + S: cipher.NewCTR(block, keyAndIV.Iv), + R: encryptedReader, + } + return decryptedReader, nil +} diff --git a/openssl_aes_ctr_duplex/openssl_aes_ctr_duplex.go b/openssl_aes_ctr_duplex/openssl_aes_ctr_duplex.go new file mode 100644 index 0000000..29ce7b1 --- /dev/null +++ b/openssl_aes_ctr_duplex/openssl_aes_ctr_duplex.go @@ -0,0 +1,39 @@ +package openssl_aes_ctr_duplex + +import ( + "github.com/nwtgck/go-piping-tunnel/util" + "hash" + "io" +) + +type opensslAesCtrDuplex struct { + encryptWriter io.WriteCloser + decryptedReader io.Reader + closeBaseReader func() error +} + +func Duplex(baseWriter io.WriteCloser, baseReader io.ReadCloser, passphrase []byte, pbkdf2Iter int, keyLen int, h func() hash.Hash) (*opensslAesCtrDuplex, error) { + encryptWriter, err := AesCtrEncryptWithPbkdf2(baseWriter, passphrase, pbkdf2Iter, keyLen, h) + if err != nil { + return nil, err + } + decryptedReader, err := AesCtrDecryptWithPbkdf2(baseReader, passphrase, pbkdf2Iter, keyLen, h) + if err != nil { + return nil, err + } + return &opensslAesCtrDuplex{encryptWriter: encryptWriter, decryptedReader: decryptedReader, closeBaseReader: baseReader.Close}, nil +} + +func (d *opensslAesCtrDuplex) Write(p []byte) (int, error) { + return d.encryptWriter.Write(p) +} + +func (d *opensslAesCtrDuplex) Read(p []byte) (int, error) { + return d.decryptedReader.Read(p) +} + +func (d *opensslAesCtrDuplex) Close() error { + wErr := d.encryptWriter.Close() + rErr := d.closeBaseReader() + return util.CombineErrors(wErr, rErr) +} diff --git a/piping_util/piping_tunnel_util.go b/piping_util/piping_tunnel_util.go index 8c210aa..a626804 100644 --- a/piping_util/piping_tunnel_util.go +++ b/piping_util/piping_tunnel_util.go @@ -10,8 +10,10 @@ import ( ) const ( - CipherTypeOpenpgp string = "openpgp" - CipherTypeAesCtr = "aes-ctr" + CipherTypeOpenpgp string = "openpgp" + CipherTypeAesCtr = "aes-ctr" + CipherTypeOpensslAes128Ctr = "openssl-aes-128-ctr" + CipherTypeOpensslAes256Ctr = "openssl-aes-256-ctr" ) type KeyValue struct { diff --git a/pmux/pmux.go b/pmux/pmux.go index e732ae7..936a33c 100644 --- a/pmux/pmux.go +++ b/pmux/pmux.go @@ -7,8 +7,8 @@ import ( "encoding/binary" "encoding/json" "fmt" + "github.com/nwtgck/go-piping-tunnel/aes_ctr_duplex" "github.com/nwtgck/go-piping-tunnel/backoff" - "github.com/nwtgck/go-piping-tunnel/crypto_duplex" "github.com/nwtgck/go-piping-tunnel/early_piping_duplex" "github.com/nwtgck/go-piping-tunnel/hb_duplex" "github.com/nwtgck/go-piping-tunnel/openpgp_duplex" @@ -193,9 +193,10 @@ func (s *server) Accept() (io.ReadWriteCloser, error) { switch s.cipherType { case piping_util.CipherTypeAesCtr: // Encrypt with AES-CTR - duplex, err = crypto_duplex.EncryptDuplexWithAesCtr(duplex, duplex, []byte(s.passphrase)) + duplex, err = aes_ctr_duplex.Duplex(duplex, duplex, []byte(s.passphrase)) case piping_util.CipherTypeOpenpgp: duplex, err = openpgp_duplex.SymmetricallyEncryptDuplexWithOpenPGP(duplex, duplex, []byte(s.passphrase)) + // NOTE: pmux does not support openssl-compatible encryption default: return nil, errors.Errorf("unexpected cipher type: %s", s.cipherType) } @@ -329,9 +330,10 @@ func (c *client) Open() (io.ReadWriteCloser, error) { switch c.cipherType { case piping_util.CipherTypeAesCtr: // Encrypt with AES-CTR - duplex, err = crypto_duplex.EncryptDuplexWithAesCtr(duplex, duplex, []byte(c.passphrase)) + duplex, err = aes_ctr_duplex.Duplex(duplex, duplex, []byte(c.passphrase)) case piping_util.CipherTypeOpenpgp: duplex, err = openpgp_duplex.SymmetricallyEncryptDuplexWithOpenPGP(duplex, duplex, []byte(c.passphrase)) + // NOTE: pmux does not support openssl-compatible encryption default: return nil, errors.Errorf("unexpected cipher type: %s", c.cipherType) } diff --git a/version/version.go b/version/version.go index 6e73a93..30e0f05 100644 --- a/version/version.go +++ b/version/version.go @@ -1,3 +1,3 @@ package version -const Version = "0.9.0" +const Version = "0.10.0"