Перейти к содержимому

        Свой корневой сертификат: куда его класть, чтобы ему доверяли - Featured image

Свой корневой сертификат: куда его класть, чтобы ему доверяли

TLS-ошибка «сертификат не доверен» почти никогда не значит, что сертификат «сломан». Обычно сломана точка доверия: клиент смотрит не в то хранилище, куда вы положили CA.

curl, openssl, Git, Python и браузер — разные клиенты. У части из них своя база корней. Один update-ca-certificates на Linux не закроет Chrome и Firefox.

Что именно добавлять

В доверенные кладут корневой сертификат CA, которым подписан серверный сертификат, а не сам localhost.crt / app.example.internal.

Типичный набор файлов:

ФайлРоль
ca.crtкорень (trust anchor) — его и импортируют
server.crtсертификат сервиса, подписанный CA
server.keyприватный ключ сервиса, только на сервере

Самоподписанный серверный сертификат без отдельного CA можно добавить как якорь сам по себе. Для стендов и внутреннего PKI обычно заводят CA: один корень, много сервисов.

Формат — PEM (-----BEGIN CERTIFICATE-----) или DER. Большинство системных утилит ждут PEM. DER тоже принимают браузеры и certutil.

Системное хранилище

Это то, чем пользуются OpenSSL-клиенты: curl, wget, git, многие агенты. Сначала сюда, потом проверять браузер.

Linux

Дистрибутивы по-разному обновляют bundle /etc/ssl/certs.

Debian, Ubuntu и производные — файл в /usr/local/share/ca-certificates/ с расширением .crt, затем пересборка bundle:

sudo cp ca.crt /usr/local/share/ca-certificates/internal-ca.crt
sudo update-ca-certificates

RHEL, Fedora, Alma, Rocky — якорь в anchors, затем extract:

sudo cp ca.crt /etc/pki/ca-trust/source/anchors/internal-ca.crt
sudo update-ca-trust extract

Alpine — пакет ca-certificates, тот же update-ca-certificates, каталог /usr/local/share/ca-certificates/.

После этого:

curl -I https://app.example.internal
openssl s_client -connect app.example.internal:443 -servername app.example.internal </dev/null 2>/dev/null | openssl x509 -noout -issuer -subject

Если curl молчит, а браузер нет — системный trust в порядке, браузер смотрит мимо.

macOS

Системная связка ключей, не пользовательская, если доверие нужно всем процессам на машине:

sudo security add-trusted-cert -d -r trustRoot \
  -k /Library/Keychains/System.keychain ca.crt

Для своего пользователя достаточно login keychain, без sudo и с ~/Library/Keychains/login.keychain-db. GUI: «Связка ключей» → «Система» → «Сертификаты» → импорт → «Всегда доверять» для SSL.

Windows

Магазин Root локальной машины:

certutil -addstore -f Root ca.crt

То же через certmgr.msc (текущий пользователь) или certlm.msc (компьютер): «Доверенные корневые центры сертификации» → импорт.

Заметка

В контейнере и CI системный bundle живёт внутри образа. Добавление CA на хосте не поможет curl в контейнере. Копируйте ca.crt в образ и выполняйте тот же update-ca-certificates / update-ca-trust на этапе сборки.

Почему браузер всё ещё ругается

Публичные корни Chrome берёт из собственного Chrome Root Store. Дополнительные CA — из ОС, но не везде.

КлиентОткуда берёт ваш CA
curl / OpenSSLсистемный bundle
Chrome / Edge на Windows и macOSхранилище ОС (после установки в систему часто достаточно)
Chrome / Chromium на LinuxNSS-база пользователя, не /etc/ssl/certs
Firefoxсвоё хранилище профиля; системные корни — только через политику и не на Linux

Поэтому «поставил CA в систему» и «открыл сайт в браузере» — разные шаги.

Chrome, Chromium, Edge

Графический интерфейс одинаковый по смыслу на любой ОС: Настройки → Конфиденциальность и безопасность → Безопасность → управление сертификатами. Вкладка центров сертификации → импорт ca.crt. Для идентификации сайтов достаточно доверия к SSL.

Прямой URL: chrome://settings/certificates (в Edge — edge://settings/certificates).

Командная строка на Linux. Chromium/Chrome держат NSS Shared DB. С M146 по умолчанию это ~/.local/share/pki/nssdb; если уже есть ~/.pki/nssdb, используется она.

Пакет с certutil: libnss3-tools (Debian-семейство), nss-tools (RHEL/Fedora/Alpine).

