> Как происходит обмен сертификатами в HTTPS (Go)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: VK

Стек: Go

> Пример ответа

Короткий ответ

Обмен сертификатами в HTTPS - это часть TLS handshake, где сервер (и опционально клиент) предъявляет свой X.509 сертификат, подписанный доверенным CA. Клиент проверяет цепочку доверия, срок действия, domain name и revocation status. В Go это автоматически обрабатывается через crypto/tls, но важно понимать, как настроить ClientAuth для mutual TLS и как кастомизировать VerifyPeerCertificate для дополнительных проверок.

Подробное объяснение

TLS handshake начинается с ClientHello, где клиент сообщает поддерживаемые cipher suites и версии протокола. Сервер отвечает ServerHello, выбирая параметры, и отправляет Certificate message - обычно полную цепочку: leaf-сертификат, промежуточные CA, но не root (root уже в доверенном хранилище клиента).

Клиент валидирует сертификат:

  1. Цепочка доверия - каждый сертификат подписан предыдущим, последний - доверенным root из системного хранилища (или кастомного пула).
  2. Срок действия - NotBefore и NotAfter.
  3. Domain name - проверка SAN (Subject Alternative Name) или CN (deprecated).
  4. Revocation - OCSP stapling или CRL (в Go по умолчанию не проверяется, если не настроить).

Для mutual TLS (mTLS) сервер запрашивает CertificateRequest, и клиент отправляет свой сертификат. Сервер проверяет его аналогично, плюс может проверить, что client cert подписан конкретным CA или содержит определённые поля.

В Go crypto/tls скрывает детали. tls.Config позволяет:

  • Certificates - серверные сертификаты.
  • ClientCAs - пул доверенных CA для клиентских сертификатов.
  • ClientAuth - политика (NoClientCert, RequestClientCert, RequireAnyClientCert, VerifyClientCertIfGiven, RequireAndVerifyClientCert).
  • VerifyConnection - callback для дополнительной проверки после стандартной.
  • VerifyPeerCertificate - raw проверка до стандартной.

Важный нюанс: Go не проверяет revocation по умолчанию. Для этого нужно использовать VerifyConnection и вручную вызывать OCSP.

На практике

Для backend-сервисов на Go обычно достаточно стандартной проверки. Но в микросервисной архитектуре часто используется mTLS для service-to-service аутентификации. В этом случае:

  1. Создаётся отдельный CA для внутренних сервисов.
  2. Каждый сервис получает сертификат, подписанный этим CA.
  3. В tls.Config указывается ClientCAs с этим CA и ClientAuth: tls.RequireAndVerifyClientCert.
  4. Для дополнительной проверки (например, что сертификат принадлежит конкретному сервису) используется VerifyConnection.

Также важно правильно настроить ServerName при исходящих соединениях, чтобы избежать ошибок валидации, особенно при использовании IP-адресов вместо DNS-имён.

Пример кода

GO
package main
import (
"crypto/tls"
"crypto/x509"
"log"
"net/http"
)
func main() {
// Загрузка серверного сертификата и ключа
serverCert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
log.Fatal(err)
}
// Пул CA для клиентских сертификатов (mTLS)
clientCAs := x509.NewCertPool()
caPEM, err := os.ReadFile("internal-ca.crt")
if err != nil {
log.Fatal(err)
}
if !clientCAs.AppendCertsFromPEM(caPEM) {
log.Fatal("failed to parse CA")
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: clientCAs,
MinVersion: tls.VersionTLS12,
VerifyConnection: func(cs tls.ConnectionState) error {
// Дополнительная проверка: сертификат должен содержать O=my-service
for _, chain := range cs.PeerCertificates {
if len(chain.Subject.Organization) > 0 && chain.Subject.Organization[0] == "my-service" {
return nil
}
}
return fmt.Errorf("client cert not authorized")
},
}
server := &http.Server{
Addr: ":8443",
TLSConfig: tlsConfig,
}
log.Fatal(server.ListenAndServeTLS("", "")) // сертификаты уже в TLSConfig
}

Как отвечать на собеседовании

Начни с краткого описания handshake: ClientHello → ServerHello → Certificate → Key Exchange → Finished. Затем переходи к деталям валидации: цепочка доверия, SAN, срок действия. Упомяни, что в Go это автоматизировано, но есть нюансы: revocation не проверяется по умолчанию, mTLS требует явной настройки.

Покажи понимание trade-off: стандартная проверка достаточна для публичных сайтов, но для внутренних сервисов нужен mTLS с отдельным CA. Если спросят про производительность - упомяни session resumption и OCSP stapling.

Что проверяет интервьюер

  • Понимание TLS handshake, а не только HTTPS как "замочка в браузере".
  • Знание X.509: что такое цепочка доверия, SAN, self-signed.
  • Практический опыт с Go: crypto/tls, tls.Config, mTLS.
  • Умение объяснить, когда стандартной проверки недостаточно и как её расширить.
  • Понимание безопасности: почему нельзя отключать проверку сертификатов (InsecureSkipVerify: true) в production.

Типичные ошибки

  • Путаница между VerifyPeerCertificate и VerifyConnection - первый вызывается до стандартной проверки, второй после.
  • Забывают, что Go не проверяет revocation - это нужно делать вручную.
  • Используют InsecureSkipVerify: true в production, что открывает MITM-атаки.
  • Не учитывают, что при mTLS сервер должен отправлять CertificateRequest, иначе клиент не пришлёт свой сертификат.
  • Ошибка с ServerName при подключении по IP - сертификат с DNS-именем не пройдёт валидацию.
  • Путают ClientCAs (для проверки клиента) и RootCAs (для проверки сервера).

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью