Files
Denozordec 4905c06b9b
Publish mtproxy_checker Docker image / test (push) Successful in 19s
Publish mtproxy_checker Docker image / build-and-push (push) Successful in 48s
init
2026-04-11 00:38:22 +07:00

112 lines
2.3 KiB
Go

package secret
import (
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"strings"
)
type Kind int
const (
KindEE Kind = iota
KindDD
)
// Parsed holds normalized MTProxy secret material for handshake and MTProxyIO.
type Parsed struct {
Kind Kind
// Key is always 16 bytes (MTProxy user secret).
Key []byte
// Domain is raw bytes after the key in ee-secrets (SNI payload); empty for dd.
Domain []byte
// RawHex is the original secret string (lowercase hex) for logging.
RawHex string
}
var (
ErrInvalidSecret = errors.New("invalid mtproxy secret")
)
// Parse decodes Telegram MTProxy secret from tg:// links (hex or legacy base64).
func Parse(secretStr string) (*Parsed, error) {
s := strings.TrimSpace(strings.ToLower(secretStr))
if s == "" {
return nil, fmt.Errorf("%w: empty", ErrInvalidSecret)
}
// Strip common tg:// noise
s = strings.TrimPrefix(s, "0x")
var raw []byte
if isHex(s) {
b, err := hex.DecodeString(s)
if err != nil {
return nil, fmt.Errorf("%w: hex: %v", ErrInvalidSecret, err)
}
raw = b
} else {
// Telegram sometimes uses base64 secrets
pad := strings.Repeat("=", (4-len(s)%4)%4)
b, err := base64.StdEncoding.DecodeString(s + pad)
if err != nil {
b, err = base64.URLEncoding.DecodeString(s + pad)
}
if err != nil {
return nil, fmt.Errorf("%w: not hex or base64: %v", ErrInvalidSecret, err)
}
raw = b
}
if len(raw) == 0 {
return nil, fmt.Errorf("%w: no payload", ErrInvalidSecret)
}
switch raw[0] {
case 0xdd:
if len(raw) != 17 {
return nil, fmt.Errorf("%w: dd secret must be 17 bytes, got %d", ErrInvalidSecret, len(raw))
}
key := make([]byte, 16)
copy(key, raw[1:])
return &Parsed{
Kind: KindDD,
Key: key,
Domain: nil,
RawHex: s,
}, nil
case 0xee:
if len(raw) < 18 {
return nil, fmt.Errorf("%w: ee secret too short", ErrInvalidSecret)
}
key := make([]byte, 16)
copy(key, raw[1:17])
domain := make([]byte, len(raw)-17)
copy(domain, raw[17:])
return &Parsed{
Kind: KindEE,
Key: key,
Domain: domain,
RawHex: s,
}, nil
default:
return nil, fmt.Errorf("%w: unknown first byte 0x%02x (expected ee/dd)", ErrInvalidSecret, raw[0])
}
}
func isHex(s string) bool {
for _, r := range s {
switch {
case r >= '0' && r <= '9', r >= 'a' && r <= 'f':
default:
return false
}
}
return len(s)%2 == 0 && len(s) >= 2
}