Як додати власний CA до сховища Ubuntu
Створіть приватний CA через openssl, підпишіть leaf-сертифікат і встановіть root у /usr/local/share/ca-certificates. Перевірено для Ubuntu 24.04 з OpenSSL 3.
Додайте власний CA до сховища довірених сертифікатів Ubuntu
Щоб додати власний CA до сховища довірених сертифікатів Ubuntu, скопіюйте кореневий сертифікат до /usr/local/share/ca-certificates/ під назвою, що закінчується на .crt, а потім виконайте sudo update-ca-certificates. CA (certificate authority) — це пара ключів, сертифікату якої дозволено підписувати інші сертифікати. Після того як система довірятиме вашому кореневому сертифікату, вона прийматиме кожен сертифікат, підписаний цим коренем. Тому HTTPS-з’єднання між вашими сервісами більше не завершуватимуться помилкою перевірки.
У цьому посібнику весь ланцюжок створюється офлайн за допомогою openssl. Ви створите кореневий ключ і кореневий сертифікат, випустите один leaf-сертифікат для сервера, а потім встановите кореневий сертифікат і побачите, як та сама команда перевірки змінить результат. Порядок має значення: перевірка до та після встановлення показує, що саме встановлення змінило результат.
Ubuntu 24.04 постачається з OpenSSL 3 і пакетом ca-certificates у стандартному образі, тому спочатку нічого встановлювати не потрібно (перевірено в August 2026).
Коли варто запускати власний CA?
Публічному CA, наприклад Let's Encrypt, потрібні ім’я в публічному DNS і сервер, до якого він може підключитися. Внутрішні імена не підходять. База даних у приватній мережі або панель адміністрування, доступна через тунель, не може отримати публічний сертифікат. Не слід також відкривати їх в Інтернеті лише заради отримання сертифіката.
Самопідписаний сертифікат в Ubuntu вирішує проблему лише для одного хоста. Кожен клієнт має довіряти цьому сертифікату. Для наступного хоста все потрібно налаштовувати знову. Приватний CA переносить це рішення на рівень вище. Клієнти один раз довіряють кореневому сертифікату. Після цього вони довіряють кожному сертифікату, підписаному кореневим сертифікатом, зокрема сертифікатам для хостів, яких ще не існує.
Витрати та ризики реальні. Кореневий ключ може підписати все, що дозволяють задані обмеження. Тому той, хто отримає доступ до ca.key, зможе випускати сертифікати, яким довірятимуть ваші машини. Захищайте його так само, як приватний ключ у керуванні ключами SSH. Якщо сервіс має публічне DNS-ім’я, не використовуйте цю схему. Натомість скористайтеся публічним CA: Certbot з nginx і Let's Encrypt потребує менше роботи й не вимагає встановлення додаткового програмного забезпечення на стороні клієнта.
Створення ключа CA та кореневого сертифіката
Працюйте в каталозі, який можете відкрити лише ви. Кореневий ключ не повинен залишати цей каталог.
install -d -m 700 ~/ca
cd ~/ca
openssl genrsa -aes256 -out ca.key 4096
chmod 600 ca.key-aes256 шифрує ключ за допомогою вибраної вами парольної фрази, і кожна наступна команда, яка підписує цим ключем, запитує її. Якщо не вказати -aes256, ключ зберігатиметься на диску у відкритому вигляді. Тоді резервної копії або облікового запису другого адміністратора буде достатньо, щоб надати комусь можливість випускати сертифікати, яким довіряють ваші машини.
Тепер створіть кореневий сертифікат, яким ключ CA підписує сам себе.
openssl req -x509 -new -key ca.key -sha256 -days 3650 \
-subj "/O=Example Internal/CN=Example Internal Root CA" \
-addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-addext "subjectKeyIdentifier=hash" \
-addext "nameConstraints=critical,permitted;DNS:internal.example" \
-out ca.crtЗамініть internal.example на суфікс імені, який ви фактично використовуєте, і прочитайте наступний розділ, перш ніж залишати це останнє розширення.
Кожне розширення виконує одну функцію.
basicConstraintsразом ізCA:TRUEперетворює цей сертифікат на сертифікат CA. Без нього клієнт відхилить будь-який сертифікат, підписаний цим ключем, навіть якщо сам підпис правильний.pathlen:0вказує, що CA може підписувати кінцеві сертифікати, але не може підписувати нижчі CA.keyUsageобмежує використання ключа підписуванням сертифікатів і списків відкликання. Тому цей самий ключ не можна випадково використати як ключ TLS-сервера.subjectKeyIdentifierнадає кореневому сертифікату ідентифікатор, на який посилаються кінцеві сертифікати. Так клієнт знаходить потрібний сертифікат-видавець у сховищі, де їх може бути кілька сотень.nameConstraintsобмежує імена, для яких цьому CA дозволено видавати сертифікати.
Перевірте створений результат, а не припускайте, що команда виконала саме те, що ви мали на увазі.
openssl x509 -noout -subject -issuer -serial -dates -in ca.crt
openssl x509 -noout -text -in ca.crtSubject і issuer містять однаковий рядок, оскільки кореневий сертифікат підписує сам себе. Серійний номер і дві дати взято з щойно створеного файлу. Тому використовуйте значення з цього виводу, а не з чужої інструкції.
Обмежте імена, які ваш CA має право підписувати
Кореневому сертифікату в системному сховищі довіряють для будь-якого імені в інтернеті, якщо не вказати інше. Це великий обсяг повноважень для одного файла на одному сервері. nameConstraints його обмежує. Якщо в кореневому сертифікаті є permitted;DNS:internal.example, ланцюжок від цього CA для імені за межами internal.example буде відхилено, навіть якщо підпис коректний.
Перевірте це, а не додавайте сертифікат до довірених.
openssl req -new -newkey rsa:2048 -nodes -keyout /tmp/outside.key -out /tmp/outside.csr \
-subj "/CN=www.example.com"
openssl x509 -req -in /tmp/outside.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 30 -sha256 \
-extfile <(printf 'subjectAltName=DNS:www.example.com\n') -out /tmp/outside.crt
openssl verify -CAfile ca.crt /tmp/outside.crt
echo $?Сертифікат буде видано, оскільки ваш CA підписує все, що ви попросите підписати. Помилка виникає під час перевірки: код завершення буде ненульовим, а OpenSSL назве обмеження, яке спрацювало. У цьому полягає цінність розширення. Навіть викрадений ключ CA не дасть змоги створити робочий сертифікат для імені за межами піддерева. Після завершення видаліть залишкові файли за допомогою rm /tmp/outside.*.
Перш ніж застосовувати обмеження, потрібно знати чотири речі. Воно має позначку critical, тому клієнт, який не розуміє це розширення, повинен відхилити ланцюжок, а не проігнорувати його. Це безпечна поведінка, але вона може спричинити проблеми зі старою TLS-бібліотекою. Дозволене піддерево для DNS-імен не обмежує SAN із IP-адресами, оскільки тип імені, для якого не вказано піддерево, залишається необмеженим. Тому додайте permitted;IP:10.0.0.0/255.255.0.0 у те саме розширення, якщо ваші сертифікати містять IP-адреси. Піддерево має охоплювати кожне ім’я, яке ви коли-небудь видаватимете, зокрема короткі імена хостів. Тому сертифікат для неповного імені app не пройде перевірку за наведеним вище прикладом. Обмеження вбудовується в кореневий сертифікат. Якщо ви зміните рішення, потрібно створити новий кореневий сертифікат і заново встановити його на кожному клієнті.
Видайте leaf-сертифікат, підписаний вашим CA
Leaf-сертифікат — це сертифікат, який сервер пред’являє клієнтам. Спочатку створіть для нього власний ключ і CSR (certificate signing request). CSR містить відкритий ключ і запитуване ім’я. Leaf-ключ підписує CSR, щоб підтвердити володіння відповідною приватною частиною ключа.
openssl req -new -newkey rsa:2048 -nodes \
-keyout app.key -out app.csr \
-subj "/CN=app.internal.example"
chmod 600 app.keyПотрібні імена вказують у файлі розширень, а не в CSR. Клієнти зіставляють ім’я хоста з subjectAltName (SAN) і повністю ігнорують common name. Тому сертифікат із CN без SAN не проходить перевірку імені хоста в жодному сучасному клієнті, незалежно від значення CN.
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:app.internal.example, DNS:api.internal.example
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:alwaysЗбережіть це як app.ext, а потім підпишіть запит за допомогою CA.
openssl x509 -req -in app.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 397 -sha256 -extfile app.ext -out app.crt-CAcreateserial записує ca.srl поруч із CA. У цьому файлі зберігається наступний серійний номер, щоб жодні два сертифікати цього CA не мали однакового номера. Зберігайте цей файл у каталозі CA. -days 397 — це параметр, а не обмеження інструмента. Тут короткий строк дії важливіший, ніж для публічного CA, оскільки приватний CA не має інфраструктури відкликання: CRL і OCSP responder відсутні, якщо ви не створите їх самостійно. Тому скомпрометований leaf-ключ залишається придатним для використання до завершення строку дії сертифіката.
Перевірте результат, перш ніж додавати його до сховища довірених сертифікатів.
openssl x509 -noout -subject -issuer -serial -dates -in app.crt
openssl x509 -noout -ext subjectAltName -in app.crtУ рядку issuer тепер указано CA, а не сам leaf-сертифікат. У рядку SAN перелічено імена, для яких дійсний цей сертифікат. Клієнт зіставляє ім’я лише з цим списком.
Перевірте з явним параметром -CAfile, перш ніж щось інсталювати
openssl verify -CAfile ca.crt app.crt
echo $?Це перевіряє лише одне: чи веде ланцюжок app.crt до сертифіката у ca.crt? Ця перевірка нічого не говорить про довірені сертифікати на цій машині, оскільки кореневий сертифікат передано OpenSSL у командному рядку. Помилка на цьому етапі означає проблему із самими сертифікатами. Виправте її, перш ніж продовжувати.
Тепер перевірте машину.
openssl verify app.crt
echo $?Без -CAfile OpenSSL використовує вбудований каталог сертифікатів. openssl version -d виводить базовий каталог, який використовує ваша збірка, а в Ubuntu каталог certs у ньому відповідає /etc/ssl/certs. Вашого кореневого сертифіката там ще немає, тому перевірка завершується помилкою: ланцюжок доходить до видавця, якого немає у сховищі, і більше немає де шукати. Зверніть увагу на код завершення. Саме він зміниться через два кроки.
Реальний клієнт підходить для перевірки краще, ніж openssl verify, оскільки він перевіряє не лише ланцюжок, а й ім’я хоста. Запустіть сертифікат і отримайте його.
openssl s_server -accept 8443 -cert app.crt -key app.key -www &
curl --resolve app.internal.example:8443:127.0.0.1 https://app.internal.example:8443/--resolve спрямовує з’єднання до 127.0.0.1, але водночас запитує app.internal.example, тому SAN збігається, а єдине відкрите питання — довіра. curl завершується помилкою та виводить причину, через яку не вдалося перевірити ланцюжок. Додайте -v, щоб отримати докладнішу інформацію. Не зупиняйте тестовий сервер.
Встановіть root у /usr/local/share/ca-certificates
sudo cp ca.crt /usr/local/share/ca-certificates/example-internal-root.crt
sudo chmod 644 /usr/local/share/ca-certificates/example-internal-root.crt
sudo update-ca-certificatesОсь що визначає, чи спрацює це взагалі:
- Ім’я файлу має закінчуватися на
.crt. На сторінці довідкиupdate-ca-certificatesзазначено, що сертифікати з розширенням.crt, знайдені в/usr/local/share/ca-certificatesі його підкаталогах, додаються та вважаються довіреними. Файл із назвоюroot.pemабоroot.cerбуде пропущено без жодного повідомлення. - Вміст має бути у форматі PEM. Це блок base64, обрамлений рядками
BEGIN CERTIFICATEіEND CERTIFICATE. Файл DER, перейменований на.crt, залишається двійковим і не читається. Перетворіть його за допомогоюopenssl x509 -inform DER -in ca.der -out ca.crt. - Тут має бути лише root. Приватному ключу CA та leaf-сертифікату не місце в сховищі довіри.
update-ca-certificates виводить кількість доданих і видалених сертифікатів. Якщо нічого не додано, причина в розширенні або форматі файлу.
Підтвердьте зміну з боку системи, а не за цим повідомленням.
ls -l /etc/ssl/certs/$(openssl x509 -noout -subject_hash -in /usr/local/share/ca-certificates/example-internal-root.crt).0
grep -c 'BEGIN CERTIFICATE' /etc/ssl/certs/ca-certificates.crtПерша команда формує ім’я файлу з власного subject hash вашого сертифіката та виводить його. update-ca-certificates створив це символічне посилання, і воно вказує назад на встановлений файл. Друга команда підраховує сертифікати в bundle з одного файлу. Виконайте її також до встановлення, щоб побачити, як кількість зміниться на один.
Коли копіюєте цей root на інші машини, перед встановленням перевірте, що копія надійшла без пошкоджень. Помилка в root-сертифікаті є найгіршою помилкою для такого файлу в системі, тому перед використанням перевіряйте його так само, як будь-яке інше завантаження, за допомогою перевірки контрольної суми.
Перевірте ще раз за системним сховищем
openssl verify app.crt
echo $?
curl --resolve app.internal.example:8443:127.0.0.1 https://app.internal.example:8443/Ті самі команди, ті самі файли сертифікатів, але інший результат. Нічого не змінилося щодо app.crt, і це той самий сервер, який ви запустили раніше. Відмінність лише в тому, що кореневий сертифікат тепер міститься у сховищі, з якого його читають ці клієнти, тому ланцюжок сертифікації завершується. Саме цей механізм потрібно запам’ятати: перевірка — це пошук видавця, якому клієнт уже довіряє, а встановлення CA додає цього видавця до сховища, у якому клієнт виконує пошук.
Зупиніть тестовий сервер командою kill %1.
Чому /etc/ssl/certs — не місце для розміщення власного файла
/etc/ssl/certs — це згенерований результат. update-ca-certificates заповнює його символічними посиланнями на фактичні файли сертифікатів і записує об’єднаний пакет /etc/ssl/certs/ca-certificates.crt поруч із ними.
Сертифікат, який ви вручну скопіюєте в цей каталог, не буде знайдений жодним механізмом. Пошук у каталозі OpenSSL відкриває лише файли, названі відповідно до subject hash сертифіката, тому файл із назвою myca.crt для нього невидимий. curl в Ubuntu читає файл пакета, а цей пакет перебудовується з зареєстрованих джерел, тому вашої копії не буде і в цьому шляху. Виконання update-ca-certificates --fresh видаляє символічні посилання в каталозі та створює їх заново, тому будь-яке самостійно створене посилання також буде видалено.
Інша частина цього розділення — /usr/share/ca-certificates. Він належить пакету ca-certificates і зазначений у /etc/ca-certificates.conf. Оновлення пакетів перезаписують цей файл. /usr/local/share/ca-certificates — це каталог, зарезервований для локального адміністратора, тому ваш CA зберігається після кожного оновлення пакету, який керує рештою сертифікатів.
Які програми ігнорують системне сховище довіри
Встановлення кореневого сертифіката виправляє проблему в кожній програмі, яка використовує OpenSSL або читає /etc/ssl/certs. Це стосується curl, wget, git, стандартного модуля ssl у Python і програм на Go, які в Linux читають системні файли. Середовища виконання, що постачають власний список сертифікатів, не залежать від цього. Саме це найчастіше спричиняє непорозуміння після успішного встановлення.
- Node.js використовує вбудований під час компіляції список. Вкажіть йому свій кореневий сертифікат через
NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/example-internal-root.crt. Задайте цю змінну в середовищі до запуску процесу, оскільки Node читає її один раз під час запуску. Актуальні випуски Node також мають параметр для читання системного сховища. Виконайтеnode --help | grep -i system-ca, щоб перевірити, чи підтримує його ваша версія. - Бібліотека
requestsу Python використовує пакетcertifi. ЗадайтеREQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crtдля цього процесу або передайтеverify="/etc/ssl/certs/ca-certificates.crt"під час виклику.pipприймає--certз тієї самої причини. - Java читає сховище ключів. В Ubuntu пакет
ca-certificates-javaвстановлює hook у/etc/ca-certificates/update.d/, томуupdate-ca-certificatesтакож оновлює сховище ключів Java, якщо цей пакет встановлено. Якщо його немає, імпортуйте кореневий сертифікат за допомогоюkeytool -importcert. - Firefox використовує власне сховище й ніколи не звертається до
/etc/ssl/certs. Імпортуйте сертифікат через його налаштування сертифікатів. Chromium у Linux читає базу даних NSS для окремого користувача. Відредагуйте її за допомогоюcertutilз пакетаlibnss3-tools. - Контейнери мають власну файлову систему, тому сховище хоста всередині контейнера не використовується. Скопіюйте кореневий сертифікат в образ і виконайте
update-ca-certificatesпід час його збирання. Врахуйте це, якщо ваші сервіси працюють у Docker Compose на VPS.
Якщо програма й далі відхиляє сертифікат після коректного встановлення, спочатку з’ясуйте, які файли вона відкриває. Не змінюйте інші налаштування, доки це не перевірите. strace -f -e trace=openat <command> 2>&1 | grep -i cert дає загальну інформацію та відповідає на це запитання за один запуск.
Тривале використання CA
Повторний випуск leaf-сертифіката — це знову етап CSR і етап підписування з тим самим файлом app.ext. Клієнтам нічого робити не потрібно, оскільки root, якому вони довіряють, не змінився. Зберігайте ca.srl і кожен файл .ext у каталозі CA, щоб наступний випуск був повторенням робочої команди, а не відновленням процедури з пам’яті.
Створюйте резервні копії ca.key і ca.crt поза сервером і залишайте їх зашифрованими. Якщо втратити ключ, випустити нові сертифікати буде неможливо: доведеться створити другий CA та встановити його root на всіх пристроях і в усіх сховищах застосунків, де був встановлений перший. Ведіть письмовий список усіх машин і сховищ застосунків, які отримали root. Лише цей список дає змогу виконати ротацію та видалення.
Коли строк дії самого root наближається до завершення, заздалегідь створіть заміну та встановіть обидва root паралельно. Наявність двох root у сховищі допустима, і клієнт прийме будь-який із них. Повторно випустіть leaf-сертифікати на основі нового root, а потім видаліть старий, коли від нього більше нічого не залежатиме.
Видаліть CA зі сховища довіри
sudo rm /usr/local/share/ca-certificates/example-internal-root.crt
sudo update-ca-certificates --fresh--fresh видаляє символічні посилання в /etc/ssl/certs і перебудовує їх на основі джерел, які ще залишилися. Тому видалений кореневий сертифікат зникає і з каталогу, і з пакета сертифікатів. Підтвердьте видалення тим самим способом, яким ви підтверджували встановлення.
openssl verify app.crt
echo $?
grep -c 'BEGIN CERTIFICATE' /etc/ssl/certs/ca-certificates.crt
ls -l /etc/ssl/certs/$(openssl x509 -noout -subject_hash -in ~/ca/ca.crt).0Перевірка знову завершується помилкою, кількість сертифікатів повертається до початкового значення, а символічне посилання з хешем зникає.
Ця команда змінює лише системне сховище. В інших місцях скасуйте встановлення вручну: очистьте NODE_EXTRA_CA_CERTS, видаліть alias із кожного Java keystore, видаліть кореневий сертифікат із профілю кожного браузера та перебудуйте кожен образ контейнера, до якого його було вбудовано. Видалення кореневого сертифіката також не анулює сертифікати, які він підписав. Вони залишаються чинними на кожній машині, яка й надалі довіряє цьому кореневому сертифікату. Саме тому для приватного CA потрібен письмовий перелік усіх місць, куди було встановлено кореневий сертифікат. CA, який неможливо повністю відкликати, створює постійну вразливість. Тому протестуйте видалення на одній машині в день налаштування, поки цей перелік ще короткий.
FAQ
Куди в Ubuntu помістити сертифікат CA?
Помістіть його в /usr/local/share/ca-certificates/. Ім’я файлу має закінчуватися на .crt, а вміст має бути у форматі PEM. Потім виконайте sudo update-ca-certificates. Цей каталог призначений для локального адміністратора, тому оновлення пакетів його не змінюють. /usr/share/ca-certificates належить пакету ca-certificates, а /etc/ssl/certs генерується з обох джерел. Тому файл, розміщений в одному з цих каталогів, буде перезаписано або проігноровано.
Чому curl усе ще відхиляє сертифікат після update-ca-certificates?
Перевіряйте можливі причини по черзі. Ім’я файлу може не закінчуватися на .crt. Сертифікат також може бути у форматі DER, а не PEM. У такому разі update-ca-certificates пропустив його й нічого не додав. У сертифікаті може не бути subjectAltName, що відповідає імені хоста. Це помилка імені хоста, а не помилка довіри. Перевірте це за допомогою openssl x509 -noout -ext subjectAltName -in app.crt. Сервер може надсилати лише кінцевий сертифікат, хоча потрібен також проміжний сертифікат. curl може використовувати інший пакет сертифікатів, указаний через CURL_CA_BUNDLE або --cacert. Сервіс, який працює тривалий час, потрібно перезапустити, оскільки більшість програм читає сховище довіри лише під час запуску.
Чи поширюється системне сховище довіри на Firefox, Chrome, Node і Java?
Ні. curl, wget, git, стандартний модуль ssl у Python і програми Go читають системні файли, тому вони починають працювати після виконання update-ca-certificates. Firefox використовує власне сховище. Chromium у Linux використовує базу даних NSS для окремого користувача. Її редагують за допомогою certutil з пакета libnss3-tools. Для Node.js потрібно вказати NODE_EXTRA_CA_CERTS на файл із кореневим сертифікатом. Java читає keystore. update-ca-certificates оновлює його лише за умови встановлення пакета ca-certificates-java. requests у Python використовує certifi і потребує REQUESTS_CA_BUNDLE.
Як видалити CA зі сховища довіри Ubuntu?
Видаліть файл із /usr/local/share/ca-certificates/ і виконайте sudo update-ca-certificates --fresh. Параметр --fresh видаляє символічні посилання в /etc/ssl/certs і створює їх повторно. Тому сертифікат одночасно зникає з хешованих символічних посилань і пакета ca-certificates.crt. Перевірте результат, виконавши openssl verify для сертифіката, підписаного цим CA, і перевіривши код завершення. Потім видаліть сертифікат з усіх інших сховищ, до яких ви його додали, оскільки ця команда їх не змінює.
Чи можна використовувати приватний CA замість Let's Encrypt для публічного сайту?
Ні. Браузер відвідувача ще не знає ваш кореневий сертифікат, тому покаже попередження на всю сторінку. Ви не можете встановити свій кореневий сертифікат на машинах, якими не керуєте. Приватний CA призначений для імен, які розпізнають лише ваші власні машини, і для клієнтів, якими ви адмініструєте. Для сайту, який відвідують сторонні користувачі, отримайте сертифікат від публічного CA.