Added rekeying (-r secs) client/server

This commit is contained in:
Russ Magee 2023-11-15 00:32:50 -08:00
parent c569a5a3c9
commit 032baf63d6
6 changed files with 80 additions and 30 deletions

View File

@ -1,4 +1,4 @@
VERSION := 0.9.5.6-rc VERSION := 0.9.6
.PHONY: lint vis clean common client server passwd\ .PHONY: lint vis clean common client server passwd\
subpkgs install uninstall reinstall scc subpkgs install uninstall reinstall scc

View File

@ -701,11 +701,11 @@ func main() { //nolint: funlen, gocyclo
port uint port uint
cmdStr string cmdStr string
tunSpecStr string // lport1:rport1[,lport2:rport2,...] tunSpecStr string // lport1:rport1[,lport2:rport2,...]
rekeySecs uint
copySrc []byte copySrc []byte
copyDst string copyDst string
copyQuiet bool copyQuiet bool
copyLimitBPS uint copyLimitBPS uint
authCookie string authCookie string
chaffEnabled bool chaffEnabled bool
@ -745,7 +745,8 @@ func main() { //nolint: funlen, gocyclo
KEX_FRODOKEM_976AES KEX_FRODOKEM_976AES
KEX_FRODOKEM_976SHAKE`) 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.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") //nolint:gocritic,nolintlint // flag.StringVar(&authCookie, "a", "", "auth cookie")
flag.BoolVar(&chaffEnabled, "e", true, "enable chaff pkts") flag.BoolVar(&chaffEnabled, "e", true, "enable chaff pkts")
flag.UintVar(&chaffFreqMin, "f", 100, "chaff pkt freq min `msecs`") //nolint:gomnd flag.UintVar(&chaffFreqMin, "f", 100, "chaff pkt freq min `msecs`") //nolint:gomnd
@ -972,6 +973,9 @@ func main() { //nolint: funlen, gocyclo
exitWithStatus(XSNetDialFailed) exitWithStatus(XSNetDialFailed)
} }
conn.RekeyHelper(rekeySecs)
defer conn.ShutdownRekey()
// === Shell terminal mode (Shell vs. Copy) setup // === Shell terminal mode (Shell vs. Copy) setup
// Set stdin in raw mode if it's an interactive session // Set stdin in raw mode if it's an interactive session

View File

@ -529,11 +529,13 @@ func main() { //nolint:funlen,gocyclo
var chaffBytesMax uint var chaffBytesMax uint
var dbg bool var dbg bool
var laddr string var laddr string
var rekeySecs uint
var useSystemPasswd bool var useSystemPasswd bool
flag.BoolVar(&vopt, "v", false, "show version") 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.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(&useSysLogin, "L", false, "use system login")
flag.BoolVar(&chaffEnabled, "e", true, "enable chaff pkts") flag.BoolVar(&chaffEnabled, "e", true, "enable chaff pkts")
@ -646,22 +648,22 @@ func main() { //nolint:funlen,gocyclo
go func() { go func() {
for { for {
sig := <-exitCh sig := <-exitCh
switch sig.String() { switch sig {
case "terminated": case syscall.SIGTERM: //"terminated":
logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig)) //nolint:errcheck logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig.String())) //nolint:errcheck
signal.Reset() signal.Reset()
syscall.Kill(0, syscall.SIGTERM) //nolint:errcheck syscall.Kill(0, syscall.SIGTERM) //nolint:errcheck
case "interrupt": case syscall.SIGINT: //"interrupt":
logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig)) //nolint:errcheck logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig.String())) //nolint:errcheck
signal.Reset() signal.Reset()
syscall.Kill(0, syscall.SIGINT) //nolint:errcheck syscall.Kill(0, syscall.SIGINT) //nolint:errcheck
case "hangup": case syscall.SIGHUP: //"hangup":
logger.LogNotice(fmt.Sprintf("[Got signal: %s - nop]", sig)) //nolint:errcheck logger.LogNotice(fmt.Sprintf("[Got signal: %s - nop]", sig.String())) //nolint:errcheck
if cpuprofile != "" || memprofile != "" { if cpuprofile != "" || memprofile != "" {
dumpProf() dumpProf()
} }
default: 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 { } else {
log.Println("Accepted client") log.Println("Accepted client")
conn.RekeyHelper(rekeySecs)
// Set up chaffing to client // Set up chaffing to client
// Will only start when runShellAs() is called // Will only start when runShellAs() is called
// after stdin/stdout are hooked up // after stdin/stdout are hooked up
@ -709,6 +713,7 @@ func main() { //nolint:funlen,gocyclo
// The loop then returns to accepting, so that // The loop then returns to accepting, so that
// multiple connections may be served concurrently. // multiple connections may be served concurrently.
go func(hc *xsnet.Conn) (e error) { go func(hc *xsnet.Conn) (e error) {
defer hc.ShutdownRekey()
defer hc.Close() defer hc.Close()
// Start login timeout here and disconnect if user/pass phase stalls // Start login timeout here and disconnect if user/pass phase stalls

View File

@ -57,9 +57,9 @@ func expandKeyMat(keymat []byte, blocksize int) []byte {
return keymat return keymat
} }
/* Support functionality to set up encryption after a channel has /* (Re-)initialize the keystream and hmac state for an xsnet.Conn, returning
been negotiated via xsnet.go a cipherStream and hash
*/ */
func (hc *Conn) getStream(keymat []byte) (rc cipher.Stream, mc hash.Hash, err error) { func (hc *Conn) getStream(keymat []byte) (rc cipher.Stream, mc hash.Hash, err error) {
var key []byte var key []byte
var block cipher.Block var block cipher.Block

View File

@ -78,6 +78,7 @@ const (
CSOTunDisconn // server -> client: tunnel rport disconnected CSOTunDisconn // server -> client: tunnel rport disconnected
CSOTunHangup // client -> server: tunnel lport hung up CSOTunHangup // client -> server: tunnel lport hung up
CSOKeepAlive // bidir keepalive packet to monitor main connection 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 // TunEndpoint.tunCtl control values - used to control workers for client

View File

@ -88,6 +88,7 @@ type (
Cols uint16 Cols uint16
keepalive uint // if this reaches zero, conn is considered dead 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 Pproc int // proc ID of command run on this conn
chaff ChaffConfig chaff ChaffConfig
tuns *map[uint16](*TunEndpoint) 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) // payload of keepalive (2 bytes) is not currently used (0x55aa fixed)
_ = binary.BigEndian.Uint16(payloadBytes[0:2]) _ = binary.BigEndian.Uint16(payloadBytes[0:2])
hc.ResetKeepAlive() 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: case CSOTermSize:
fmt.Sscanf(string(payloadBytes), "%d %d", &hc.Rows, &hc.Cols) fmt.Sscanf(string(payloadBytes), "%d %d", &hc.Rows, &hc.Cols)
log.Printf("[TermSize pkt: rows %v cols %v]\n", 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 // Write a byte slice
// //
// See go doc io.Writer // 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) n, err = hc.WritePacket(b, CSONone)
//logger.LogDebug("[-Write]")
return n, err return n, err
} }
// Write a byte slice with specified ctrlStatOp byte // Write a byte slice with specified ctrlStatOp byte
func (hc *Conn) WritePacket(b []byte, ctrlStatOp byte) (n int, err error) { func (hc *Conn) WritePacket(b []byte, ctrlStatOp byte) (n int, err error) {
hc.Lock()
defer hc.Unlock()
//log.Printf("[Encrypting...]\r\n") //log.Printf("[Encrypting...]\r\n")
var hmacOut []uint8 var hmacOut []uint8
var payloadLen uint32 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...)...)...) 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)) payloadLen = uint32(len(b))
if hc.logPlainText { if hc.logPlainText {
log.Printf(" >:ptext:\r\n%s\r\n", hex.Dump(b[0:payloadLen])) 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 { } else {
//fmt.Println("[a]WriteError!") //fmt.Println("[a]WriteError!")
} }
hc.Unlock()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -1593,6 +1595,40 @@ func (hc *Conn) SetupChaff(msecsMin uint, msecsMax uint, szMax uint) {
hc.chaff.szMax = szMax 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 // Helper routine to spawn a chaffing goroutine for each Conn
func (hc *Conn) chaffHelper() { func (hc *Conn) chaffHelper() {
go func() { go func() {
@ -1605,7 +1641,9 @@ func (hc *Conn) chaffHelper() {
min := int(hc.chaff.msecsMin) min := int(hc.chaff.msecsMin)
nextDuration = rand.Intn(int(hc.chaff.msecsMax)-min) + min nextDuration = rand.Intn(int(hc.chaff.msecsMax)-min) + min
_, _ = rand.Read(bufTmp) _, _ = rand.Read(bufTmp)
//logger.LogDebug("[+chaffHelper]")
_, err := hc.WritePacket(bufTmp, CSOChaff) _, err := hc.WritePacket(bufTmp, CSOChaff)
//logger.LogDebug("[-chaffHelper]")
if err != nil { if err != nil {
log.Println("[ *** error - chaffHelper shutting down *** ]") log.Println("[ *** error - chaffHelper shutting down *** ]")
hc.chaff.shutdown = true hc.chaff.shutdown = true
@ -1642,7 +1680,9 @@ func (hc *Conn) keepaliveHelper() {
for { for {
nextDuration := 10000 nextDuration := 10000
bufTmp := []byte{0x55, 0xaa} bufTmp := []byte{0x55, 0xaa}
//logger.LogDebug("[+keepaliveHelper]")
_, err := hc.WritePacket(bufTmp, CSOKeepAlive) _, err := hc.WritePacket(bufTmp, CSOKeepAlive)
//logger.LogDebug("[-keepaliveHelper]")
//logger.LogDebug(fmt.Sprintf("[keepalive]\n")) //logger.LogDebug(fmt.Sprintf("[keepalive]\n"))
if err != nil { if err != nil {
logger.LogDebug(fmt.Sprintf("[ *** error - keepaliveHelper quitting *** ]\n")) logger.LogDebug(fmt.Sprintf("[ *** error - keepaliveHelper quitting *** ]\n"))