Feat: ConnAuth HMAC verification gated by YAMA_CONN_AUTH_KEY
Verify the HMAC-SHA256 signature and timestamp of device sub-connections (screen/terminal/file) when the YAMA_CONN_AUTH_KEY environment variable is set. With the key set, a sub-connection whose signature or clock skew fails is answered with a non-OK ConnAuth status and dropped; without the key the legacy always-OK behavior is preserved. The key is never compiled in: it is read from the environment at startup, so no secret enters the repository. Source-IP binding in hub.BindScreenConn remains in place as defense-in-depth. Co-Authored-By: deepseek-v4-pro
This commit is contained in:
1 parent
4ff965a2b4
commit
7651ee8602
3 files changed
+222
-19
No files matched your search
@@ -0,0 +1,103 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/yuanyuanxiang/SimpleRemoter/server/go/protocol"
|
||||
)
|
||||
|
||||
// connAuthTestKey is an arbitrary non-secret key used only to exercise the
|
||||
// verifyConnAuth code path in tests. The production key is never compiled in:
|
||||
// it is read from the YAMA_CONN_AUTH_KEY environment variable at startup.
|
||||
const connAuthTestKey = "test-conn-auth-key"
|
||||
|
||||
// signConnAuth reproduces the client's ConnAuth signature: hex(HMAC-SHA256(key,
|
||||
// clientID||timestamp||nonce)) as 64 lowercase hex chars. Mirrors
|
||||
// client/IOCPClient.cpp PerformConnAuth + license.cpp signMessage.
|
||||
func signConnAuth(key string, clientID, ts uint64, nonce [16]byte) string {
|
||||
mac := hmac.New(sha256.New, []byte(key))
|
||||
var msg [32]byte
|
||||
binary.LittleEndian.PutUint64(msg[0:8], clientID)
|
||||
binary.LittleEndian.PutUint64(msg[8:16], ts)
|
||||
copy(msg[16:32], nonce[:])
|
||||
mac.Write(msg[:])
|
||||
return hex.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
||||
func testNonce() [16]byte {
|
||||
var n [16]byte
|
||||
for i := range n {
|
||||
n[i] = byte(i)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// TestConnAuthHmacAlgorithmVector pins the signing algorithm and byte layout
|
||||
// (HMAC-SHA256 over clientID||timestamp||nonce, lowercase hex) against a vector
|
||||
// computed independently with Python's hmac/hashlib (not Go's crypto). The key
|
||||
// value is irrelevant here — what matters is that the server reproduces the
|
||||
// exact signing the client performs. The real key is supplied at runtime via
|
||||
// YAMA_CONN_AUTH_KEY, never compiled in.
|
||||
func TestConnAuthHmacAlgorithmVector(t *testing.T) {
|
||||
const want = "e59bf1259c77e065afa604cb5b1794b0c8c03fbb7e3473f5c3c34c2b94a90406"
|
||||
if got := signConnAuth(connAuthTestKey, 0x1122334455667788, 1700000000, testNonce()); got != want {
|
||||
t.Fatalf("HMAC mismatch:\n got %s\n want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyConnAuth(t *testing.T) {
|
||||
h := &MyHandler{connAuthKey: []byte(connAuthTestKey)}
|
||||
|
||||
build := func(ts uint64, sig []byte) []byte {
|
||||
pkt := make([]byte, protocol.ConnAuthPacketSize)
|
||||
pkt[0] = protocol.TokenConnAuth
|
||||
binary.LittleEndian.PutUint64(pkt[protocol.ConnAuthOffClientID:], 0x1122334455667788)
|
||||
binary.LittleEndian.PutUint64(pkt[protocol.ConnAuthOffTimestamp:], ts)
|
||||
for i := range 16 {
|
||||
pkt[protocol.ConnAuthOffNonce+i] = byte(i)
|
||||
}
|
||||
copy(pkt[protocol.ConnAuthOffSignature:], sig)
|
||||
return pkt
|
||||
}
|
||||
|
||||
nonce := testNonce()
|
||||
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
ts := uint64(time.Now().Unix())
|
||||
sig := signConnAuth(connAuthTestKey, 0x1122334455667788, ts, nonce)
|
||||
if got := h.verifyConnAuth(build(ts, []byte(sig))); got != protocol.ConnAuthStatusOK {
|
||||
t.Fatalf("expected OK, got status %d", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("bad_signature", func(t *testing.T) {
|
||||
ts := uint64(time.Now().Unix())
|
||||
sig := signConnAuth(connAuthTestKey, 0x1122334455667788, ts, nonce)
|
||||
pkt := build(ts, []byte(sig))
|
||||
pkt[protocol.ConnAuthOffSignature] ^= 0xff
|
||||
if got := h.verifyConnAuth(pkt); got != protocol.ConnAuthStatusBadSignature {
|
||||
t.Fatalf("expected BadSignature, got %d", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("clock_skew", func(t *testing.T) {
|
||||
// Just outside the 300 s tolerance, with a signature valid for that
|
||||
// timestamp, so the skew check fires before the signature check.
|
||||
ts := uint64(time.Now().Unix()) - protocol.ConnAuthTimestampToleranceSec - 1
|
||||
sig := signConnAuth(connAuthTestKey, 0x1122334455667788, ts, nonce)
|
||||
if got := h.verifyConnAuth(build(ts, []byte(sig))); got != protocol.ConnAuthStatusClockSkew {
|
||||
t.Fatalf("expected ClockSkew, got %d", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("bad_size", func(t *testing.T) {
|
||||
if got := h.verifyConnAuth([]byte{protocol.TokenConnAuth}); got != protocol.ConnAuthStatusBadSize {
|
||||
t.Fatalf("expected BadSize, got %d", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user