From d465c1ee5b761bf281e168e150dfea8cf3c12974 Mon Sep 17 00:00:00 2001 From: Russ Magee Date: Fri, 16 Feb 2018 18:46:29 -0800 Subject: [PATCH] Initial experiments: HMAC on stream --- demo/server/server.go | 22 +++++++++++----------- herradurakex.go | 2 +- hkexauth.go | 5 +---- hkexchan.go | 16 +++++++++++++--- hkexnet.go | 28 +++++++++++++++------------- 5 files changed, 41 insertions(+), 32 deletions(-) diff --git a/demo/server/server.go b/demo/server/server.go index 8d9b81b..f9693c4 100644 --- a/demo/server/server.go +++ b/demo/server/server.go @@ -75,7 +75,7 @@ func runShellAs(who string, cmd string, interactive bool, conn hkex.Conn) (err e var uid, gid uint32 fmt.Sscanf(u.Uid, "%d", &uid) fmt.Sscanf(u.Gid, "%d", &gid) - fmt.Println("uid:", uid, "gid:", gid) + log.Println("uid:", uid, "gid:", gid) // Need to clear server's env and set key vars of the // target user. This isn't perfect (TERM doesn't seem to @@ -155,14 +155,14 @@ func main() { } defer l.Close() - fmt.Println("Serving on", laddr) + log.Println("Serving on", laddr) for { // Wait for a connection. conn, err := l.Accept() if err != nil { log.Fatal(err) } - fmt.Println("Accepted client") + log.Println("Accepted client") // Handle the connection in a new goroutine. // The loop then returns to accepting, so that @@ -179,7 +179,7 @@ func main() { n, err := fmt.Fscanf(c, "%d %d %d %d\n", &len1, &len2, &len3, &len4) if err != nil || n < 4 { - fmt.Println("[Bad cmdSpec fmt]") + log.Println("[Bad cmdSpec fmt]") return err } //fmt.Printf(" lens:%d %d %d %d\n", len1, len2, len3, len4) @@ -187,27 +187,27 @@ func main() { rec.op = make([]byte, len1, len1) _, err = io.ReadFull(c, rec.op) if err != nil { - fmt.Println("[Bad cmdSpec.op]") + log.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]") + log.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]") + log.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]") + log.Println("[Bad cmdSpec.authCookie]") return err } @@ -229,19 +229,19 @@ func main() { // Returned hopefully via an EOF or exit/logout; // Clear current op so user can enter next, or EOF rec.op[0] = 0 - fmt.Println("[Command complete]") + log.Println("[Command complete]") } else if rec.op[0] == 's' { log.Println("[Running shell]") runShellAs(string(rec.who), string(rec.cmd), true, 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]") + log.Println("[Exiting shell]") } else { log.Println("[Bad cmdSpec]") } return }(conn) } //endfor - fmt.Println("[Exiting]") + log.Println("[Exiting]") } diff --git a/herradurakex.go b/herradurakex.go index 4961b02..9e38b8d 100644 --- a/herradurakex.go +++ b/herradurakex.go @@ -25,7 +25,6 @@ package herradurakex golang implementation by Russ Magee (rmagee_at_gmail.com) */ - /* This is the core KEx algorithm. For client/server net support code, See hkexnet.go for a golang/pkg/net for the compatible Conn interface using this to transparently negotiate keys and secure a network channel. */ @@ -148,6 +147,7 @@ func (h *HerraduraKEx) FA() { h.fa = h.fscxRevolve(h.PeerD, h.b, h.intSz-h.pubSz) h.fa.Xor(h.fa, h.a) } + // Output HerraduraKEx type value as a string. Implements Stringer interface. func (h *HerraduraKEx) String() string { return fmt.Sprintf("s:%d p:%d\na:%s\nb:%s\nd:->%s\n<-PeerD:%s\nfa:%s", diff --git a/hkexauth.go b/hkexauth.go index 2f6b4ef..4c861e6 100644 --- a/hkexauth.go +++ b/hkexauth.go @@ -5,7 +5,6 @@ package herradurakex import ( "bytes" "encoding/csv" - "fmt" "io" "io/ioutil" "log" @@ -19,7 +18,7 @@ func AuthUser(username string, auth string, fname string) (valid bool, allowedCm b, e := ioutil.ReadFile(fname) if e != nil { valid = false - fmt.Println("ERROR: Cannot read hkexsh.passwd file!") + log.Println("ERROR: Cannot read hkexsh.passwd file!") log.Fatal(e) } r := csv.NewReader(bytes.NewReader(b)) @@ -46,8 +45,6 @@ func AuthUser(username string, auth string, fname string) (valid bool, allowedCm } break } - - //fmt.Println(record) } return } diff --git a/hkexchan.go b/hkexchan.go index 4c4d40e..828d4fd 100644 --- a/hkexchan.go +++ b/hkexchan.go @@ -8,6 +8,7 @@ import ( "crypto" "crypto/aes" "crypto/cipher" + "encoding/hex" "fmt" "hash" "log" @@ -43,6 +44,7 @@ been negotiated via hkexnet.go func (hc Conn) getStream(keymat *big.Int) (rc cipher.Stream, mc hash.Hash) { var key []byte var block cipher.Block + var iv []byte var ivlen int var err error @@ -54,7 +56,7 @@ func (hc Conn) getStream(keymat *big.Int) (rc cipher.Stream, mc hash.Hash) { key = keymat.Bytes()[0:aes.BlockSize] block, err = aes.NewCipher(key) ivlen = aes.BlockSize - iv := keymat.Bytes()[aes.BlockSize : aes.BlockSize+ivlen] + iv = keymat.Bytes()[aes.BlockSize : aes.BlockSize+ivlen] rc = cipher.NewOFB(block, iv) log.Printf("[cipher AES_256 (%d)]\n", copts) break @@ -62,7 +64,7 @@ func (hc Conn) getStream(keymat *big.Int) (rc cipher.Stream, mc hash.Hash) { key = keymat.Bytes()[0:twofish.BlockSize] block, err = twofish.NewCipher(key) ivlen = twofish.BlockSize - iv := keymat.Bytes()[twofish.BlockSize : twofish.BlockSize+ivlen] + iv = keymat.Bytes()[twofish.BlockSize : twofish.BlockSize+ivlen] rc = cipher.NewOFB(block, iv) log.Printf("[cipher TWOFISH_128 (%d)]\n", copts) break @@ -79,7 +81,7 @@ func (hc Conn) getStream(keymat *big.Int) (rc cipher.Stream, mc hash.Hash) { // // I assume the other two check bounds and only // copy what's needed whereas blowfish does no such check. - iv := keymat.Bytes()[blowfish.BlockSize : blowfish.BlockSize+ivlen] + iv = keymat.Bytes()[blowfish.BlockSize : blowfish.BlockSize+ivlen] rc = cipher.NewOFB(block, iv) log.Printf("[cipher BLOWFISH_64 (%d)]\n", copts) break @@ -109,5 +111,13 @@ func (hc Conn) getStream(keymat *big.Int) (rc cipher.Stream, mc hash.Hash) { panic(err) } + // Feed the IV into the hmac: all traffic in the connection must + // feed its data into the hmac afterwards, so both ends can xor + // that with the stream to detect corruption. + _, _ = mc.Write(iv) + var currentHash []byte + currentHash = mc.Sum(currentHash) + log.Printf("Channel init hmac(iv):%s\n", hex.EncodeToString(currentHash)) + return } diff --git a/hkexnet.go b/hkexnet.go index 7b22376..0ed1d5c 100644 --- a/hkexnet.go +++ b/hkexnet.go @@ -27,10 +27,10 @@ import ( "encoding/hex" "fmt" "hash" - "io" "log" "math/big" "net" + "strings" "time" ) @@ -289,25 +289,27 @@ func (hl HKExListener) Accept() (hc Conn, err error) { // // See go doc io.Reader func (c Conn) Read(b []byte) (n int, err error) { - log.Printf("[Decrypting...]\n") + //log.Printf("[Decrypting...]\r\n") - //c.c.SetReadDeadline(time.Now().Add(1 * time.Second)) n, err = c.c.Read(b) + // Normal client 'exit' from interactive session will cause + // (on server side) err.Error() == ": use of closed network connection" if err != nil && err.Error() != "EOF" { - //if neterr, ok := err.(net.Error); ok { - // fmt.Printf("[Read() timeout - %s]\n", neterr) - //} else { - // panic(err) - //} + if !strings.HasSuffix(err.Error(), "use of closed network connection") { + log.Println("unexpected Read() err:", err) + } else { + log.Println("[Client hung up]") + } } - log.Printf(" ctext:%+v\n", b[:n]) // print only used portion + log.Printf(" <:ctext:\r\n%s\r\n", hex.Dump(b[:n])) //EncodeToString(b[:n])) // print only used portion + db := bytes.NewBuffer(b[:n]) // The StreamReader acts like a pipe, decrypting // whatever is available and forwarding the result // to the parameter of Read() as a normal io.Reader rs := &cipher.StreamReader{S: c.r, R: db} n, err = rs.Read(b) - log.Printf(" ptext:%+v\n", b[:n]) + log.Printf(" <-ptext:\r\n%s\r\n", hex.Dump(b[:n])) //EncodeToString(b[:n])) return } @@ -315,8 +317,8 @@ func (c Conn) Read(b []byte) (n int, err error) { // // See go doc io.Writer func (c Conn) Write(b []byte) (n int, err error) { - log.Printf("[Encrypting...]\n") - log.Printf(" ptext:%+v\n", b) + //log.Printf("[Encrypting...]\r\n") + log.Printf(" :>ptext:\r\n%s\r\n", hex.Dump(b)) //EncodeToString(b)) var wb bytes.Buffer // The StreamWriter acts like a pipe, forwarding whatever is // written to it through the cipher, encrypting as it goes @@ -325,7 +327,7 @@ func (c Conn) Write(b []byte) (n int, err error) { if err != nil { panic(err) } - log.Printf(" ctext:%+v\n", wb.Bytes()) + log.Printf(" ->ctext:\r\n%s\r\n", hex.Dump(wb.Bytes())) //EncodeToString(b)) // print only used portion n, err = c.c.Write(wb.Bytes()) return }