diff --git a/Makefile b/Makefile index 0c7b358..8f7e048 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION := 0.9.6 +VERSION := 0.9.7 .PHONY: lint vis clean common client server passwd\ subpkgs install uninstall reinstall scc diff --git a/xs/xs.go b/xs/xs.go index e941b4c..b232afc 100755 --- a/xs/xs.go +++ b/xs/xs.go @@ -702,6 +702,7 @@ func main() { //nolint: funlen, gocyclo cmdStr string tunSpecStr string // lport1:rport1[,lport2:rport2,...] rekeySecs uint + remodRequested bool // true: when rekeying, switch to random cipher/hmac alg copySrc []byte copyDst string copyQuiet bool @@ -745,8 +746,9 @@ 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`") + flag.BoolVar(&remodRequested, "R", false, "Borg Countermeasures (remodulate cipher/hmac alg on each rekey)") //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 @@ -967,7 +969,13 @@ func main() { //nolint: funlen, gocyclo if kcpMode != "unused" { proto = "kcp" } - conn, err := xsnet.Dial(proto, server, cipherAlg, hmacAlg, kexAlg, kcpMode) + + remodExtArg := "" + if remodRequested { + remodExtArg = "OPT_REMOD" + } + // Pass opt to Dial() via extensions arg + conn, err := xsnet.Dial(proto, server, cipherAlg, hmacAlg, kexAlg, kcpMode, remodExtArg) if err != nil { fmt.Println(err) exitWithStatus(XSNetDialFailed) diff --git a/xsd/xsd.go b/xsd/xsd.go index e7081c1..e90e3e3 100755 --- a/xsd/xsd.go +++ b/xsd/xsd.go @@ -530,12 +530,14 @@ func main() { //nolint:funlen,gocyclo var dbg bool var laddr string var rekeySecs uint + var remodSupported bool // true: when rekeying, switch to random cipher/hmac alg var useSystemPasswd bool flag.BoolVar(&vopt, "v", false, "show version") flag.UintVar(&rekeySecs, "r", 300, "rekey interval in `secs`") - flag.StringVar(&laddr, "l", ":2000", "interface[:port] to listen") //nolint:gomnd,lll + flag.BoolVar(&remodSupported, "R", false, "Borg Countermeasures (remodulate cipher/hmac alg on each rekey)") + 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") @@ -702,6 +704,22 @@ func main() { //nolint:funlen,gocyclo } else { log.Println("Accepted client") + // Only enable cipher alg changes on re-key if we were told + // to support it (launching xsd with -R), *and* the client + // proposes to use it. + if !remodSupported { + if (conn.Opts() & xsnet.CORemodulateShields) != 0 { + logger.LogDebug("[client proposed cipher/hmac remod, but we don't support it.]") + conn.Close() + continue + } + } else { + if conn.Opts()&xsnet.CORemodulateShields != 0 { + logger.LogDebug("[cipher/hmac remodulation active]") + } else { + logger.LogDebug("[cipher/hmac remodulation inactive]") + } + } conn.RekeyHelper(rekeySecs) // Set up chaffing to client diff --git a/xsnet/chan.go b/xsnet/chan.go index 40babee..4b4e82d 100644 --- a/xsnet/chan.go +++ b/xsnet/chan.go @@ -22,6 +22,7 @@ import ( "blitter.com/go/cryptmt" "blitter.com/go/hopscotch" + "blitter.com/go/xs/logger" "github.com/aead/chacha20/chacha" "golang.org/x/crypto/blowfish" "golang.org/x/crypto/twofish" @@ -57,9 +58,19 @@ func expandKeyMat(keymat []byte, blocksize int) []byte { return keymat } -/* (Re-)initialize the keystream and hmac state for an xsnet.Conn, returning - a cipherStream and hash - */ +// Choose a cipher and hmac alg from supported sets, given two uint8 values +func getNewStreamAlgs(cb uint8, hb uint8) (config uint32) { + // Get new cipher and hash algs (clamped to valid values) based on + // the input rekeying data + c := (cb % CAlgNoneDisallowed) + h := (hb % HmacNoneDisallowed) + config = uint32(h<<8) | uint32(c) + logger.LogDebug(fmt.Sprintf("[Chose new algs [%d:%d]", h, c)) + return +} + +// (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 b491a87..87015b3 100644 --- a/xsnet/consts.go +++ b/xsnet/consts.go @@ -122,5 +122,19 @@ const ( HmacNoneDisallowed ) +// Conn opts outside of basic kex/cipher/hmac connect config +const ( + CONone = iota + CORemodulateShields // if set, rekeying also reselects random cipher/hmac alg +) + +type COValue uint32 + // Available HMACs for hkex.Conn type CSHmacAlg uint32 + +// Some bounds-checking consts +const ( + REKEY_SECS_MIN = 1 + CHAFF_FREQ_MSECS_MIN = 1 +) diff --git a/xsnet/net.go b/xsnet/net.go index 3a6789d..f200c63 100644 --- a/xsnet/net.go +++ b/xsnet/net.go @@ -241,7 +241,7 @@ func (hc *Conn) SetConnOpts(copts uint32) { // // Consumers of this lib may use this for protocol-level options not part // of the KEx or encryption info used by the connection. -func (hc Conn) Opts() uint32 { +func (hc *Conn) Opts() uint32 { return hc.opts } @@ -363,6 +363,9 @@ func (hc *Conn) applyConnExtensions(extensions ...string) { log.Println("[extension arg = H_SHA512]") hc.cipheropts &= (0xFFFF00FF) hc.cipheropts |= (HmacSHA512 << 8) + case "OPT_REMOD": + log.Println("[extension arg = OPT_REMOD]") + hc.opts |= CORemodulateShields //default: // log.Printf("[Dial ext \"%s\" ignored]\n", s) } @@ -1351,6 +1354,11 @@ func (hc *Conn) Read(b []byte) (n int, err error) { //logger.LogDebug(fmt.Sprintf("[Got rekey [%02x %02x %02x ...]\n", // payloadBytes[0], payloadBytes[1], payloadBytes[2])) rekeyData := payloadBytes + if (hc.opts & CORemodulateShields) != 0 { + hc.Lock() + hc.cipheropts = getNewStreamAlgs(rekeyData[0], rekeyData[1]) + hc.Unlock() + } hc.r, hc.rm, err = hc.getStream(rekeyData) case CSOTermSize: fmt.Sscanf(string(payloadBytes), "%d %d", &hc.Rows, &hc.Cols) @@ -1585,7 +1593,9 @@ func (hc *Conn) StartupChaff() { } func (hc *Conn) ShutdownChaff() { + hc.Lock() hc.chaff.shutdown = true + hc.Unlock() log.Println("Chaffing SHUTDOWN") } @@ -1596,16 +1606,28 @@ func (hc *Conn) SetupChaff(msecsMin uint, msecsMax uint, szMax uint) { } func (hc *Conn) ShutdownRekey() { + hc.Lock() hc.rekey = 0 + hc.Unlock() } func (hc *Conn) RekeyHelper(intervalSecs uint) { + if intervalSecs < REKEY_SECS_MIN { + intervalSecs = REKEY_SECS_MIN + } + go func() { + hc.Lock() hc.rekey = intervalSecs + hc.Unlock() + for { - if hc.rekey != 0 { + hc.Lock() + rekey := hc.rekey + hc.Unlock() + if rekey != 0 { //logger.LogDebug(fmt.Sprintf("[rekeyHelper Loop]\n")) - time.Sleep(time.Duration(hc.rekey) * time.Second) + time.Sleep(time.Duration(rekey) * time.Second) // Send rekey to other end rekeyData := make([]byte, 64) @@ -1615,6 +1637,9 @@ func (hc *Conn) RekeyHelper(intervalSecs uint) { //logger.LogDebug("[+rekeyHelper]") _, err = hc.WritePacket(rekeyData, CSORekey) hc.Lock() + if (hc.opts & CORemodulateShields) != 0 { + hc.cipheropts = getNewStreamAlgs(rekeyData[0], rekeyData[1]) + } hc.w, hc.wm, err = hc.getStream(rekeyData) //logger.LogDebug("[-rekeyHelper]") hc.Unlock() @@ -1631,11 +1656,21 @@ func (hc *Conn) RekeyHelper(intervalSecs uint) { // Helper routine to spawn a chaffing goroutine for each Conn func (hc *Conn) chaffHelper() { + // Enforce bounds on chaff frequency and pkt size + hc.Lock() + if hc.chaff.msecsMin < CHAFF_FREQ_MSECS_MIN { + hc.chaff.msecsMin = CHAFF_FREQ_MSECS_MIN + } + hc.Unlock() + go func() { var nextDuration int for { //logger.LogDebug(fmt.Sprintf("[chaffHelper Loop]\n")) - if !hc.chaff.shutdown { + hc.Lock() + shutdown := hc.chaff.shutdown + hc.Unlock() + if !shutdown { var bufTmp []byte bufTmp = make([]byte, rand.Intn(int(hc.chaff.szMax))) min := int(hc.chaff.msecsMin) @@ -1646,7 +1681,9 @@ func (hc *Conn) chaffHelper() { //logger.LogDebug("[-chaffHelper]") if err != nil { log.Println("[ *** error - chaffHelper shutting down *** ]") + hc.Lock() hc.chaff.shutdown = true + hc.Unlock() break } } else { @@ -1670,7 +1707,9 @@ func (hc *Conn) ShutdownKeepAlive() { } func (hc *Conn) ResetKeepAlive() { + hc.Lock() hc.keepalive = 3 + hc.Unlock() log.Println("KeepAlive RESET") } @@ -1689,7 +1728,9 @@ func (hc *Conn) keepaliveHelper() { break } time.Sleep(time.Duration(nextDuration) * time.Millisecond) + hc.Lock() hc.keepalive -= 1 + hc.Unlock() //logger.LogDebug(fmt.Sprintf("[keepAlive is now %d]\n", hc.keepalive)) //if rand.Intn(8) == 0 {