diff --git a/README.md b/README.md index 03036a1..5877579 100644 --- a/README.md +++ b/README.md @@ -90,5 +90,6 @@ Usage: Flags: -h, --help help for piping-duplex -s, --server string Piping Server URL (default "https://ppng.io") + -c, --symmetric use symmetric passphrase protection -v, --version show version ``` diff --git a/cmd/root.go b/cmd/root.go index 8d4a387..e6c5e9f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,9 +2,12 @@ package cmd import ( "fmt" + "github.com/mattn/go-tty" "github.com/nwtgck/go-piping-duplex" + "github.com/nwtgck/go-piping-duplex/util" "github.com/nwtgck/go-piping-duplex/version" "github.com/spf13/cobra" + "io" "os" ) @@ -14,6 +17,7 @@ const ( var server string var showsVersion bool +var usesPassphrase bool func init() { cobra.OnInitialize() @@ -22,6 +26,7 @@ func init() { defaultServer = "https://ppng.io" } RootCmd.Flags().StringVarP(&server, "server", "s", defaultServer, "Piping Server URL") + RootCmd.Flags().BoolVarP(&usesPassphrase, "symmetric", "c", false, "use symmetric passphrase protection") RootCmd.Flags().BoolVarP(&showsVersion, "version", "v", false, "show version") } @@ -37,6 +42,21 @@ var RootCmd = &cobra.Command{ if len(args) != 2 { return fmt.Errorf("Your ID and peer ID are required\n") } + var passphrase string + if usesPassphrase { + tty, err := tty.Open() + if err != nil { + return err + } + defer tty.Close() + fmt.Fprint(tty.Output(), "Passphrase: ") + passphrase, err = tty.ReadPasswordNoEcho() + if err != nil { + return err + } + fmt.Fprintln(tty.Output(), "[INFO] End-to-end encrypted") + } + var _ = passphrase selfId := args[0] peerId := args[1] _, _ = fmt.Fprintf(os.Stderr, "[INFO] Server: %s\n", server) @@ -46,8 +66,23 @@ var RootCmd = &cobra.Command{ return err } _, _ = fmt.Fprintln(os.Stderr, "[INFO] Established!") - input := os.Stdin + var input io.Reader = os.Stdin + if usesPassphrase { + input = util.OpenpgpSymmetricallyEncrypt(input, []byte(passphrase)) + } output := os.Stdout - return piping_duplex.Duplex(server, selfId, peerId, input, output) + r, err := piping_duplex.Duplex(server, selfId, peerId, input) + if err != nil { + return err + } + if usesPassphrase { + var decrypted, err = util.OpenpgpSymmetricallyDecrypt(r, []byte(passphrase)) + if err != nil { + return err + } + r = decrypted + } + io.Copy(output, r) + return nil }, } diff --git a/go.mod b/go.mod index ca5cc73..962117f 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,8 @@ module github.com/nwtgck/go-piping-duplex go 1.14 -require github.com/spf13/cobra v0.0.5 +require ( + github.com/mattn/go-tty v0.0.3 + github.com/spf13/cobra v0.0.5 + golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 +) diff --git a/go.sum b/go.sum index baada52..8f4d77c 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,16 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= +github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -23,10 +31,18 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8 h1:RB0v+/pc8oMzPsN97aZYEwNuJ6ouRJ2uhjxemJ9zvrY= +github.com/tcnksm/go-input v0.0.0-20180404061846-548a7d7a8ee8/go.mod h1:IlWNj9v/13q7xFbaK4mbyzMNwrZLaWSHx/aibKIZuIg= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/piping_duplex.go b/piping_duplex.go index 65ff13a..182e987 100644 --- a/piping_duplex.go +++ b/piping_duplex.go @@ -9,44 +9,35 @@ import ( ) func Wait(server string, selfId string, peerId string) error { - return Duplex(server, selfId, peerId, strings.NewReader("OK"), ioutil.Discard) + r, err := Duplex(server, selfId, peerId, strings.NewReader("OK")) + if err != nil { + return err + } + _, err = io.Copy(ioutil.Discard, r) + return err } -func Duplex(server string, selfPath string, peerPath string, input io.Reader, output io.Writer) error { - c := make(chan error) - go func() { - url, err := util.UrlJoin(server, peerPath) - if err != nil { - c <- err - return - } - res, err := http.Get(url) - if err != nil { - c <- err - return - } - _, err = io.Copy(output, res.Body) - c <- err - }() + +func Duplex(server string, selfPath string, peerPath string, r io.Reader) (io.Reader, error) { + postUrl, err := util.UrlJoin(server, selfPath) + if err != nil { + return nil, err + } go func() { // TODO: hard code contentType := "application/octet-stream" - url, err := util.UrlJoin(server, selfPath) + _, err = http.Post(postUrl, contentType, r) if err != nil { - c <- err - return + panic(err) } - _, err = http.Post(url, contentType, input) - c <- err }() - var err error - err = <- c + getUrl, err := util.UrlJoin(server, peerPath) if err != nil { - return err + return nil, err } - err = <- c + res, err := http.Get(getUrl) if err != nil { - return err + return nil, err } - return nil + return res.Body, nil } diff --git a/util/util.go b/util/util.go index 2724dfb..0fc69b9 100644 --- a/util/util.go +++ b/util/util.go @@ -1,6 +1,8 @@ package util import ( + "golang.org/x/crypto/openpgp" + "io" "net/url" "path" ) @@ -14,3 +16,34 @@ func UrlJoin(rawurl string, elem ...string) (string, error) { u.Path = path.Join(append([]string{u.Path}, elem...)...) return u.String(), nil } + +func OpenpgpSymmetricallyEncrypt(plain io.Reader, passphrase []byte) io.Reader { + // (base: https://gist.github.com/eliquious/9e96017f47d9bd43cdf9) + pr, pw := io.Pipe() + + go func() { + defer pw.Close() + w, err := openpgp.SymmetricallyEncrypt(pw, passphrase, nil, nil) + if err != nil { + panic(err) + } + _, err = io.Copy(w, plain) + if err != nil { + panic(err) + } + w.Close() + }() + + return pr +} + +func OpenpgpSymmetricallyDecrypt(encrypted io.Reader, passphrase []byte) (io.Reader, error) { + // (base: https://github.com/golang/crypto/blob/a2144134853fc9a27a7b1e3eb4f19f1a76df13c9/openpgp/write_test.go#L129) + md, err := openpgp.ReadMessage(encrypted, nil, func(keys []openpgp.Key, symmetric bool) ([]byte, error) { + return passphrase, nil + }, nil) + if err != nil { + return nil, err + } + return md.UnverifiedBody, nil +} diff --git a/version/version.go b/version/version.go index 31646b0..c62f7e6 100644 --- a/version/version.go +++ b/version/version.go @@ -1,3 +1,3 @@ package version -const Version = "0.1.0" +const Version = "0.2.0"