NSSDB="${HOME}/.pki/nssdb"
[ -d "$HOME/.local/share/pki/nssdb" ] && NSSDB="${HOME}/.local/share/pki/nssdb"

mkdir -p "$NSSDB"
certutil -d "sql:${NSSDB}" -N --empty-password 2>/dev/null || true
certutil -d "sql:${NSSDB}" -A -t "C,," -n "Internal CA" -i ./ca.crt
certutil -d "sql:${NSSDB}" -L

Три поля в -t — SSL, почта, подпись кода. C — доверенный CA. Нужна ещё выдача клиентских сертификатов — "CT,,". Самоподписанный серверный сертификат без CA — "P,,".

Браузер лучше полностью закрыть и открыть заново: фоновые процессы Chrome сертификаты не подхватывают.

На Windows и macOS отдельный импорт в NSS обычно не нужен: достаточно системного хранилища.

Firefox

Своя база на каждый профиль. Snap/Flatpak/корпоративная сборка смотрят в другие пути — импорт в «обычный» профиль туда не попадает.

GUI: Настройки → Приватность и защита → Сертификаты → «Просмотреть сертификаты» → «Центры сертификации» → импорт. Отметить доверие при идентификации веб-сайтов.

CLI — та же certutil, каталог профиля, не ~/.pki/nssdb.

Linux: ~/.mozilla/firefox/<id>.default-release/ macOS: ~/Library/Application Support/Firefox/Profiles/<id>.default-release/ Windows: %APPDATA%\Mozilla\Firefox\Profiles\<id>.default-release\

PROFILE=$(find ~/.mozilla/firefox -maxdepth 1 -type d -name '*.default-release' | head -n 1)
certutil -d "$PROFILE" -A -t "C,," -n "Internal CA" -i ./ca.crt

Подставить свой путь к профилю, если профилей несколько.

Политика: один файл на все профили

Для парка машин надёжнее policies.json, чем ручной импорт.

Путь к политике:

ОСКуда класть
Linux/etc/firefox/policies/policies.json или distribution/policies.json в каталоге установки
macOSFirefox.app/Contents/Resources/distribution/policies.json
Windowsdistribution\policies.json рядом с firefox.exe, либо GPO

ImportEnterpriseRoots заставляет Firefox доверять корням из хранилища ОС. Работает на Windows и macOS. На Linux Mozilla это не реализует: «системного» магазина в том же смысле нет.

На Linux (и как явный список на любой ОС) — Certificates.Install: имя файла или абсолютный путь. Если указано только имя, Firefox ищет его здесь:

  • Linux: /usr/lib/mozilla/certificates, /usr/lib64/mozilla/certificates, ~/.mozilla/certificates
  • macOS: /Library/Application Support/Mozilla/Certificates, ~/Library/Application Support/Mozilla/Certificates
  • Windows: %LOCALAPPDATA%\Mozilla\Certificates, %APPDATA%\Mozilla\Certificates
{
  "policies": {
    "Certificates": {
      "Install": ["internal-ca.crt"]
    }
  }
}

PEM и DER подходят. После смены политики Firefox перезапустить.

security.enterprise_roots.enabled в about:config — то же, что ImportEnterpriseRoots: Windows и macOS, не Linux. На Linux тот же эффект дают Certificates.Install или PKCS#11-модуль p11-kit-trust.

CLI и рантаймы, которые вас всё равно обойдут

Даже после системного CA часть стека живёт со своим bundle.

СтекЧто делаетЧто поставить
Python (certifi, часть requests/httpx)свой Mozilla-bundleSSL_CERT_FILE, REQUESTS_CA_BUNDLE или truststore
Node.jsсвой списокNODE_EXTRA_CA_CERTS=/path/to/ca.crt
Javacacerts внутри JDKkeytool -importcert -alias internal-ca -file ca.crt -keystore "$JAVA_HOME/lib/security/cacerts"
Gitчасто OpenSSL/Schannel ОСсистемный CA; иначе http.sslCAInfo
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/internal-ca.pem

На macOS и Windows пути к системному bundle другие; для Node проще указать сам ca.crt.

Короткий чеклист

  1. Импортировать CA, не листовой сертификат сервиса.
  2. Положить его в хранилище ОС и проверить curl / openssl s_client.
  3. Chrome на Linux — отдельно в NSS; на Windows/macOS часто хватает шага 2.
  4. Firefox — профиль или policies.json; ImportEnterpriseRoots только Windows/macOS.
  5. В контейнере, JDK и Node проверить свой bundle, а не только ОС.

Универсальной одной команды нет. Зато есть предсказуемая схема: система → браузер → рантайм.