FILE / ScuroNeko/mtg

mtglib/secret.go

Исходный файл и его история в репозитории.
FILE e6d444546f53e46137c9feaf8394881f16e144f6
Files
mtg/mtglib/secret.go
T

144 lines
3.9 KiB
Go

package mtglib
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
)
const secretFakeTLSFirstByte byte = 0xee
var secretEmptyKey [SecretKeyLength]byte
// Secret is a data structure that presents a secret.
//
// Telegram secret is not a simple string like
// "ee367a189aee18fa31c190054efd4a8e9573746f726167652e676f6f676c65617069732e636f6d".
// Actually, this is a serialized datastructure of 2 parts: key and host.
//
// ee367a189aee18fa31c190054efd4a8e9573746f726167652e676f6f676c65617069732e636f6d
// |-|-------------------------------|-------------------------------------------
// p key hostname
//
// Serialized secret starts with 'ee'. Actually, in the past we also had
// 'dd' secrets and prefixless ones. But this is history. Currently,
// we do have only 'ee' secrets which mean faketls + protection from
// statistical attacks on a length. 'ee' is a byte 238 (0xee).
//
// After that, we have 16 bytes of the key. This is a random generated
// secret data of the proxy and this data is used to derive
// authentication schemas. These secrets are mixed into hmacs and sha256
// checksums which are used to build AEAD ciphers for obfuscated2
// protocol and ensure faketls handshake.
//
// Host is a domain fronting hostname in latin1 (ASCII) encoding. This
// hostname should be used for SNI in faketls and MTG verifies it. Also,
// this is when mtg gets about a domain fronting hostname.
//
// Secrets can be serialized into 2 forms: hex and base64. If
// you decode both forms into bytes, you'll get the same byte array.
// Telegram clients nowadays accept all forms.
type Secret struct {
// Key is a set of bytes used for traffic authentication.
Key [SecretKeyLength]byte
// Host is a domain fronting hostname.
Host string
}
// MarshalText is to support text.Marshaller interface.
func (s Secret) MarshalText() ([]byte, error) {
if s.Valid() {
return []byte(s.String()), nil
}
return nil, nil
}
// MarshalText is to support text.Unmarshaller interface.
func (s *Secret) UnmarshalText(data []byte) error {
text := string(data)
if text == "" {
return ErrSecretEmpty
}
decoded, err := hex.DecodeString(text)
if err != nil {
decoded, err = base64.RawURLEncoding.DecodeString(text)
}
if err != nil {
return fmt.Errorf("incorrect secret format: %w", err)
}
if len(decoded) < 2 { // nolint: gomnd // we need at least 1 byte here
return fmt.Errorf("secret is truncated, length=%d", len(decoded))
}
if decoded[0] != secretFakeTLSFirstByte {
return fmt.Errorf("incorrect first byte of secret: %#x", decoded[0])
}
decoded = decoded[1:]
if len(decoded) < SecretKeyLength {
return fmt.Errorf("secret has incorrect length %d", len(decoded))
}
copy(s.Key[:], decoded[:SecretKeyLength])
s.Host = string(decoded[SecretKeyLength:])
if s.Host == "" {
return fmt.Errorf("hostname cannot be empty: %s", text)
}
return nil
}
// Valid checks if this secret is valid and can be used in proxy.
func (s Secret) Valid() bool {
return s.Key != secretEmptyKey && s.Host != ""
}
// String is to support fmt.Stringer interface.
func (s Secret) String() string {
return s.Base64()
}
// Base64 returns a base64-encoded form of this secret.
func (s Secret) Base64() string {
return base64.RawURLEncoding.EncodeToString(s.makeBytes())
}
// Hex returns a hex-encoded form of this secret (ee-secret).
func (s Secret) Hex() string {
return hex.EncodeToString(s.makeBytes())
}
func (s *Secret) makeBytes() []byte {
data := append([]byte{secretFakeTLSFirstByte}, s.Key[:]...)
data = append(data, s.Host...)
return data
}
// GenerateSecret makes a new secret with a given hostname.
func GenerateSecret(hostname string) Secret {
s := Secret{
Host: hostname,
}
if _, err := rand.Read(s.Key[:]); err != nil {
panic(err)
}
return s
}
// ParseSecret parses a secret (both hex and base64 forms).
func ParseSecret(secret string) (Secret, error) {
s := Secret{}
return s, s.UnmarshalText([]byte(secret))
}