# OpenSSL: проверка и разбор TLS-сертификатов в CLI

Индекс LLMS: [llms.txt](/llms.txt)

---

Уже забыли, когда последний раз сертификат на проде протухал неожиданно? Знакомо. OpenSSL умеет отвечать на вопросы о TLS-сертификатах быстрее, чем любой чекер из маркетплейса. Разбираем ключевые сценарии без воды.

## Базовый разбор сертификата

Первая команда, с которой начинается любая диагностика — текстовый дамп сертификата.

```bash
openssl x509 -text -noout -in cert.pem
```

Вывод показывает Subject, Issuer, сроки валидности, алгоритм подписи и публичный ключ. Для быстрой справки без простыни:

```bash
# Только subject
openssl x509 -noout -subject -in cert.pem

# Только issuer
openssl x509 -noout -issuer -in cert.pem

# Только fingerprint (SHA-256)
openssl x509 -noout -fingerprint -sha256 -in cert.pem
```

Флаг `-in` принимает путь к файлу. Если сертификат скачан через браузер — обычно в формате PEM или DER. OpenSSL понимает оба, но для DER нужен дополнительный флаг:

```bash
openssl x509 -inform DER -in cert.der -text -noout
```

> [!NOTE]
> `-noout` убирает base64-блок из вывода. Полезно, когда нужен только структурированный результат, а не копия сертификата.

## Срок действия: dates и проверка на просрочку

Для мониторинга удобнее получить только даты:

```bash
openssl x509 -noout -dates -in cert.pem
```

Типичный вывод:

```
notBefore=Jan 15 00:00:00 2024 GMT
notAfter=Jan 14 23:59:59 2025 GMT
```

Для автоматизации удобнее получить timestamp и считать разницу:

```bash
# Оставшиеся дни до экспайра
not_after=$(openssl x509 -noout -enddate -in cert.pem | cut -d= -f2)
days_left=$(( ($(date -d "$not_after" +%s) - $(date +%s)) / 86400 ))
echo "$days_left days left"
```

Если `days_left` отрицательный — сертификат уже просрочен.

> [!TIP]
> Для проверки сразу нескольких хостов из inventory удобен однострочник:

```bash
for host in api.example.com admin.example.com; do
  echo -n "$host: "
  echo | openssl s_client -servername "$host" -connect "$host":443 2>/dev/null \
    | openssl x509 -noout -enddate
done
```

`-servername` передаёт SNI — без него некоторые хосты отдают дефолтный сертификат.

## Проверка цепочки: s_client и verify

Подключение с выводом сертификатов:

```bash
openssl s_client -connect example.com:443 -showcerts </dev/null
```

В выводе будет цепочка от leaf-сертификата до root CA. Для фильтрации только сертификатов:

```bash
openssl s_client -connect example.com:443 -showcerts </dev/null \
  | awk '/-----BEGIN/,/-----END/{if(/-----BEGIN/)a=1;a;if(/-----END/)a=0}' > chain.pem
```

Проверка цепочки через системный store:

```bash
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt chain.pem
```

Если проверка падает с `error 20 at 0 depth lookup`, значит, промежуточный CA не найден. Обычная причина — на сервере некорректно настроен chain.

> [!WARNING]
> `openssl verify` по умолчанию использует системный store. В Ubuntu это `/etc/ssl/certs/ca-certificates.crt`, в Alpine — отдельный пакет `ca-certificates`. Если проверка не работает — проверьте, что пакет установлен.

Короткая проверка без сохранения в файл:

```bash
echo | openssl s_client -connect example.com:443 2>/dev/null \
  | openssl verify
```

Вывод `Verify return code: 0 (ok)` означает успех.

Чтобы не залипать в интерактивном режиме и завершать процесс с ошибкой при плохой цепочке:

```bash
openssl s_client -connect example.com:443 -servername example.com \
  -quiet -verify_return_error </dev/null
```

Принудительная версия протокола или cipher — когда нужно убедиться, что сервер ещё принимает конкретный handshake:

```bash
openssl s_client -connect example.com:443 -servername example.com \
  -tls1_2 -cipher ECDHE-RSA-AES256-GCM-SHA384 -quiet </dev/null
```

