Как в Go проверить валидность TLS/x509 сертификата?

Ответ

Для проверки валидности сертификата в 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): Проверяет, что сертификат предназначен для тех целей, для которых он используется (например, для аутентификации сервера).