diff --git a/Makefile b/Makefile index e61084f..0c7b358 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION := 0.9.5.6-rc +VERSION := 0.9.6 .PHONY: lint vis clean common client server passwd\ subpkgs install uninstall reinstall scc diff --git a/xs/xs.go b/xs/xs.go index 0f7cc85..e941b4c 100755 --- a/xs/xs.go +++ b/xs/xs.go @@ -701,11 +701,11 @@ func main() { //nolint: funlen, gocyclo port uint cmdStr string tunSpecStr string // lport1:rport1[,lport2:rport2,...] - - copySrc []byte - copyDst string - copyQuiet bool - copyLimitBPS uint + rekeySecs uint + copySrc []byte + copyDst string + copyQuiet bool + copyLimitBPS uint authCookie string chaffEnabled bool @@ -745,7 +745,8 @@ func main() { //nolint: funlen, gocyclo KEX_FRODOKEM_976AES KEX_FRODOKEM_976SHAKE`) flag.StringVar(&kcpMode, "K", "unused", "KCP `alg`, one of [KCP_NONE | KCP_AES | KCP_BLOWFISH | KCP_CAST5 | KCP_SM4 | KCP_SALSA20 | KCP_SIMPLEXOR | KCP_TEA | KCP_3DES | KCP_TWOFISH | KCP_XTEA] to use KCP (github.com/xtaci/kcp-go) reliable UDP instead of TCP") //nolint:lll - flag.UintVar(&port, "p", 2000, "``port") //nolint:gomnd,lll + flag.UintVar(&port, "p", 2000, "``port") //nolint:gomnd,lll + flag.UintVar(&rekeySecs, "r", 300, "rekey interval in `secs`") //nolint:gocritic,nolintlint // flag.StringVar(&authCookie, "a", "", "auth cookie") flag.BoolVar(&chaffEnabled, "e", true, "enable chaff pkts") flag.UintVar(&chaffFreqMin, "f", 100, "chaff pkt freq min `msecs`") //nolint:gomnd @@ -972,6 +973,9 @@ func main() { //nolint: funlen, gocyclo exitWithStatus(XSNetDialFailed) } + conn.RekeyHelper(rekeySecs) + defer conn.ShutdownRekey() + // === Shell terminal mode (Shell vs. Copy) setup // Set stdin in raw mode if it's an interactive session diff --git a/xsd/xsd.go b/xsd/xsd.go index 428d434..e7081c1 100755 --- a/xsd/xsd.go +++ b/xsd/xsd.go @@ -529,11 +529,13 @@ func main() { //nolint:funlen,gocyclo var chaffBytesMax uint var dbg bool var laddr string + var rekeySecs uint var useSystemPasswd bool flag.BoolVar(&vopt, "v", false, "show version") - flag.StringVar(&laddr, "l", ":2000", "interface[:port] to listen") + flag.UintVar(&rekeySecs, "r", 300, "rekey interval in `secs`") + flag.StringVar(&laddr, "l", ":2000", "interface[:port] to listen") //nolint:gomnd,lll flag.StringVar(&kcpMode, "K", "unused", `set to one of ["KCP_NONE","KCP_AES", "KCP_BLOWFISH", "KCP_CAST5", "KCP_SM4", "KCP_SALSA20", "KCP_SIMPLEXOR", "KCP_TEA", "KCP_3DES", "KCP_TWOFISH", "KCP_XTEA"] to use KCP (github.com/xtaci/kcp-go) reliable UDP instead of TCP`) //nolint:lll flag.BoolVar(&useSysLogin, "L", false, "use system login") flag.BoolVar(&chaffEnabled, "e", true, "enable chaff pkts") @@ -646,22 +648,22 @@ func main() { //nolint:funlen,gocyclo go func() { for { sig := <-exitCh - switch sig.String() { - case "terminated": - logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig)) //nolint:errcheck + switch sig { + case syscall.SIGTERM: //"terminated": + logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig.String())) //nolint:errcheck signal.Reset() syscall.Kill(0, syscall.SIGTERM) //nolint:errcheck - case "interrupt": - logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig)) //nolint:errcheck + case syscall.SIGINT: //"interrupt": + logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig.String())) //nolint:errcheck signal.Reset() syscall.Kill(0, syscall.SIGINT) //nolint:errcheck - case "hangup": - logger.LogNotice(fmt.Sprintf("[Got signal: %s - nop]", sig)) //nolint:errcheck + case syscall.SIGHUP: //"hangup": + logger.LogNotice(fmt.Sprintf("[Got signal: %s - nop]", sig.String())) //nolint:errcheck if cpuprofile != "" || memprofile != "" { dumpProf() } default: - logger.LogNotice(fmt.Sprintf("[Got signal: %s - ignored]", sig)) //nolint:errcheck + logger.LogNotice(fmt.Sprintf("[Got signal: %s - ignored]", sig.String())) //nolint:errcheck } } }() @@ -700,6 +702,8 @@ func main() { //nolint:funlen,gocyclo } else { log.Println("Accepted client") + conn.RekeyHelper(rekeySecs) + // Set up chaffing to client // Will only start when runShellAs() is called // after stdin/stdout are hooked up @@ -709,6 +713,7 @@ func main() { //nolint:funlen,gocyclo // The loop then returns to accepting, so that // multiple connections may be served concurrently. go func(hc *xsnet.Conn) (e error) { + defer hc.ShutdownRekey() defer hc.Close() // Start login timeout here and disconnect if user/pass phase stalls diff --git a/xsnet/chan.go b/xsnet/chan.go index 6e5a0f3..40babee 100644 --- a/xsnet/chan.go +++ b/xsnet/chan.go @@ -57,9 +57,9 @@ func expandKeyMat(keymat []byte, blocksize int) []byte { return keymat } -/* Support functionality to set up encryption after a channel has -been negotiated via xsnet.go -*/ +/* (Re-)initialize the keystream and hmac state for an xsnet.Conn, returning + a cipherStream and hash + */ func (hc *Conn) getStream(keymat []byte) (rc cipher.Stream, mc hash.Hash, err error) { var key []byte var block cipher.Block diff --git a/xsnet/consts.go b/xsnet/consts.go index e6a47fb..b491a87 100644 --- a/xsnet/consts.go +++ b/xsnet/consts.go @@ -78,6 +78,7 @@ const ( CSOTunDisconn // server -> client: tunnel rport disconnected CSOTunHangup // client -> server: tunnel lport hung up CSOKeepAlive // bidir keepalive packet to monitor main connection + CSORekey // TODO: rekey/re-select session cipher/hash algs ) // TunEndpoint.tunCtl control values - used to control workers for client diff --git a/xsnet/net.go b/xsnet/net.go index 4460042..3a6789d 100644 --- a/xsnet/net.go +++ b/xsnet/net.go @@ -88,6 +88,7 @@ type ( Cols uint16 keepalive uint // if this reaches zero, conn is considered dead + rekey uint // if nonzero, rekeying interval in seconds Pproc int // proc ID of command run on this conn chaff ChaffConfig tuns *map[uint16](*TunEndpoint) @@ -1345,6 +1346,12 @@ func (hc *Conn) Read(b []byte) (n int, err error) { // payload of keepalive (2 bytes) is not currently used (0x55aa fixed) _ = binary.BigEndian.Uint16(payloadBytes[0:2]) hc.ResetKeepAlive() + case CSORekey: + // rekey + //logger.LogDebug(fmt.Sprintf("[Got rekey [%02x %02x %02x ...]\n", + // payloadBytes[0], payloadBytes[1], payloadBytes[2])) + rekeyData := payloadBytes + hc.r, hc.rm, err = hc.getStream(rekeyData) case CSOTermSize: fmt.Sscanf(string(payloadBytes), "%d %d", &hc.Rows, &hc.Cols) log.Printf("[TermSize pkt: rows %v cols %v]\n", hc.Rows, hc.Cols) @@ -1461,13 +1468,18 @@ func (hc *Conn) Read(b []byte) (n int, err error) { // Write a byte slice // // See go doc io.Writer -func (hc Conn) Write(b []byte) (n int, err error) { +func (hc *Conn) Write(b []byte) (n int, err error) { + //logger.LogDebug("[+Write]") n, err = hc.WritePacket(b, CSONone) + //logger.LogDebug("[-Write]") return n, err } // Write a byte slice with specified ctrlStatOp byte func (hc *Conn) WritePacket(b []byte, ctrlStatOp byte) (n int, err error) { + hc.Lock() + defer hc.Unlock() + //log.Printf("[Encrypting...]\r\n") var hmacOut []uint8 var payloadLen uint32 @@ -1495,15 +1507,6 @@ func (hc *Conn) WritePacket(b []byte, ctrlStatOp byte) (n int, err error) { b = append([]byte{byte(padSide)}, append([]byte{byte(padLen)}, append(b, padBytes...)...)...) } - // N.B. Originally this Lock() surrounded only the - // calls to binary.Write(hc.c ..) however there appears - // to be some other unshareable state in the Conn - // struct that must be protected to serialize main and - // chaff data written to it. - // - // Would be nice to determine if the mutex scope - // could be tightened. - hc.Lock() payloadLen = uint32(len(b)) if hc.logPlainText { log.Printf(" >:ptext:\r\n%s\r\n", hex.Dump(b[0:payloadLen])) @@ -1561,7 +1564,6 @@ func (hc *Conn) WritePacket(b []byte, ctrlStatOp byte) (n int, err error) { } else { //fmt.Println("[a]WriteError!") } - hc.Unlock() if err != nil { log.Println(err) @@ -1593,6 +1595,40 @@ func (hc *Conn) SetupChaff(msecsMin uint, msecsMax uint, szMax uint) { hc.chaff.szMax = szMax } +func (hc *Conn) ShutdownRekey() { + hc.rekey = 0 +} + +func (hc *Conn) RekeyHelper(intervalSecs uint) { + go func() { + hc.rekey = intervalSecs + for { + if hc.rekey != 0 { + //logger.LogDebug(fmt.Sprintf("[rekeyHelper Loop]\n")) + time.Sleep(time.Duration(hc.rekey) * time.Second) + + // Send rekey to other end + rekeyData := make([]byte, 64) + _, err := crand.Read(rekeyData) + //logger.LogDebug(fmt.Sprintf("[rekey [%02x %02x %02x ...]\n", + // rekeyData[0], rekeyData[1], rekeyData[2])) + //logger.LogDebug("[+rekeyHelper]") + _, err = hc.WritePacket(rekeyData, CSORekey) + hc.Lock() + hc.w, hc.wm, err = hc.getStream(rekeyData) + //logger.LogDebug("[-rekeyHelper]") + hc.Unlock() + if err != nil { + log.Printf("[rekey WritePacket err! (%v) rekey dying ...]\n", err) + return + } + } else { + return + } + } + }() +} + // Helper routine to spawn a chaffing goroutine for each Conn func (hc *Conn) chaffHelper() { go func() { @@ -1605,7 +1641,9 @@ func (hc *Conn) chaffHelper() { min := int(hc.chaff.msecsMin) nextDuration = rand.Intn(int(hc.chaff.msecsMax)-min) + min _, _ = rand.Read(bufTmp) + //logger.LogDebug("[+chaffHelper]") _, err := hc.WritePacket(bufTmp, CSOChaff) + //logger.LogDebug("[-chaffHelper]") if err != nil { log.Println("[ *** error - chaffHelper shutting down *** ]") hc.chaff.shutdown = true @@ -1642,7 +1680,9 @@ func (hc *Conn) keepaliveHelper() { for { nextDuration := 10000 bufTmp := []byte{0x55, 0xaa} + //logger.LogDebug("[+keepaliveHelper]") _, err := hc.WritePacket(bufTmp, CSOKeepAlive) + //logger.LogDebug("[-keepaliveHelper]") //logger.LogDebug(fmt.Sprintf("[keepalive]\n")) if err != nil { logger.LogDebug(fmt.Sprintf("[ *** error - keepaliveHelper quitting *** ]\n"))