Vlastní certifikační autorita
Vlastní certifikační autorita
Můžeš si certifikáty vystavovat sám. Funguje to, je to zadarmo a na první pohled je to elegantní řešení pro vnitřní služby, které nemají veřejnou doménu.
Většinou to ale dělat nemáš. Tahle stránka nejdřív vysvětlí proč, a teprve pak — když víš, do čeho jdeš — jak na to pořádně.
Jak certifikáty fungují je v TLS a HTTPS, veřejné certifikáty v certifikátech a Let's Encrypt.
Proč to nedělat
Vystavit certifikát je pět minut. Ta práce není v tom.
Musíš kořenový certifikát dostat na každé zařízení. Na notebook, na telefon, na tablet, na televizi, na tiskárnu. A pak na notebook návštěvy. A pak na nový telefon. Tahle práce nikdy neskončí a nedá se zautomatizovat pro zařízení, která nespravuješ.
Spousta zařízení ti to neumožní vůbec. Chytré televize, IP kamery, herní konzole, IoT krabičky. Buď nemají kam certifikát nahrát, nebo mají vlastní úložiště důvěry, ke kterému se nedostaneš.
Android tvoji CA aplikacím nevnutí. Od Androidu 7 aplikace ve výchozím nastavení nedůvěřují certifikátům, které nainstaloval uživatel. Prohlížeč ano, aplikace ne. Znamená to, že ti přes vlastní CA přestane fungovat mobilní klient Nextcloudu, Home Assistantu i Jellyfinu, a nezmůžeš s tím nic — je to úmyslné opatření proti odposlechu.
Kořenový klíč je univerzální paklíč. Kdo ho ukradne, může vystavit důvěryhodný certifikát na seznam.cz, google.com i na tvou banku — a všechna zařízení, kde máš CA nainstalovanou, mu uvěří. Přestaneš chránit provoz a začneš vyrábět jednotný bod selhání, který je zajímavější než cokoliv jiného v síti. Dá se to zmenšit omezením jmen, ale ne odstranit.
Obnova je tvůj problém. U Let's Encrypt řeší 90denní platnost časovač, o kterém nevíš. U vlastní CA je obnova ruční práce, nebo si musíš postavit vlastní ACME server — a pak jsi na složitosti tam, kde jsi nechtěl být.
Nic z toho nepotřebuješ. Doména stojí dvě stovky ročně a s DNS-01 ověřením dostaneš plnohodnotný certifikát i pro nas.example.cz, které ukazuje na 192.168.1.10 a z internetu není dostupné vůbec. Wildcard *.example.cz pokryje všechny vnitřní služby jedním certifikátem, obnovuje se sám a věří mu úplně všechno, včetně té televize.
| Veřejná doména + DNS-01 | Vlastní CA | |
|---|---|---|
| Cena | ~200 Kč / rok za doménu | zdarma |
| Instalace na zařízení | žádná | na každé zařízení ručně |
| Funguje v aplikacích na Androidu | ano | ne |
| Funguje na televizi a v IoT | ano | většinou ne |
| Obnova | automatická | tvoje starost |
| Riziko při úniku klíče | jen tvoje doména | všechny weby na všech zařízeních |
| Vnitřní jména ve veřejných logech | ano (CT) | ne |
Doporučení: kup si doménu a použij DNS-01. Zbytek téhle stránky je pro případy, kdy to opravdu nejde.
Kdy to naopak smysl dává
Tohle nejsou výmluvy, to jsou legitimní důvody:
Síť bez internetu. Průmyslová síť, laboratoř, oddělený segment. ACME nemá jak fungovat, protože se Let's Encrypt nemá kam dovolat.
Klientské certifikáty (mTLS). Když se má klient serveru prokázat certifikátem, žádná veřejná CA ti ho nevystaví. Vlastní CA je tady jediná cesta a je to její nejsprávnější použití.
Certifikát pro holou IP adresu. Veřejné CA nevystaví certifikát pro 192.168.1.1 ani pro 10.0.0.5. Když musíš mít HTTPS na adrese bez jména, jsi odkázaný na sebe.
Vnitřní jména, která nikam nesmí. Každý certifikát od veřejné CA se zapíše do veřejných logů certifikační transparentnosti. ucetnictvi.firma.cz je tím pádem veřejná informace. U vlastní CA ne.
Vývoj na localhostu. Na to je mkcert a je to naprosto v pořádku.
Šifrování mezi službami uvnitř. Když posíláš provoz mezi servery přes nedůvěryhodnou síť a obě strany spravuješ, vlastní CA je správný nástroj — protože obě strany spravuješ.
Všimni si, co mají společného: buď spravuješ všechna zapojená zařízení, nebo se bavíme o strojích, ne o lidech s telefony.
Jak to funguje
Rekapitulace z TLS a HTTPS, protože bez tohohle nedávají další příkazy smysl.
kořenová CA (root) ← tenhle certifikát rozvezeš na zařízení
└── mezilehlá CA (intermediate)
└── certifikát pro nas.doma ← tenhle nasadíš na server
Zařízení věří kořenovému certifikátu, protože jsi ho tam vlastníma rukama nainstaloval. Všemu, co je pod ním, věří odvozeně.
Server posílá svůj certifikát a mezilehlé, kořenový ne. Ten už klient má. Když mezilehlý zapomeneš, dostaneš klasickou chybu „funguje v prohlížeči, nefunguje v curl".
Proč vůbec mezilehlá CA? Aby kořenový klíč mohl ležet offline. Denní práci dělá mezilehlá; kdyby ti unikla, odvoláš ji a vystavíš novou pod stejným kořenem — a nemusíš znovu obcházet všechna zařízení. To je celý smysl toho mezikroku. Pokud kořenový klíč necháš ležet na stejném serveru vedle mezilehlého, můžeš krok vynechat a nic tím neztratíš, protože nic nechrání.
Kterou cestou
| Nástroj | Pro co | Náročnost |
|---|---|---|
Caddy tls internal |
služby za jednou proxy | jeden řádek |
| mkcert | vývoj na vlastním stroji | jeden příkaz |
| step-ca | skutečná vnitřní CA s automatickou obnovou | půl dne |
| openssl ručně | pochopení, mTLS, jednorázové věci | hodina a pak už navždy ručně |
Ruční openssl je poslední volba, ne první. Je tu hlavně proto, abys věděl, co ostatní nástroje dělají za tebe.
Caddy tls internal
Když už Caddy používáš, máš vlastní CA hotovou:
router.doma {
tls internal
reverse_proxy 192.168.1.1
}
Caddy si při startu vyrobí kořenovou a mezilehlou CA a vystavuje pod ní certifikáty. Kořenový certifikát najdeš v datovém svazku:
# na hostiteli
/var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt
# v Dockeru
docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt .
Na stroji, kde Caddy běží, ho do systémového úložiště přidá jedním příkazem:
caddy trust # a caddy untrust ho zase odebere
Na ostatní zařízení ho musíš dostat ručně.
Certifikáty jsou záměrně krátkodobé — koncový platí 12 hodin, mezilehlý 7 dní, kořenový zhruba 10 let. Zní to šíleně, ale Caddy je obnovuje sám a ty o tom nevíš. Je to ta správná úvaha: krátká platnost místo odvolávání.
Kde to končí: tahle CA obslouží jen to, co teče přes tenhle jeden Caddy. Nemá ACME rozhraní, takže si u ní jiný stroj o certifikát požádat nemůže. Pro certifikát na poštovní server, do databáze nebo na druhý hypervizor ti je k ničemu — a přesně tam začíná step-ca.
Datový svazek musí být trvalý. Když ho ztratíš, Caddy vyrobí novou CA a ty musíš znovu obejít všechna zařízení. Viz Caddy.
mkcert pro vývoj
Nástroj na jednu jedinou věc: certifikáty pro localhost a vývojová jména na tvém vlastním počítači.
apt install mkcert libnss3-tools # Ubuntu 24.04, balíček je v universe
mkcert -install # vyrobí CA a přidá ji do úložišť důvěry
mkcert localhost 127.0.0.1 ::1 app.test
libnss3-tools doinstaluj vždycky. Balíček mkcert na něm nezávisí, ale bez něj neumí certifikát vložit do Firefoxu ani do Chrome, protože ty mají vlastní úložiště.
Vypadnou dva soubory localhost+3.pem a localhost+3-key.pem a prohlížeč je bez řečí přijme.
mkcert -CAROOT # kde leží kořenový klíč
mkcert -uninstall # odebere CA z úložišť důvěry
Nepoužívej ho jako CA pro celou síť. mkcert nemá odvolávání, nemá evidenci vystavených certifikátů, nemá obnovu a jeho kořenový klíč leží nezašifrovaný v domovském adresáři. Kopírovat na další stroje se smí jen rootCA.pem, nikdy rootCA-key.pem — na to upozorňuje i jeho vlastní dokumentace. Jakmile máš víc než jeden stroj, chceš step-ca.
step-ca: vlastní CA s ACME
Když už vlastní CA opravdu potřebuješ, tohle je jediná varianta, kterou se dá dlouhodobě provozovat. step-ca od Smallstepu je plnohodnotná certifikační autorita, která umí ACME — tedy stejný protokol, jakým mluví Let's Encrypt. Certifikáty se pak obnovují samy a ty se o ně nestaráš.
To je zásadní rozdíl proti ručnímu openssl: neřešíš obnovu.
Instalace na Ubuntu 24.04
Smallstep má vlastní APT repozitář, takže se to aktualizuje s systémem. Pozor na Suites: debs — vypadá to jako překlep, ale je to správně:
curl -fsSL https://packages.smallstep.com/keys/apt/repo-signing-key.gpg \
-o /etc/apt/keyrings/smallstep.asc
cat > /etc/apt/sources.list.d/smallstep.sources <<'EOF'
Types: deb
URIs: https://packages.smallstep.com/stable/debian
Suites: debs
Components: main
Signed-By: /etc/apt/keyrings/smallstep.asc
EOF
apt update && apt install step-cli step-ca
Balíček nainstaluje jen binárku. Nepřidá systemd jednotku, uživatele ani /etc/step-ca — to všechno je ruční práce podle dokumentace Smallstepu. Počítej s tím, návody, které tvrdí opak, jsou zastaralé.
Založení CA
step ca init \
--name "Doma CA" \
--dns ca.doma \
--address :8443 \
--provisioner martin@doma \
--deployment-type standalone \
--acme
Zeptá se na heslo ke kořenovému klíči — vymysli dlouhé a ulož do správce hesel. Konfigurace a klíče skončí v ~/.step (tedy $(step path)), ne v /etc/step-ca; přesun do systémového umístění je další ruční krok.
--acme rovnou založí ACME poskytovatele. Poslouchání na :8443 místo :443 ti ušetří řešení oprávnění k privilegovaným portům.
Příkaz vypíše otisk kořenového certifikátu. Zapiš si ho, budeš ho potřebovat na každém klientovi.
Delší platnost certifikátů
Výchozí platnost je 24 hodin (minimum 5 minut, maximum 24 hodin). To je záměr — místo odvolávání se spoléhá na to, že certifikát rychle vyprší. Pro službu, která si o certifikát umí říct sama, to nech být. Když potřebuješ delší:
step ca provisioner update acme --x509-default-dur=720h
systemctl restart step-ca
Napojení klientů
Caddy se na vlastní CA přepne v globálních volbách:
{
acme_ca https://ca.doma:8443/acme/acme/directory
acme_ca_root /etc/ssl/certs/doma-root.crt
}
nas.doma {
reverse_proxy 127.0.0.1:5000
}
Certbot potřebuje kromě --server ještě REQUESTS_CA_BUNDLE, protože je v Pythonu a systémovému úložišti nevěří:
REQUESTS_CA_BUNDLE=/etc/ssl/certs/doma-root.crt \
certbot certonly --standalone \
--server https://ca.doma:8443/acme/acme/directory \
-d nas.doma
A stroj, kterému chceš jen předat důvěru:
step ca bootstrap --ca-url https://ca.doma:8443 --fingerprint <otisk> --install
Tohle je ta cesta, kterou jdi, pokud vlastní CA opravdu potřebuješ. Zbytek stránky je ruční varianta.
Ručně přes openssl
Pro pochopení, pro klientské certifikáty a pro jednorázové věci. Všechny příkazy jsou ověřené na Ubuntu 24.04 s OpenSSL 3.0.13.
Používám eliptické klíče (P-256). Jsou menší, rychlejší a dnes je podporuje všechno. RSA 4096 potřebuješ jen pro prehistorická zařízení — pak vyměň openssl ecparam ... za openssl genrsa -out root.key 4096.
Kořenová CA
#!/bin/bash
# vytvor-ca.sh — jednorázově, kořenový klíč pak ulož mimo server
set -euo pipefail
mkdir -p ca && cd ca
umask 077
openssl ecparam -name prime256v1 -genkey -noout -out root.key
openssl req -x509 -new -key root.key -sha256 -days 3650 -out root.crt \
-subj "/CN=Doma Root CA/O=Doma" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-addext "subjectKeyIdentifier=hash" \
-addext "nameConstraints=critical,permitted;DNS:doma,permitted;IP:192.168.1.0/255.255.255.0"
echo "Hotovo. Otisk kořenového certifikátu:"
openssl x509 -in root.crt -noout -fingerprint -sha256
Omezení jmen (nameConstraints)
Ten poslední -addext je nejdůležitější řádek celého skriptu a skoro nikdo ho nepoužívá.
Omezuje, na co smí tvoje CA vystavovat certifikáty. Bez něj je kořenový klíč paklíč ke všemu — kdo ho získá, vystaví si důvěryhodný certifikát na jakoukoliv doménu na světě. S ním umí vystavit jen na doma a na tvůj rozsah IP adres.
Že to funguje, si ověříš:
$ openssl verify -CAfile root.crt podvrzeny-google.crt
error 47 at 0 depth lookup: permitted subtree violation
Zápis má čtyři pasti a všechny stojí za přečtení, protože tři z nich vedou k falešnému pocitu bezpečí.
Doména se píše bez tečky na začátku. permitted;DNS:doma pokrývá doma, nas.doma i a.b.doma. Varianta permitted;DNS:.doma, kterou najdeš ve většině návodů, znamená jen podřízená jména — samotné doma pak neprojde. Zároveň se porovnává po celých částech jména, takže zladoma se dovnitř nevejde.
Krátká jména přestanou fungovat. DNS:nas bez domény je mimo permitted;DNS:doma a certifikát neprojde ověřením. S omezením jmen musíš všude používat plná jména. V domácí síti to zabolí hned první den.
Omezení platí zvlášť pro každý druh jména. Tohle je největší past. Když omezíš jen DNS, IP adresy a e-maily zůstanou úplně volné:
# kořen omezený jen na permitted;DNS:doma
DNS:nas.doma -> OK (čekáš)
IP:8.8.8.8 -> OK (nečekáš!)
email:kdokoli@evil.com -> OK (nečekáš!)
Proto jsou v skriptu povolené typy dva. Když v síti používáš víc rozsahů nebo IPv6, musíš je vyjmenovat všechny. A excluded;email:. ti nepomůže — zapíše se do certifikátu, ale nevynucuje nic; zamknout se druh jména dá jedině tím, že pro něj přidáš úzké permitted.
IP rozsah se píše plnou maskou. 192.168.1.0/255.255.255.0, nikoliv /24 — zápis s prefixem openssl odmítne s bad ip address.
Omezení vynucují OpenSSL, prohlížeče i Windows. Starší a vestavěná zařízení ho můžou ignorovat, takže to není neprůstřelné — ale zdarma to výrazně zmenší škodu při úniku klíče.
Mezilehlá CA
#!/bin/bash
# vytvor-mezilehlou.sh
set -euo pipefail
cd ca
umask 077
openssl ecparam -name prime256v1 -genkey -noout -out int.key
openssl req -new -key int.key -out int.csr -subj "/CN=Doma Issuing CA/O=Doma"
cat > int.ext <<'EOF'
basicConstraints=critical,CA:TRUE,pathlen:0
keyUsage=critical,keyCertSign,cRLSign
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always
EOF
openssl x509 -req -in int.csr -CA root.crt -CAkey root.key \
-out int.crt -days 1825 -sha256 -extfile int.ext
rm -f int.csr int.ext
pathlen:0 znamená „pode mnou už žádná další CA být nesmí". Vystavuje jen koncové certifikáty.
Teď kořenový klíč ze serveru odnes. Na šifrovaný flash disk do šuplíku, kopii jinam. Na serveru zůstane root.crt (veřejný, ten se rozváží) a int.key s int.crt, kterými se denně podepisuje.
Vystavení certifikátu pro službu
#!/bin/bash
# vystav.sh nas.doma 192.168.1.10 nas.jinak.doma
#
# První parametr je hlavní jméno, další jsou doplňkové jmenné a IP záznamy.
set -euo pipefail
[ $# -ge 1 ] || { echo "pouziti: $0 <jmeno> [dalsi-jmeno|IP ...]"; exit 1; }
CN="$1"; shift
DNY=395
cd "$(dirname "$0")/ca"
umask 077
# subjectAltName: hlavní jméno musí být i tady, samotné CN dnes nikdo nečte
SAN="DNS:${CN}"
for x in "$@"; do
if [[ "$x" =~ ^[0-9.]+$ || "$x" == *:* ]]; then
SAN="${SAN},IP:${x}"
else
SAN="${SAN},DNS:${x}"
fi
done
openssl ecparam -name prime256v1 -genkey -noout -out "${CN}.key"
openssl req -new -key "${CN}.key" -out "${CN}.csr" -subj "/CN=${CN}"
cat > "${CN}.ext" <<EOF
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=${SAN}
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
EOF
openssl x509 -req -in "${CN}.csr" -CA int.crt -CAkey int.key \
-out "${CN}.crt" -days "${DNY}" -sha256 -extfile "${CN}.ext"
# fullchain: certifikát + mezilehlá. Kořenová sem NEPATŘÍ.
cat "${CN}.crt" int.crt > "${CN}.fullchain.crt"
rm -f "${CN}.csr" "${CN}.ext"
openssl verify -CAfile root.crt -untrusted int.crt "${CN}.crt"
echo "Nasaď: ${CN}.fullchain.crt + ${CN}.key"
Použití:
./vystav.sh nas.doma 192.168.1.10
./vystav.sh proxmox.doma 192.168.1.5
Čtyři věci, na kterých se to nejčastěji láme:
openssl x509 -req nepřebírá z CSR vůbec nic. Nejde jen o subjectAltName — bez -extfile vypadne certifikát úplně bez rozšíření. Vygeneruješ hezký CSR se všemi jmény, podepíšeš ho a dostaneš certifikát, kterému žádný prohlížeč neuvěří.
Nespoléhej na -copy_extensions copy. OpenSSL 3 umí rozšíření z CSR převzít, jenže převezme i to, co si tam žadatel napsal — včetně basicConstraints=CA:TRUE. Kdo ti pošle takový CSR, dostane od tebe podepsanou plnohodnotnou certifikační autoritu a může si pod tvým kořenem vystavit certifikát na cokoliv. Ověřeno prakticky: takhle vzniklý certifikát projde openssl verify i jako www.google.com. U vlastního CSR to nevadí, u cizího je to zadní vrátka. Drž se -extfile.
Common Name se dnes neuznává. Prohlížeče se dívají výhradně na subjectAltName. Certifikát jen s CN=nas.doma je neplatný, i když vypadá správně — proto skript hlavní jméno vkládá do SAN taky.
Nasazuj fullchain, ne samotný .crt. Stejná past jako u Let's Encrypt, viz certifikáty.
Sériová čísla řešit nemusíš: když -CAcreateserial ani -CAserial neuvedeš, OpenSSL 3 vygeneruje náhodné dvacetibajtové číslo a žádný soubor nezaloží — dokumentace to označuje za doporučený postup. Jen pozor, že pokud ti ve složce zůstane starý soubor int.srl, openssl ho tiše použije a přepne se na řadu za sebou jdoucích čísel.
Klientský certifikát pro mTLS
Jediný rozdíl je extendedKeyUsage:
cat > klient.ext <<'EOF'
basicConstraints=critical,CA:FALSE
keyUsage=critical,digitalSignature
extendedKeyUsage=clientAuth
subjectAltName=email:martin@doma
EOF
openssl ecparam -name prime256v1 -genkey -noout -out martin.key
openssl req -new -key martin.key -out martin.csr -subj "/CN=martin"
openssl x509 -req -in martin.csr -CA int.crt -CAkey int.key \
-out martin.crt -days 365 -sha256 -extfile klient.ext
# do prohlížeče a do telefonu se nosí PKCS#12
openssl pkcs12 -export -out martin.p12 \
-inkey martin.key -in martin.crt -certfile int.crt
.p12 se importuje do prohlížeče, do telefonu i do Windows. Vyžádá si heslo — nastav ho, prázdné heslo některé systémy odmítnou.
Ověřování klientů pak zapneš na proxy, v nginxu:
ssl_client_certificate /etc/ssl/doma/ca-chain.crt;
ssl_verify_client on;
ssl_verify_depth 2;
Tohle je použití, kde je vlastní CA jednoznačně správná volba — veřejná CA ti klientský certifikát nevystaví.
Instalace na koncová zařízení
Rozváží se jen kořenový certifikát (root.crt). Nikdy klíč.
Zkontroluj si, že posíláš správný soubor:
openssl x509 -in root.crt -noout -subject -dates -fingerprint -sha256
Dobrá zpráva na úvod: na Ubuntu a Debianu jedna instalace pokryje OpenSSL, curl, wget, Go, systémový Python i Javu. Zvlášť pak musíš řešit už jen Firefox, Chrome, Node a Python v pip/venv.
Linux (Debian, Ubuntu)
cp root.crt /usr/local/share/ca-certificates/doma-root.crt
update-ca-certificates
Přípona musí být .crt a v souboru smí být jeden certifikát v PEM. Soubor s příponou .pem se tiše přeskočí — update-ca-certificates vypíše „0 added, 0 removed" a ty budeš hodinu hledat, proč to nefunguje.
Odebrání:
rm /usr/local/share/ca-certificates/doma-root.crt
update-ca-certificates --fresh
Bez --fresh se certifikát ze svazku sice odstraní, ale v /etc/ssl/certs zůstanou viset mrtvé symlinky. A nepoužívej --default — ten sice --fresh zahrnuje, ale zároveň vrátí zpátky všechny distribuční CA včetně těch, které sis v /etc/ca-certificates.conf vypnul.
Linux (RHEL, Fedora, AlmaLinux)
cp root.crt /etc/pki/ca-trust/source/anchors/doma-root.crt
update-ca-trust
Tady na příponě nezáleží a přijímá se PEM i DER.
Alpine a kontejnery
V Dockeru se na vlastní CA naráží pořád — kontejner má vlastní úložiště důvěry a o tom hostitelském neví.
# Debian/Ubuntu základ
COPY doma-root.crt /usr/local/share/ca-certificates/
RUN update-ca-certificates
# Alpine základ — balíček je potřeba doinstalovat
COPY doma-root.crt /usr/local/share/ca-certificates/
RUN apk add --no-cache ca-certificates && update-ca-certificates
To apk add u Alpine vynechat nejde. Odlehčené obrazy mají jen hotový svazek certifikátů, ne nástroj update-ca-certificates, takže bez něj build spadne. Alpine si naopak nevšímá přípony souboru — pravidlo s .crt platí jen pro Debian.
Bez přestavby obrazu se dá certifikát podstrčit i za běhu, ale musíš rovnou přepsat celý svazek:
volumes:
- ./doma-bundle.crt:/etc/ssl/certs/ca-certificates.crt:ro
doma-bundle.crt je spojení systémového svazku a tvého kořenového certifikátu. Připojit jen samotnou CA do /usr/local/share/ca-certificates/ nestačí, protože v běžícím kontejneru už nikdo update-ca-certificates nespustí.
Windows
V příkazovém řádku jako správce:
certutil -addstore -f Root doma-root.crt
Bez práv správce jde certifikát přidat jen pro sebe:
certutil -user -addstore -f Root doma-root.crt
Odebrání je certutil -delstore Root "Doma Root CA", kontrola v certmgr.msc → Důvěryhodné kořenové certifikační autority. V doméně se to rozváží přes zásady skupiny (GPO), ne ručně.
Pozor na jméno certutil. Ve Windows a v Linuxu jsou to dva úplně různé programy, které si nerozumí přepínače. Ten linuxový je níž u Chrome.
macOS
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain doma-root.crt
-d znamená pro celý systém (a vyžaduje práva správce), -r trustRoot nastaví plnou důvěru. Bez -d skončí certifikát jen v uživatelské klíčence.
iOS a iPadOS
Tady je dvoukrokový postup, na kterém uvízne každý.
- Pošli si
root.crtmailem nebo přes AirDrop a otevři ho. Telefon nabídne stažení profilu. - Nastavení → Obecné → VPN a správa zařízení → nainstaluj profil.
- A teď to podstatné: Nastavení → Obecné → Informace → Nastavení důvěryhodnosti certifikátů → přepnout přepínač u tvé CA.
Bez třetího kroku je certifikát nainstalovaný a nedůvěryhodný. Nikde se to nepíše a chová se to, jako bys nic neudělal.
Android
Nastavení → Zabezpečení → Šifrování a přihlašovací údaje → Instalovat certifikát → certifikát CA. Cesta se liší podle výrobce. Telefon tě varuje, že tvoje síť může být monitorovaná — což je přesně pravda.
A pak zjistíš, že to aplikacím nepomohlo. Od Androidu 7 aplikace ve výchozím nastavení uživatelské CA ignorují; věří jen systémovým. Chrome a ostatní prohlížeče tvoji CA použijí, mobilní klient Nextcloudu nebo Home Assistantu ne.
Obejít se to nedá. Aplikace by musela sama mít v network_security_config napsáno, že uživatelským CA věří, což dělá málokdo. A od Androidu 14 se systémové úložiště přesunulo do neměnného modulu aktualizovaného přes Google Play, takže ani telefon s root přístupem už certifikát jednoduše nepřidá.
Je to ten nejsilnější argument proti vlastní CA v domácnosti a je dobré na něj narazit teď, ne až po dvou večerech práce.
Firefox
Firefox má vlastní úložiště důvěry a systémové ignoruje. Certifikát nainstalovaný do systému v něm prostě nefunguje.
Ruční cesta, která funguje vždycky: Nastavení → Soukromí a zabezpečení → Certifikáty → Zobrazit certifikáty → záložka Autority → Importovat → zaškrtnout „Důvěřovat tomuto CA pro identifikaci webových stránek".
Na víc strojů se to dá rozvézt zásadou v /etc/firefox/policies/policies.json:
{
"policies": {
"Certificates": {
"Install": ["/usr/local/share/ca-certificates/doma-root.crt"]
}
}
}
Nehledej security.enterprise_roots.enabled. Ta volba, kterou doporučuje polovina internetu, v linuxových sestaveních Firefoxu neexistuje — Mozilla ji odmítla doplnit. Funguje na Windows a na macOS, na Linuxu ne. Na Linuxu použij Certificates.Install výše.
Na Ubuntu 24.04 je Firefox snap, ale cesta /etc/firefox/policies/policies.json platí i pro něj. Ověř si to:
snap connections firefox | grep etc-firefox
A po restartu prohlížeče zkontroluj about:policies, jestli se zásada opravdu načetla.
Chrome a Chromium na Linuxu
Taky nepoužívají systémové úložiště, ale NSS databázi uživatele:
apt install libnss3-tools
certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "Doma Root CA" -i doma-root.crt
certutil -d sql:$HOME/.pki/nssdb -L # kontrola
Tohle certutil je z balíčku libnss3-tools a s tím windowsovým nemá společného nic než jméno.
Programy s vlastním úložištěm
Klasický zdroj „vždyť to mám nainstalované, proč to nefunguje":
| Prostředí | Co udělat |
|---|---|
| Node.js | NODE_EXTRA_CA_CERTS=/cesta/doma-root.crt |
| Python v pip/venv/Dockeru | REQUESTS_CA_BUNDLE=… (requests), SSL_CERT_FILE=… (httpx, urllib) |
| Python z balíčku distribuce | nic, systémové úložiště stačí |
| curl | nic; jednorázově --cacert, nebo CURL_CA_BUNDLE |
| Java z distribuce | nic, ca-certificates-java importuje automaticky |
| Java z tarballu nebo v kontejneru | keytool -importcert -cacerts -alias doma -file doma-root.crt (heslo changeit) |
| Git | git config --global http.sslCAInfo /cesta/doma-root.crt |
| Go | nic, používá systémové úložiště |
Python je tu zrádný, protože se dvě obvyklé situace chovají opačně: python3-requests z balíčku distribuce je upravený tak, aby četl systémový svazek, kdežto certifi nainstalované přes pip si nese vlastní seznam a systém úplně ignoruje. Ve venv a v kontejnerech tedy proměnnou nastavit musíš.
Nikdy to neřeš vypnutím ověřování (curl -k, verify=False, insecureSkipVerify). Tím zahazuješ přesně tu vlastnost, kvůli které jsi celou CA stavěl.
Platnost a obnova
Kolem maximální platnosti koluje spousta nesmyslů, tak si to srovnejme.
Apple. Známé omezení na 398 dní platí jen pro certifikáty od autorit, které Apple sám předinstaloval. Na tvoji vlastní CA nedopadá. Zato dopadá starší pravidlo 825 dní, které se na ručně přidané kořeny vztahuje — a to je tvůj skutečný strop. Desetiletý certifikát na iPhonu ani na Macu fungovat nebude.
Chrome. Na certifikáty z lokálně přidaných kořenů nevynucuje žádnou maximální platnost. Ani chystané zkracování veřejných certifikátů na 47 dní se privátních autorit netýká — pravidla CA/Browser Fora se na vnitřní PKI výslovně nevztahují.
Rozumné nastavení: kořenová CA 10 let, mezilehlá 5 let, koncové certifikáty kolem roku. Pod 825 dní se vejdeš s rezervou a jednou ročně si aspoň ověříš, že postup ještě funguje.
Krátká platnost má ještě jeden důvod: odvolávání v praxi nefunguje. CRL a OCSP si musíš sám provozovat a klienti je stejně často neověřují. Jediná spolehlivá obrana proti ukradenému certifikátu je, že brzy vyprší. Proto step-ca vystavuje ve výchozím stavu na 24 hodin a Caddy na 12.
Připomínku na obnovu si nastav hned, protože za rok si nevzpomeneš:
# co všechno mám a do kdy to platí
for f in ca/*.crt; do
printf '%-28s %s\n' "$f" "$(openssl x509 -in "$f" -noout -enddate)"
done
A po každé výměně certifikátu nezapomeň službu znovu načíst — systemctl reload nginx nebo docker compose restart.
Ochrana kořenového klíče
Zopakuju to, protože je to ta část, kterou lidé odbydou:
Kořenový klíč nepatří na server vystavený do internetu. Šifrovaný flash disk, druhá kopie jinde. Když ho někdo získá, může se vydávat za jakýkoliv web, který má tvoje omezení jmen povolený — a bez něj za jakýkoliv web vůbec.
chmod 600 ca/*.key
Klíč můžeš zašifrovat heslem (openssl ec -aes256 -in root.key -out root.key.enc), ale u mezilehlé CA to znamená zadávat heslo při každém podpisu. Proto se to řeší tak, jak je popsáno výše: kořen zašifrovaný a offline, mezilehlá dostupná.
A hlavně — až tě to přestane bavit, CA odinstaluj. Opuštěná CA, jejíž klíč leží zapomenutý na starém disku, je horší než žádná. Kořenový certifikát ze zařízení odeber stejnou cestou, jakou jsi ho tam dal.
Diagnostika
# co je v certifikátu
openssl x509 -in nas.doma.crt -text -noout
# platí řetěz?
openssl verify -CAfile root.crt -untrusted int.crt nas.doma.crt
# co doopravdy posílá server
openssl s_client -connect nas.doma:443 -servername nas.doma -CAfile root.crt < /dev/null
# vidí systém moji CA?
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt root.crt
| Problém | Příčina |
|---|---|
unable to get local issuer certificate |
klient nemá kořenový certifikát, nebo server neposílá mezilehlý |
self-signed certificate in certificate chain |
nasadil jsi cert.crt místo fullchain.crt |
Funguje v prohlížeči, ne v curl |
totéž — chybí mezilehlý certifikát |
NET::ERR_CERT_COMMON_NAME_INVALID |
chybí subjectAltName, nepoužil jsi -extfile |
permitted subtree violation |
jméno je mimo nameConstraints kořenové CA, nebo jsi použil krátké jméno bez domény |
update-ca-certificates hlásí „0 added" |
soubor nemá příponu .crt |
| Funguje na počítači, ne v mobilní aplikaci | Android — uživatelské CA aplikace neuznávají |
| Na iPhonu pořád varování | neproběhlo zapnutí v Nastavení důvěryhodnosti certifikátů |
| Funguje v Chrome, ne ve Firefoxu | Firefox má vlastní úložiště důvěry |
| Funguje na hostiteli, ne v kontejneru | kontejner má vlastní úložiště důvěry |
| Na iPhonu neplatný, jinde v pořádku | platnost delší než 825 dní |
Shrnutí
Vlastní certifikační autorita je funkční nástroj s úzkým využitím: klientské certifikáty, sítě bez internetu, stroje které spravuješ všechny. Pro „chci mít doma všude zelený zámeček" je to špatný nástroj — zaplatíš za něj opakovanou ruční prací na každém zařízení, nefunkčními mobilními aplikacemi a klíčem, který musíš navždy hlídat.
Doména za dvě stovky ročně a DNS-01 ten problém řeší lépe, levněji a jednou provždy.
A jestli přesto jdeš do vlastní CA: použij step-ca, nastav omezení jmen a kořenový klíč nenechávej na serveru.