diff --git a/mtglib/secret.go b/mtglib/secret.go index c76eed2..d9c1ff3 100644 --- a/mtglib/secret.go +++ b/mtglib/secret.go @@ -11,11 +11,43 @@ 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 [SecretKeyLength]byte + // 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 @@ -24,6 +56,7 @@ func (s Secret) MarshalText() ([]byte, error) { return nil, nil } +// MarshalText is to support text.Unmarshaller interface. func (s *Secret) UnmarshalText(data []byte) error { text := string(data) if text == "" { @@ -62,18 +95,22 @@ func (s *Secret) UnmarshalText(data []byte) error { 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()) } @@ -85,6 +122,7 @@ func (s *Secret) makeBytes() []byte { return data } +// GenerateSecret makes a new secret with a given hostname. func GenerateSecret(hostname string) Secret { s := Secret{ Host: hostname, @@ -97,6 +135,7 @@ func GenerateSecret(hostname string) Secret { return s } +// ParseSecret parses a secret (both hex and base64 forms). func ParseSecret(secret string) (Secret, error) { s := Secret{}