Ответ
Для проверки валидности сертификата в 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): Проверяет, что сертификат предназначен для тех целей, для которых он используется (например, для аутентификации сервера).
Ответ 18+ 🔞
Да ты посмотри, какая хуйня творится в мире TLS! Все эти сертификаты, цепочки доверия... Прям как в жизни, блядь — чтобы кому-то поверить, нужно знать, кто его породил, и чтоб этот предок не был каким-нибудь пидарасом шерстяным.
Вот смотри, в 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, // Кому мы верим
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(не позже) для каждой бумажки в цепочке. А то вдруг он вчера сдох, а мы и не в курсе. - Имя, ёпта: Если в
opts.DNSNameчто-то указали, проверяет, а выдан ли сертификат именно на этот домен. Чтобы к «google.com» не лез какой-нибудь «pornhub.biz» со своим удостоверением. - Цель использования: Смотрит, можно ли этот сертификат вообще использовать для серверной аутентификации. Мало ли он только для подписи почты или для открывания дверей в сортире.
Вот такая, блядь, бюрократия. Но без неё сейчас никуда — все эти шифрования, рукопожатия... Чистая магия, ебать мои старые костыли.