You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
181 lines
4.1 KiB
181 lines
4.1 KiB
package dcs |
|
|
|
import ( |
|
"bytes" |
|
"crypto/aes" |
|
"crypto/cipher" |
|
"crypto/rsa" |
|
"crypto/sha256" |
|
"encoding/base64" |
|
"math/big" |
|
"net" |
|
"sort" |
|
"sync" |
|
|
|
"github.com/go-faster/errors" |
|
|
|
"github.com/gotd/td/bin" |
|
"github.com/gotd/td/internal/crypto" |
|
"github.com/gotd/td/tg" |
|
) |
|
|
|
var dnsKey struct { |
|
rsa.PublicKey |
|
once sync.Once |
|
eBig *big.Int |
|
} |
|
|
|
//nolint:gochecknoinits |
|
func init() { |
|
dnsKey.once.Do(func() { |
|
k, err := crypto.ParseRSAPublicKeys([]byte(`-----BEGIN RSA PUBLIC KEY----- |
|
MIIBCgKCAQEAyr+18Rex2ohtVy8sroGPBwXD3DOoKCSpjDqYoXgCqB7ioln4eDCF |
|
fOBUlfXUEvM/fnKCpF46VkAftlb4VuPDeQSS/ZxZYEGqHaywlroVnXHIjgqoxiAd |
|
192xRGreuXIaUKmkwlM9JID9WS2jUsTpzQ91L8MEPLJ/4zrBwZua8W5fECwCCh2c |
|
9G5IzzBm+otMS/YKwmR1olzRCyEkyAEjXWqBI9Ftv5eG8m0VkBzOG655WIYdyV0H |
|
fDK/NWcvGqa0w/nriMD6mDjKOryamw0OP9QuYgMN0C9xMW9y8SmP4h92OAWodTYg |
|
Y1hZCxdv6cs5UnW9+PWvS+WIbkh+GaWYxwIDAQAB |
|
-----END RSA PUBLIC KEY-----`)) |
|
if err != nil { |
|
panic(err) |
|
} |
|
|
|
dnsKey.PublicKey = *k[0] |
|
dnsKey.eBig = big.NewInt(int64(dnsKey.E)) |
|
}) |
|
} |
|
|
|
// parseDNSList decodes raw encrypted simple config. |
|
// |
|
// Notice that parseDNSList does not decode base64, user should do it manually. |
|
func parseDNSList(input [256]byte) (tg.HelpConfigSimple, error) { |
|
// See https://github.com/tdlib/td/blob/master/td/telegram/ConfigManager.cpp#L148. |
|
x := new(big.Int).SetBytes(input[:]) |
|
y := new(big.Int).Exp(x, dnsKey.eBig, dnsKey.N) |
|
|
|
dataRSA := make([]byte, 256) |
|
if !crypto.FillBytes(y, dataRSA) { |
|
return tg.HelpConfigSimple{}, errors.New("dataRSA has invalid size") |
|
} |
|
|
|
block, err := aes.NewCipher(dataRSA[:32]) |
|
if err != nil { |
|
return tg.HelpConfigSimple{}, err |
|
} |
|
d := cipher.NewCBCDecrypter(block, dataRSA[16:32]) |
|
dataCBC := dataRSA[32:] |
|
d.CryptBlocks(dataCBC, dataCBC) |
|
|
|
decrypted := dataCBC[:len(dataCBC)-16] |
|
decryptedHash := sha256.Sum256(decrypted) |
|
hash := dataCBC[len(dataCBC)-16:] |
|
|
|
if !bytes.Equal(decryptedHash[:16], hash) { |
|
return tg.HelpConfigSimple{}, errors.New("hash mismatch") |
|
} |
|
|
|
var cfg tg.HelpConfigSimple |
|
if err := cfg.Decode(&bin.Buffer{Buf: decrypted[4:]}); err != nil { |
|
return tg.HelpConfigSimple{}, err |
|
} |
|
return cfg, nil |
|
} |
|
|
|
type sortByLen []string |
|
|
|
func (s sortByLen) Len() int { |
|
return len(s) |
|
} |
|
|
|
func (s sortByLen) Less(i, j int) bool { |
|
return len(s[i]) > len(s[j]) |
|
} |
|
|
|
func (s sortByLen) Swap(i, j int) { |
|
s[i], s[j] = s[j], s[i] |
|
} |
|
|
|
// DNSConfig is DC connection config obtained from DNS. |
|
type DNSConfig struct { |
|
// Date field of HelpConfigSimple. |
|
Date int |
|
// Expires field of HelpConfigSimple. |
|
Expires int |
|
// Rules field of HelpConfigSimple. |
|
Rules []tg.AccessPointRule |
|
} |
|
|
|
// Options returns DC options from this config. |
|
func (d DNSConfig) Options() (r []tg.DCOption) { |
|
convertIP := func(ip int) string { |
|
return net.IPv4( |
|
byte(ip), |
|
byte(ip>>8), |
|
byte(ip>>16), |
|
byte(ip>>24), |
|
).String() |
|
} |
|
for _, rule := range d.Rules { |
|
for _, ip := range rule.IPs { |
|
switch ip := ip.(type) { |
|
case *tg.IPPort: |
|
r = append(r, tg.DCOption{ |
|
ID: rule.DCID, |
|
IPAddress: convertIP(ip.Ipv4), |
|
Port: ip.Port, |
|
}) |
|
case *tg.IPPortSecret: |
|
r = append(r, tg.DCOption{ |
|
TCPObfuscatedOnly: true, |
|
ID: rule.DCID, |
|
IPAddress: convertIP(ip.Ipv4), |
|
Port: ip.Port, |
|
Secret: ip.Secret, |
|
}) |
|
} |
|
} |
|
} |
|
return r |
|
} |
|
|
|
// ParseDNSConfig parses tg.HelpConfigSimple from TXT response. |
|
func ParseDNSConfig(txt []string) (DNSConfig, error) { |
|
encoding := base64.StdEncoding |
|
const ( |
|
decodedLen = 256 |
|
encodedLen = 344 |
|
) |
|
sort.Sort(sortByLen(txt)) |
|
|
|
var totalLength int |
|
for i := range txt { |
|
totalLength += len(txt[i]) |
|
} |
|
if totalLength != encodedLen { |
|
return DNSConfig{}, errors.Errorf("invalid input length %d", totalLength) |
|
} |
|
|
|
var ( |
|
encoded [encodedLen]byte |
|
decoded [decodedLen]byte |
|
) |
|
n := 0 |
|
for i := range txt { |
|
n += copy(encoded[n:], txt[i]) |
|
} |
|
|
|
if _, err := encoding.Decode(decoded[:], encoded[:]); err != nil { |
|
return DNSConfig{}, errors.Wrap(err, "decode") |
|
} |
|
|
|
cfg, err := parseDNSList(decoded) |
|
if err != nil { |
|
return DNSConfig{}, errors.Wrap(err, "decrypt config") |
|
} |
|
|
|
return DNSConfig{ |
|
Date: cfg.Date, |
|
Expires: cfg.Expires, |
|
Rules: cfg.Rules, |
|
}, nil |
|
}
|
|
|