Ответ
Для проверки валидности сертификата в Go используется пакет crypto/x509
и метод Certificate.Verify()
.
Основная идея — проверить, что цепочка сертификатов от сервера ведет к одному из доверенных корневых сертификатов (root CA), которые хранятся в системе или предоставлены вручную.
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"log"
)
func main() {
// Устанавливаем TLS-соединение с сервером
conn, err := tls.Dial("tcp", "google.com:443", &tls.Config{
// InsecureSkipVerify = true нужен, чтобы получить сертификат,
// даже если он не проходит автоматическую проверку.
// Мы проведем проверку вручную.
InsecureSkipVerify: true,
})
if err != nil {
log.Fatalf("Не удалось подключиться: %v", err)
}
defer conn.Close()
// Получаем цепочку сертификатов от сервера
certs := conn.ConnectionState().PeerCertificates
if len(certs) == 0 {
log.Fatal("Сервер не предоставил сертификат")
}
// Загружаем системный пул корневых сертификатов
rootCAs, err := x509.SystemCertPool()
if err != nil {
log.Fatalf("Не удалось загрузить системные сертификаты: %v", err)
}
// Создаем опции для проверки. Мы доверяем системным корневым сертификатам.
opts := x509.VerifyOptions{
Roots: rootCAs,
// Можно указать DNS-имя для дополнительной проверки
DNSName: "google.com",
}
// Проверяем сертификат сервера (первый в цепочке)
// Метод Verify сам построит и проверит всю цепочку до корневого.
_, err = certs[0].Verify(opts)
if err != nil {
fmt.Printf("Сертификат недействителен: %vn", err)
} else {
fmt.Println("Сертификат действителен!")
}
}
Что именно проверяет cert.Verify(opts)
?
- Цепочку доверия: Проверяет, что каждый сертификат в цепочке подписан предыдущим, и вся цепочка заканчивается доверенным корневым сертификатом из
opts.Roots
. - Срок действия: Убеждается, что текущее время находится в промежутке между
NotBefore
иNotAfter
для каждого сертификата в цепочке. - Имя хоста (DNS Name): Если указано в
opts.DNSName
, проверяет, что сертификат выдан для этого домена. - Назначение ключа (Key Usage): Проверяет, что сертификат предназначен для тех целей, для которых он используется (например, для аутентификации сервера).