connector: hex-decode mtproxy secret
Go / Lint (old) (push) Failing after 5m14s
Go / Lint (latest) (push) Failing after 5m19s
Go / Lint (old) (pull_request) Failing after 5m14s
Go / Lint (latest) (pull_request) Failing after 4m40s

dcs.MTProxy expects raw secret bytes. Carrying them verbatim through a
YAML string field is impossible: real secrets contain bytes >= 0x80
(faketls starts with 0xee, secured with 0xdd) which cannot survive a
unicode string round-trip, so the value reached the bridge corrupted or
empty (gotd then logged "invalid secret").

Accept the standard hex form printed by mtg/MTProxy tooling
(e.g. "ee" + 16-byte secret + cloak domain hex) and decode it before
handing the bytes to gotd.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Igor Artamonov
2026-05-01 10:14:58 +03:00
parent e3bb26aee1
commit b00e2d8955
3 changed files with 62 additions and 1 deletions
+2
View File
@@ -61,6 +61,8 @@ proxy:
# Proxy IP address/domain name and port.
address: "127.0.0.1:1080"
# Proxy authentication (optional). Put MTProxy secret in password field.
# For mtproxy, the secret must be hex-encoded (the same form mtg/MTProxy
# tools print, e.g. "ee" + 16-byte secret + cloak domain hex for faketls).
username:
password:
+23 -1
View File
@@ -17,13 +17,31 @@
package connector
import (
"encoding/hex"
"fmt"
"strings"
"golang.org/x/net/proxy"
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/dcs"
)
// decodeMTProxySecret parses an MTProxy secret string into raw bytes.
// MTProxy secrets are binary (faketls secrets begin with 0xEE, secured with 0xDD)
// and cannot be carried verbatim in a YAML string field, so we accept the standard
// hex encoding (optionally prefixed with "ee"/"dd") used by mtg/MTProxy tooling.
func decodeMTProxySecret(s string) ([]byte, error) {
s = strings.TrimSpace(s)
if s == "" {
return nil, fmt.Errorf("mtproxy secret is empty")
}
b, err := hex.DecodeString(s)
if err != nil {
return nil, fmt.Errorf("mtproxy secret must be hex-encoded: %w", err)
}
return b, nil
}
func GetProxyDialFunc(cfg ProxyConfig) (dcs.DialFunc, error) {
switch cfg.Type {
// we can't proxy HTTP through mtproxy
@@ -54,7 +72,11 @@ func GetProxyResolver(cfg ProxyConfig) (dcs.Resolver, error) {
resolver := dcs.Plain(dcs.PlainOptions{Dial: dialer})
return resolver, nil
case "mtproxy":
return dcs.MTProxy(cfg.Address, []byte(cfg.Password), dcs.MTProxyOptions{})
secret, err := decodeMTProxySecret(cfg.Password)
if err != nil {
return nil, err
}
return dcs.MTProxy(cfg.Address, secret, dcs.MTProxyOptions{})
default:
return nil, fmt.Errorf("unsupported proxy type %s", cfg.Type)
}
+37
View File
@@ -0,0 +1,37 @@
package connector
import (
"bytes"
"testing"
)
func TestDecodeMTProxySecret(t *testing.T) {
// faketls secret: 0xee + 16 bytes + cloak domain ("working-name.ru" = 15 bytes)
hexSecret := "ee971746d927f4c0138b18447bfe1269bc70312e776f726b696e672d6e616d652e7275"
want := []byte{
0xee,
0x97, 0x17, 0x46, 0xd9, 0x27, 0xf4, 0xc0, 0x13,
0x8b, 0x18, 0x44, 0x7b, 0xfe, 0x12, 0x69, 0xbc,
0x70, 0x31, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x69,
0x6e, 0x67, 0x2d, 0x6e, 0x61, 0x6d, 0x65, 0x2e,
0x72, 0x75,
}
got, err := decodeMTProxySecret(hexSecret)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !bytes.Equal(got, want) {
t.Fatalf("decoded bytes mismatch:\n got=%x\nwant=%x", got, want)
}
if _, err := decodeMTProxySecret(" " + hexSecret + "\n"); err != nil {
t.Fatalf("whitespace should be tolerated: %v", err)
}
if _, err := decodeMTProxySecret(""); err == nil {
t.Fatal("expected error for empty secret")
}
if _, err := decodeMTProxySecret("not-hex!!"); err == nil {
t.Fatal("expected error for non-hex secret")
}
}