From a49a5d4cc205b9e7c553e25ad2d8f256e0d2eeca Mon Sep 17 00:00:00 2001 From: Russ Magee Date: Thu, 3 May 2018 23:53:47 -0700 Subject: [PATCH] Locking in WritePacket() apparently working, client and server-side chaffing functional --- hkexnet.go | 131 +++++++++++++++++++++++++++++---------------- hkexsh/hkexsh.go | 30 ++--------- hkexshd/hkexshd.go | 11 +++- 3 files changed, 100 insertions(+), 72 deletions(-) diff --git a/hkexnet.go b/hkexnet.go index c6b1346..af938ee 100644 --- a/hkexnet.go +++ b/hkexnet.go @@ -23,6 +23,7 @@ import ( "io" "log" "math/big" + "math/rand" "net" "strings" "sync" @@ -52,12 +53,17 @@ type Conn struct { WinCh chan WinSize Rows uint16 Cols uint16 - Rwmut sync.Mutex - r cipher.Stream //read cipherStream - rm hash.Hash - w cipher.Stream //write cipherStream - wm hash.Hash - dBuf *bytes.Buffer //decrypt buffer for Read() + + Rwmut *sync.Mutex + chaff bool + chaffMsecsMin int //msecs min interval + chaffMsecsMax int //msecs max interval + + r cipher.Stream //read cipherStream + rm hash.Hash + w cipher.Stream //write cipherStream + wm hash.Hash + dBuf *bytes.Buffer //decrypt buffer for Read() } // ConnOpts returns the cipher/hmac options value, which is sent to the @@ -141,7 +147,7 @@ func Dial(protocol string, ipport string, extensions ...string) (hc *Conn, err e return nil, err } // Init hkexnet.Conn hc over net.Conn c - hc = &Conn{c: c, h: New(0, 0), dBuf: new(bytes.Buffer)} + hc = &Conn{c: c, h: New(0, 0), Rwmut: &sync.Mutex{}, dBuf: new(bytes.Buffer)} hc.applyConnExtensions(extensions...) // Send hkexnet.Conn parameters to remote side @@ -268,13 +274,15 @@ func (hl HKExListener) Accept() (hc Conn, err error) { // Open raw Conn c c, err := hl.l.Accept() if err != nil { - return Conn{c: nil, h: nil, cipheropts: 0, opts: 0, - r: nil, w: nil}, err + hc := Conn{c: nil, h: nil, cipheropts: 0, opts: 0, Rwmut: &sync.Mutex{}, + r: nil, w: nil} + return hc, err } log.Println("[Accepted]") hc = Conn{c: c, h: New(0, 0), WinCh: make(chan WinSize, 1), - dBuf: new(bytes.Buffer)} + Rwmut: &sync.Mutex{}, + dBuf: new(bytes.Buffer)} // Read in hkexnet.Conn parameters over raw Conn c // d is value for Herradura key exchange @@ -341,6 +349,7 @@ func (c Conn) Read(b []byte) (n int, err error) { log.Println("unexpected Read() err:", err) } else { log.Println("[Client hung up]") + // TODO: Stop chaff if active } return 0, err } @@ -440,40 +449,44 @@ func (c Conn) WritePacket(b []byte, op byte) (n int, err error) { var hmacOut []uint8 var payloadLen uint32 - log.Printf(" :>ptext:\r\n%s\r\n", hex.Dump(b)) + c.Rwmut.Lock() + { + log.Printf(" :>ptext:\r\n%s\r\n", hex.Dump(b)) - payloadLen = uint32(len(b)) + payloadLen = uint32(len(b)) - // Testing: '`1' will trigger a chaff packet - //if payloadLen == 2 && string(b) == "`1" { - // op = CSOChaff - //} + // Calculate hmac on payload + c.wm.Write(b) + hmacOut = c.wm.Sum(nil)[0:4] - // Calculate hmac on payload - c.wm.Write(b) - hmacOut = c.wm.Sum(nil)[0:4] + log.Printf(" (%04x> HMAC(o):%s\r\n", payloadLen, hex.EncodeToString(hmacOut)) - log.Printf(" (%04x> HMAC(o):%s\r\n", payloadLen, hex.EncodeToString(hmacOut)) + var wb bytes.Buffer + // The StreamWriter acts like a pipe, forwarding whatever is + // written to it through the cipher, encrypting as it goes + ws := &cipher.StreamWriter{S: c.w, W: &wb} + _, err = ws.Write(b) + if err != nil { + panic(err) + } + log.Printf(" ->ctext:\r\n%s\r\n", hex.Dump(wb.Bytes())) - var wb bytes.Buffer - // The StreamWriter acts like a pipe, forwarding whatever is - // written to it through the cipher, encrypting as it goes - ws := &cipher.StreamWriter{S: c.w, W: &wb} - _, err = ws.Write(b) - if err != nil { - panic(err) + ctrlStatOp := op + + err = binary.Write(c.c, binary.BigEndian, &ctrlStatOp) + if err == nil { + // Write hmac LSB, payloadLen followed by payload + err = binary.Write(c.c, binary.BigEndian, hmacOut) + if err == nil { + err = binary.Write(c.c, binary.BigEndian, payloadLen) + if err == nil { + n, err = c.c.Write(wb.Bytes()) + } + } + } } - log.Printf(" ->ctext:\r\n%s\r\n", hex.Dump(wb.Bytes())) + c.Rwmut.Unlock() - ctrlStatOp := op - - //{ - _ = binary.Write(c.c, binary.BigEndian, &ctrlStatOp) - // Write hmac LSB, payloadLen followed by payload - _ = binary.Write(c.c, binary.BigEndian, hmacOut) - _ = binary.Write(c.c, binary.BigEndian, payloadLen) - n, err = c.c.Write(wb.Bytes()) - //} if err != nil { //panic(err) log.Println(err) @@ -481,12 +494,42 @@ func (c Conn) WritePacket(b []byte, op byte) (n int, err error) { return } -// hkexsh.Copy() is a modified version of io.Copy() with locking, -// on a passed-in mutex, around the actual call to Write() to permit -// multiple producers to write hkexsh buffers to the same destination. -// -// (Used to generate chaff during sessions) -func Copy(m *sync.Mutex, dst io.Writer, src io.Reader) (written int64, err error) { +func (c *Conn) Chaff(enable bool, msecsMin int, msecsMax int, szMax int) { + c.chaff = enable + c.chaffMsecsMin = msecsMin //move these to params of chaffHelper() ? + c.chaffMsecsMax = msecsMax + + if enable { + log.Println("Chaffing ENABLED") + c.chaffHelper(szMax) + } +} + +// Helper routine to spawn a chaffing goroutine for each Conn +// TODO: if/when server->client chaffing is added, server must +// todo: ensure this is turned off on client hangup +func (c *Conn) chaffHelper(szMax int) { + go func() { + for { + var nextDuration int + if c.chaff { + chaff := make([]byte, rand.Intn(szMax)) + min := c.chaffMsecsMin + nextDuration = rand.Intn(c.chaffMsecsMax-min) + min + _, _ = rand.Read(chaff) + _, err := c.WritePacket(chaff, CSOChaff) + if err != nil { + log.Println("[ *** error writing chaff - end chaffing *** ]") + break + } + } + time.Sleep(time.Duration(nextDuration) * time.Millisecond) + } + }() +} + +// hkexsh.Copy() is a modified version of io.Copy() +func Copy(dst io.Writer, src io.Reader) (written int64, err error) { // // If the reader has a WriteTo method, use it to do the copy. // // Avoids an allocation and a copy. // if wt, ok := src.(io.WriterTo); ok { @@ -501,9 +544,7 @@ func Copy(m *sync.Mutex, dst io.Writer, src io.Reader) (written int64, err error for { nr, er := src.Read(buf) if nr > 0 { - m.Lock() nw, ew := dst.Write(buf[0:nr]) - m.Unlock() if nw > 0 { written += int64(nw) } diff --git a/hkexsh/hkexsh.go b/hkexsh/hkexsh.go index 4d1bb22..d37535a 100644 --- a/hkexsh/hkexsh.go +++ b/hkexsh/hkexsh.go @@ -13,7 +13,6 @@ import ( "io" "io/ioutil" "log" - "math/rand" "os" "os/exec" "os/signal" @@ -21,7 +20,6 @@ import ( "strings" "sync" "syscall" - "time" hkexsh "blitter.com/go/hkexsh" isatty "github.com/mattn/go-isatty" @@ -194,8 +192,6 @@ func main() { } }() - //m := &sync.Mutex{} - if isInteractive { // Handle pty resizes (notify server side) ch := make(chan os.Signal, 1) @@ -213,40 +209,22 @@ func main() { panic(err) } termSzPacket := fmt.Sprintf("%d %d", rows, cols) - conn.Rwmut.Lock() conn.WritePacket([]byte(termSzPacket), hkexsh.CSOTermSize) - conn.Rwmut.Unlock() } }() ch <- syscall.SIGWINCH // Initial resize. - // client chaffing goroutine - // TODO: Consider making this a feature of hkexsh.Conn itself - wg.Add(1) - go func() { - defer wg.Done() - for { - chaff := make([]byte, rand.Intn(512)) - nextDurationMin := 1000 //ms - nextDuration := rand.Intn(5000-nextDurationMin) + nextDurationMin - _, _ = rand.Read(chaff) - conn.Rwmut.Lock() - conn.WritePacket(chaff, hkexsh.CSOChaff) - conn.Rwmut.Unlock() - time.Sleep(time.Duration(nextDuration) * time.Millisecond) - } - }() - // client writer (to server) goroutine wg.Add(1) go func() { defer wg.Done() - // io.Copy() expects EOF so this will + // Copy() expects EOF so this will // exit with outerr == nil //!_, outerr := io.Copy(conn, os.Stdin) + conn.Chaff(true, 100, 500, 32) // enable client->server chaffing _, outerr := func(conn *hkexsh.Conn, r io.Reader) (w int64, e error) { - return hkexsh.Copy(&conn.Rwmut, conn, r) + return hkexsh.Copy(conn, r) }(conn, os.Stdin) if outerr != nil { @@ -258,6 +236,8 @@ func main() { } } log.Println("[Sent EOF]") + //FIXME: regression circa. April 30 2018 on 'exit' from client, + //fixme: Enter/RETURN required prior to actua client exit wg.Done() // client hung up, close WaitGroup to exit client }() } diff --git a/hkexshd/hkexshd.go b/hkexshd/hkexshd.go index bad499b..a207847 100644 --- a/hkexshd/hkexshd.go +++ b/hkexshd/hkexshd.go @@ -130,10 +130,17 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn) (err // Copy stdin to the pty.. (bgnd goroutine) go func() { - _, _ = io.Copy(ptmx, conn) + _, _ = hkexsh.Copy(ptmx, conn) }() + // ..and the pty to stdout. - _, _ = io.Copy(conn, ptmx) + // --(FIXME: server->client chaffing can't work here as-is, since we + // --pty.Start()ed the command above, and that command has no + // --knowledge of another thread which would do chaffing. + // --Modify pty somehow to slave the command through hkexsh.Copy() ? + conn.Chaff(true, 100, 500, 32) + _, _ = hkexsh.Copy(conn, ptmx) + //_, _ = io.Copy(conn, ptmx) //err = c.Run() // returns when c finishes.