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
This commit is contained in:
Russ Magee 2018-01-20 20:37:27 -08:00
parent 732005d9bf
commit e3842e4219
5 changed files with 110 additions and 138 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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]")
}

View File

@ -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)
}

View File

@ -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)