This commit is contained in:
onysd 2026-07-18 10:33:17 +03:00
parent 6b29556ef8
commit 8875cd33fd
14 changed files with 136 additions and 4 deletions

View file

@ -38,6 +38,7 @@ func (s *Server) runServerExchange(ctx context.Context, conn transport.Conn) (ex
timeout: exchange.DefaultTimeout,
key: s.key,
dc: s.dc,
strictDC: s.strictDC,
log: s.log.Named("exchange"),
rng: compatServerRNG{rand: s.rand},
commitKey: s.commitExchangeAuthKey,
@ -65,6 +66,7 @@ type serverExchangeCompat struct {
timeout time.Duration
key exchange.PrivateKey
dc int
strictDC bool
log *zap.Logger
rng compatServerRNG
commitKey func(context.Context, exchange.ServerExchangeResult, int) error
@ -345,10 +347,23 @@ func (s serverExchangeCompat) validatePQInnerDataDC(d mt.PQInnerDataClass) error
switch innerDataDC := d.(type) {
case *mt.PQInnerDataDC:
if innerDataDC.DC != s.dc {
if !s.strictDC {
// Lenient by default (Options.StrictDC doc has the full
// rationale): telesrv is a single physical backend, and the
// OwpenGram client forks deliberately alias dc_id 1..5 to this
// one server, so a client-chosen dc_id that isn't our
// configured DC is expected, not an error. dc_id plays no
// role in key derivation, so accepting it doesn't weaken the
// exchange.
s.log.Debug("Accepted permanent auth key DC mismatch (lenient mode)",
zap.Int("server_dc", s.dc),
zap.Int("client_dc", innerDataDC.DC))
return nil
}
return wrongDCError(s.dc, innerDataDC.DC)
}
case *mt.PQInnerDataTempDC:
if !sameDCByAbs(innerDataDC.DC, s.dc) {
if !sameDCByAbs(innerDataDC.DC, s.dc) && s.strictDC {
return wrongDCError(s.dc, innerDataDC.DC)
}
if innerDataDC.DC < 0 {

View file

@ -366,8 +366,8 @@ func TestKeyExchangeAcceptsAndroidMediaTempNegativeDC(t *testing.T) {
}
}
func TestKeyExchangeRejectsWrongNegativeTempDC(t *testing.T) {
ex := serverExchangeCompat{dc: 2, log: zaptest.NewLogger(t)}
func TestKeyExchangeRejectsWrongNegativeTempDCWhenStrict(t *testing.T) {
ex := serverExchangeCompat{dc: 2, strictDC: true, log: zaptest.NewLogger(t)}
err := ex.validatePQInnerDataDC(&mt.PQInnerDataTempDC{DC: -3})
var exErr *exchange.ServerExchangeError
if !errors.As(err, &exErr) {
@ -378,6 +378,38 @@ func TestKeyExchangeRejectsWrongNegativeTempDC(t *testing.T) {
}
}
// TestKeyExchangeAcceptsMismatchedDCByDefault asserts that, in the default
// lenient mode, neither permanent nor temp key exchange requires dc_id to
// equal the server's configured DC. telesrv is always a single physical
// backend; the OwpenGram client forks deliberately alias dc_id 1..5 to it
// (see Options.StrictDC doc), so a mismatched client-chosen dc_id must not be
// rejected — doing so previously broke every account whose client picked a
// starting dc_id other than the server's.
func TestKeyExchangeAcceptsMismatchedDCByDefault(t *testing.T) {
ex := serverExchangeCompat{dc: 2, log: zaptest.NewLogger(t)}
if err := ex.validatePQInnerDataDC(&mt.PQInnerDataDC{DC: 3}); err != nil {
t.Fatalf("permanent DC mismatch: err = %v, want nil (lenient by default)", err)
}
if err := ex.validatePQInnerDataDC(&mt.PQInnerDataTempDC{DC: -3}); err != nil {
t.Fatalf("temp DC mismatch: err = %v, want nil (lenient by default)", err)
}
}
// TestKeyExchangeRejectsMismatchedPermanentDCWhenStrict asserts that
// strictDC=true still enforces exact DC-ID equality for permanent-key
// exchange (kept for a hypothetical future real multi-DC deployment).
func TestKeyExchangeRejectsMismatchedPermanentDCWhenStrict(t *testing.T) {
ex := serverExchangeCompat{dc: 2, strictDC: true, log: zaptest.NewLogger(t)}
err := ex.validatePQInnerDataDC(&mt.PQInnerDataDC{DC: 3})
var exErr *exchange.ServerExchangeError
if !errors.As(err, &exErr) {
t.Fatalf("err = %T %v, want ServerExchangeError", err, err)
}
if exErr.Code != codec.CodeWrongDC {
t.Fatalf("error code = %d, want %d", exErr.Code, codec.CodeWrongDC)
}
}
func TestDecodeCompatPQInnerDataTemp(t *testing.T) {
want := mt.PQInnerData{
Pq: []byte{0x0f},

View file

@ -301,6 +301,22 @@ type Options struct {
// DC 是本 server 的 DC ID。默认 2。
DC int
// StrictDC turns on exact DC-ID validation for the permanent-key exchange
// (default off = lenient). telesrv is always a single physical backend —
// there is no real multi-DC federation behind it — but the OwpenGram
// client forks intentionally run in "single-server backend" mode, where
// dc_id 1..5 all alias to this one server (see owpengram_servers.cpp /
// ApplyServerToDcOptions in the desktop client) so that any old data
// referencing a specific dc_id still resolves correctly. When tdesktop
// adds a new local account it picks its own starting dc_id (its usual
// multi-DC load-spreading behavior, unrelated to which physical server
// it's actually talking to) — that choice is not guaranteed to equal our
// configured DC. Strict validation would reject those accounts with
// "-444 wrong dc_id" even though they are connecting to the right (and
// only) server; dc_id is a client-side routing label here, not part of
// key derivation, so accepting the mismatch does not weaken the exchange.
// The switch exists for a hypothetical future real multi-DC deployment.
StrictDC bool
// RSAKey 是 server RSA 私钥用于密钥交换。nil 时无法完成握手。
RSAKey *rsa.PrivateKey
// AuthKeys 持久化 auth key。默认内存实现。
@ -480,6 +496,7 @@ type Server struct {
outboundScratchPool *outboundScratchPool
dc int
strictDC bool
key exchange.PrivateKey
authKeys store.AuthKeyStore
conns *SessionManager
@ -531,6 +548,7 @@ func New(opts Options) *Server {
outboundControlBudget: newOutboundTrackedBudget(defaultOutboundControlMaxBytes),
outboundScratchPool: newOutboundScratchPool(opts.OutboundWriteGlobalMaxBytes),
dc: opts.DC,
strictDC: opts.StrictDC,
key: exchange.PrivateKey{RSA: opts.RSAKey},
authKeys: opts.AuthKeys,
conns: conns,