From d4f50bfdc0add54a5aaeb8c7d28cacb941655c96 Mon Sep 17 00:00:00 2001 From: Russ Magee Date: Sun, 15 Dec 2019 11:38:04 -0800 Subject: [PATCH] xsd: Added -aK,-aC,-aH to control accepted client proposals --- xs/xs.go | 21 ++++++---- xsd/xsd.go | 107 ++++++++++++++++++++++++++++++++++++++++++++++-- xsnet/consts.go | 16 +++++--- xsnet/net.go | 75 +++++++++++++++++++++++++++++++-- 4 files changed, 199 insertions(+), 20 deletions(-) diff --git a/xs/xs.go b/xs/xs.go index f385bbe..c670e25 100755 --- a/xs/xs.go +++ b/xs/xs.go @@ -34,9 +34,9 @@ import ( _ "net/http/pprof" xs "blitter.com/go/xs" - "blitter.com/go/xs/xsnet" "blitter.com/go/xs/logger" "blitter.com/go/xs/spinsult" + "blitter.com/go/xs/xsnet" isatty "github.com/mattn/go-isatty" ) @@ -821,23 +821,25 @@ func main() { cmdStr = string(copySrc) } } - + proto := "tcp" if kcpMode != "unused" { - proto = "kcp" + proto = "kcp" } conn, err := xsnet.Dial(proto, server, cipherAlg, hmacAlg, kexAlg, kcpMode) if err != nil { fmt.Println(err) exitWithStatus(3) } - defer conn.Close() // nolint: errcheck - // From this point on, conn is a secure encrypted channel // Set stdin in raw mode if it's an interactive session // TODO: send flag to server side indicating this // affects shell command used var oldState *xs.State + defer conn.Close() // nolint: errcheck + + // From this point on, conn is a secure encrypted channel + if shellMode { if isatty.IsTerminal(os.Stdin.Fd()) { oldState, err = xs.MakeRaw(int(os.Stdin.Fd())) @@ -869,7 +871,11 @@ func main() { rec := xs.NewSession(op, []byte(uname), []byte(remoteHost), []byte(os.Getenv("TERM")), []byte(cmdStr), []byte(authCookie), 0) sendErr := sendSessionParams(&conn, rec) if sendErr != nil { - log.Fatal(sendErr) + _ = xs.Restore(int(os.Stdin.Fd()), oldState) // nolint: errcheck,gosec + rec.SetStatus(254) + fmt.Fprintln(os.Stderr, "Error: server rejected secure proposal params") // nolint: errcheck + exitWithStatus(int(rec.Status())) + //log.Fatal(sendErr) } //Security scrub @@ -924,13 +930,14 @@ func main() { } if rec.Status() != 0 { - _ = xs.Restore(int(os.Stdin.Fd()), oldState) // nolint: errcheck,gosec + _ = xs.Restore(int(os.Stdin.Fd()), oldState) // nolint: errcheck,gosec fmt.Fprintln(os.Stderr, "Session exited with status:", rec.Status()) // nolint: errcheck } } if oldState != nil { _ = xs.Restore(int(os.Stdin.Fd()), oldState) // nolint: gosec + oldState = nil } exitWithStatus(int(rec.Status())) diff --git a/xsd/xsd.go b/xsd/xsd.go index 0860049..a7ac458 100755 --- a/xsd/xsd.go +++ b/xsd/xsd.go @@ -23,14 +23,15 @@ import ( "os/signal" "os/user" "path" + "strings" "sync" "syscall" "unsafe" "blitter.com/go/goutmp" xs "blitter.com/go/xs" - "blitter.com/go/xs/xsnet" "blitter.com/go/xs/logger" + "blitter.com/go/xs/xsnet" "github.com/kr/pty" ) @@ -429,9 +430,73 @@ func GenAuthToken(who string, connhost string) string { return fmt.Sprintf("%s:%s", tokenA, hex.EncodeToString(tokenB)) } -// Demo of a simple server that listens and spawns goroutines for each -// connecting client. Note this code is identical to standard tcp -// server code, save for declaring 'xsnet' rather than 'net' +var ( + aKEXAlgs allowedKEXAlgs + aCipherAlgs allowedCipherAlgs + aHMACAlgs allowedHMACAlgs +) + +type allowedKEXAlgs []string // TODO +type allowedCipherAlgs []string // TODO +type allowedHMACAlgs []string // TODO + +func (a allowedKEXAlgs) allowed(k xsnet.KEXAlg) bool { + for i := 0; i < len(a); i++ { + if a[i] == "KEX_all" || a[i] == k.String() { + return true + } + } + return false +} + +func (a *allowedKEXAlgs) String() string { + return fmt.Sprintf("allowedKEXAlgs: %v", *a) +} + +func (a *allowedKEXAlgs) Set(value string) error { + *a = append(*a, strings.TrimSpace(value)) + return nil +} + +func (a allowedCipherAlgs) allowed(c xsnet.CSCipherAlg) bool { + for i := 0; i < len(a); i++ { + if a[i] == "C_all" || a[i] == c.String() { + return true + } + } + return false +} + +func (a *allowedCipherAlgs) String() string { + return fmt.Sprintf("allowedCipherAlgs: %v", *a) +} + +func (a *allowedCipherAlgs) Set(value string) error { + *a = append(*a, strings.TrimSpace(value)) + return nil +} + +func (a allowedHMACAlgs) allowed(h xsnet.CSHmacAlg) bool { + for i := 0; i < len(a); i++ { + if a[i] == "H_all" || a[i] == h.String() { + return true + } + } + return false +} + +func (a *allowedHMACAlgs) String() string { + return fmt.Sprintf("allowedHMACAlgs: %v", *a) +} + +func (a *allowedHMACAlgs) Set(value string) error { + *a = append(*a, strings.TrimSpace(value)) + return nil +} + +// Main server that listens and spawns goroutines for each +// connecting client. Note this code is mostly identical to standard +// tcp server code, save for declaring 'xsnet' rather than 'net' // Listener and Conns. The KEx and encrypt/decrypt is done within the type. // Compare to 'serverp.go' in this directory to see the equivalence. // TODO: reduce gocyclo @@ -453,6 +518,11 @@ func main() { flag.UintVar(&chaffFreqMax, "F", 5000, "chaff pkt freq max (msecs)") flag.UintVar(&chaffBytesMax, "B", 64, "chaff pkt size max (bytes)") flag.BoolVar(&dbg, "d", false, "debug logging") + + flag.Var(&aKEXAlgs, "aK", `List of allowed KEX algs (eg. 'KEXAlgA KEXAlgB ... KEXAlgN') (default allow all)`) + flag.Var(&aCipherAlgs, "aC", `List of allowed ciphers (eg. 'CipherAlgA CipherAlgB ... CipherAlgN') (default allow all)`) + flag.Var(&aHMACAlgs, "aH", `List of allowed HMACs (eg. 'HMACAlgA HMACAlgB ... HMACAlgN') (default allow all)`) + flag.Parse() if vopt { @@ -486,6 +556,22 @@ func main() { log.SetOutput(ioutil.Discard) } + // Set up allowed algs, if specified (default allow all) + if len(aKEXAlgs) == 0 { + aKEXAlgs = []string{"KEX_all"} + } + logger.LogNotice(fmt.Sprintf("Allowed KEXAlgs: %v\n", aKEXAlgs)) // nolint: gosec,errcheck + + if len(aCipherAlgs) == 0 { + aCipherAlgs = []string{"C_all"} + } + logger.LogNotice(fmt.Sprintf("Allowed CipherAlgs: %v\n", aCipherAlgs)) // nolint: gosec,errcheck + + if len(aHMACAlgs) == 0 { + aHMACAlgs = []string{"H_all"} + } + logger.LogNotice(fmt.Sprintf("Allowed HMACAlgs: %v\n", aHMACAlgs)) // nolint: gosec,errcheck + // Set up handler for daemon signalling exitCh := make(chan os.Signal, 1) signal.Notify(exitCh, os.Signal(syscall.SIGTERM), os.Signal(syscall.SIGINT), os.Signal(syscall.SIGHUP), os.Signal(syscall.SIGUSR1), os.Signal(syscall.SIGUSR2)) @@ -522,9 +608,22 @@ func main() { log.Println("Serving on", laddr) for { // Wait for a connection. + // Then check if client-proposed algs are allowed conn, err := l.Accept() if err != nil { log.Printf("Accept() got error(%v), hanging up.\n", err) + } else if !aKEXAlgs.allowed(conn.KEX()) { + log.Printf("Accept() rejected for banned KEX alg %d, hanging up.\n", conn.KEX()) + conn.SetStatus(xsnet.CSEKEXAlgDenied) + conn.Close() + } else if !aCipherAlgs.allowed(conn.CAlg()) { + log.Printf("Accept() rejected for banned Cipher alg %d, hanging up.\n", conn.CAlg()) + conn.SetStatus(xsnet.CSECipherAlgDenied) + conn.Close() + } else if !aHMACAlgs.allowed(conn.HAlg()) { + log.Printf("Accept() rejected for banned HMAC alg %d, hanging up.\n", conn.HAlg()) + conn.SetStatus(xsnet.CSEHMACAlgDenied) + conn.Close() } else { log.Println("Accepted client") diff --git a/xsnet/consts.go b/xsnet/consts.go index aac023e..a32d501 100644 --- a/xsnet/consts.go +++ b/xsnet/consts.go @@ -29,6 +29,7 @@ const ( KEX_NEWHOPE_SIMPLE // 'NewHopeLP-Simple' - https://eprint.iacr.org/2016/1157 KEX_resvd14 KEX_resvd15 + KEX_invalid = 255 ) // Sent from client to server in order to specify which @@ -38,12 +39,15 @@ type KEXAlg uint8 // Extended exit status codes - indicate comm/pty issues // rather than remote end normal UNIX exit codes const ( - CSENone = 1024 + iota - CSETruncCSO // No CSOExitStatus in payload - CSEStillOpen // Channel closed unexpectedly - CSEExecFail // cmd.Start() (exec) failed - CSEPtyExecFail // pty.Start() (exec w/pty) failed - CSEPtyGetNameFail // failed to obtain pty name + CSENone = 1024 + iota + CSETruncCSO // No CSOExitStatus in payload + CSEStillOpen // Channel closed unexpectedly + CSEExecFail // cmd.Start() (exec) failed + CSEPtyExecFail // pty.Start() (exec w/pty) failed + CSEPtyGetNameFail // failed to obtain pty name + CSEKEXAlgDenied // server rejected proposed KEX alg + CSECipherAlgDenied // server rejected proposed Cipher alg + CSEHMACAlgDenied // server rejected proposed HMAC alg ) // Extended (>255 UNIX exit status) codes diff --git a/xsnet/net.go b/xsnet/net.go index c2edde9..cc5b238 100644 --- a/xsnet/net.go +++ b/xsnet/net.go @@ -70,9 +70,10 @@ type ( // Conn is a connection wrapping net.Conn with KEX & session state Conn struct { - kex KEXAlg // KEX/KEM proposal (client -> server) - m *sync.Mutex // (internal) - c *net.Conn // which also implements io.Reader, io.Writer, ... + kex KEXAlg // KEX/KEM proposal (client -> server) + + m *sync.Mutex // (internal) + c *net.Conn // which also implements io.Reader, io.Writer, ... logCipherText bool // somewhat expensive, for debugging logPlainText bool // INSECURE and somewhat expensive, for debugging @@ -105,6 +106,67 @@ func (t *TunEndpoint) String() string { return fmt.Sprintf("[%d:%s:%d]", t.Lport, t.Peer, t.Rport) } +func (k *KEXAlg) String() string { + switch *k { + case KEX_HERRADURA256: + return "KEX_HERRADURA256" + case KEX_HERRADURA512: + return "KEX_HERRADURA512" + case KEX_HERRADURA1024: + return "KEX_HERRADURA1024" + case KEX_HERRADURA2048: + return "KEX_HERRADURA2048" + case KEX_KYBER512: + return "KEX_KYBER512" + case KEX_KYBER768: + return "KEX_KYBER768" + case KEX_KYBER1024: + return "KEX_KYBER1024" + case KEX_NEWHOPE: + return "KEX_NEWHOPE" + case KEX_NEWHOPE_SIMPLE: + return "KEX_NEWHOPE_SIMPLE" + default: + return "KEX_ERR_UNK" + } +} + +func (hc *Conn) CAlg() CSCipherAlg { + return CSCipherAlg(hc.cipheropts & 0x0FF) +} + +func (c *CSCipherAlg) String() string { + switch *c & 0x0FF { + case CAlgAES256: + return "C_AES_256" + case CAlgTwofish128: + return "C_TWOFISH_128" + case CAlgBlowfish64: + return "C_BLOWFISH_64" + case CAlgCryptMT1: + return "C_CRYPTMT1" + case CAlgWanderer: + return "C_WANDERER" + default: + return "C_ERR_UNK" + } +} + +func (hc *Conn) HAlg() CSHmacAlg { + return CSHmacAlg((hc.cipheropts >> 8) & 0x0FF) +} + +func (h *CSHmacAlg) String() string { + switch (*h >> 8) & 0x0FF { + case HmacSHA256: + return "H_SHA256" + case HmacSHA512: + return "C_SHA512" + default: + return "H_ERR_UNK" + } +} + func _initLogging(d bool, c string, f logger.Priority) { if Log == nil { Log, _ = logger.New(f, fmt.Sprintf("%s:xsnet", c)) @@ -129,6 +191,10 @@ func (hc *Conn) Unlock() { hc.m.Unlock() } +func (hc Conn) KEX() KEXAlg { + return hc.kex +} + func (hc Conn) GetStatus() CSOType { return *hc.closeStat } @@ -935,6 +1001,9 @@ func (hl *HKExListener) Accept() (hc Conn, err error) { default: return Conn{}, err } + + // Finally, ensure alg proposed by client is allowed by server config + //if hc.kex.String() { log.Println("[hc.Accept successful]") return }