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

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

---

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:

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

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

```bash
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/`.

После этого:

```bash
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

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

```bash
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` локальной машины:

```powershell
certutil -addstore -f Root ca.crt
```

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

> [!NOTE]
> В контейнере и 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 на Linux | NSS-база пользователя, не `/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).

```bash
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\`

```bash
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` в каталоге установки |
| macOS | `Firefox.app/Contents/Resources/distribution/policies.json` |
| Windows | `distribution\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`

```json
{
  "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-bundle | `SSL_CERT_FILE`, `REQUESTS_CA_BUNDLE` или `truststore` |
| Node.js | свой список | `NODE_EXTRA_CA_CERTS=/path/to/ca.crt` |
| Java | `cacerts` внутри JDK | `keytool -importcert -alias internal-ca -file ca.crt -keystore "$JAVA_HOME/lib/security/cacerts"` |
| Git | часто OpenSSL/Schannel ОС | системный CA; иначе `http.sslCAInfo` |

```bash
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, а не только ОС.

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