Для внутреннего CA передайте бандл явно. Без `-CAfile` частный PKI обычно отвечает `Verify return code: 21 (unable to get local issuer certificate)`:

```bash
openssl s_client -connect example.com:443 -servername example.com \
  -CAfile /etc/ssl/certs/ca-bundle.crt -verify_return_error -quiet </dev/null
```

## Извлечение CN и SAN

**Common Name** извлекается напрямую:

```bash
openssl x509 -noout -subject -in cert.pem | grep -oP '(?<=CN = )[^,]+'
```

Но CN давно недостаточно — современные сертификаты используют **Subject Alternative Names (SAN)**. OpenSSL 1.1.1+ умеет вытащить их чисто:

```bash
openssl x509 -noout -ext subjectAltName -in cert.pem
```

Вывод:

```
X509v3 Subject Alternative Name:
    DNS:example.com, DNS:www.example.com, DNS:api.example.com, IP:192.0.2.1
```

Для получения только списка DNS-имен:

```bash
openssl x509 -noout -ext subjectAltName -in cert.pem \
  | grep -oP '(?<=DNS:)[^,]+'
```

> [!NOTE]
> Если SAN отсутствует (старый сертификат), браузеры падают в fallback на CN. При проверке API-ендпоинтов это объясняет, почему `curl` ругается, а браузер открывает.

## Сравнение сроков экспайри нескольких хостов

Скрипт для чека по списку хостов — практическая основа мониторинга:

```bash
#!/bin/bash
# check-certs.sh — проверка сроков действия сертификатов

check_host() {
  local host=$1
  local port=${2:-443}
  
  echo | timeout 5 openssl s_client -servername "$host" -connect "$host:$port" 2>/dev/null \
    | openssl x509 -noout -enddate 2>/dev/null \
    | cut -d= -f2 \
    | while read date; do
        ts=$(date -d "$date" +%s)
        now=$(date +%s)
        days=$(( (ts - now) / 86400 ))
        printf "%-30s %3d days  %s\n" "$host" "$days" "$date"
      done
}

# Пример использования
for h in api.example.com admin.example.com legacy.internal; do
  check_host "$h"
done
```

Типичный вывод:

```
api.example.com                   45 days  Jan 14 23:59:59 2025 GMT
admin.example.com               -12 days  Dec  1 23:59:59 2024 GMT
legacy.internal                -120 days  Aug  5 23:59:59 2024 GMT
```

Отрицательные значения — просроченные сертификаты. В продакшене удобно завернуть в cron с уведомлением в чат при пороге меньше 30 дней.

## Таблица ключевых флагов

| Команда | Флаг | Назначение |
|---------|------|------------|
| `x509` | `-text` | Полный дамп в текст |
| `x509` | `-noout` | Не выводить base64-блок |
| `x509` | `-dates` | NotBefore, NotAfter |
| `x509` | `-subject` | Subject (CN, O, OU) |
| `x509` | `-issuer` | Issuer (выдавщий CA) |
| `x509` | `-fingerprint -sha256` | Отпечаток сертификата |
| `x509` | `-enddate` | Только срок окончания |
| `x509` | `-ext san` / `subjectAltName` | Альтернативные имена |
| `s_client` | `-connect host:port` | Соединение с TLS |
| `s_client` | `-servername name` | SNI (обязателен для vhost) |
| `s_client` | `-showcerts` | Вывести всю цепочку |
| `s_client` | `-quiet` | Не заходить в интерактивный режим |
| `s_client` | `-tls1_2` / `-tls1_3` | Принудительная версия TLS |
| `s_client` | `-verify_return_error` | Ненулевой код при ошибке валидации |
| `verify` | `-CAfile path` | Файл доверенных CA |
| `verify` | `-partial_chain` | Принять неполную цепочку |

> [!TIP]
> `openssl s_client` умеет не только читать сертификаты. С флагом `-starttls smtp` или `-starttls pop3` проверяет почтовые серверы. `-http` извлекает HTTP-заголовки через TLS-соединение. Это полезно для диагностики miTM-фильтров.

Все команды работают из коробки в любом дистрибутиве Linux. Никаких зависимостей, кроме самого OpenSSL — утилита есть на каждом сервере. Если нет — ставьтся за секунду: `apt install openssl` или `apk add openssl`.
