From e3842e4219c2252689fdcf10ce90e0b8c1e862bd Mon Sep 17 00:00:00 2001 From: Russ Magee Date: Sat, 20 Jan 2018 20:37:27 -0800 Subject: [PATCH] Removed channel-based server loop goroutine, solving eaten initial byte issue. Made receivers on hkex.Conn mutators *Conn again (whoops) TODO: Consider: padding (? probably not, XORKeyStream OFB/CBC/etc. modes prevent constant header/crib exposure, and would add lots of complexity to Read/Write) TODO: Add CTR, other modes --- demo/client/client.go | 31 +++++++-- demo/dbgserver/server.go | 2 +- demo/server/server.go | 147 +++++++++++++++++---------------------- hkexchan.go | 21 ++---- hkexnet.go | 47 ++++--------- 5 files changed, 110 insertions(+), 138 deletions(-) diff --git a/demo/client/client.go b/demo/client/client.go index 0b8cc11..c9035c2 100644 --- a/demo/client/client.go +++ b/demo/client/client.go @@ -14,6 +14,14 @@ import ( "golang.org/x/sys/unix" ) +type cmdSpec struct { + op []byte + who []byte + cmd []byte + authCookie []byte + status int +} + // Demo of a simple client that dials up to a simple test server to // send data. // @@ -39,6 +47,7 @@ func main() { flag.StringVar(&server, "s", "localhost:2000", "server hostname/address[:port]") flag.Parse() + //log.SetOutput(os.Stdout) log.SetOutput(ioutil.Discard) conn, err := hkex.Dial("tcp", server, cAlg, hAlg) @@ -60,6 +69,19 @@ func main() { fmt.Println("NOT A TTY") } + rec := &cmdSpec{op: []byte{'s'}, + who: []byte("ABCD"), + cmd: []byte("EFGH"), + authCookie: []byte("99"), + status: 0} + + _, err = fmt.Fprintf(conn, "%d %d %d %d\n", len(rec.op), len(rec.who), len(rec.cmd), len(rec.authCookie)) + _, err = conn.Write(rec.op) + _, err = conn.Write(rec.who) + _, err = conn.Write(rec.cmd) + _, err = conn.Write(rec.authCookie) + + //client reader (from server) goroutine wg.Add(1) go func() { // By deferring a call to wg.Done(), @@ -82,11 +104,12 @@ func main() { } } if isInteractive { - log.Println("[Got Write EOF]") - wg.Done() // client hanging up, close WaitGroup to exit client + log.Println("[Got EOF]") + wg.Done() // server hung up, close WaitGroup to exit client } }() + // client writer (to server) goroutine wg.Add(1) go func() { defer wg.Done() @@ -100,8 +123,8 @@ func main() { os.Exit(2) } } - log.Println("[Got Read EOF]") - wg.Done() // server hung up, close WaitGroup to exit client + log.Println("[Sent EOF]") + wg.Done() // client hung up, close WaitGroup to exit client }() // Wait until both stdin and stdout goroutines finish diff --git a/demo/dbgserver/server.go b/demo/dbgserver/server.go index 295d8cb..10ba2f1 100644 --- a/demo/dbgserver/server.go +++ b/demo/dbgserver/server.go @@ -62,7 +62,7 @@ func main() { } }(ch, eCh) - ticker := time.Tick(time.Second/100) + ticker := time.Tick(time.Second / 100) Term: // continuously read from the connection for { diff --git a/demo/server/server.go b/demo/server/server.go index bb7c00b..5742a74 100644 --- a/demo/server/server.go +++ b/demo/server/server.go @@ -4,13 +4,12 @@ import ( "flag" "fmt" "io" - "io/ioutil" "log" + "os" "os/exec" "os/user" "strings" "syscall" - "time" hkex "blitter.com/herradurakex" "github.com/kr/pty" @@ -32,13 +31,13 @@ const ( OpX = 'x' // exec ) -type Op uint8 +//type Op uint8 -type cmdRunner struct { - op Op - who string - arg string - authCookie string +type cmdSpec struct { + op []byte + who []byte + cmd []byte + authCookie []byte status int } @@ -95,7 +94,7 @@ func main() { flag.StringVar(&laddr, "l", ":2000", "interface[:port] to listen") flag.Parse() - log.SetOutput(ioutil.Discard) + log.SetOutput(os.Stdout /*ioutil.Discard*/) // Listen on TCP port 2000 on all available unicast and // anycast IP addresses of the local system. @@ -119,85 +118,63 @@ func main() { // multiple connections may be served concurrently. go func(c hkex.Conn) (e error) { defer c.Close() - var connOp *byte = nil - ch := make(chan []byte) - chN := 0 - eCh := make(chan error) - // Start a goroutine to read from our net connection - go func(ch chan []byte, eCh chan error) { - for { - // try to read the data - data := make([]byte, 512) - chN, err = c.Read(data) - if err != nil { - // send an error if it's encountered - eCh <- err - return - } - // send data if we read some. - ch <- data[0:chN] - } - }(ch, eCh) + //We use io.ReadFull() here to guarantee we consume + //just the data we want for the cmdSpec, and no more. + //Otherwise data will be sitting in the channel that isn't + //passed down to the command handlers. + var rec cmdSpec + var len1, len2, len3, len4 uint32 - ticker := time.Tick(time.Second / 100) - Term: - // continuously read from the connection - for { - select { - // This case means we recieved data on the connection - case data := <-ch: - // Do something with the data - fmt.Printf("Client sent %+v\n", data[0:chN]) - if connOp == nil { - // Initial xmit - get op byte - // Have op here and first block of data[] - connOp = new(byte) - *connOp = data[0] - fmt.Printf("[* connOp '%c']\n", *connOp) - } - if len(data) > 1 { - data = data[1:chN] - chN -= 1 - } - - if len(data) > 0 { - // From here, one could pass all subsequent data - // between client/server attached to an exec.Cmd, - // as data to/from a file, etc. - if connOp != nil && *connOp == 's' { - fmt.Println("[Running shell]") - runCmdAs("larissa", "bash -l -i", conn) - // Returned hopefully via an EOF or exit/logout; - // Clear current op so user can enter next, or EOF - connOp = nil - fmt.Println("[Exiting shell]") - conn.Close() - } - if strings.Trim(string(data), "\r\n") == "exit" { - conn.Close() - } - } - //fmt.Printf("Client sent %s\n", string(data)) - // This case means we got an error and the goroutine has finished - case err := <-eCh: - // handle our error then exit for loop - if err.Error() == "EOF" { - fmt.Printf("[Client disconnected]\n") - } else { - fmt.Printf("Error reading client data! (%+v)\n", err) - } - break Term - // This will timeout on the read. - case <-ticker: - // do nothing? this is just so we can time out if we need to. - // you probably don't even need to have this here unless you want - // do something specifically on the timeout. - } + n, err := fmt.Fscanf(c, "%d %d %d %d\n", &len1, &len2, &len3, &len4) + if err != nil || n < 4 { + fmt.Println("[Bad cmdSpec fmt]") + return err + } + fmt.Printf(" lens:%d %d %d %d\n", len1, len2, len3, len4) + + rec.op = make([]byte, len1, len1) + _, err = io.ReadFull(c, rec.op) + if err != nil { + fmt.Println("[Bad cmdSpec.op]") + return err + } + rec.who = make([]byte, len2, len2) + _, err = io.ReadFull(c, rec.who) + if err != nil { + fmt.Println("[Bad cmdSpec.who]") + return err + } + + rec.cmd = make([]byte, len3, len3) + _, err = io.ReadFull(c, rec.cmd) + if err != nil { + fmt.Println("[Bad cmdSpec.cmd]") + return err + } + + rec.authCookie = make([]byte, len4, len4) + _, err = io.ReadFull(c, rec.authCookie) + if err != nil { + fmt.Println("[Bad cmdSpec.authCookie]") + return err + } + + fmt.Printf("[cmdSpec: op:%c who:%s cmd:%s auth:%s]\n", + rec.op[0], string(rec.who), string(rec.cmd), string(rec.authCookie)) + + if rec.op[0] == 's' { + fmt.Println("[Running shell]") + runCmdAs("larissa", "bash -l -i", conn) + // Returned hopefully via an EOF or exit/logout; + // Clear current op so user can enter next, or EOF + rec.op[0] = 0 + fmt.Println("[Exiting shell]") + } else { + fmt.Println("[Bad cmdSpec]") } - // Shut down the connection. - //c.Close() return }(conn) - } + } //endfor + fmt.Println("[Exiting]") } diff --git a/hkexchan.go b/hkexchan.go index cdc096c..4fbf73e 100644 --- a/hkexchan.go +++ b/hkexchan.go @@ -8,6 +8,7 @@ import ( "crypto/aes" "crypto/cipher" "fmt" + "log" "math/big" "os" @@ -29,16 +30,6 @@ const ( HmacNoneDisallowed ) -type ChanOp uint8 - -const ( - ChanOpNop = '.' - ChanOpEcho = 'e' // For testing - echo client data to stderr - //ChanOpFileWrite = "w" - //ChanOpFileRead = "r" - //ChanOpRemoteCmd = "x" -) - /*TODO: HMAC derived from HKEx FA.*/ /* Support functionality to set up encryption after a channel has been negotiated via hkexnet.go @@ -59,7 +50,7 @@ func (hc Conn) getStream(keymat *big.Int) (ret cipher.Stream) { ivlen = aes.BlockSize iv := keymat.Bytes()[aes.BlockSize : aes.BlockSize+ivlen] ret = cipher.NewOFB(block, iv) - fmt.Printf("[cipher AES_256 (%d)]\n", copts) + log.Printf("[cipher AES_256 (%d)]\n", copts) break case CAlgTwofish128: key = keymat.Bytes()[0:twofish.BlockSize] @@ -67,7 +58,7 @@ func (hc Conn) getStream(keymat *big.Int) (ret cipher.Stream) { ivlen = twofish.BlockSize iv := keymat.Bytes()[twofish.BlockSize : twofish.BlockSize+ivlen] ret = cipher.NewOFB(block, iv) - fmt.Printf("[cipher TWOFISH_128 (%d)]\n", copts) + log.Printf("[cipher TWOFISH_128 (%d)]\n", copts) break case CAlgBlowfish64: key = keymat.Bytes()[0:blowfish.BlockSize] @@ -84,9 +75,10 @@ func (hc Conn) getStream(keymat *big.Int) (ret cipher.Stream) { // copy what's needed whereas blowfish does no such check. iv := keymat.Bytes()[blowfish.BlockSize : blowfish.BlockSize+ivlen] ret = cipher.NewOFB(block, iv) - fmt.Printf("[cipher BLOWFISH_64 (%d)]\n", copts) + log.Printf("[cipher BLOWFISH_64 (%d)]\n", copts) break default: + log.Printf("[invalid cipher (%d)]\n", copts) fmt.Printf("DOOFUS SET A VALID CIPHER ALG (%d)\n", copts) os.Exit(1) } @@ -94,9 +86,10 @@ func (hc Conn) getStream(keymat *big.Int) (ret cipher.Stream) { hopts := (hc.cipheropts >> 8) & 0xFF switch hopts { case HmacSHA256: - fmt.Printf("[nop HmacSHA256 (%d)]\n", hopts) + log.Printf("[nop HmacSHA256 (%d)]\n", hopts) break default: + log.Printf("[invalid hmac (%d)]\n", hopts) fmt.Printf("DOOFUS SET A VALID HMAC ALG (%d)\n", hopts) os.Exit(1) } diff --git a/hkexnet.go b/hkexnet.go index bab843d..9fdd27f 100644 --- a/hkexnet.go +++ b/hkexnet.go @@ -39,7 +39,6 @@ type Conn struct { h *HerraduraKEx cipheropts uint32 // post-KEx cipher/hmac options opts uint32 // post-KEx protocol options (caller-defined) - op uint8 // post-KEx 'op' (caller-defined) r cipher.Stream w cipher.Stream } @@ -57,7 +56,7 @@ func (c Conn) ConnOpts() uint32 { // peer as part of KEx but not part of the KEx itself. // // opts - bitfields for cipher and hmac alg. to use after KEx -func (c Conn) SetConnOpts(copts uint32) { +func (c *Conn) SetConnOpts(copts uint32) { c.cipheropts = copts } @@ -77,31 +76,11 @@ func (c Conn) Opts() uint32 { // of the KEx of encryption info used by the connection. // // opts - a uint32, caller-defined -func (c Conn) SetOpts(opts uint32) { +func (c *Conn) SetOpts(opts uint32) { c.opts = opts } -// Op returns the 'op' value, which is sent to the peer -// but is not itself part of the KEx or connection (cipher/hmac) setup. -// -// Consumers of this lib may use this to indicate connection-specific -// operations not part of the KEx or encryption info used by the connection. -func (c Conn) Op() uint8 { - return c.op -} - -// SetOp sets the 'op' value, which is sent to the peer -// but is not itself part of the KEx or connection (cipher/hmac) setup. -// -// Consumers of this lib may use this to indicate connection-specific -// operations not part of the KEx or encryption info used by the connection. -// -// op - a uint8, caller-defined -func (c Conn) SetOp(op uint8) { - c.op = op -} - -func (c Conn) applyConnExtensions(extensions ...string) { +func (c *Conn) applyConnExtensions(extensions ...string) { for _, s := range extensions { switch s { case "C_AES_256": @@ -143,20 +122,20 @@ func Dial(protocol string, ipport string, extensions ...string) (hc *Conn, err e if err != nil { return nil, err } - hc = &Conn{c: c, h: New(0, 0), cipheropts: 0, opts: 0, op: 0, r: nil, w: nil} + hc = &Conn{c: c, h: New(0, 0), cipheropts: 0, opts: 0, r: nil, w: nil} hc.applyConnExtensions(extensions...) - fmt.Fprintf(c, "0x%s\n%08x:%08x:%02x\n", hc.h.d.Text(16), - hc.cipheropts, hc.opts, hc.op) + fmt.Fprintf(c, "0x%s\n%08x:%08x\n", hc.h.d.Text(16), + hc.cipheropts, hc.opts) d := big.NewInt(0) _, err = fmt.Fscanln(c, d) if err != nil { return nil, err } - _, err = fmt.Fscanf(c, "%08x:%08x:%02x\n", - &hc.cipheropts, &hc.opts, &hc.op) + _, err = fmt.Fscanf(c, "%08x:%08x\n", + &hc.cipheropts, &hc.opts) if err != nil { return nil, err } @@ -265,15 +244,15 @@ func (hl HKExListener) Accept() (hc Conn, err error) { } log.Println("[Accepted]") - hc = Conn{c: c, h: New(0, 0), cipheropts: 0, opts: 0, op: 0, r: nil, w: nil} + hc = Conn{c: c, h: New(0, 0), cipheropts: 0, opts: 0, r: nil, w: nil} d := big.NewInt(0) _, err = fmt.Fscanln(c, d) if err != nil { return hc, err } - _, err = fmt.Fscanf(c, "%08x:%08x:%02x\n", - &hc.cipheropts, &hc.opts, &hc.op) + _, err = fmt.Fscanf(c, "%08x:%08x\n", + &hc.cipheropts, &hc.opts) if err != nil { return hc, err } @@ -283,8 +262,8 @@ func (hl HKExListener) Accept() (hc Conn, err error) { hc.h.FA() log.Printf("**(s)** FA:%s\n", hc.h.fa) - fmt.Fprintf(c, "0x%s\n%08x:%08x:%02x\n", hc.h.d.Text(16), - hc.cipheropts, hc.opts, hc.op) + fmt.Fprintf(c, "0x%s\n%08x:%08x\n", hc.h.d.Text(16), + hc.cipheropts, hc.opts) hc.r = hc.getStream(hc.h.fa) hc.w = hc.getStream(hc.h.fa)