Locking in WritePacket() apparently working, client and server-side chaffing functional

This commit is contained in:
Russ Magee 2018-05-03 23:53:47 -07:00
parent 6d606bbbd9
commit a49a5d4cc2
3 changed files with 100 additions and 72 deletions

View file

@ -23,6 +23,7 @@ import (
"io" "io"
"log" "log"
"math/big" "math/big"
"math/rand"
"net" "net"
"strings" "strings"
"sync" "sync"
@ -52,12 +53,17 @@ type Conn struct {
WinCh chan WinSize WinCh chan WinSize
Rows uint16 Rows uint16
Cols uint16 Cols uint16
Rwmut sync.Mutex
r cipher.Stream //read cipherStream Rwmut *sync.Mutex
rm hash.Hash chaff bool
w cipher.Stream //write cipherStream chaffMsecsMin int //msecs min interval
wm hash.Hash chaffMsecsMax int //msecs max interval
dBuf *bytes.Buffer //decrypt buffer for Read()
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 // 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 return nil, err
} }
// Init hkexnet.Conn hc over net.Conn c // 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...) hc.applyConnExtensions(extensions...)
// Send hkexnet.Conn parameters to remote side // Send hkexnet.Conn parameters to remote side
@ -268,13 +274,15 @@ func (hl HKExListener) Accept() (hc Conn, err error) {
// Open raw Conn c // Open raw Conn c
c, err := hl.l.Accept() c, err := hl.l.Accept()
if err != nil { if err != nil {
return Conn{c: nil, h: nil, cipheropts: 0, opts: 0, hc := Conn{c: nil, h: nil, cipheropts: 0, opts: 0, Rwmut: &sync.Mutex{},
r: nil, w: nil}, err r: nil, w: nil}
return hc, err
} }
log.Println("[Accepted]") log.Println("[Accepted]")
hc = Conn{c: c, h: New(0, 0), WinCh: make(chan WinSize, 1), 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 // Read in hkexnet.Conn parameters over raw Conn c
// d is value for Herradura key exchange // 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) log.Println("unexpected Read() err:", err)
} else { } else {
log.Println("[Client hung up]") log.Println("[Client hung up]")
// TODO: Stop chaff if active
} }
return 0, err return 0, err
} }
@ -440,40 +449,44 @@ func (c Conn) WritePacket(b []byte, op byte) (n int, err error) {
var hmacOut []uint8 var hmacOut []uint8
var payloadLen uint32 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 // Calculate hmac on payload
//if payloadLen == 2 && string(b) == "`1" { c.wm.Write(b)
// op = CSOChaff hmacOut = c.wm.Sum(nil)[0:4]
//}
// Calculate hmac on payload log.Printf(" (%04x> HMAC(o):%s\r\n", payloadLen, hex.EncodeToString(hmacOut))
c.wm.Write(b)
hmacOut = c.wm.Sum(nil)[0:4]
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 ctrlStatOp := op
// The StreamWriter acts like a pipe, forwarding whatever is
// written to it through the cipher, encrypting as it goes err = binary.Write(c.c, binary.BigEndian, &ctrlStatOp)
ws := &cipher.StreamWriter{S: c.w, W: &wb} if err == nil {
_, err = ws.Write(b) // Write hmac LSB, payloadLen followed by payload
if err != nil { err = binary.Write(c.c, binary.BigEndian, hmacOut)
panic(err) 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 { if err != nil {
//panic(err) //panic(err)
log.Println(err) log.Println(err)
@ -481,12 +494,42 @@ func (c Conn) WritePacket(b []byte, op byte) (n int, err error) {
return return
} }
// hkexsh.Copy() is a modified version of io.Copy() with locking, func (c *Conn) Chaff(enable bool, msecsMin int, msecsMax int, szMax int) {
// on a passed-in mutex, around the actual call to Write() to permit c.chaff = enable
// multiple producers to write hkexsh buffers to the same destination. c.chaffMsecsMin = msecsMin //move these to params of chaffHelper() ?
// c.chaffMsecsMax = msecsMax
// (Used to generate chaff during sessions)
func Copy(m *sync.Mutex, dst io.Writer, src io.Reader) (written int64, err error) { 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. // // If the reader has a WriteTo method, use it to do the copy.
// // Avoids an allocation and a copy. // // Avoids an allocation and a copy.
// if wt, ok := src.(io.WriterTo); ok { // 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 { for {
nr, er := src.Read(buf) nr, er := src.Read(buf)
if nr > 0 { if nr > 0 {
m.Lock()
nw, ew := dst.Write(buf[0:nr]) nw, ew := dst.Write(buf[0:nr])
m.Unlock()
if nw > 0 { if nw > 0 {
written += int64(nw) written += int64(nw)
} }

View file

@ -13,7 +13,6 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"math/rand"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
@ -21,7 +20,6 @@ import (
"strings" "strings"
"sync" "sync"
"syscall" "syscall"
"time"
hkexsh "blitter.com/go/hkexsh" hkexsh "blitter.com/go/hkexsh"
isatty "github.com/mattn/go-isatty" isatty "github.com/mattn/go-isatty"
@ -194,8 +192,6 @@ func main() {
} }
}() }()
//m := &sync.Mutex{}
if isInteractive { if isInteractive {
// Handle pty resizes (notify server side) // Handle pty resizes (notify server side)
ch := make(chan os.Signal, 1) ch := make(chan os.Signal, 1)
@ -213,40 +209,22 @@ func main() {
panic(err) panic(err)
} }
termSzPacket := fmt.Sprintf("%d %d", rows, cols) termSzPacket := fmt.Sprintf("%d %d", rows, cols)
conn.Rwmut.Lock()
conn.WritePacket([]byte(termSzPacket), hkexsh.CSOTermSize) conn.WritePacket([]byte(termSzPacket), hkexsh.CSOTermSize)
conn.Rwmut.Unlock()
} }
}() }()
ch <- syscall.SIGWINCH // Initial resize. 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 // client writer (to server) goroutine
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
// io.Copy() expects EOF so this will // Copy() expects EOF so this will
// exit with outerr == nil // exit with outerr == nil
//!_, outerr := io.Copy(conn, os.Stdin) //!_, 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) { _, 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) }(conn, os.Stdin)
if outerr != nil { if outerr != nil {
@ -258,6 +236,8 @@ func main() {
} }
} }
log.Println("[Sent EOF]") 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 wg.Done() // client hung up, close WaitGroup to exit client
}() }()
} }

View file

@ -130,10 +130,17 @@ func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn) (err
// Copy stdin to the pty.. (bgnd goroutine) // Copy stdin to the pty.. (bgnd goroutine)
go func() { go func() {
_, _ = io.Copy(ptmx, conn) _, _ = hkexsh.Copy(ptmx, conn)
}() }()
// ..and the pty to stdout. // ..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. //err = c.Run() // returns when c finishes.