Уровень безопасности TLS
Введение
Уровень TLS в Xray-core обеспечивает стандартное TLS-шифрование с обширными возможностями настройки. Он поддерживает как стандартный crypto/tls Go, так и uTLS (refraction-networking/utls) для имитации TLS-отпечатков, динамическую генерацию сертификатов из CA, OCSP-скрепление, закрепление сертификатов, возобновление сессий, ECH (Encrypted Client Hello), произвольные наборы шифров, предпочтения кривых и журналирование ключей. TLS не является транспортом сам по себе, а представляет собой уровень безопасности, оборачивающий любой транспорт.
Ключевые файлы
transport/internet/tls/tls.go-- Основные типы:Conn,UConn, реестр отпечатковtransport/internet/tls/config.go--Config.GetTLSConfig(), управление сертификатами, параметрыtransport/internet/tls/pin.go-- Функции хеширования сертификатов для закрепленияtransport/internet/tls/ech.go-- Поддержка ECH (Encrypted Client Hello)transport/internet/tls/grpc.go-- Вспомогательные функции TLS для gRPCtransport/internet/tls/unsafe.go-- Небезопасный доступ к пулу сертификатов
Типы соединений
Conn (стандартный TLS)
tls.Conn (tls/tls.go:26-28) оборачивает crypto/tls.Conn из Go:
type Conn struct {
*tls.Conn
}Функции:
- Таймаут корректного закрытия: 250 мс таймаут для TLS close notify, после которого базовое соединение принудительно закрывается (
tls.go:30-38) - Запись MultiBuffer: Компактирует и эффективно записывает мультибуферы (
tls.go:40-45) - NegotiatedProtocol: Сообщает результат ALPN (
tls.go:54-57)
UConn (имитация TLS-отпечатков uTLS)
tls.UConn (tls/tls.go:71-73) оборачивает utls.UConn для имитации TLS-отпечатков:
type UConn struct {
*utls.UConn
}Метод WebsocketHandshakeContext (tls.go:94-117) принудительно устанавливает ALPN http/1.1 для WebSocket-соединений с использованием uTLS:
func (c *UConn) WebsocketHandshakeContext(ctx context.Context) error {
c.BuildHandshakeState()
// Найти ALPNExtension и переопределить на ["http/1.1"]
// Пересобрать состояние рукопожатия
return c.HandshakeContext(ctx)
}Интерфейс
Оба типа реализуют Interface (tls.go:15-21):
type Interface interface {
net.Conn
HandshakeContext(ctx context.Context) error
VerifyHostname(host string) error
HandshakeContextServerName(ctx context.Context) string
NegotiatedProtocol() string
}Имитация TLS-отпечатков
Реестр отпечатков
Определены три уровня отпечатков (tls/tls.go:185-257):
PresetFingerprints (рекомендуемые варианты для GUI):
| Имя | ID uTLS |
|---|---|
chrome | HelloChrome_Auto |
firefox | HelloFirefox_Auto |
safari | HelloSafari_Auto |
ios | HelloIOS_Auto |
android | HelloAndroid_11_OkHttp |
edge | HelloEdge_Auto |
360 | Hello360_Auto |
qq | HelloQQ_Auto |
random | Случайно выбирается при запуске |
randomized | HelloRandomizedALPN с произвольными весами |
randomizednoalpn | HelloRandomizedNoALPN с произвольными весами |
ModernFingerprints: Конкретные версии браузеров (Chrome 83-131, Firefox 99-120 и т.д.)
OtherFingerprints: Устаревшие версии и специальные отпечатки.
Случайный выбор отпечатка
При запуске (tls.go:145-167) "random" назначается случайно выбранному современному отпечатку:
func init() {
bigInt, _ := rand.Int(rand.Reader, big.NewInt(int64(len(ModernFingerprints))))
stopAt := int(bigInt.Int64())
// Выбрать stopAt-й элемент из ModernFingerprints
PresetFingerprints["random"] = v
}Вариант "randomized" использует встроенную рандомизацию uTLS с настроенными весами:
- Принудительно TLS 1.3 (
TLSVersMax_Set_VersionTLS13 = 1) - Отключен P256 для первого обмена ключами (
FirstKeyShare_Set_CurveP256 = 0)
GetFingerprint
GetFingerprint (tls.go:169-183) ищет отпечатки во всех трех картах:
func GetFingerprint(name string) (fingerprint *utls.ClientHelloID) {
if name == "" { return &utls.HelloChrome_Auto }
if fingerprint = PresetFingerprints[name]; fingerprint != nil { return }
if fingerprint = ModernFingerprints[name]; fingerprint != nil { return }
if fingerprint = OtherFingerprints[name]; fingerprint != nil { return }
return // nil = стандартный Go TLS
}Пустое имя по умолчанию соответствует Chrome Auto. Если совпадение не найдено, возвращается nil, что сигнализирует вызывающему использовать стандартный Go TLS.
Создание клиента/сервера
Клиент
// tls.go:60-63 -- Стандартный Go TLS
func Client(c net.Conn, config *tls.Config) net.Conn {
tlsConn := tls.Client(c, config)
return &Conn{Conn: tlsConn}
}
// tls.go:124-127 -- uTLS с отпечатком
func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) net.Conn {
utlsConn := utls.UClient(c, copyConfig(config), *fingerprint)
return &UConn{UConn: utlsConn}
}Сервер
// tls.go:66-69
func Server(c net.Conn, config *tls.Config) net.Conn {
tlsConn := tls.Server(c, config)
return &Conn{Conn: tlsConn}
}Копирование конфигурации для uTLS
copyConfig (tls.go:133-143) преобразует crypto/tls.Config в utls.Config:
func copyConfig(c *tls.Config) *utls.Config {
return &utls.Config{
Rand: c.Rand,
RootCAs: c.RootCAs,
ServerName: c.ServerName,
InsecureSkipVerify: c.InsecureSkipVerify,
VerifyPeerCertificate: c.VerifyPeerCertificate,
KeyLogWriter: c.KeyLogWriter,
EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList,
}
}Управление сертификатами
GetTLSConfig
Config.GetTLSConfig (tls/config.go:364-481) строит tls.Config:
func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
root, _ := c.getCertPool()
// Создание RandCarrier с информацией для закрепления/проверки
config := &tls.Config{
InsecureSkipVerify: c.AllowInsecure,
Rand: randCarrier,
ClientSessionCache: globalSessionCache,
RootCAs: root,
NextProtos: c.NextProtocol,
SessionTicketsDisabled: !c.EnableSessionResumption,
VerifyPeerCertificate: randCarrier.verifyPeerCert,
}
// Применение опций (WithDestination, WithNextProto и т.д.)
// Настройка GetCertificate (динамический или статический)
// Установка ServerName, CurvePreferences, MinVersion, MaxVersion
// Настройка CipherSuites, KeyLogWriter, ECH
return config
}Типы сертификатов
Сертификаты имеют два режима использования:
- ENCIPHERMENT (
Certificate_ENCIPHERMENT): Обычный серверный/клиентский сертификат - AUTHORITY_ISSUE (
Certificate_AUTHORITY_ISSUE): CA-сертификат для динамической выдачи
Динамическая генерация сертификатов
При наличии сертификатов AUTHORITY_ISSUE getGetCertificateFunc (config.go:172-244) генерирует сертификаты по запросу:
func getGetCertificateFunc(c *tls.Config, ca []*Certificate) func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
domain := hello.ServerName
// Проверка существующих сертификатов, удаление просроченных
// Генерация нового сертификата из CA для домена
newCert, _ := issueCertificate(rawCert, domain)
c.Certificates = append(c.Certificates, *newCert)
return issuedCertificate, nil
}
}Это позволяет реализовать сценарии MitM и устраняет необходимость в предварительно сгенерированных сертификатах.
Выбор статического сертификата
getNewGetCertificateFunc (config.go:246-274) сопоставляет сертификаты по SNI:
func getNewGetCertificateFunc(certs []*tls.Certificate, rejectUnknownSNI bool) ... {
// Сопоставление по CommonName или DNSNames
// Поддержка подстановочного сопоставления (*.example.com)
// Если rejectUnknownSNI, возврат ошибки для неизвестных доменов
// В противном случае возврат первого сертификата как запасного
}Горячая перезагрузка и OCSP
setupOcspTicker (config.go:96-131) периодически:
- Перезагружает файлы сертификатов с диска (если заданы
CertificatePath/KeyPath) - Получает данные OCSP-скрепления
Интервал по умолчанию: 3600 секунд (1 час) или значение конфигурации OcspStapling.
Закрепление сертификатов
PinnedPeerCertSha256
SHA-256 хеши доверенных сертификатов. Проверка в RandCarrier.verifyPeerCert (config.go:283-350):
func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
// Проверка, совпадает ли листовой сертификат с любым закрепленным хешем -> успех
// Проверка, совпадает ли CA-сертификат с любым закрепленным хешем -> проверка листового по закрепленному CA
// Если задан VerifyPeerCertByName, проверка по нескольким доменным именам
// В противном случае проверка по ServerName
}Структура RandCarrier (config.go:352-357) использует tls.Config.Rand для передачи состояния проверки (поскольку VerifyPeerCertificate не имеет механизма для передачи произвольных данных):
type RandCarrier struct {
Config *tls.Config
RootCAs *x509.CertPool
VerifyPeerCertByName []string
PinnedPeerCertSha256 [][]byte
}Генерация хеша
GenerateCertHash (tls/pin.go:10-19) вычисляет SHA-256 сертификата:
func GenerateCertHash[T *x509.Certificate | []byte](cert T) []byte {
var out [32]byte
switch v := any(cert).(type) {
case *x509.Certificate:
out = sha256.Sum256(v.Raw)
case []byte:
out = sha256.Sum256(v)
}
return out[:]
}Система опций
Функции Option (config.go:484) модифицируют tls.Config после начального построения:
type Option func(*tls.Config)Доступные опции:
- WithDestination: Устанавливает
ServerNameиз адреса назначения (только если еще не задан) (config.go:490-496) - WithOverrideName: Принудительно устанавливает конкретный
ServerName(config.go:498-502) - WithNextProto: Устанавливает ALPN-протоколы (только если еще не заданы) (
config.go:505-511)
Приоритет разрешения SNI
В силу структуры GetTLSConfig (config.go:415-417) порядок разрешения ServerName следующий:
ServerNameиз конфигурации (если не пустой и не "frommitm")- Опция
WithDestination(адрес назначения) - Пустое значение (для серверных конфигураций)
Управление сессиями
Глобальный LRU-кэш сессий (config.go:24):
var globalSessionCache = tls.NewLRUClientSessionCache(128)Билеты сессий отключены по умолчанию (SessionTicketsDisabled: !c.EnableSessionResumption).
Конфигурация версии и шифров
Версия TLS
Настраиваемые минимальная/максимальная версии (config.go:427-447):
"1.0"до"1.3"
Наборы шифров
Произвольный список наборов шифров из разделенных двоеточием имен (config.go:449-459):
if len(c.CipherSuites) > 0 {
for _, n := range strings.Split(c.CipherSuites, ":") {
config.CipherSuites = append(config.CipherSuites, id[n])
}
}Предпочтения кривых
ParseCurveName (config.go:525-545) сопоставляет строковые имена с tls.CurveID:
| Имя | Кривая |
|---|---|
curvep256 | P-256 |
curvep384 | P-384 |
curvep521 | P-521 |
x25519 | X25519 |
x25519mlkem768 | X25519MLKEM768 (постквантовый) |
secp256r1mlkem768 | SecP256r1MLKEM768 |
secp384r1mlkem1024 | SecP384r1MLKEM1024 |
ConfigFromStreamSettings
Функция извлечения (config.go:514-523) вызывается каждым транспортом для проверки наличия конфигурации TLS:
func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config {
if settings == nil { return nil }
config, ok := settings.SecuritySettings.(*Config)
if !ok { return nil }
return config
}Возвращает nil, когда TLS не настроен, сигнализируя транспорту пропустить TLS-обертку.
Примечания по реализации
- ALPN по умолчанию: Если
NextProtocolне настроен, по умолчанию используется["h2", "http/1.1"](config.go:423-425). - Таймаут закрытия: Закрытие TLS имеет жесткий таймаут 250 мс для предотвращения зависания при корректном завершении (
tls.go:30-38). - Поддержка MitM: Функция
IsFromMitm(config.go:547-549) проверяет наличие специального маркера"frommitm"в ServerName, используемого функциями MitM-прокси. - VerifyPeerCertByName: При установке
InsecureSkipVerifyпринудительно устанавливается вtrue(config.go:393-397), и проверка выполняется вручную вverifyPeerCertпо нескольким доменным именам. - Журналирование ключей: Включается через конфигурацию
MasterKeyLog, запись в файл для отладки в Wireshark (config.go:461-468). - ECH: Encrypted Client Hello применяется через
ApplyECH, когда настроеныEchConfigListилиEchServerKeys(config.go:469-478). - Срок действия сертификата: Проверяется с запасом в 2 минуты (
config.go:133-142). - Построение цепочки: Когда
BuildChainравен true для CA-сертификата, CA-сертификат добавляется к вновь выданным сертификатам (config.go:155).